若是这是第二次看到个人文章,欢迎 文末扫码订阅我哟~ 👉
本文长度为 4229字,建议阅读 11分钟。
这是本系列中既「数据一致性」后的第二章节——「高可用」的完结篇。html
前面几篇中z哥跟你聊了聊作「高可用」的意义,以及如何作「负载均衡」和「高可用三剑客」(熔断、限流、降级,文末会附上前文链接:))。此次,咱们来聊一聊在保证对外高可用的同时,憋出的“内伤”该如何经过「补偿」机制来自行消化。前端
以电商的购物场景为例:程序员
客户端 ---->购物车微服务 ---->订单微服务 ----> 支付微服务。
这种调用链很是广泛。算法
那么为何须要考虑补偿机制呢?sql
正如以前几篇文章所说,一次跨机器的通讯可能会通过DNS 服务,网卡、交换机、路由器、负载均衡等设备,这些设备都不必定是一直稳定的,在数据传输的整个过程当中,只要任意一个环节出错,都会致使问题的产生。json
而在分布式场景中,一个完整的业务又是由屡次跨机器通讯组成的,因此产生问题的几率成倍数增长。微信
可是,这些问题并不彻底表明真正的系统没法处理请求,因此咱们应当尽量的自动消化掉这些异常。网络
可能你会问,以前也看到过「补偿」和「事务补偿」或者「重试」,它们之间的关系是什么?数据结构
你其实能够不用太纠结这些名字,从目的来讲都是同样的。就是一旦某个操做发生了异常,如何经过内部机制将这个异常产生的「不一致」状态消除掉。架构
题外话:在Z哥看来,无论用什么方式,只要经过额外的方式解决了问题均可以理解为是「补偿」,因此「事务补偿」和「重试」都是「补偿」的子集。前者是一个逆向操做,然后者则是一个正向操做。只是从结果来看,二者的意义不一样。「事务补偿」意味着“放弃”,当前操做必然会失败。
▲事务补偿
「重试」则还有处理成功的机会。这两种方式分别适用于不一样的场景。
▲重试
由于「补偿」已是一个额外流程了,既然可以走这个额外流程,说明时效性并非第一考虑的因素,因此作补偿的核心要点是:宁肯慢,不可错。
所以,不要草率的就肯定了补偿的实施方案,须要谨慎的评估。虽然说错误没法100%避免,可是抱着这样的一个心态或多或少能够减小一些错误的发生。
作「补偿」的主流方式就前面提到的「事务补偿」和「重试」,如下会被称做「回滚」和「重试」。
咱们先来聊聊「回滚」。相比「重试」,它逻辑上更简单一些。
Z哥将回滚分为2种模式,一种叫「显式回滚」(调用逆向接口),一种叫「隐式回滚」(无需调用逆向接口)。
最多见的就是「显式回滚」。这个方案无非就是作2个事情:
首先要肯定失败的步骤和状态,从而肯定须要回滚的范围。一个业务的流程,每每在设计之初就制定好了,因此肯定回滚的范围比较容易。但这里惟一须要注意的一点就是:若是在一个业务处理中涉及到的服务并非都提供了「回滚接口」,那么在编排服务时应该把提供「回滚接口」的服务放在前面,这样当后面的工做服务错误时还有机会「回滚」。
其次要能提供「回滚」操做使用到的业务数据。「回滚」时提供的数据越多,越有益于程序的健壮性。由于程序能够在收到「回滚」操做的时候能够作业务的检查,好比检查帐户是否相等,金额是否一致等等。
因为这个中间状态的数据结构和数据大小并不固定,因此Z哥建议你在实现这点的时候能够将相关的数据序列化成一个json,而后存放到一个nosql类型的存储中。
「隐式回滚」相对来讲运用场景比较少。它意味着这个回滚动做你不须要进行额外处理,下游服务内部有相似“预占”而且“超时失效”的机制的。例如:
电商场景中,会将订单中的商品先预占库存,等待用户在 15 分钟内支付。若是没有收到用户的支付,则释放库存。
下面聊聊能够有不少玩法,也更容易陷入坑里的「重试」。
「重试」最大的好处在于,业务系统能够不须要提供「逆向接口」,这是一个对长期开发成本特别大的利好,毕竟业务是每天在变的。因此,在可能的状况下,应该优先考虑使用「重试」。
不过,相比「回滚」来讲「重试」的适用场景更少一些,因此咱们第一步首先要判断,当前场景是否适合「重试」。好比:
若是肯定要进行「重试」,咱们还须要选定一个合适的「重试策略」。主流的「重试策略」主要是如下几种。
策略1.当即重试。有时故障是候暂时性,多是因网络数据包冲突或硬件组件流量高峰等事件形成的。在此状况下,适合当即重试操做。不过,当即重试次数不该超过一次,若是当即重试失败,应改用其它的策略。
策略2.固定间隔。应用程序每次尝试的间隔时间相同。 这个好理解,例如,固定每 3 秒重试操做。(如下全部示例代码中的具体的数字仅供参考。)
策略1和策略2多用于前端系统的交互式操做中。
策略3.增量间隔。每一次的重试间隔时间增量递增。好比,第一次0秒、第二次3秒、第三次6秒,九、十二、15这样。
return (retryCount - 1) * incrementInterval;
使得失败次数越多的重试请求优先级排到越后面,给新进入的重试请求让道。
策略4.指数间隔。每一次的重试间隔呈指数级增长。和增量间隔“异曲同工”,都是想让失败次数越多的重试请求优先级排到越后面,只不过这个方案的增加幅度更大一些。
return 2 ^ retryCount;
策略5.全抖动。在递增的基础上,增长随机性(能够把其中的指数增加部分替换成增量增加。)。适用于将某一时刻集中产生的大量重试请求进行压力分散的场景。
return random(0 , 2 ^ retryCount);
策略6.等抖动。在「指数间隔」和「全抖动」之间寻求一个中庸的方案,下降随机性的做用。适用场景和「全抖动」同样。
var baseNum = 2 ^ retryCount;
return baseNum + random(0 , baseNum);
三、四、五、6策略的表现状况大体是这样。(x轴为重试次数)
为何说「重试」有坑呢?
正如前面聊到的那样,出于对开发成本考虑,你在作「重试」的时候多是复用的常规调用的接口。那么此时就不得不提一个「幂等性」问题。
若是实现「重试」选用的技术方案不能100%确保不会重复发起重试,那么「幂等性」问题是一个必需要考虑的问题。哪怕技术方案能够确保100%不会重复发起重试,出于对意外状况的考量,尽可能也考虑一下「幂等性」问题。
幂等性:无论对程序发起几回重复调用,程序表现的状态(全部相关的数据变化)与调用一次的结果是一致的话,就是保证了幂等性。这意味着能够根据须要重复或重试操做,而不会致使意外的影响。对于非幂等操做,算法可能必须跟踪操做是否已经执行。
因此,一旦某个功能支持「重试」,那么整个链路上的接口都须要考虑幂等性问题,不能由于服务的屡次调用而致使业务数据的累计增长或减小。
知足「幂等性」其实就是须要想办法识别重复的请求,而且将其过滤掉。思路就是:
第1点,咱们可使用一个全局惟一id生成器或者生成服务(能够扩展阅读,分布式系统中的必备良药 —— 全局惟一单据号生成)。 或者简单粗暴一些,使用官方类库自带的Guid、uuid之类的也行。
而后经过rpc框架在发起调用的客户端中,对每一个请求增长一个惟一标识的字段进行赋值。
第2点,咱们能够在服务端经过Aop的方式切入到实际的处理逻辑代码以前和以后,一块儿配合作验证。
大体的代码思路以下。
【方法执行前】
if(isExistLog(requestId)){ //1.判断请求是否已被接收过。 对应序号3 var lastResult = getLastResult(); //2.获取用于判断以前的请求是否已经处理完成。 对应序号4 if(lastResult == null){ var result = waitResult(); //挂起等待处理完成 return result; } else{ return lastResult; } } else{ log(requestId); //3.记录该请求已接收 } //do something.. 【方法执行后】 logResult(requestId, result); //4.将结果也更新一下。
若是「补偿」这个工做是经过MQ来进行的话,这事就能够直接在对接MQ所封装的SDK中作。在生产端赋值全局惟一标识,在消费端经过惟一标识消重。
再聊一些Z哥积累的最佳实践吧(划重点:)),都是针对「重试」的,的确这也是工做中最经常使用的方案。
「重试」特别适合在高负载状况下被「降级」,固然也应当受到「限流」和「熔断」机制的影响。当「重试」的“矛”与「限流」和「熔断」的“盾”搭配使用,效果才是最好。
须要衡量增长补偿机制的投入产出比。一些不是很重要的问题时,应该「快速失败」而不是「重试」。
过分积极的重试策略(例如间隔过短或重试次数过多)会对下游服务形成不利影响,这点必定要注意。
必定要给「重试」制定一个终止策略。
当回滚的过程很困难或代价很大的状况下,能够接受很长的间隔及大量的重试次数,DDD中常常被提到的「saga」模式其实也是这样的思路。不过,前提是不会由于保留或锁定稀缺资源而阻止其余操做(好比一、二、三、四、5几个串行操做。因为2一直没处理完成致使三、四、5无法继续进行)。
这篇咱们先聊了下作「补偿」的意义,以及作补偿的2个方式「回滚」和「重试」的实现思路。
而后,提醒你要注意「重试」的时候须要考虑幂等性问题,而且z哥也给出了一个解决思路。
最后,分享了几个z哥总结的针对「重试」的最佳实践。
但愿对你有所帮助。
Question:
你以前有哪些时候是经过本身人工来作「补偿」的经历吗?欢迎吐槽~
z哥本身就有屡次熬到半夜才把“意外”形成的混乱清理干净,刻骨铭心啊😂。
相关文章:
做者:Zachary
出处:https://www.cnblogs.com/Zacha...
▶关于做者:张帆(Zachary,我的微信号:Zachary-ZF)。坚持用心打磨每一篇高质量原创。欢迎扫描下方的二维码加入哦~。
按期发表原创内容: 架构设计丨分布式系统丨产品丨运营丨一些思考。若是你是初级程序员,想提高但不知道如何下手。又或者作程序员多年,陷入了一些瓶颈想拓宽一下视野。欢迎关注个人公众号「跨界架构师」,回复「技术」,送你一份我长期收集和整理的思惟导图。
若是你是运营,面对不断变化的市场一筹莫展。又或者想了解主流的运营策略,以丰富本身的“仓库”。欢迎关注个人公众号「跨界架构师」,回复「运营」,送你一份我长期收集和整理的思惟导图。