摘要:微服务后,一次用户调用可能拆成多系统间的服务调用,任一服务调用异常,都可能导致用户请求最终失败。一个系统异常会影响所有依赖该系统所提供服务的服务消费者,甚至服务雪崩。
微服务后,一次用户调用可能拆成多系统间的服务调用,任一服务调用异常,都可能导致用户请求最终失败。一个系统异常会影响所有依赖该系统所提供服务的服务消费者,甚至服务雪崩。
所以针对服务调用,要设置超时时间,避免依赖服务迟迟不返回调用结果,拖死服务消费者。
但超时时间设定需考量:
太短,可能有些服务调用还没来得及执行完,就被丢弃太长,可能拖垮服务消费者需根据正常情况,服务提供者的服务水平决定。按服务提供者线上真实服务水平,取P999(99.9%)或P9999(99.99%)值的调用都在多少ms内返回为准。
2 重试虽设置超时时间可及时止损,但服务调用结果毕竟失败,而大部分情况,调用失败只是因为偶发网络问题或个别服务提供者节点异常,能换个节点再访问说不定就成功。
若一次服务调用失败概率1%,则连续两次服务调用失败概率0.01%,失败率大大降低。所以,还要设置服务调用超时后的重试次数。
若某服务调用的超时时间置100ms,重试次数置1,则当服务调用超过100ms后,服务消费者就会立即发起第二次服务调用,不会再等待第一次调用返回的结果。
3 双发由前文连续两次调用都失败概率0.01%,可得简单的提高服务调用成功率的办法-双发,每次服务消费者要发起服务调用时,都同时发起两次服务调用:
可提高调用成功率两次服务调用哪个先返回,就采用哪次的返回结果,平均响应时间也比一次调用更快但这样一次调用会给后端服务两倍压力,所消耗资源也加倍,“鲁莽”双发不可取。
更聪明的双发,“备份请求”(Backup Requests)。服务消费者发起一次服务调用后,给定时间内,若没返回请求结果,则Consumer立刻发起另一次服务调用。
注意该设定时间通常比超时时间短得多,如超时时间取P999,则备份请求时间可能取P99或P90,因为若在P99或P90时间内调用还没返回结果,大概率可认为这次请求属于慢请求,再调用理论上返回要更快。
实际线上服务,P999因长尾效应,可能远大于P99和P90。 如一个服务的P999=1s,而P99=200ms、P90=50ms,这样,若备份请求时间取P90,则第二次请求等待的时间只有50ms。
注意备份请求要设置一个最大重试比例,避免服务端异常时,大部分请求的响应时间都超过P90,导致请求量翻倍,给服务提供者造成更大压力。经验之谈,最大重试比例置15%:
尽量体现备份请求的优势不给服务提供者额外增加太大压力4 熔断前面手段在服务Provider偶发异常时很有效,但若Provider故障,短时间都无法恢复,无论超时重试还是双发:
无法提高服务调用成功率由于重试,还给Provider带来更大压力,加剧故障就需服务Consumer能探测到Provider故障,并短时间内停止请求,给Provider故障恢复时间,待Provider恢复后,再继续请求。如电路,电流负载过高,保险丝就熔断。
把客户端的每次服务调用,通过断路器封装,使用断路器来监控每一次服务调用。若某段时间内,服务调用失败次数达到一定阈值,则断路器就会被触发,后续的服务调用就直接返回,也就不会再向Provider发起请求。
熔断之后,一旦Provider恢复,服务调用咋恢复?这牵扯
断路器最经典实现Hystrix。Hystrix就包含三种状态:关闭、打开、半打开。Hystrix会把每次服务调用都用HystrixCommand封装,实时记录每次服务调用的状态,包括成功、失败、超时还是被线程拒绝。
当一段时间内服务调用失败率>阈值,断路器就会进入打开状态,新的服务调用会直接返回,不会向 Provider发起调用。再等设定的时间间隔后,断路器又会进入半打开,新的服务调用又可重新发给Provider;若一段时间内服务调用的失败率依然>阈值,断路器会重新打开,否则,断路器被关闭。
决定断路器是否打开的失败率阈值通过如下参数设定:
HystrixCommandProperties.circuitBreakerErrorThresholdPercentage;决定断路器何时进入半打开的时间间隔通过如下参数设定:
HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds统计指定时间段内服务调用失败率:
默认情况下,滑动窗口包含10个桶,每个桶时间宽度1s,每个桶内记录这1s内所有服务调用中成功的、失败的、超时的以及被线程拒绝的次数。
当新1s到来,滑动窗口往前滑动,丢弃掉最旧的1个桶,把最新1个桶包进来。
任意时刻,Hystrix都会取滑动窗口内所有服务调用的失败率作为断路器开关状态的判断依据,这10个桶内记录:
滑动窗口内所有服务的调用失败率 =(失败的+超时的+被线程拒绝的调用次数)/总调用次数
大部分服务调用都要设置超时时间及重试次数,但对非幂等的不可以重试,如大部分上行请求都是非幂等。
双发是在重试基础上的优化,减少超时等待的时间,对于长尾请求很有效。采用双发后,服务调用的P999能大幅减少,是提高服务调用成功率的有效手段。
熔断能很好地解决依赖服务故障引起的连锁反应,对于大规模服务调用的必不可少,尤其是对非关键路径的调用,即使调用失败也对最终结果影响不大的情况下,更应该引入熔断。
魔都架构师 | 全网30W技术追随者 大厂分布式系统/数据中台实战专家 主导交易系统百万级流量调优 & 车联网平台架构 AIGC应用开发先行者 | 区块链落地实践者 以技术驱动创新,我们的征途是改变世界! 实战干货:编程严选网来源:JavaEdge