使用Hystrix提高系统可用性

今天稍微复杂点的互联网应用,服务端基本都是分布式的,大量的服务支撑起整个系统,服务之间也难免有大量的依赖关系,依赖都是通过网络连接起来。

然而任何一个服务的可用性都不是 100% 的,网络亦是脆弱的。当我依赖的某个服务不可用的时候,我自身是否会被拖死?当网络不稳定的时候,我自身是否会被拖死?这些在单机环境下不太需要考虑的问题,在分布式环境下就不得不考虑了。假设我有5个依赖的服务,他们的可用性都是99.95%,即一年不可用时间约为4个多小时,那么是否意味着我的可用性最多就是 99.95% 的5次方,99.75%(近乎一天),再加上网络不稳定因素、依赖服务可能更多,可用性会更低。考虑到所依赖的服务必定会在某些时间不可用,考虑到网络必定会不稳定,我们应该怎么设计自身服务?即,怎么为出错设计?

Michael T. Nygard 在在精彩的《Release It!》一书中总结了很多提高系统可用性的模式,其中非常重要的两条是:

  • 使用超时
  • 使用断路器

第一条,通过网络调用外部依赖服务的时候,都必须应该设置超时。在健康的情况下,一般局域往的一次远程调用在几十毫秒内就返回了,但是当网络拥堵的时候,或者所依赖服务不可用的时候,这个时间可能是好多秒,或者压根就僵死了。通常情况下,一次远程调用对应了一个线程或者进程,如果响应太慢,或者僵死了,那一个进程/线程,就被拖死,短时间内得不到释放,而进程/线程都对应了系统资源,这就等于说我自身服务资源会被耗尽,导致自身服务不可用。假设我的服务依赖于很多服务,其中一个非核心的依赖如果不可用,而且没有超时机制,那么这个非核心依赖就能拖死我的服务,尽管理论上即使没有它我在大部分情况还能健康运转的。

断路器其实我们大家都不陌生(你会换保险丝么?),如果你家没有断路器,当电流过载,或者短路的时候,电路不断开,电线就会升温,造成火灾,烧掉房子。有了断路器之后,电流过载的时候,保险丝就会首先烧掉,断开电路,不至于引起更大的灾难(只不过这个时候你得换保险丝)。

当我们的服务访问某项依赖有大量超时的时候,再让新的请求去访问已经没有太大意义,那只会无谓的消耗现有资源。即使你已经设置超时1秒了,那明知依赖不可用的情况下再让更多的请求,比如100个,去访问这个依赖,也会导致100个线程1秒的资源浪费。这个时候,断路器就能帮助我们避免这种资源浪费,在自身服务和依赖之间放一个断路器,实时统计访问的状态,当访问超时或者失败达到某个阈值的时候(如50%请求超时,或者连续20次请失败),就打开断路器,那么后续的请求就直接返回失败,不至于浪费资源。断路器再根据一个时间间隔(如5分钟)尝试关闭断路器(或者更换保险丝),看依赖是否恢复服务了。

超时机制和断路器能够很好的保护我们的服务,不受依赖服务不可用的影响太大,具体可以参看文章《 使用熔断器设计模式保护软件》。然而具体实现这两个模式还是有一定的复杂度的,所幸 Netflix 开源的 Hystrix框架 帮我们大大简化了超时机制和断路器的实现,Hystrix:供分布式系统使用,提供延迟和容错功能,隔离远程系统、访问和第三方程序库的访问点,防止级联失败,保证复杂的分布系统在面临不可避免的失败时,仍能有其弹性。在Codeplex上有一个.NET的移植版本https://hystrixnet.codeplex.com/

使用Hystrix,需要通过Command封装对远程依赖的调用:

public class GetCurrentTimeCommand : HystrixCommand<long>

{

private static long currentTimeCache;

public GetCurrentTimeCommand()

: base(HystrixCommandSetter.WithGroupKey("TimeGroup")

.AndCommandKey("GetCurrentTime")

.AndCommandPropertiesDefaults(new HystrixCommandPropertiesSetter().WithExecutionIsolationThreadTimeout(TimeSpan.FromSeconds(1.0)).WithExecutionIsolationThreadInterruptOnTimeout(true)))

{

}

protected override long Run()

{

using (WebClient wc = new WebClient())

{

string content = wc.DownloadString("http://tycho.usno.navy.mil/cgi-bin/time.pl");

XDocument document = XDocument.Parse(content);

currentTimeCache = long.Parse(document.Element("usno").Element("t").Value);

return currentTimeCache;

}

}

protected override long GetFallback()

{

return currentTimeCache;

}

}

