为什么微服务重试机制很重要?当我们应用单个应用时,所有的逻辑计算都在一个进程中,除了进程掉电之外几乎不可能出现处理失败的情况。但是,当我们将单体应用拆分成一个个子服务时,服务之间的相互调用,无论是RPC还是HTTP,都依赖于网络。网络是脆弱的,时不时的请求会抖动失败。比如我们的Server1调用Server2下单的时候,网络可能会超时。此时Server1需要返回给用户提示“网络错误”,这样我们的服务质量就会下降,可能会收到用户的投诉,降低产品。竞争力。这就是为什么许多产品都有内部接口维度SLA指标的原因。当成功率低于一定水平时,需要与负责人的业绩挂钩,促进产品的稳定性。对于网络抖动等情况,最简单的解决方案之一就是重试。RetryMechanism重试机制:同步和异步模式常见的重试模式有两种:就地重试和异步重试。就地重试很好理解,就是在调用下游服务失败时,程序重新发起;异步重试就是将请求信息丢到某个mq中,然后一个程序消费这个事件进行重试。一般来说,就地重试实现简单,可以解决大部分网络抖动问题。但是如果服务追求强一致性,希望下游出现故障时不影响正常的服务计算,此时可以考虑使用异步重试,上游服务可以快速响应异步消费者的用户请求完成重试。RetryAlgorithm无论是异步模式还是同步模式,重试都有几种固定的算法:线性退避:每次失败等待固定的时间。随机退避:每次失败都会等待一段随机的时间来重试。指数退避:连续重试时,每次等待时间都是前一次等待时间的倍数。Syntheticbackoff:结合多种方法,比如线性+随机抖动,指数+随机抖动。再加上随机抖动,可以在很多服务无法防止雪崩时分散下游的重试请求。为什么我们需要等待并重试?由于网络抖动或者下游负载过高,立即重试成功的概率远低于过段时间重试,相当于让下游喘口气。重试风暴在微服务架构中,一定要注意避免重试风暴的产生。那么,什么是重试风暴?如图,数据库负载过高。这时候Server3向它的请求就会失败。但是因为配置了重试机制,Server3最多向数据库发起3次请求。然而,就在这个时候,荒唐的事情出现了。为了避免抖动,上游的每个服务都设置了3次超时重试机制。这显然是一个业务要求。上面由于有3个链接的存在,就变成了一个对象。27(3^(n))次请求到数据库!对于已经快要崩溃的数据库来说,情况更糟。在微服务架构中,一个请求通常会经过几个甚至上百个服务。如果每一个都这样重试,数据库压力稍微高一点问题不大,但是很可能因为重试导致雪崩。如何防止重试风暴单实例限流首先,我们接受单实例(进程)线程的请求,所以可以在单进程的粒度上进行限流。关于限流,我们通常使用令牌桶或者滑动窗口来实现。下面是一个简单实用的滑动窗口实现。如下图,每一秒都会产生一个Bucket,我们在Bucket中记录这一秒内到下游接口的成功次数和失败次数。进而可以计算出每秒的失败率,结合失败率和请求失败的次数,判断是否需要重试。每个Bucket在一定时间后过期。如果下游大面积失效,此时不适合重试。我们可以配置失败率超过10%就不重试这样的策略,这样在单机层面就可以避免很多不必要的重试。防止在链接级别重试的最佳方法是仅在最下游级别(上图中的Server3)重试。GoogleSRE指出Google在内部使用特殊的错误代码来实现这一点:同意一个特殊的Business状态代码,这意味着失败,但不要再试了。如果任何链路从下游收到这个错误,它不会重试并继续透传给上游。通过这种模式,在数据库抖动的情况下,最下游只有3次重试请求,上游服务判断状态码就知道不能重试,不会再重试。另外,在一些异常的业务情况下,也可以通过状态码来区分不需要重试的状态。这种方式可以有效避免重试风暴,但缺点是业务方需要和状态码的逻辑强耦合,一般需要公司层面的框架约束。超时优化在重试中,最头疼的场景就是超时场景。我们知道网络超时了。有可能请求根本没有到达下游服务,也有可能已经到达下游并处理完毕,但来不及返回。这是两军之间的典型问题。关于超时的情况,显然不能通过错误码来识别,比如A->B->C->D,如果C失败,B可以拿到错误码返回给A,但是因为A请求B超时,因此获取不到错误码,此时A会发起重试。那么有什么办法可以优化超时情况,避免不必要的重试呢?我觉得有几个地方可以做:上游重试请求不重试超时导致的重试请求,在requestFlag标记中包含一个请求。如果下游发现上游发起的请求是超时的,如果超时,在向下游请求时出错,则不会重试。比如A->B->C,A请求B超时重试,那么重试的时候会带上一个Flag,B在A的重试请求中找到这个Flag。如果此时对C的请求失败,则不会重试请求,从而避免重试被放大。合理设置每个环节A->B->C的超时时间,B->C加上超时时间最多为1s,那么A->B的超时时间应该>=1秒,否则B可能无法到重试C结束后,A发起重试请求。对于此类问题,我们可以通过分析离线数据,找出链路中的不合理配置。通过以上优化,我们可以在一定程度上避免超时带来的重试风暴。重试降低延迟我们主要讲解了重试是为了保证请求SLA和避免重试风暴的手段,但实际上在实际应用过程中,一些低延迟的业务场景往往会使用重试来优化,这种优化措施是备份请求。比如用户下单的接口,我们希望延迟低一些,因为延迟高了,用户下单的可能就少了,直接影响到企业的盈利能力。假设我们的接口延迟p95为300ms,即95%的用户可以在300ms内完成订单。虽然看起来不错,但可能会有“长尾效应”。尾部5%对业务重要也至关重要。针对这种情况,常见的优化方案是backupRequest。简单的说,策略是这样的:如果一个正常请求的超时时间是1s,那么当超时时间超过xms时(eg.如果旧请求超时,新请求正常落在300ms以内,那么我们的请求就不会本次超时,会在超时时间内完成,这种机制对于延迟敏感的业务非常有效,但是请求必须重试,你已经掌握了微服务的重试机制,相信你在工作中遇到的问题也能游刃有余,下面简单总结一下:微服务重试非常重要,因为它可以避免一些网络波动导致的请求失败,提高服务稳定性。重试机制分为同步和异步两种模式。各有特点,需要结合业务选择。常见的重试算法包括线性退避、指数退避、随机退避以及它们两者的组合。重试风暴是微服务中的一大隐患。我们可以通过限制单机的重试流程,同意重试状态码来避免。超时场景重试优化。当上游发起的超时流量被下游接收到后,下游不会重复重试;合理配置链接超时时间。对于延迟敏感的业务,可以使用备份请求来降低长尾效应。
