我终于搞懂了微服务,太不容易了...( 五 )


在超时设置的时候,对于同步与异步的接口也是有区分的 。对于同步接口,超时设置的值不仅需要考虑到下游接口,还需要考虑上游接口 。
而对于异步来说,由于接口已经快速返回,可以不用考虑上游接口,只需考虑自身在异步线程里的阻塞时长,所以超时时间也放得更宽一些 。
②容错机制
请求调用永远不能保证成功,那么当请求失败时候,服务消费者可以如何进行容错呢?
通常容错机制分为以下这些:

  • FailTry:失败重试 。就是指最常见的重试机制,当请求失败后视图再次发起请求进行重试 。
这样从概率上讲,失败率会呈指数下降 。对于重试次数来说,也需要选择一个恰当的值,如果重试次数太多,就有可能引起服务恶化 。
另外,结合超时时间来说,对于性能有要求的服务,可以在超时时间到达前的一段提前量就发起重试,从而在概率上优化请求调用 。当然,重试的前提是幂等操作 。
  • FailOver:失败切换 。和上面的策略类似,只不过 FailTry 会在当前实例上重试 。而 FailOver 会重新在可用节点列表中根据负载均衡算法选择一个节点进行重试 。
  • FailFast:快速失败 。请求失败了就直接报一个错,或者记录在错误日志中,这没什么好说的 。
另外,还有很多形形色色的容错机制,大多是基于自己的业务特性定制的,主要是在重试上做文章,例如每次重试等待时间都呈指数增长等 。
第三方框架也都会内置默认的容错机制,例如 Ribbon 的容错机制就是由 retry 以及 retry next 组成,即重试当前实例与重试下一个实例 。
这里要多说一句,Ribbon 的重试次数与重试下一个实例次数是以笛卡尔乘积的方式提供的噢!
③熔断
上一节将的容错机制,主要是一些重试机制,对于偶然因素导致的错误比较有效,例如网络原因 。
但如果错误的原因是服务提供者自身的故障,那么重试机制反而会引起服务恶化 。
这时候我们需要引入一种熔断的机制,即在一定时间内不再发起调用,给予服务提供者一定的恢复时间,等服务提供者恢复正常后再发起调用 。这种保护机制大大降低了链式异常引起的服务雪崩的可能性 。
在实际应用中,熔断器往往分为三种状态,打开、半开以及关闭 。引用一张 MartinFowler 画的原理图:
我终于搞懂了微服务,太不容易了...

文章插图
 
在普通情况下,断路器处于关闭状态,请求可以正常调用 。当请求失败达到一定阈值条件时,则打开断路器,禁止向服务提供者发起调用 。
当断路器打开后一段时间,会进入一个半开的状态,此状态下的请求如果调用成功了则关闭断路器,如果没有成功则重新打开断路器,等待下一次半开状态周期 。
断路器的实现中比较重要的一点是失败阈值的设置 。可以根据业务需求设置失败的条件为连续失败的调用次数,也可以是时间窗口内的失败比率,失败比率通过一定的滑动窗口算法进行计算 。
另外,针对断路器的半开状态周期也可以做一些花样,一种常见的计算方法是周期长度随着失败次数呈指数增长 。
具体的实现方式可以根据具体业务指定,也可以选择第三方框架例如 Hystrix 。
④隔离
隔离往往和熔断结合在一起使用,还是以 Hystrix 为例,它提供了两种隔离方式:
信号量隔离:使用信号量来控制隔离线程,你可以为不同的资源设置不同的信号量以控制并发,并相互隔离 。当然实际上,使用原子计数器也没什么不一样 。
线程池隔离:通过提供相互隔离的线程池的方式来隔离资源,相对来说消耗资源更多,但可以更好地应对突发流量 。
⑤降级
降级同样大多和熔断结合在一起使用,当服务调用者这方断路器打开后,无法再对服务提供者发起调用了,这时候可以通过返回降级数据来避免熔断造成的影响 。
降级往往用于那些错误容忍度较高的业务 。同时降级的数据如何设置也是一门学问 。
一种方法是为每个接口预先设置好可接受的降级数据,但这种静态降级的方法适用性较窄 。
还有一种方法,是去线上日志系统/流量录制系统中捞取上一次正确的返回数据作为本次降级数据,但这种方法的关键是提供可供稳定抓取请求的日志系统或者流量采样录制系统 。
另外,针对降级我们往往还会设置操作开关,对于一些影响不大的采取自动降级,而对于一些影响较大的则需进行人为干预降级 。


推荐阅读