然后在需要的时候调用这个Command:

GetCurrentTimeCommand command = new GetCurrentTimeCommand();

long currentTime = command.Execute();

上述是同步调用,当然如果业务逻辑允许且更追求性能,或许可以选择异步调用:

该例中,不论 WebClient. DownloadString () 自身有没有超时机制(可能你会发现很多远程调用接口自身并没有给你提供超时机制),用 HystrixCommand 封装过后,超时是强制的,默认超时时间是1秒,当然你可以根据需要自己在构造函数中调节 Command 的超时时间,例如说2秒:

HystrixCommandSetter.WithGroupKey("TimeGroup")

.AndCommandKey("GetCurrentTime")

.AndCommandPropertiesDefaults(new HystrixCommandPropertiesSetter().WithExecutionIsolationThreadTimeout(TimeSpan.FromSeconds(2.0)).WithExecutionIsolationThreadInterruptOnTimeout(true))

当Hystrix执行命令超时后,Hystrix 执行命令超时或者失败之后,是会尝试去调用一个 fallback 的,这个 fallback 即一个备用方案,要为 HystrixCommand 提供 fallback,只要重写 protected virtual R GetFallback()方法即可。

一般情况下,Hystrix 会为 Command 分配专门的线程池,池中的线程数量是固定的,这也是一个保护机制,假设你依赖很多个服务,你不希望对其中一个服务的调用消耗过多的线程以致于其他服务都没线程调用了。默认这个线程池的大小是10,即并发执行的命令最多只能有是个了,超过这个数量的调用就得排队,如果队伍太长了(默认超过5),Hystrix就立刻走 fallback 或者抛异常。

