“什么是分布式系统?这取决于看系统的角度。对于坐在键盘前使用IBM我的电脑的人来讲,电脑不是一个分布式的系统。但对于在电脑主板上趴着的虫子来讲,这台电脑就是一个分布式系统。” —— Leslie Lamport程序员
引言算法
分布式一致性问题随处可见,任何一个实体/联接模型,均可能存在分布式一致性问题。若是把单机拆开来看,CPU、内存、I/O设备组成的机箱自己就是一个小型的分布式系统,须要确保对这个系统操做的最终一致性。幸运的是这部分工做已经交给操做系统和数据库软件来帮咱们完成。而在大型分布式企业级应用中,分布式最终一致性方案须要根据系统自身特色量身定制,是系统设计的重点。近年来随着沪江业务的快速增加和微服务治理推广,本地ACID事务早已不能知足业务和系统的发展需求。大部分业务流程都须要跨多微服务的调用来协做完成,而且要求系统确保分布式最终一致性。数据库
能够选择分布式事务框架方案,目前主流的分布式事务框架大体可分为3类实现 :后端
基于XA协议的两阶段提交(2PC)方案微信
基于支付宝最先提出的TCC(Try、Confirm、Cancel)方案网络
基于ebay最先提出的消息队列异步确保方案架构
此外还有较轻的解决方案,业务系统能够根据自身须要,选择经过幂等/重试、状态机、恢复日志、异步校验等技术来确保最终一致性。并发
重型武器框架
采用分布式事务框架的方案,最终一致性由分布式事务框架保证,业务程序员对框架细节彻底透明。选择这种方案,须要注意几个点。首先,因为分阶段提交协议自己的脆弱性,主流分阶段提交协议如2PC,3PC, TCC都没法彻底确保最终一致性,要采用异步校验的手段兜底。其次,分阶段提交协议带来的高延迟,屡次协议通讯RTT带来的时间损耗。第三,基于消息队列异步确保的分布式事务框架实现,须要考虑消息可靠性和业务侵入问题。分布式事务框架也有巨大的优点,首先,分布式事务被框架封装成切面,业务开发只需关心纯业务。其次,分布式事务的代码开发量大大减小。对一致性和代码质量有极高要求的银行、金融领域,分布式事务框架是最佳选择。异步
轻型武器
不一样于采用分布式事务框架的最终一致性方案,程序员也能够选择经过幂等/重试、状态机、恢复日志、异步校验等技术来确保最终一致性。这种方案不受限于平台和框架,系统较精简灵活,初期业务系统大都基于这种分布式一致性解决方案。不过这种方案对业务开发的要求更高,分布式一致性逻辑要业务程序员代码实现,容易出现bug。
其实,主流的分布式事务框架也是经过这些基本的系统机制如幂等/重试、状态机、恢复日志、异步校验等来确保的最终一致性,对比两种方案,下文主要围绕后一种展开论述,讨论5点使系统达成分布式最终一致性的技术实践。
原则
一、CAP定理
以下三属性,任何一个联网的共享数据系统最多只能同时知足 2 个 :
一致性(Consistency) : 每次读取都会收到最新的写入或错误
可用性(Availability) : 每一个请求都会收到一个不是错误的响应
分区容忍性(Partition tolerance) : 节点之间的网络丢弃(或延迟)了任意数量的消息,系统仍继续运行
因为分区容忍性是可伸缩的最基本要求,放弃分区容忍性等于放弃可伸缩,因此分区容忍性是必选项,大部分的分布式系统都是在C和A之间作选择。须要注意的是,虽然C都叫一致性,但CAP定理中的C和数据库事务ACID中的C是彻底不一样的两个定义。
二、BASE原则
Basically Available : 基本可用
Soft state : 软状态
Eventual consistency : 最终一致性
BASE原则发展自CAP定理,舍弃了系统的强一致性而选择AP,但每一个应用能够根据自身的业务特色,采用适当的方式来使系统达到最终一致性。用较通俗的话来描述就是 : “过程宽松,结果严格,你的老板不关心过程,只看结果”。NoSQL数据库Cassandra就是遵循的BASE原则设计。不过也有分布式系统设计不是遵循BASE原则,而是选择CAP中的CP,如HBase。固然,系统对CAP三者的取舍并非一成不变,能够根据实际须要改变策略。
实践
一、重试
重试机制可使分布式不一致数据自动恢复,前提是重试接口要提供幂等保证。重试机制是达成分布式最终一致性的重要手段。例如,超时重传是TCP协议保证数据可靠性的一个重要机制,核心思想其实就是重试。在此我向你们推荐一个架构学习交流裙。交流学习裙号:687810532,里面会分享一些资深架构师录制的视频录像
同步重试 : 在上次请求失败或超时,程序再次发起同步调用请求。后端程序不推荐同步重试,其一由于同步等待占用系统线程资源,其二由于重试引发的流量放大,可能致使系统雪崩。
异步重试 : 经过异步系统(消息队列或调度中间件)对失败或超时请求再次发起调用。推荐这种方式的重试,重试的时间间隔能够设置为根据重试次数指数增加,超太重试阈值仍未成功,能够报警通知并由人工订正。
重试也是提升系统可用性的一种有效手段。若是一个服务的可用性为98%(有1个9),1次重试以后其可用性可达到99.96%(3个9),2次重试能够达到99.9992%(5个9)。
二、幂等
幂等的数学定义为
用通俗的话来讲就是 : 相同的操做执行屡次 和 执行一次产生的效果是同样的。有的操做是自然幂等的,如查询、删除操做。有的操做是人为使其幂等,例如TCP的超时重传操做就是幂等的,不管客户端将一个seq字节传送多少次,服务端窗口只会用一次该字节。幂等实现方式有不少 :
基于记录的悲观锁,MySQL中经过SELECT FOR UPDATE语句实现。这种实现方式要设置AUTOCOMMIT=0,加锁和更新记录在同一个事务中,长时间锁定记录会下降系统的TPS,高并发场景不推荐使用。
基于记录版本号或状态机的乐观锁方案,适用于更新数据场景。例如,用户下单购买一个商品的扣库存操做实现幂等,能够用以下SQL语句实现 : UPDATE stocktable SET stock = stock - 1, version = version + 1 WHERE product_id = 123 and version = 1
基于数据库惟一索引的去重表,适用于插入和更新数据的场景,由数据库唯一索引确保屡次插入和更新操做只有一次生效。
基于全局惟一标识token实现,这种方案要注意几点 : 一、这里校验token是否可以使用 和 置token为已使用,是一个CAS原子操做,须要确保在一个原子操做中。 二、若是token存储使用的是Redis,那么验证token的CAS操做可使用原子自增操做incr,若是Redis值大于1则token不可以使用,反之可以使用。还有一种实现方式是token生成系统将token预先写入Redis,用删除操做来校验token是否被使用,删除成功表明token未被使用可执行操做。 三、若是token存储使用的是MySQL,根据token分库分表和建唯一索引,同时经过insert语句来判断token是否存在,若是insert失败则token不可以使用,反之可以使用。
三、状态机
状态机是表示实体的状态根据条件转移的数学模型。经过状态机模型,系统能够判断当前不一致状态,以及如何校订不一致状态到一致状态。这样说可能比较抽象,咱们拿发微信群红包的例子来讲明。当你点开发红包按钮,输入总金额、红包个数、标题,点击支付成功后。其实根据时间前后红包系统后台至少经历过这样一个状态机 :
一、当输入总金额、红包个数、标题点击提交,首前后台建立一个初始化状态(INIT)红包
二、接着系统将根据你输入的总金额和个数n将红包拆分红n分,此时红包的状态为拆分红功(SPLITTED)
三、此时红包后台会监听异步支付消息,若是支付成功则将红包置为支付成功(PAID)
四、以后红包系统会通知微信IM系统,发送消息通知群里的用户,此时红包状态为(NOTIFIED)
5.一、群里的用户把红包抢光了,红包状态被系统置为已抢光(RUNOUT)
5.二、还有一种可能,若是群里都是程序员,忙着撸代码,没时间抢红包,必定时间后红包自动退款到支付帐号,红包状态便为(REFUND)
这只是一个正常业务流程的红包状态机,异常状况如拆分失败、支付失败、通知失败、退款失败等状况也同理存在一个状态机器。为了方便业务实体状态回滚和校订,状态机要尽可能设计精简,转移到下一个状态的边尽量的只有一条路径(终结状态会例外),这样在回滚和校订时可以明确前一个状态和后一个状态。举个例子,若是系统发现红包一直处于PAID状态,而并无流转到NOTIFIED状态,可以判断是通知群用户出现异常,能够根据实际状况从新通知群用户或者将超期红包退款。
四、恢复日志
恢复日志是程序现场的记录,也是业务数据恢复的重要依据。恢复日志log要求全局惟一的requestId来标示请求(实际的业务场景可采用不会重复有含义的业务id),出现异常,能够根据requestId维度redo和undo业务操做,恢复日志具体可分为三部分 :
requestId请求开始时,记录REQUEST START requestId
本地修改时,记录所有的(requestId,x,originalValue, destValue)四元组,x表明操做对象,修改前x的值为originalValue,本次修改的目的操做值为destValue
requestId结束时,记录REQUEST End requestId
恢复日志是系统从不一致的状态恢复到一致状态的重要数据,丢失恢复日志,意味着不一致可能没法恢复。为何是可能,由于有时能够经过状态机对不一致的状态进行恢复。
五、异步校验
完全解决分布式一致性问题,有著名的Paxos算法,经过该算法分布式系统自发达成一致性。而在具体的业务场景,彻底不须要系统自发的达成共识,咱们只要在业务系统外部加上严格的业务约束,用来仲裁业务系统的状态。经过异步校验,能够发现分布式系统中的异常状态,并经过恢复日志进行脚本批量恢复或者人工处理恢复,根据校验的粒度有 :
根据业务实体id校验,使用消息队列,将须要校验业务id投递给校验系统,进行异步校验。
根据时间维度批量校验,使用异步调度框架,根据时间粒度批量获取进行异步校验。
此外,并非全部系统都有可靠消息队列和调度服务支撑,业务系统能够增长一个本地业务id校验回执字段,校验系统根据校验步骤回调设置校验回执字段,并对校验未经过的数据进行重校验或者订正。
总结
分布式最终一致性问题,后端程序员在实际开发中常常遇到。在实际系统开发中为了确保最终一致性,每每须要组合多个技术点打出组合拳,由于招数是死的,程序员是活的。总结上面提到的技术点,咱们能够经过幂等和重试机制,使得不一致数据可以自动恢复;经过异步校验机制发现业务系统的不一致数据;经过状态机和恢复日志,纠正不一致的业务数据。最后,感谢阅读本文,欢迎留言讨论。在此我向你们推荐一个架构学习交流裙。交流学习裙号:687810532,里面会分享一些资深架构师录制的视频录像