分类: Java
2019-05-21 15:31:32
如果想要了解hystrix框架,首先需要大家能够对一些专业术语能够有所了解,以方便在学习过程中能够清楚的知道hystrix到底在做什么,为什么这么做。
熔断
l 超时
l 断路器
l 资源隔离
服务“熔断”,这一词个人感觉可以用一个英文单词“break”在表述上回更加贴切,在生活中同样有现成的例子可以描述——家庭用电的“保险丝”跳闸。有过生活常识的都知道 ,当家用电器超过电流负载、或者入室电路出现短路时,在链接室内和公共电路主干线上的总闸“保险丝”会快速熔断掉,以保证室内瞬间停电,起到安全的作用。其实hystrix提供的服务熔断功能也是类似的道理,只不过再次基础上将“室内”和“室外”归纳成了服务的上、下游。上游服务A依赖下游服务B(A ->B),如图所示图1:
图1 熔断模式下的上下游服务调用
上游服务A在调用下游服务B时,存在断开和闭合两个状态,状态间的切换根据下游服务B的运行状态决定。
期初了解服务熔断的时候一直在想一个问题,服务熔断和服务降级有什么关系?是一个吗?如果不一致,那么又有什么区别呢?不知道大家在学习和工作中是否会遇到这个疑问。因为从概念上将,这个两个技术的核心思想都是在终止服务间的直接调用。服务降级——是在某种情况下,如高峰期,为了能够保证主链路的业务,需要将边缘业务进行服务降级,后期以补偿或非补偿的方式进行恢复。被降级的服务在被恢复之前不会被访问。当然也存在更加细粒度的降级方案,比如接口级别降级等。看上去,跟熔断没什么区别。确实可以看似成一个东西,但是经过一段时间的研究后发现,服务熔断与降级之间存在本质的区别:
1、主动与被动的区别
主动与被动,主要体现在执行过程中,是否存在人为的主动干预,“服务熔断”是通过配置规则对下游服务进行健壮性分析而做出的被动熔断操作,不考虑是否为关键链路;“服务降级”是工作人员根据目前的运行状况执行的主动降级策略,用于保证主链路业务不受影响,
2、执行动机不同
执行动机的不同主要表现在“服务熔断”通过对下游服务的健壮性统计分析而做出的为了保证上游服务业务正常进行及资源使用的安全;“服务降级”是为了保证主链路业务不被边缘链路干扰而做出的决策,类似于“剪枝”的过程。
超时(又称为 timeout)。服务响应的时间(RT)是我们在开发过程中衡量一个服务吞吐的主要指标,通常通过90线、95线、99线等规则进行对服务进行考量。在开发过程中,大家一定要非常非常关注每一个接口的RT,一般对于一个正常业务接口的请求,非高峰期情况下要求在100ms以内,高峰期情况下不能超过300ms,如果不在这个范围内的自身业务接口,就需要对这些接口进行合理的优化,除非有一些特殊接口,如依赖外部接口(其他公司的开源接口),这种严重依赖下游RT的接口可以选择通过做本地缓存的方式进行优化,如果无法做本地缓存只能只待下游提供方了(扯远了~)。如果RT时间特别长会带来的后果是什么呢?大家可以想一下,对于java开发的服务来说,真正处理请求的是JVM(它不在本文讨论范围内),JVM是搭建在物理机上的一个容器平台,用来解析并执行java字节码文件,以实现对java的支持(早期java跨平台的主要因素),那么jvm启动后就像其他的应用程序一样是操作系统的一个进程,操作系统对于每一个进程在启动时都会分配一定的系统资源和可扩展资源限制(如:文件句柄数、CPU配额等),也就是说如果一个进行被分配的资源被全部占用时,新的请求就需要等待被占用资源的释放或者直接在操作系统层面被reject掉。回到我们要讨论的RT,如果RT时间过长,那么直接导致每个请求占用的系统资源时间同样变长了,那么在服务接收了一定数量的请求后,其他请求就会被阻塞,此时严重影响了服务吞吐能力。所以,大家在做研发时一定要关注每一个接口的RT时间,切记不要忽略RT的抖动。RT的抖动可能暗示着服务存在着潜在风险。
图2 超时引起的流量阻塞
“断路器”即上文中提到的“breaker”,在服务熔断过程中起到开关的作用。想了好久 ,用什么样的生活样例,做比喻会比较形象一点呢。最后选择了用这个:
在hystrix中将断路器命名为“circuit breaker”,每一个断路器通过监控都对应一个command,并监控其的运行状态,通过与断路规则与状态进行匹配,最终来判断该command是否应该被熔断,熔断后的所有请求都将走fallback一种特殊处理程序进行处理。后续将用一章来讲解“断路器”以及其监控指标。
在“超时”一节中我们聊到了资源使用问题,对,每一个进程的启动都将使用系统资源(文件句柄、CPU、IO、磁盘等)。上述资源都对于系统来说都是非常稀缺的资源,合理分配和利用操作系统的资源对于使用者来说是一个非常大的挑战。在以往的服务体系结构中,一个服务启动后,所有的“请求”共用整个服务被分配的所有资源,比如:Thread资源:
图3 共用线程池
这种共享资源的方式带来很大问题,前文说道请求超时就是一个比较好的例子,当服务A对服务B存在依赖调用时,如果服务B业务处理存在大量超时,就会增加服务A中该接口占用大量的系统资源而没有及时进行释放,使得其他接口无法正常获取到系统资源而被阻塞,如果阻塞时间超过系统设置的超时时间则直接影响到了用户请求未被处理。针对上述问题,能够更合理的分配资源,hystrix提供了如何将资源进行隔离的方案:线程池隔离和信号量隔离。线程池隔离:即通过为每个下游配置一个固定配额的线程池,当服务调用下游时使用该线程池中的线程执行下游服务的调用,使用后再归还线程池,如果该下游服务出现超时或者不稳定状态时,上游服务将只占用仅有的配置的线程池数量,从而起到隔离作用。如下图4:
图4 线程池隔离
Hystrix是什么?官方给出的解释是:“In a distributed environment, inevitably some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions between these distributed services by adding latency tolerance and fault tolerance logic. Hystrix does this by isolating points of access between the services, stopping cascading failures across them, and providing fallback options, all of which improve your system overall resiliency.”
译文:“在分布式环境中,某些服务之间的依赖失败是不可避免的。Hystrix作为一个类库,通过提供延迟控制和容错逻辑,帮助我们保证(个人认为control翻译成“保证”比“控制”更好一些)这些分布式服务之间的交互过程。Hystrix 通过“服务隔离”、阻止服务依赖导致的级联失败、并提供fallback处理,以提高我们系统的整体健壮性”。大体的意思是,通过hystrix提供的“服务隔离”、“容错处理”、“服务熔断”等方式保证了分布式环境下相互依赖的服务之间的健壮性。
故Hystrix被设计成:
1、当服务之间存在依赖关系时,尤其是通过第三方client(HttpClient)进行网络访问的服务提供延迟保护和容错控制。
2、在复杂分布式系统中,避免级联失败
3、快速失败和快速恢复
4、Fallback和优雅降级
5、接近实时的监控、告警、操作控制。
上面是基于hystrix GitHub上的wiki进行翻译的内容,大体上官方要表述的意思是:在复杂的分布式微服务的环境中,通过引入hystrix框架,通过hystrix提供的“服务隔离”、“服务熔断”、“服务监控”、“容错处理”等功能,可以保证系统的稳定性和可伸缩性。
Hystrix 由Netflix API团队在2011年开始的弹性工程项目演变而来。在2012年,Hystrix不断的发展且日趋成熟,许多Netflix内部项目团队开始使用它。如今,在Netflix内部,都有数以百亿计的线程隔离和数以千亿计的信号量隔离调用通过hystrix被执行。这也为hystrix整体的在线时长和弹性恢复得到了戏剧性的改善。
下面的链接描述了更多围绕hystrix的内容以及对hystrix应用上的挑战:
Hystrix能做什么?要从其能够解决的问题才能够了解。
在复杂的分布式环境中,应用服务之间存在着错综复杂的依赖关系,依赖关系之间不可避免的会存在不稳定情况,如果主应用不与这些不稳定的外部依赖关系进行隔离,就存在被这些问题拖垮的风险。
例如:一个服务对下游服务存在30个依赖关系,下游每个服务稳定性保证99.99%(4个9,对于一般的公司已经是非常好的状态),那么对于30个 99.99%的服务来说组合起来,对于上游服务A来说其稳定性只能达到99.7%,如果有10亿次请求,那么就会存在300万次失败(现实中往往情况会更糟)。健康状况下服务依赖关系如下:
图5 正常情况下的依赖状态(hystrix官网)
当存在一个下游依赖出现故障时,依赖状态如下:
图6 依赖服务出现故障(hystrix官网)
此时将间接导致上游服务的不可用,用户请求失败。随着时间的推移,失败的用户请越来越多,堆积在上游服务请求链路中,如图7:
图7 下游依赖项出现故障导致请求阻塞(hystrix官网)
随着堆积的异常请求越来越多,到时上游服务处理请求的资源被完全占用,最终将导致上游服务的不可用。高并发情况下,一个单一的后端依赖服务的延迟将导致所有服务器实例瞬间达到饱和。任何的这种通过网络或者客户端类库访问的应用都将是产生失败的风险点。比访问失败更糟糕的是,这些应用程序将导致请求的延迟增加,资源被占用,新的请求被加入到队列,最后将导致全系统的级联失败。
当使用第三方客户端类库——“黑盒子”时,其所有的实现细节、网络和资源配置都被隐藏在内部,且配置不尽相同时,我们根本无法对其变更进行实时监控时,将大大加重上述问题的最终结果。
这些象征着失败和延迟现象需要进行管理和隔离,以保证单一的失败依赖关系不会拖垮整个服务。
为此Hystrix做了如下事情:
1、防止任何单一依赖关系使用整个应用的全部用户线程资源(例如:tomcat)
2、降低负载、快速失败代替排队等待
3、提供失败处理程序(fallback)以保证用户请求正常返回
4、使用熔断技术(例如:隔板模式、泳道模式和断路器模式)以限制单一依赖关系带来的负面影响
5、通过近实时的度量统计、监控、告警,优化time-to-discovery(没想好具体怎么翻译:“及时发现问题”),大意应该是以保证hystrix能够及时发现出现问题的依赖关系。
为了实现上述的目标,hystrix做了如下方案:
1、将所有的外部调用或者依赖项封装成一个HystrixCommand或者HystrixObservableCommand对象,并让 *Command对象分别在隔离的线程中执行。
2、允许用户为每一个command定义自定义的超时时间阈值,根据不同的依赖项定义用户自定义阈值,可以在某种程度上提高99.5线。
3、为每一个依赖项维护一个小的线程池(或者信号量),如果它满了,则对于该依赖项的新的请求将立即被拒绝,而不是进行排队等待资源的释放。
4、度量统计请求“成功数”、“失败数”、“超时数”以及“线程池拒绝数”等指标
5、如果对于某一个依赖项调用错误率超过阈值,则可以手动或者自动触发断路器断开一段时间(简称:熔断)。
6、当请求报错、超时、被拒绝或者熔断时,执行错误处理程序(fallback)
7、近实时的监控指标和配置的变更
当我们使用hystrix来包装下游依赖项后,上图中的显示的架构图变成了下面图所示,每一个依赖项彼此之间隔离,当出现延迟时并受资源的限制,并且在依赖项出现错误时,能够决定响应是否通过容错处理程序(fallback)进行处理并返回结果。如图8所示:
图8 使用hystrix后的依赖项状态(hystrix官网)