写在最前面:
随着微服务的浪潮如火如荼,很多熟悉而又陌生的词语摆在了我们面前,正确理解他们是根据需求纳入这些心法的关键。很多概念就是,一解释就懂,一问就犯迷糊,一讨论就吵架。
1、基本概念: 关键字(降级 熔断 限流 雪崩效应)
降级:非正常情况的一种紧急预案,当流量激增或者其他原因导致服务器资源不够时,服务主动放弃一部分请求的处理从而快速释放资源的一种手段。
比如:你和女朋友出门旅行,MM想把自己所有的衣服都带去,但是因为储物空间有限(资源),只能忍痛割舍部分自己可能用到较少的衣物(主动放弃部分边缘业务)从而才能将自己用的比较多也最喜欢的衣服(主业务)装满箱子(占用资源)。
类似的情况很多比如:双十一商城部分功能暂时下线、大型直播主动间主动下线送礼功能等。
思考:降级的方式有哪些?
熔断:最早的熔断应该出现在电路保险丝上,当用电量过大(比如短路)避免引起线路火灾等情况,在入户的节点设置保险丝,通过低熔点的保险丝自己熔断保护整个线路正常工作。后来整个词语广泛应对到股市等各行各业。
在分布式系统的范围,熔断仍然保留了最原始的意义,当资源负载过高无法按预期时间返回时,请求方自行进行熔断保护以免产生因为一点而引起全局的瘫痪。
思考:客户端请求服务端不是有超时时间吗?为什么还需要熔断机制?
限流:每个服务方均有自己的处理能力,让请求方的诉求大于服务方的时候。服务方为了保证自己能够正常工作,通过限制请求量的方式将请求量控制到自己能够应对的范围内。
思考:限流和熔断的差别在哪里?
雪崩效应:在说雪崩效应之前先解释下涟漪效应,涟漪效应顾名思义,一个石子落入水中,产生涟漪一层一层向外传递,这里其实说白了就是联系是普遍存在的并且能够通过一定介质传播的。反过来再说说雪崩效应,在登雪山时是不能沿山路扔石子的,因为石子的撞击会引起波动涟漪,波动不断被放大后形成雪崩现象。
在分布式系统中,往往因为链路服务的抖动引起涟漪效应导致整个链路不稳定,而最终形成整个集群不可用。
思考:雪崩效应是表象,原因可能是因为没有超时机制、熔断、限流以及降级机制而引起的。
其他名词:超时、重试、幂等、关键路径、最长路径...
2、Hystrix是什么?
hystrix对应的中文名是“豪猪”,代表了一种防御机制,这与hystrix本身的功能不谋而合,因此Netflix团队将该框架命名为Hystrix,并使用了对应的卡通形象做作为logo。
在分布式系统中,许多依赖不可避免的会调用失败,失败原因有很多,比如超时、异常、甚至是硬件故障或系统bug。hystrix是保证依赖出现问题的情况下,不会导致整体集群服务瘫痪的一款容错框架 -> 控制涟漪的传递,避免雪崩的的产生。
hystrix提供了熔断、隔离、Fallback、cache、监控等功能帮我们实现这个目标。
3、实现场景
下面通过一个官方的场景还原下hystrix的应用场景:
正常的服务
用户请求的多个服务(A,H,I,P)均能正常访问并返回。App Container可以是我们的应用容器,比如tomcat,jetty 也可以是一个用来处理外部请求的线程池(比如netty的worker线程池)
涟漪现象产生
当请求的服务中出现无法访问、异常、超时等问题时(图中的I),那么用户的请求将会被阻塞。
雪崩发生
任何的RPC都可能会面临三种情况:成功、失败、超时。如果一次用户请求所依赖外部服务(A,H,I,P)有任何一个不可用,就有可能导致整个用户请求被阻塞。考虑到应用容器的线程数目基本都是固定的(比如tomcat的线程池默认200),当在高并发的情况下,某一外部依赖的服务超时阻塞,就有可能使得整个主线程池被占满。更进一步,线程池被占满就会导致整个服务不可用,而依赖该服务的其他服务,就又可能会重复产生上述问题。最终产生雪崩效应。
针对雪崩情景,我们可以采取多种方案防止问题的产生:
1. 弹性扩容,比如我们目前的云平台。
2. 缓存机制,将关键数据通过缓存的方式访问以提高rt。
3. 业务预热,通过业务预热的方式让数据持续可控的递增,避免指数式数据洪峰。
4. 重试机制降级,减少或关闭重试机制。
5. 边缘业务降级,将非核心业务关闭以增加关键业务资源的可用性。
6. 机房异地灾备,避免硬件设备的影响。
7. 针对服务不可用,使用依赖隔离通过降级、熔断等策略。
hystrix能够解决服务不可用问题的解决方案。
4、hystrix的依赖隔离
Hystrix 通过使用『舱壁模式』(将船的底部划分成一个个的舱室,这样一个舱室进水不会导致整艘船沉没。将系统所有依赖服务隔离起来,一个依赖延迟升高或者失败,不会导致整个系统失败)来隔离依赖服务,并限制访问这些依赖服务的并发度。
Hystrix在用户请求和服务之间加入了线程池。
Hystrix为每个依赖调用分配一个小的线程池,如果线程池已满调用将被立即拒绝,默认不采用排队.加速失败判定时间。线程数是可以被设定的。
SCF目前没有客户端的队列-这个使我们能够发挥的关键点
原理:
用户的请求将不再直接访问服务,而是通过线程池中的空闲线程来访问服务,如果线程池已满,则会进行降级处理,用户的请求不会被阻塞,至少可以看到一个执行结果(例如返回友好的提示信息),而不是无休止的等待或者看到系统崩溃。其本质是将服务视为资源,当请求该资源的数量超过了线程池中的数量限制时则不可以再对该资源进行访问,从而保护该资源不会过载而造成阻塞。
例如:将银行的柜员窗口看成服务,窗口外是排队办理业务的客户,假定每个客户的业务需要5分钟,则越靠后的用户等待时间越长。同时,排队的人越多,占用的空间越多(银行大厅被挤爆,导致想去别的窗口办理业务的人根本无法进入大厅,甚至排队排到了大厅门外,此时已经雪崩)。线程池则规定了每个窗口前可以排队的人数上限(例如10人),此时新来的第11人是不可以再到这个窗口前排队的,从而保证该窗口不会因为排队人数过多而阻塞。第11人获得的返回结果可以是去其他窗口办理,或者是无法办理。这时大厅内还有足够的空间留给其他窗口办理业务的人员进出。A窗口排满了, 但是不影响B窗口的正常使用。这就是隔离的效果。如果不隔离,则会发生所有资源都被A窗口占用,其他窗口根本无法正常运作。
hystrix还提供了信号量隔离机制
5、hystrix的实现原理
1和2是使用Hystrix的第一步,创建一个 HystrixCommand 或者 HystrixObservableCommand 对象来表示你需要发给依赖服务的请求。你可以向构造器传递任意参数。
第3步 请求接收后,会先看是否存在缓存数据,如果存在,则不会继续请求服务,直接返回缓存数据。如果不存在缓存数据,则继续进行第4步。
第4步 将判断熔断器是否为开启状态,如果开启(已经熔断),则调用第8步FallBack(降级)处理。如果未开启,则继续调用第5步。
第5步检测当前依赖的线程池是否已满,如果已满,也会调用第8步FallBack(降级)处理,同时进行第7步将结果上报给熔断器,此时上报的状态为【拒绝】。如果未满,则继续进行第6步。
第6步执行的是run方法。
- HystrixCommand.run() - 返回单个响应或抛出异常
- HystrixObservableCommand.construct()- 返回发出响应或发送onError通知的Observable
run方法执行过程中如果发生HystrixBadRequestException以外的异常,也将调用第8步FallBack(降级)处理,同时进行第7步将结果上报给熔断器,此时上报的状态为【失败】。如果run方法执行没有异常但是超过预设的时限(默认1秒)也将调用第8步FallBack(降级)处理,同时进行第7步将结果上报给熔断器,此时上报的状态为【超时】。
如果没有异常也未超时,则进行第9步返回结果,同时进行第7步将结果上报给熔断器,此时上报的状态为【成功】。
在第8步的降级处理中
如果没有实现getFallback的将直接抛出异常
如果降级逻辑调用成功直接返回
如果降级逻辑调用失败抛出异常