根据你的具体需要,你可能会想要调整某个Command的线程池大小,例如你对某个依赖的调用平均响应时间为200ms,而峰值的QPS是200,那么这个并发至少就是 0.2 x 200 = 40 (Little's Law),考虑到一定的宽松度,这个线程池的大小设置为60可能比较合适:

public GetCurrentTimeCommand()

: base(HystrixCommandSetter.WithGroupKey("TimeGroup")

.AndCommandKey("GetCurrentTime")

.AndCommandPropertiesDefaults(new HystrixCommandPropertiesSetter().WithExecutionIsolationThreadTimeout(TimeSpan.FromSeconds(1.0)).WithExecutionIsolationThreadInterruptOnTimeout(true))

.AndThreadPoolPropertiesDefaults(new HystrixThreadPoolPropertiesSetter().WithCoreSize(60) // size of thread pool

.WithKeepAliveTime(TimeSpan.FromMinutes(1.0)) // minutes to keep a thread alive (though in practice this doesn't get used as by default we set a fixed size)

.WithMaxQueueSize(100) // size of queue (but we never allow it to grow this big ... this can't be dynamically changed so we use 'queueSizeRejectionThreshold' to artificially limit and reject)

.WithQueueSizeRejectionThreshold(10) // number of items in queue at which point we reject (this can be dyamically changed)

.WithMetricsRollingStatisticalWindow(10000) // milliseconds for rolling number

.WithMetricsRollingStatisticalWindowBuckets(10)))

{

}

说了这么多,还没提到Hystrix的断路器,其实对于使用者来说,断路器机制默认是启用的,但是编程接口默认几乎不需要关心这个,机制和前面讲的也差不多,Hystrix会统计命令调用,看其中失败的比例,默认当超过50%失败后,开启断路器,那之后一段时间的命令调用直接返回失败(或者走fallback),5秒之后,Hystrix再尝试关闭断路器,看看请求是否能正常响应。下面的几行Hystrix源码展示了它如何统计失败率的:

public HealthCounts GetHealthCounts()

{

// we put an interval between snapshots so high-volume commands don't

// spend too much unnecessary time calculating metrics in very small time periods

long lastTime = this.lastHealthCountsSnapshot;

long currentTime = ActualTime.CurrentTimeInMillis;

if (currentTime - lastTime >= this.properties.MetricsHealthSnapshotInterval.Get().TotalMilliseconds || this.healthCountsSnapshot == null)

{

if (Interlocked.CompareExchange(ref this.lastHealthCountsSnapshot, currentTime, lastTime) == lastTime)

{

// our thread won setting the snapshot time so we will proceed with generating a new snapshot

// losing threads will continue using the old snapshot

long success = counter.GetRollingSum(HystrixRollingNumberEvent.Success);

long failure = counter.GetRollingSum(HystrixRollingNumberEvent.Failure); // fallbacks occur on this

long timeout = counter.GetRollingSum(HystrixRollingNumberEvent.Timeout); // fallbacks occur on this

long threadPoolRejected = counter.GetRollingSum(HystrixRollingNumberEvent.ThreadPoolRejected); // fallbacks occur on this

long semaphoreRejected = counter.GetRollingSum(HystrixRollingNumberEvent.SemaphoreRejected); // fallbacks occur on this

long shortCircuited = counter.GetRollingSum(HystrixRollingNumberEvent.ShortCircuited); // fallbacks occur on this

long totalCount = failure + success + timeout + threadPoolRejected + shortCircuited + semaphoreRejected;

long errorCount = failure + timeout + threadPoolRejected + shortCircuited + semaphoreRejected;

healthCountsSnapshot = new HealthCounts(totalCount, errorCount); }

}

return healthCountsSnapshot;

}

其中 failure 表示命令本身发生错误、success 自然不必说,timeout 是超时、threadPoolRejected 表示当线程池满后拒绝的命令调用、shortCircuited 表示断路器打开后拒绝的命令调用,semaphoreRejected 使用信号量机制(而不是线程池)拒绝的命令调用。

本文来自合作伙伴“doNET跨平台”,了解相关信息可以关注“opendotnet”微信公众号

时间: 2024-09-20 05:16:11

使用Hystrix提高系统可用性的相关文章

如何提高微服务架构的可用性

业界通常用多少个9来衡量系统的可用性,如99.99%表示一年中有1小时左右的不可用时间.任何一个服务的可用性都不会是100%,意味着在服务运行时间里还是有可能发生故障.当把功能集中且运行在同一个应用中的单体架构拆分成多个相互独立的微服务架构后,虽然可以降低一损俱损的全局性故障风险,但由于微服务之间存在大量的依赖关系, 随着微服务个数的增多,依赖关系也将会变得越来越复杂,而且每个微服务都有可能发生故障,如果不能做好相互依赖的隔离,避免故障的连锁反应,结果可能比单体更糟糕.假设有100个微服务,并且

微服务转型,雪崩效应是绕不过的一道坎

1.星火燎原 1.1农民眼中的微服务 本文讲的是微服务转型,雪崩效应是绕不过的一道坎,近年来,微服务就象一把燎原的大火,窜了出来并在整个技术社区烧了起来,微服务架构被认为是IT软件服务化架构演进的目标.为什么微服务这么火,微服务能给企业带来什么价值? 1.1.1 以种植农作物的思想来理解微服务 我们以耕种为例来看如何充分利用一块田地的: 先在地里种植了一排排玉米: 后来发现玉米脚下空地可以利用,再间隔一段距离再种上豆角,豆角长大后顺着玉米杆往上爬,最后紧紧地缠绕在玉米杆上: 再后来发现每排玉米之

UNIX平台廉价双机容错方案

由于有多种方式来提高系统的可用性,但每种方式又各有利弊,因此设计者往往需根据用户的实际可靠性与可用性的要求选择不同的解决方案.这也取决于系统的需求:硬件的容错方案往往运行速度较快但投资规模也大,软件容错方案可能占用系统开销,但比硬件方案更灵活,尤其是在系统已经建立起来之后,并且投资小.本文所介绍的容错系统就是利用浪潮英信服务器可靠性平台和Western Micro的专用技术,通过软件方式,而不借助阵列柜或其它专用硬件来实现系统容错,达到提高系统可用性的目的. 设备选型 在本方案中,我们使用浪潮英

Java面试笔试题大汇总(最全+详细答案)

声明:有人说, 有些面试题很变态,个人认为其实是因为我们基础不扎实或者没有深入.本篇文章来自一位很资深的前辈对于最近java面试题目所做的总结归纳,有170道题目 ,知识面很广 ,而且这位前辈对于每个题都自己测试给出了答案 ,如果你对某个题有疑问或者不明白,可以电脑端登录把题目复制下来然后发表评论,大家一起探讨,也可以电脑端登录后关注我给我发私信,我们一起进步! 以下内容来自这位前辈 2013年年底的时候,我看到了网上流传的一个叫做<Java面试题大全>的东西,认真的阅读了以后发现里面的很多题

杰和科技多款商显方案亮相2017英特尔RCA论坛

 6月16日,英特尔主办的2017年"零售业重装上阵"主题论坛Retail Comes Alive(简称:RCA)在深圳华侨城威尼斯睿途酒店盛大召开.跻身英特尔IOT联盟的杰和科技受邀参与本次活动,并在会上向业内人士展示了包括kabylake.Skylake多平台的D67.P269等多款面向数字标牌应用,支持高清.多屏拼接的商显解决方案.   伴随第二次数字革命的推进,20万亿美元的零售业势必会发生更大的裂变.中国商业联合会日前发布的消费品市场运行报告显示,全国百家大型零售企业销售终于

网站架构模式

模式描述对重复发生时间的通用解决方案的核心. 模式的价值在于可重复性. 网站架构模式 为了解决高并发访问,海量数据,高可靠运行,提出了很多解决方案实现高性能,高可用,易伸缩,可扩展,安全等技术架构. 分层 横向分成, mvc, 视图,业务,数据库层 进制跨层调用 会给以后的优化带来很多的方便 可以每层部署到不同的物理设备 分割 纵向切割, 按功能和服务包装成高内聚低耦合的模块单元. 一方面有利于开发和维护,一方面利于分布式部署,提高并发处理能力和功能扩展能力 例如将网站分为:购物,论坛,搜索,广

我在服务器上部署了一个监控,结果服务器都关了,监控还是显示正常,测试ping服务器还是通的

问题描述 我在服务器上部署了一个监控,结果服务器都关了,监控还是显示正常,测试ping服务器还是通的 我在服务器上部署了一个监控,结果服务器都关了,监控还是显示正常,测试ping服务器还是通的 解决方案 ip有重复吧,实际ping的是另外一台机器. 是不是有相同设置的备用机? 解决方案二: 是不是服务器外有防火墙,ping的是防火墙的转换地址呢 解决方案三: 你ping的是网关ip么>你的监控是实时的吗 解决方案四: 最好的监控是否alive还是自己在应用层发送和接受心跳来判断 解决方案五: 我

阿里HBase超详实践总结 | 一文读懂大数据时代的结构化存储

前言 时间回到2011年,Hadoop作为新生事物,在阿里巴巴已经玩得风生水起,上千台规模的"云梯"是当时国内名声显赫的计算平台. 这一年,Hadoop的好兄弟HBase由毕玄大师带入淘宝,开启了它的阿里之旅.从最初的淘宝历史交易记录,到去年的支付宝消费记录存储在线历史存储统一:从蚂蚁安全风控的多年存储演进,到HBase.TT.Galaxy的大数据激情迭代:HBase在阿里经历过年轻的苦涩,释放过青春的活力,也付出过成长的代价.几代人的不懈努力下,五年陈的HBase开始表现出更成熟.更

大数据时代的结构化存储—HBase在阿里的应用实践

前言 时间回到2011年,Hadoop作为新生事物,在阿里巴巴已经玩得风生水起,上千台规模的"云梯"是当时国内名声显赫的计算平台. 这一年,Hadoop的好兄弟HBase由毕玄大师带入淘宝,开启了它的阿里之旅.从最初的淘宝历史交易记录,到去年的支付宝消费记录存储在线历史存储统一:从蚂蚁安全风控的多年存储演进,到HBase.TT.Galaxy的大数据激情迭代:HBase在阿里经历过年轻的苦涩,释放过青春的活力,也付出过成长的代价.几代人的不懈努力下,五年陈的HBase开始表现出更成熟.更