当前位置: 首页 > 科技观察

分布式系列第一弹:分布式一致性!

时间:2023-03-22 13:28:47 科技观察

背??景在互联网时代和环境下,为了快速响应需求,提高系统吞吐量,往往会进行微服务改造,将复杂的系统和数据进行拆分;此时,一致性指的是分布式服务系统之间的弱点。一致性,包括应用系统一致性和数据一致性;生活中一致性的例子:银行办理转账时,扣掉你账户上的余额,然后增加别人账户上的余额;如果你的账户余额扣款成功,则增加如果别人的账户余额扣款失败,那么你将损失资金。反之,如果你的账户余额扣款失败,别人的账户余额增加成功,银行就会损失资金,银行需要赔付;下面介绍学习分布式一致性的理论和实践方案!基础理论ACID数据库管理系统(DBMS)在写入或更新数据的过程中,为保证事务的正确可靠,必须具备四个特性:原子性、一致性和隔离性。),耐久性。在数据库系统中,事务是指由一系列数据库操作组成的一个完整的逻辑过程。比如银行转账,从原账户中扣除金额,向目标账户中添加金额,这两个数据库操作加起来构成了一个完整的逻辑流程,不可分割。这个过程称为事务,具有ACID特性CAP理论一致性(Consistency)在分布式环境中,一致性是指数据在多个副本之间能否保持一致。在一致性要求下,当系统对数据的一致性状态进行更新操作时,应保证系统的数据仍处于一致性状态。可用性(Availability)是指系统提供的服务必须始终处于可用状态。对于用户的每一次操作请求,总能在限定的时间内返回结果。“限时”是指对于用户的一个操作请求,系统必须能够在规定的时间内返回相应的处理结果。如果超过这个时间范围,则认为系统不可用。“返回结果”是另一个非常重要的可用性指标,它要求系统在处理完用户请求后,能够返回一个正常的响应结果。分区容错(PartitionTolerance)分布式系统仍然需要能够处理任何网络分区故障。保证对外服务满足一致性和可用性,除非整个网络环境出现故障。实际情况CAP理论证明,任何分布式系统只能同时满足两点,不能同时满足C和A。那么P不能满足吗?满足C要求所有服务器的数据必须相同,也就是说要实现数据同步,同步需要时间吗?肯定是必须的,而且机器越多,同步时间肯定越慢。这里的问题来了,我们也同时遇到了A,也就是说我需要一个很短的同步时间。在这种情况下,机器不能太多,也就是说P不能满足C和P,那么A能不能满足呢?需要很多台服务器满足P,假设有1000台服务器,同时满足C,也就是说要保证每台机器的数据相同,同步的时间可以很长。在此情况下,我们当然无法保证用户随时访问各服务器所获得的数据是最新的。如果要获取最新的,就得等到所有的同步都结束了,才能获取到,但是我们的A要求你在短时间内就可以获取到想要的数据,这不是自相矛盾吗,所以这里的A不能满足分布式对于分布式系统来说,网络问题是必然会出现的异常情况,所以分区容错就成了分布式系统必须面对和解决的问题。因此,往往需要花精力在如何根据业务特点在C(一致性)和A(可用性)之间找到平衡点。)是三个短语的缩写。BASE理论是CAP中一致性和可用性之间权衡的结果。它源于对大型互联网系统分布式实践的总结,基于CAP定理逐步演化而来。BASE理论的核心思想是:即使不能实现强一致性,各个应用也可以根据自己的业务特点采用合适的方法使系统达到最终一致性。接下来我们看一下BASE中的三个要素:基本可用(BasicallyAvailable)基本可用是指当一个分布式系统出现不可预知的故障时,允许其失去部分可用性(注意这绝不等同于系统不可用)。例如:响应时间损失。正常情况下,在线搜索引擎需要在0.5秒内将相应的查询结果返回给用户,但由于故障,查询结果的响应时间会增加1到2秒。在电子商务网站购物时,消费者几乎可以顺利完成每一笔订单。但是在一些节日的购物高峰期,由于消费者购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到降级页面软状态(SoftState)软状态是指允许系统中的数据以一种中间状态存在,相信这种中间状态的存在不会影响系统整体的可用性,即允许系统的数据副本在不同节点上存在延迟它们之间数据同步的过程。最终一致(EventuallyConsistent)最终一致性强调的是所有的数据副本经过一段时间的同步后,最终能够达到一致的状态。所以,最终一致性的本质是系统需要保证最终的数据能够一致,而不需要实时保证系统数据的强一致性。总的来说,BASE理论是面向大规模高可用和可扩展的分布式系统的,这与传统事务的ACID特性相反。它与ACID的强一致性模型完全不同,而是通过牺牲强一致性获得的。可用性,允许数据在一段时间内不一致,但最终达到一致的状态。但同时,在实际的分布式场景中,不同的业务和组件对数据的一致性有不同的要求。因此,在具体的分布式系统架构设计过程中,ACID特性和BASE理论往往结合在一起。DistributedConsensusProtocolTwo-PhaseCommitProtocol(2PC)Phase1(VotingPhase):协调节点向所有参与节点询问是否可以执行提交操作,并开始等待各参与节点的响应。参与节点执行查询发起至今的所有事务操作,并将Undo信息和Redo信息写入日志(注意:如果成功,说明这里每个参与者都实际执行过事务操作)每个参与节点响应协调节点发起的查询,如果参与节点实际执行交易操作成功,则返回“同意”消息;如果参与节点的交易操作实际执行失败,则返回“中止”消息。当参与节点获得的相应消息都“同意”时:协调节点向所有参与节点发送“正式提交”请求。协调节点发送“Complete”消息并收到所有参与节点反馈的“Complete”消息后,交易完成出现问题:资源同步阻塞。在数据提交过程中,所有参与处理的服务器都处于阻塞状态。如果其他线程要访问临界区资源,则需要等待本地执行完成后会话请求释放临界区资源。因此,使用两阶段提交算法也会降低并发程序执行的效率。单点问题此外,单点问题可能发生。单点问题也称为单点服务器故障问题,是指当分布式集群系统的调度服务器发生故障时,由于缺少协调器,整个集群无法执行两阶段提交算法。单点问题也是两阶段提交的最大缺点,所以在使用两阶段提交算法时通常会做一些改进以满足系统稳定性的要求。数据不一致发生在Commit阶段。当统计集群中的服务器可以进行事务操作时,协调服务器会向这些处理事务操作的服务器发送commit提交请求。如果在这个过程中,其中一台或几台服务器出现网络故障,无法接收到协调服务器的提交请求,导致这些服务器无法完成最终的数据变更,就会造成整个分布式集群的数据不一致。三阶段提交协议(three-phasecommitprotocol,3PC)实际上是在两阶段算法基础上的优化和改进。为了解决两阶段协议中的同步阻塞等问题,三阶段提交协议在协调者和参与者中都引入了超时机制,将两阶段提交协议的第一阶段拆分为两个步骤:查询,然后再锁定资源,最后提交。注意事项一旦进入阶段3,如果协调器出现问题或者协调器与参与者之间的网络出现故障,即参与者无法及时收到协调器的DoCommit或abort请求,对于这种异常情况,参与者将在等待超时后,继续进行事务提交。三阶段提交协议存在的问题参与者收到PreCommit消息后,如果网络发生分区,协调者与部分参与者无法进行正常的网络通信,部分参与者仍会提交交易,必然会发生数据损坏。不一致。TCC无论是2PC还是3PC,都存在大粒度资源锁定的问题。我们先设想一个场景,用户在电商网站购买1000元商品,余额支付800元,红包支付200元。看一下2PC中的流程:prepare阶段:订单系统插入一条订单记录,未提交系统从余额系统中减去800元,锁住记录,写入redo和undo日志,减去200如果红包没有提交,从系统中抽取元,并添加到记录Lock,写入redo和undo日志,不提交commit阶段:订单系统提交,订单记录余额系统提交,释放锁红包系统提交,释放锁。为什么这是一个大粒度的资源锁?这是因为在prepare阶段,当数据库从用户余额中减去800元时,为了保持隔离,记录会被锁定。在事务提交之前,其他事务不能再访问该记录。但实际上,我们只需要预留其中的800元,并不需要锁定整个用户余额。这是2PC和3PC的局限性,因为它们是资源层协议,不能提供更灵活的资源锁定操作。为了解决这个问题,TCC应运而生。TCC本质上是一个两阶段提交协议,但与JTA中的两阶段协议不同,它是一个服务层协议,因此开发者可以根据业务自由控制资源锁定的粒度。下面看一下TCC协议的运行过程。TCC将事务提交流程分为三个阶段:try-confirm-cancel(其实TCC是try、confirm、cancel的缩写):try:完成业务检查,预留业务资源confirm:使用预留资源进行业务操作(需要保证幂等性)cancel:取消业务操作的执行,释放预留资源(需要保证幂等性)流程如下:事务发起者向事务协调者发起事务请求,事务协调者调用try方法所有交易参与者完成资源的预留。这个时候并没有真正执行业务,只是为后面要执行的具体业务预留了资源。这样就完成了第一阶段。如果事务协调器在参与者的try方法预留资源时发现资源不足,就会调用参与者的cancel方法回滚预留的资源。需要注意的是cancel方法需要实现业务幂等性,因为调用可能会失败(比如参与者因为网络原因接受了请求,但是交易协调者因为网络原因没有收到回执)会重试。如果事务协调器发现所有参与者的try方法都返回OK,则事务协调器不检查资源,直接调用所有参与者的confirm方法,直接进行具体的业务操作。如果协调者发现所有参与者的确认方法都OK,则分布式事务结束。如果协调器发现部分参与者的确认方法失败,或者由于网络原因没有收到回执,协调器将重试。如果重试一定次数还是失败,一般做事务补偿。通过一个支付场景看下TCC在这个场景下的流程:Try操作tryX下单系统创建待支付订单tryY冻结账户红包200元tryZ冻结资金账户800元确认操作confirmX订单更新成功paymentconfirmY扣除账户红包200元confirmZ从资金账户中扣除800元取消操作cancelX订单处理异常,资金红包退回,订单支付失败cancelY冻结红包失败,账户余额退回,订单支付失败cancelZ余额冻结失败,账户红包被退回,订单支付失败可见,我们使用冻结代替原来的账户锁(实际操作中,冻结操作可以通过数据库+日志减法实现),这样冻结操作后,其他事务也可以使用事务提交前的账户余额,提高并发性。综上所述,与两阶段提交协议相比,TCC主要有以下区别:2PC位于资源层,TCC位于服务层。2PC的接口由第三方厂商实现,TCC的接口由开发者实现。TCC可以更灵活地控制资源锁定的粒度。TCC对应用程序具有高度侵入性。业务逻辑的每个分支都需要实现三个操作:try、confirm、cancel。应用侵入性强,改造成本高。比如你的订单服务只有一个接口//修改代码statusorderClient.updateStatus();它必须拆分为三个接口,即:orderClient.tryUpateStatus();orderClient.confirmUpateStatus();orderClient.cancelUpateStatus();目前TCC的实现有以下几种tcc-transactions:ByteTCCspring-cloud-rest-tccfinalconsistentmodecacheconsistencymode高并发系统中一个共同的核心需求就是亿级的读需求。显然,关系型数据库并不是解决高并发读取需求的最佳方案。互联网的经典方法是使用缓存。常见的缓存方式分为本地缓存和分布式缓存两种;如果性能要求不是很高,首选分布式缓存;对于实时数据,对于性能和分布式一致性要求不高的可以使用本地缓存,比如一些人员的配置。即使短时间内不同机器的配置不同,也不会影响正常的业务流程。数据库和缓存只需要维护弱一致性,不需要强一致性,常用的缓存解决方案参考:美团面试题:缓存一致性,这是我的答案!查询模式服务操作需要提供查询接口,将操作执行状态输出到外部。服务操作的使用者可以通过查询接口了解服务操作的执行状态,然后根据不同的状态进行不同的处理操作。例如:定时任务监控正在生成的订单,发送单个群消息,RD收到群消息查询订单的具体状态,判断系统是否有问题,是否需要人工修复补偿模式。如果整个操作处于异常状态,我们需要纠正操作中有问题的子操作,这可能需要重新执行未完成的子操作,后者取消已经完成的子操作,并使整个分布式系统通过修复保持一致。使系统最终一致的努力称为补偿自动恢复:程序根据不一致发生的环境继续未完成的操作,或者自动回滚现有已完成的操作,以实现一致通知操作:如果程序无法自动恢复,并且在设计上考虑了不一致的场景,可以提供操作功能,可以通过操作进行人工补偿通知技术:如果不幸,系统无法自动回复,并且没有操作功能,必须通过技术解决手段,包括数据库更改或代码更改。这是最糟糕的情况。例如:监听到正在生成的订单后,系统自动将传入的消息再次推送重新生成入库订单并重试。如果系统无法自动恢复,则需要RD访问来定位和修复问题。异步保证模式异步保证模式是一种典型的补偿模式。常适用于对响应时间要求不高的用户。我们通常将这类操作从主流程中去掉,异步处理,处理完成后通过通知系统通知用户结果。该方案最大的优势是可以切高并发流量的峰值,例如:电子商务系统在物流、配送以及支付系统中的计费和入账的实践中,要执行的异步操作是长期封装保存在仓库中,然后通过定时未完成的任务进行补偿操作来实现异步保证模式。只要计时系统足够健壮,任何任务最终都会成功执行。例如:采购系统会发布和消耗预算,并同步记录日志。后期通过异步和定时任务重试来保证发布和消费的成功。周期校对模式是主要操作流程中系统之间进行校对操作,后期可以异步批量校对操作状态。如果发现不一致的操作,将进行补偿。补偿操作与补偿模式下的补偿操作一致。实现定期校对的关键是分发。系统需要自始至终有一个唯一的ID。常用的唯一ID生成方案就是一个例子:财务端的对账系统定期检查结算数据和业务单据数据的一致性。可靠消息模式可以使用消息队列进行异步操作,通过消息队列解耦调用方和被调用方,提高系统响应速度,同时达到消峰目的;对于消息队列,我们??需要建立专门的设施来保证消息的可靠发送和处理器的幂等消息的可靠发送。在发送消息之前,将消息持久化到数据库,标记状态为等待发送,然后发送消息。如果发送成功,将消息修改为发送成功。定时任务定期从数据库中检索。对于一定时间内未发送的消息,使用第三方消息管理器发送消息。发送消息前,先向第三方消息管理器发送预消息。消息管理器会将其持久化到数据库并将状态标记为未决。发送成功后,将消息标记为发送成功。定时任务从数据库中取出一定时间内未发送的消息,业务系统回头检查是否应该继续发送,并根据查询结果判断消息的状态。消息处理器的幂等性保证了消息一定要发送出去,所以需要有一个重试机制,有了重试机制,消息会重复出现,那么我们就需要对重复进行处理,常见的几种方案使用数据库表唯一索引防止重复,拒绝重复请求使用分布式中间件Redis防止重复状态机重载。与文档相关的业务会涉及到状态机,状态会在不同的情况下发生变化。如果状态机已经处于下一个状态,此时理论上不可能改变之前的状态,保证有限状态机的幂等性,使用乐观锁防止重用,数据更新是有条件的。设计系统时也是如此。合理选择乐观锁,通过版本或者其他条件做乐观锁,保证并发时及时更新例如:文档存储的http接口,前端提交时添加唯一id,防重复提交通过Redis,防止重复创建订单和订单对接存储系统进行mq异步交互,通过订单的中间状态Retry,下游做防重