分布式概念

这篇具有很好参考价值的文章主要介绍了分布式概念。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、CAP定理和BASE定理

1.1 CAP定理

  在分布式系统中,一个Web应用最多只能同时支持的两个属性:

  1. 一致性(C): 在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
  2. 可用性(A): 在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
  3. 分区容错性(P): 即使出现单个组件无法可用,操作依然可以完成。
  • 1、一致性
      更新操作成功并返回客户端完成后,所有节点在同一时间的数据完全一致,不能存在中间状态。分布式环境中,一致性是指多个应用之间能否保持一致的特性。
      数据一致性分为强一致性、弱一致性、最终一致性。

  如果时刻保证客户端看到的数据都是一致的,那么称之为强一致性
  如果允许存在中间状态,只要求经过一段时间后,数据最终是一致的,则称之为最终一致性
  此外,如果允许存在部分数据不一致,那么就称之为弱一致性

  • 2、可用性
      系统提供的服务必须一直处于可用哪个的状态,对于用户的每个操作请求总是能够在有限的时间内返回结果。
      有限时间内:对于用户的一个操作请求,系统必须能够在指定的时间(响应时间)内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的。
      返回正常结果:要求系统在完成对用户请求的处理后,返回一个正常的响应结果。正常的响应结果通常能够明确地反映出对请求的处理结果,即成功或失败。
  • 3、分区容错性
      分布式系统在遇到任何网络分区故障时,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障。
      网络分区,是指分布式系统中,不同的节点分布在不同的子网络(机房/异地网络)中,由于一些特殊的原因导致这些子网络之间出现网络不连通的状态,但各个子网络的内部网络是正常的,从而导致整个系统的网络环境被切分成了若干孤立的区域。

1.2 CAP取舍

  CAP理论就是说在分布式存储系统中,最多只能实现上面的两点。而由于当前的网络硬件肯定会出现延迟丢包等问题,所以分区容忍性是我们必须需要实现的。所以我们只能在一致性和可用性之间进行权衡。3种取舍策略:

  • 放弃P
      放弃P的同时也就意味着放弃了系统的扩展性,也就是分布式节点受限,没办法部署子节点,这是违背分布式系统设计的初衷的。
  • 放弃A
      如果不要求A(可用),相当于每个请求都需要在服务器之间保持强一致,而P(分区)会导致同步时间无限延长(也就是等待数据同步完才能正常访问服务),一旦发生网络故障或者消息丢失等情况,就要牺牲用户的体验,等待所有数据全部一致了之后再让用户访问系统。
      设计成CP的系统其实不少,最典型的就是分布式数据库,如Redis、HBase等。对于这些分布式数据库来说,数据的一致性是最基本的要求,因为如果连这个标准都达不到,那么直接采用关系型数据库就好,没必要再浪费资源来部署分布式数据库。
  • 放弃C
      要高可用并允许分区,则需放弃一致性。一旦分区发生,节点之间可能会失去联系,为了高可用,每个节点只能用本地数据提供服务,而这样会导致全局数据的不一致性。典型的应用就如抢购手机场景,可能前几秒你浏览商品的时候页面提示是有库存的,当你选择完商品准备下单的时候,系统提示你下单失败,商品已售完。这其实就是先在 A(可用性)方面保证系统可以正常的服务,然后在数据的一致性方面做了些牺牲,虽然多少会影响一些用户体验,但也不至于造成用户购物流程的严重阻塞。

  对于分布式系统来说,P是不能放弃的,因此通常是在可用性和强一致性之间权衡。

  在互联网领域的绝大多数的场景中,都需要牺牲强一致性来换取系统的高可用性,系统往往只需要保证最终一致性。

  • 为什么分布式系统中无法同时保证一致性和可用性
      对于分布式系统而言,分区容错性是一个最基本的要求,因此基本上我们在设计分布式系统的时候只能从一致性(C)和可用性(A)之间进行取舍。
      如果保证了一致性(C):对于节点N1和N2,当往N1里写数据时,N2上的操作必须被暂停,只有当N1同步数据到N2时才能对N2进行读写请求,在N2被暂停操作期间客户端提交的请求会收到失败或超时。显然,这与可用性是相悖的。
      如果保证了可用性(A):那就不能暂停N2的读写操作,但同时N1在写数据的话,这就违背了一致性的要求。

  对于多数大型互联网应用的场景,主机众多、部署分散,且现在的集群规模越来越大,所以节点故障、网络故障是常态,且要保证服务可用性达到 N 个 9,即保证 P 和 A,舍弃C(退而求其次保证最终一致性)。虽然某些地方会影响客户体验,但没达到造成用户流程的严重程度。
  对于涉及到钱财这样不能有一丝让步的场景,C 必须保证。网络发生故障宁可停止服务,这是保证 CA,舍弃 P。

  • CAP和ACID中,A和C的区别
      ACID中的A指的是原子性(Atomicity),是指事务被视为一个不可分割的最小操作单元,事务中的所有操作要么全部提交成功,要么全部失败回滚;CAP中的A指的是可用性(Availability),是指集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。
      ACID一致性是有关数据库规则,数据库总是从一个一致性的状态转换到另外一个一致性的状态;CAP的一致性是分布式多服务器之间复制数据令这些服务器拥有同样的数据,由于网速限制,这种复制在不同的服务器上所消耗的时间是不固定的,集群通过组织客户端查看不同节点上还未同步的数据维持逻辑视图,这是一种分布式领域的一致性概念。

  ACID里的一致性指的是事务执行前后,数据库完整性。而CAP的一致性,指的是分布式节点的数据的一致性。

1.3 BASE定理

  CAP是分布式系统设计理论,BASE是CAP理论中AP方案的延伸。对于C,我们采用的方式和策略就是保证最终一致性。
  BASE是Basically Available(基本可用)、Soft state(软状态)和Eventually consistent(最终一致性)三个短语的缩写。BASE基于CAP定理演化而来,核心思想是即使无法做到强一致性,但每个应用都可以根据自身业务特点,采用适当的方式来使系统达到最终一致性。

  • 1、Basically Available(基本可用)
      基本可用是指分布式系统在出现不可预知的故障的时候,允许损失部分可用性,但不等于系统不可用。两个就是“基本可用”的典型例子:
      1、响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
      2、功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
  • 2、Soft state(软状态)
      指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性。
      允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
  • 3、Eventually consistent(最终一致性)
      强调系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。其本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。

  在实际工程实践中,最终一致性存在以下五类主要变种:

  • 1、因果一致性
      因果一致性是指,如果进程A在更新完某个数据项后通知了进程B,那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值,并且如果进程B要对该数据项进行更新操作的话,务必基于进程A更新后的最新值,即不能发生丢失更新情况。与此同时,与进程A无因果关系的进程C的数据访问则没有这样的限制。
  • 2、读己之所写
      读己之所写是指,进程A更新一个数据项之后,它自己总是能够访问到更新过的最新值,而不会看到旧值。也就是说,对于单个数据获取者而言,其读取到的数据一定不会比自己上次写入的值旧。因此,读己之所写也可以看作是一种特殊的因果一致性。
  • 3、会话一致性
      会话一致性将对系统数据的访问过程框定在了一个会话当中:系统能保证在同一个有效的会话中实现“读己之所写”的一致性,也就是说,执行更新操作之后,客户端能够在同一个会话中始终读取到该数据项的最新值。
  • 4、单调读一致性
      单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后,那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。
  • 5、单调写一致性
      单调写一致性是指,一个系统需要能够保证来自同一个进程的写操作被顺序地执行。

  在实际系统实践中,可以将其中的若干个变种互相结合起来,以构建一个具有最终一致性的分布式系统。事实上,可以将其中的若干个变种相互结合起来,以构建一个具有最终一致性特性的分布式系统。
  总的来说,BASE理论面向的是大型高可用可扩展的分布式系统,和传统事务的ACID特性使相反的,它完全不同于ACID的强一致性模型,而是提出通过牺牲强一致性来获得可用性,并允许数据在一段时间内是不一致的,但最终达到一致状态。

二、分布式事务

分布式概念,【综合】,分布式,CAP,BASE,分布式事务

2.1 刚性事务

  在X/Open DTP(Distributed Transaction Process)模型里,有三个角色:
  AP:Application,应用程序。也就是业务层。哪些操作属于一个事务,就是AP定义的。
  TM:Transaction Manager,事务管理器。接收AP的事务请求,对全局事务进行管理,管理事务分支状态,协调RM的处理,通知RM哪些操作属于哪些全局事务以及事务分支等等。这个也是整个事务调度模型的核心部分。
  RM:Resource Manager,资源管理器。一般是数据库,也可以是其他的资源管理器,如消息队列(如JMS数据源),文件系统等。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  XA把参与事务的角色分成AP、RM、TM。AP,即应用,也就是我们的业务服务。RM指的是资源管理器,即DB、MQ等。TM则是事务管理器。
  AP自己操作TM,当需要事务时,AP向TM请求发起事务,TM负责整个事务的提交,回滚等。
  XA规范是 X/Open 组织定义的分布式事务处理(DTP,Distributed Transaction Processing)标准。

  XA 规范描述了全局的事务管理器与局部的资源管理器之间的接口。XA规范的目的是允许的多个资源(如数据库,应用服务器,消息队列等)在同一事务中访问,这样可以使ACID属性跨越应用程序而保持有效。
  XA规范使用两阶段提交(2PC,Two-Phase Commit)协议来保证所有资源同时提交或回滚任何特定的事务。

  XA规范(XA Specification) 是X/OPEN 提出的分布式事务处理规范。XA则
规范了TM与RM之间的通信接口,在TM与多个RM之间形成一个双向通信桥梁,从而在多个数据库资源下保证ACID四个特性。目前知名的数据库,如Oracle、DB2、mysql等,都是实现了XA接口的,都可以作为RM。
  XA是数据库的分布式事务,强一致性,在整个过程中,数据一张锁住状态,即从prepare到commit、rollback的整个过程中,TM一直把持着数据库的锁,如果有其他⼈要修改数据库的该条数据,就必须等待锁的释放,存在长事务风险。

  • XA协议的实现
      1、两阶段提交(2PC)
      2、三阶段提交(3PC):对 2PC协议的⼀种扩展。
      3、Seata。Seata 是一款开源的分布式事务解决方案,致力于在微服务架构下提供高性能和简单易用的分布式事务服务。Seata为用户提供了AT、TCC、SAGA 和XA事务模式。
      4、作为Java平台上事务规范JTA(Java Transaction API)也定义了对XA事务的支持。实际上,JTA是基于XA架构上建模的,在JTA中,事务管理器抽象为TransactionManager接口,并通过底层事务服务(即JTS)实现。像很多其他的Java规范一样,JTA仅仅定义了接口,具体的实现则是由供应商(如J2EE厂商)负责提供,目前JTA的实现主要由以下2种:
  1. J2EE容器所提供的JTA实现(JBoss)。
    2.独立的JTA实现:如JOTM、Atomikos。

  这些实现可以应用在那些不使用J2EE应用服务器的环境里用以提供分布事
事务保证。如Tomcat、Jetty以及普通的java应用。
  5、JTS规范。为了规范事务开发,Java增加了关于事务的规范,即JTA和JTS。
  JTA定义了一套接口,其中约定了几种主要的角色:TransactionManager、UserTransaction、Transaction、XAResource,并定义了这些角色之间需要遵守的规范,如Transaction的委托给TransactionManager等。
  TransactionManager:事务管理器,可以创建新的事务,并设置事务的相关属性,还允许应用获取创建后的事务,并且将事务与线程绑定。具体有以下方法:

  begin:创建新的事务,并且将事务与线程关联,如果不支持嵌套事务并且事务已存在将抛出 NotSupportedException 异常。
  commit:提交与线程关联的事务,并断开线程与事务的关联。如果没有事务与线程关联将抛出异常。实现将触发 Transaction.commit、XAResource.prepare、XAResource.commit 方法调用。
  rollback:回滚线程关联的事务,并断开线程与事务的关联。如果没有事务与线程关联也将抛出异常。实现将触发 Transaction.rollback、XAResource.rollback 方法的调用。
  getTransaction:获取线程关联的事务 Transaction 对象,如果没有事务与当前线程关联则返回 null。
  setTansactionalTimeout:设置线程关联事务的超时秒数。
  getStatus:获取线程关联事务的状态。
  setRollbackOnly:设置线程关联事务的唯一结果是回滚。
  suspend:暂时挂起与线程关联的事务,将断开线程与事务的关联,方法返回的 Transaction 对象可作为 resume 方法参数恢复线程与事务的关系。实现将触发 Transaction.delistResource 与 XResource.end 方法的调用。
  resume:恢复指定事务与线程之间的关联。实现将触发 Transaction.enlistResource、XAResource.start 方法的调用。

  Transaction:事务接口,用于对活动的事务进行操作,这里活动的事务是指未提交的事务,对其他事务不可见。

  enlistResource:将资源添加到事务,以便执行两阶段提交,允许添加多个资源,资源的最终的操作结果将与事务保持一致,即要么提交、要么回滚。实现将触发 XAResource.start 方开启事务分支。
  delistResource:解除资源与事务的关联。实现将触发 XAResource.end 方法调用结束事务分支。
  registerSynchronization:注册 Synchronization,接口方法将在事务完成前后被回调。
  commit:与 TransactionManager.commit 含义相同。
  getStatus:与 TransactionManager.getStatus 含义相同。
  rollback:与 TransactionManager.rollback 含义相同。
  setRollbackOnly:与 TransactionManager.setRollbackOnly 含义相同。

  XAResource:用来表示分布式事务角色中的资源管理器,资源适配器将实现 XAResource 接口以支持事务与资源的关联,允许不同的线程同时对 XAResource 进行操作,但一个 XAResource 同时只能关联一个事务。事务提交时,触发资源管理器准备与提交。具体方法:

  start:开启事务分支,全局事务通过该方法与事务资源关联,由资源适配器获取资源(连接)时隐式触发。一但调用该方法资源将一直保持开启状态,直到释放(close)资源。
  end:结束事务分支,解除资源与事务的关联,调用该方法后可再调用 start 方法与其他事务建立关联。
  prepare:为 xid 指定的事务提交做准备。
  commit:提交 xid 指定的事务分支。
  rollback:回滚 xid 指定的事务分支。
  isSameRM:判断当前资源管理器是否与给定的资源管理器相同,用于 Transaction.enlistResource 添加资源时判断资源是否已添加。
  recover:用于意外导致事务管理器与资源管理器断开通信后,事务管理器恢复时查询准备好的事务。XAResource 在故障发生时未持久化,事务管理器需要有某种方法查找 XAResource,如通过 JNDI 查找机制,事务管理器可以忽略不属于它的事务。
  forget:用于忘记准备好的事务分支。
  getTransactionTimeout:获取事务超时秒数。
  setTransactionTimeout:设置事务超时秒数。

  JTS也是一组规范,上面提到JTA中需要角色之间的交互,那应该如何交互?JTS就是约定了交互细节的规范。
  总体上来说JTA更多的是从框架的⻆度来约定程序角色的接口,而JTS则是从具体实现的⻆度来约定程序角色之间的接口,两者各司其职。
  6、Seata AT模式。Seata AT模式是增强型2pc模式。AT 模式: 两阶段提交协议的演变,没有一直锁表:

一阶段:业务数据和回滚日志记录在同一个本地事务中提交,释放本地锁和连接资源。
二阶段:提交异步化,非常快速地完成。或回滚通过一阶段的回滚日志进行反向补偿。

  • XA的主要限制
      1、必须要拿到所有数据源,且数据源还要支持XA协议。目前MySQL中只有InnoDB存储引擎支持XA协议。
      性能比较差,要把所有涉及到的数据都要锁定,是强一致性的,会产生长事务。
2.1.1 两阶段提交协议

  2PC分为两个阶段处理,阶段一:提交事务请求;阶段二:执行事务提交。
  如果阶段一超时或者出现异常,2PC的阶段二就会中断事务。

  • 阶段一:提交事务请求
      1、事务询问。协调者向所有参与者发送事务内容,询问是否可以执行提交操作,并开始等待各参与者进⾏响应;
      2、执行事务。各参与者节点,执行事务操作,并将Undo和Redo操作计入本机事务日志;
      3、各参与者向协调者反馈事务问询的响应。成功执行返回Yes,否则返回No。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  • 阶段二:执行事务提交
      协调者在阶段二决定是否最终执行事务提交操作。这一阶段包含两种情形。其中一种情形为:所有参与者reply Yes,那么执行事务提交。
  1. 发送提交请求。协调者向所有参与者发送Commit请求;
  2. 事务提交。参与者收到Commit请求后,会正式执行事务提交操作,并在完成提交操作之后,释放在整个事务执行期间占用的资源;
  3. 反馈事务提交结果。参与者在完成事务提交后,写协调者发送Ack消息确认;
  4. 完成事务。协调者在收到所有参与者的Ack后,完成事务。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  • 阶段二:中断事务
      事情总会出现意外,当存在某一参与者向协调者发送No响应,或者等待超时。协调者只要无法收到所有参与者的Yes响应,就会中断事务。
  1. 发送回滚请求。协调者向所有参与者发送Rollback请求;
  2. 回滚。参与者收到请求后,利用本机Undo信息,执行Rollback操作。并在回滚结束后释放该事务所占用的系统资源;
  3. 反馈回滚结果。参与者在完成回滚操作后,向协调者发送Ack消息;
  4. 中断事务。协调者收到所有参与者的回滚Ack消息后,完成事务中断。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  2pc中存在两个角色:事务协调者(seata等)和事务参与者,事务参与者通常是指应用的数据库。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  2PC方案实际很少用,一般来说某个系统内部如果出现跨多个库的这么一个操作,是不合规的。 现在微服务,一个大的系统分成几十甚至上百个服务。一般来说,我们的规定和规范,是要求每个服务只能操作自己对应的一个数据库。

  • 二阶段事务的缺点
      1)同步阻塞问题/性能问题。执行过程中,所有参与节点都是事务阻塞型的。XA协议遵循强一致性。在事务执行过程中,各个节点占用着数据库资源,只有当所有节点准备完毕,事务协调者才会通知提交,参与者提交后释放资源。这样的过程有着非常明显的性能问题。
      2)单点故障。事务协调者是整个XA模型的核心,一旦事务协调者节点挂掉,参与者收不到提交或是回滚通知,参与者会一直处于中间状态无法完成事务。
      3)数据不一致/脑裂问题。在二阶段提交的阶段二中,当协调者向参与者发送 commit 请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,导致只有一部分参与者接受到了commit请求。于是整个分布式系统便出现了数据部一致性的现象(脑裂现象)。
      4)数据状态不确定(二阶段无法解决的问题 )。 协调者再发出 commit 消息之后宕机,而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者,这条事务的状态也是不确定的,没人知道事务是否被已经提交。
2.1.2 三阶段提交协议

  三阶段提交协议(Three-phase commit protocol),是二阶段提交(2PC)的改进版本。
  与两阶段提交不同的是,三阶段提交有两个改动点:

  1、引入超时机制。同时在协调者和参与者中都引入超时机制。
  2、在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。也就是说,除了引入超时机制之外,3PC把2PC的准备阶段再次一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  • 阶段1、CanCommit
      1、事务询问。协调者向所有参与者发送包含事务内容的canCommit的请求,询问是否可以执行事务提交,并等待应答;
      2、各参与者反馈事务询问。正常情况下,如果参与者认为可以顺利执行事
    务,则返回Yes,否则返回No。

  • 阶段2、PreCommit
      在本阶段,协调者会根据上一阶段的反馈情况来决定是否可以执行事务的
    PreCommit操作。有以下两种可能:
    【执行事务预提交】
      1、发送预提交请求。协调者向所有节点发出PreCommit请求,并进入prepared阶段;
      2、事务预提交。参与者收到PreCommit请求后,会执行事务操作,并将Undo和Redo日志写入本机事务日志;
      3、各参与者成功执行事务操作,同时将反馈以Ack响应形式发送给协调者,同事等待最终的Commit或Abort指令。
    【中断事务】
      假如任意一个参与者向协调者发送No响应,或者等待超时,协调者在没有得到所有参与者响应时,即可以中断事务。
      1、发送中断请求。 协调者向所有参与者发送Abort请求;
      2、中断事务。无论是收到协调者的Abort请求,还是等待协调者请求过程中出现超时,参与者都会中断事务。

  • 阶段3、doCommit
      在这个阶段,会真正的进行事务提交,同样存在两种可能。
    【执行提交】
      1、发送提交请求。假如协调者收到了所有参与者的Ack响应,那么将从预提交转换到提交状态,并向所有参与者,发送doCommit请求;
      2、事务提交。参与者收到doCommit请求后,会正式执行事务提交操作,并在完成提交操作后释放占用资源;
      3、反馈事务提交结果。参与者将在完成事务提交后,向协调者发送Ack消
    息;
      4、完成事务。协调者接收到所有参与者的Ack消息后,完成事务。
    【中断事务】
      在该阶段,假设正常状态的协调者接收到任一个参与者发送的No响应,或在超时时间内,仍旧没收到反馈消息,就会中断事务。
      1、发送中断请求。协调者向所有的参与者发送abort请求;
      2、事务回滚。参与者收到abort请求后,会利用阶段二中的Undo消息执行事务回滚,并在完成回滚后释放占用资源;
      3、反馈事务回滚结果。参与者在完成回滚后向协调者发送Ack消息;
      4、中端事务。协调者接收到所有参与者反馈的Ack消息后,完成事务中断。

2.1.3 2PC和3PC的区别

  3PC有效降低了2PC带来的参与者阻塞范围,并且能够在出现单点故障后
继续达成一致;
  但3PC带来了新的问题,在参与者收到preCommit消息后,如果网络出现分区,协调者和参与者无法进行后续的通信,这种情况下,参与者在等待超时后,依旧会执行事务提交,这样会导致数据的不一致。

  • 2PC和3PC的区别
      三阶段提交协议在协调者和参与者中都引入超时机制,并且把两阶段提交协议的第一个阶段拆分成了两步:询问,然后再锁资源,最后真正提交。三阶段提交的三个阶段分别为:can_commit,pre_commit,do_commit。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
      在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者abort请求时,会在等待超时之后,继续进行事务的提交。
  • 3PC主要解决的单点故障问题
      相对于2PC,3PC主要解决的单点故障问题,并减少阻塞, 因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。
      但是这种机制也会导致数据一致性问题,因为,由于网络原因,协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。
  • 3PC相对于2PC阶段到底优化了什么地方
      相比较2PC阶段,3PC对于协调者(Coordinator)和参与者(Partcipant)都设置了超时时间,而2PC只有协调者才拥有超时机制。这解决了什么问题呢?
      这个优化点,主要是避免了参与者在长时间无法与协调者节点通讯(协调者挂掉了)的情况下,无法释放资源的问题,因为参与者自身拥有超时机制会在超时后,自动进行本地commit从而进行释放资源。而这种机制也侧面降低了整个事务的阻塞时间和范围。
2.1.4 XA规范的问题

  XA也算分布式事务处理的规范了,但在互联网中很少使用,究其原因有以下几个:

  • 性能(阻塞性协议,增加响应时间、锁时间、死锁);
  • 数据库支持完善度(MySQL 5.7之前都有缺陷);
    *协调者依赖独立的J2EE中间件(早期重量级Weblogic、Jboss、后期轻量级Atomikos、Narayana和Bitronix);
  • 运维复杂,DBA缺少这方面经验;
  • 并不是所有资源都支持XA协议。

2.2 柔性事务

  在电商领域等互联网场景下,传统的事务在数据库性能和处理能力上都暴露出了瓶颈。柔性事务有两个特性:基本可用和柔性状态。

  基本可用是指分布式系统出现故障的时候允许损失一部分的可用性。
  柔性状态是指允许系统存在中间状态,这个中间状态不会影响系统整体的可用性,如数据库读写分离的主从同步延迟等。柔性事务的一致性指的是最终一致性。

  柔性事务主要分为补偿型和通知型,补偿型事务又分:TCC、Saga;通知型事务分:MQ事务消息、最大努力通知型。

  补偿型事务都是同步的,通知型事务都是异步的。

2.2.1 通知型事务

  通知型事务的主流实现是通过MQ(消息队列)来通知其他事务参与者自己事务的执行状态,引入MQ组件,有效的将事务参与者进行解耦,各参与者都可以异步执行,所以通知型事务又被称为异步事务。
  通知型事务主要适用于那些需要异步更新数据,并且对数据的实时性要求较低的场景,主要包含:异步确保型事务和最大努力通知事务。
  异步确保型事务:主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,如订单和购物车、收货与清算、支付与结算等等场景;
  最大努力通知:主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,如充值平台与运营商、支付对接等等跨网络系统级别对接。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

2.2.2 异步确保型

  指将一系列同步的事务操作修改为基于消息队列异步执行的操作,来避免分布式事务中同步阻塞带来的数据操作性能的下降。
  基于MQ的事务消息方案主要依靠MQ的半消息机制来实现投递消息和参与者自身本地事务的一致性保障。半消息机制实现原理其实借鉴的2PC的思路,是二阶段提交的广义拓展。
  半消息:在原有队列消息执行后的逻辑,如果后面的本地逻辑出错,则不发送该消息,如果通过则告知MQ发送。
  流程:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  1. 事务发起首先先发送半消息到MQ;
  2. MQ通知发送方消息发送成功;
  3. 在发送半消息成功后执行本地事务;
  4. 根据本地事务执行结果返回commit或者是rollback;
  5. 如果消息是rollback, MQ将丢弃该消息不投递;如果是commit,MQ将会消息发送给消息订阅方;
  6. 订阅方根据消息执行本地事务;
  7. 订阅方执行本地事务成功后再从MQ中将该消息标记为已消费;
  8. 如果执行本地事务过程中,执行端挂掉,或者超时,MQ服务器端将不停的询问producer来获取事务状态;
  9. Consumer端的消费成功机制由MQ保证。

  举个例自子,假设存在业务规则:某笔订单成功后,为用户加一定的积分。在这条规则里,管理订单数据源的服务为事务发起方,管理积分数据源的服务为事务跟随者。基于消息队列实现的事务存在以下操作:

订单服务创建订单,提交本地事务;
订单服务发布一条消息;
积分服务收到消息后加积分。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  它的整体流程是比较简单的,同时业务开发工作量也不大:编写订单服务里订单创建的逻辑、编写积分服务里增加积分的逻辑。该事务形态过程简单,性能消耗小,发起方与跟随方之间的流量峰谷可以使用队列填平,同时业务开发工作量也基本与单机事务没有差别,都不需要编写反向的业务逻辑过程。因此基于消息队列实现的事务是我们除了单机事务外最优先考虑使用的形态。
  有一些第三方的MQ是支持事务消息的,这些消息队列,支持半消息机制,如RocketMQ、ActiveMQ。但是有一些常用的MQ也不支持事务消息,如RabbitMQ、Kafka。
  以阿里的 RocketMQ 中间件为例,其思路大致为:

  1. producer(用A系统表示)发送半消息到broker,这个半消息包含完整的消息内容, 在producer端和普通消息的发送逻辑一致;
  2. broker存储半消息,半消息存储逻辑与普通消息一致,只是属性有所不同,topic是固定的RMQ_SYS_TRANS_HALF_TOPIC,queueId也是固定为0,这个tiopic中的消息对消费者是不可见的,所以里面的消息永远不会被消费。这就保证了在半消息提交成功之前,消费者是消费不到这个半消息的;
  3. broker端半消息存储成功并返回后,A系统执行本地事务,并根据本地事务的执行结果来决定半消息的提交状态为提交或者回滚;
  4. A系统发送结束半消息的请求,并带上提交状态(提交 or 回滚);
  5. broker端收到请求后,首先从RMQ_SYS_TRANS_HALF_TOPIC的queue中查出该消息,设置为完成状态。如果消息状态为提交,则把半消息从RMQ_SYS_TRANS_HALF_TOPIC队列中复制到这个消息原始topic的queue中去(之后这条消息就能被正常消费了);如果消息状态为回滚,则什么也不做;
  6. producer发送的半消息结束请求是 oneway 的,也就是发送后就不管了,只靠这个是⽆法保证半消息一定被提交的,rocketMq提供了个兜底方案,这个方案叫消息反查机制,Broker启动时,会启动一个TransactionalMessageCheckService 任务,该任务会定时从半消息队列中读出所有超时未完成的半消息,针对每条未完成的消息,Broker会给对应的Producer发送一个消息反查请求,根据反查结果来决定这个半消息是需要提交还是回滚,或者后面继续来反查;
  7. consumer(用B系统表示)消费消息,执行本地数据变更(至于B是否能消费成功,消费失败是否重试,这属于正常消息消费需要考虑的问题);

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  在rocketMq中,不论是producer收到broker存储半消息成功返回后执行本地事务,还是broker向producer反查消息状态,都是通过回调机制完成。
  producer端的代码示例:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  半消息发送时,会传入一个回调类TransactionListener,使用时必须实现其中的两个方法,executeLocalTransaction方法会在broker返回半消息存储成功后执行,我们会在其中执行本地事务;checkLocalTransaction方法会在broker向producer发起反查时执行,我们会在其中查询库表状态。两个方法的返回值都是消息状态,就是告诉broker应该提交或者回滚半消息。
  有时候我们目前的MQ组件并不支持事务消息,或者我们想尽量少的侵入业务方。这时我们需要另外一种方案“基于DB本地消息表”。
  本地消息表是目前业界使用的较多的方案之一,它的核心思想就是将分布式事务拆分成本地事务进行处理。
  本地消息表流程:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  发送消息方:

  1、需要有一个消息表,记录着消息状态相关信息。
  2、业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
  3、在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录。
  4、消息会发到消息消费方,如果发送失败,即进行重试。

  消息消费方:

  1、处理消息队列中的消息,完成自己的业务逻辑。
  2、如果本地事务处理成功,则表明已经处理成功了。
  3、如果本地事务处理失败,那么就会重试执行。
  4、如果是业务层面的失败,给消息生产方发送一个业务补偿消息,通知进行回滚等操作。

  生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
  本地消息表的优点:

  本地消息表建设成本较低,实现了可靠消息的传递确保了分布式事务的最终一致性。
  无需提供回查方法,进一步减少的业务的侵入。
  在某些场景下,还可以进一步利用注解等形式进行解耦,有可能实现无业务代码侵入式的实现。

  本地消息表的缺点:

  本地消息表与业务耦合在一起,难于做成通用性,不可独立伸缩。
  本地消息表是基于数据库来做的,而数据库是要读写磁盘IO的,因此在高并发下是有性能瓶颈的。

  MQ事务消息和本地消息表的共同点:

  1、 事务消息都依赖MQ进行事务通知,所以都是异步的。
  2、 事务消息在投递方都是存在重复投递的可能,需要有配套的机制去降低重复投递率,实现更友好的消息投递去重。
  3、 事务消息的消费方,因为投递重复的无法避免,因此需要进行消费去重设计或者服务幂等设计。

  MQ事务消息和本地消息表的不同点:
  MQ事务消息:

  需要MQ支持半消息机制或者类似特性,在重复投递上具有较好的去重处理;
  具有较大的业务侵⼊性,需要业务方进行改造,提供对应的本地操作成功的回查功能;

  DB本地消息表:

  使用了数据库来存储事务消息,降低了对MQ的要求,但是增加了存储成本;
  事务消息使用了异步投递,增加了消息重复投递的可能性。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务

2.2.3 最大努力通知型

  最大努力通知方案的目标,就是发起通知方通过一定的机制,最大努力将业务处理结果通知到接收方。
  最大努力通知型的最终一致性:本质是通过引入定期校验机制实现最终一致性,对业务的侵入性较低,适合于对最终一致性敏感度度较低、业务链路较短的场景。
  最大努力通知事务主要用于外部系统,因为外部的网络环境更加复杂和不可信,所以只能尽最大努力去通知实现数据最终一致性,如充值平台与运营商、支付对接、商户通知等等跨平台、跨企业的系统间业务交互场景;
  异步确保型事务主要适用于内部系统的数据最终一致性保障,因为内部相对比较可控,如订单和购物车、收货与清算、支付与结算等等场景。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  普通消息是无法解决本地事务执行和消息发送的⼀致性问题的。因为消息发送是个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。
  所以,通知型事务的难度在于: 投递消息和参与者本地事务的一致性保障。
  因为核心要点一致,都是为了保证消息的一致性投递,所以,最大努力通知事务在投递流程上跟异步确保型是一样的,也有两个分支:基于MQ自身的事务消息方案、基于DB的本地事务消息表方案。

  • MQ事务消息方案
      要实现最大努力通知,可以采用MQ的ACK机制。
      最大努力通知事务在投递之前,跟异步确保型流程都差不多,关键在于投递后的处理。
      因为异步确保型在于内部的事务处理,所以MQ和系统是直连并且无需严格的权限、安全等方面的思路设计。最大努力通知事务在于第三方系统的对接,所以最大努力通知事务有几个特性:

  业务主动方在完成业务处理后,向业务被动方(第三方系统)发送通知消息,允许存在消息丢失。
  业务主动方提供递增多挡位时间间隔(5min、10min、30min、1h、24h),用于失败重试调用业务被动方的接口;在通知N次之后就不再通知,报警+记日志+人工介入。
  业务被动方提供幂等的服务接口,防止通知重复消费。
  业务主动方需要有定期校验机制,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  过程:

  1. 业务活动的主动方,在完成业务处理之后,向业务活动的被动方发送消息,允许消息丢失。
  2. 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知。
  3. 主动方提供校对查询接口给被动方按需校对查询,用于恢复丢失的业务消息。
  4. 业务活动的被动方如果正常接收了数据,就正常返回响应,并结束事务。
  5. 如果被动方没有正常接收,根据定时策略,向业务活动主动方查询,恢复丢失的业务消息。

  特点:

  1. 用到的服务模式:可查询操作、幂等操作;
  2. 被动方的处理结果不影响主动方的处理结果;
  3. 适用于对业务最终一致性的时间敏感度低的系统;
  4. 适合跨企业的系统间的操作,或者企业内部⽐较独⽴的系统间的操作,如银行通知、商户通知等;
  • 本地消息表方案
      要实现最大努力通知,也可以采用定期检查本地消息表的机制 。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
      发送消息方需要有一个消息表,记录着消息状态相关信息。
      业务数据和消息表在同一个数据库,要保证它俩在同一个本地事务。直接利用本地事务,将业务数据和事务消息直接写入数据库。
      在本地事务中处理完业务数据和写消息表操作后,通过写消息到 MQ 消息队列。使用专门的投递工作线程进行事务消息投递到MQ,根据投递ACK去删除事务消息表记录。
      消息会发到消息消费方,如果发送失败,即进行重试。
      生产方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。
      最大努力通知事务在于第三方系统的对接,所以最大努力通知事务有几个特性:

  业务主动方在完成业务处理后,向业务被动方(第三方系统)发送通知消息,允许存在消息丢失。
  业务主动方提供递增多挡位时间间隔(5min、10min、30min、1h、24h),用于失败重试调用业务被动方的接口;在通知N次之后就不再通知,报警+记日志+人工介入。
  业务被动方提供幂等的服务接口,防止通知重复消费。
  业务主动方需要有定期校验机制,对业务数据进行兜底;防止业务被动方无法履行责任时进行业务回滚,确保数据最终一致性。

  • 最大努力通知事务 VS 异步确保型事务
      从参与者来说:最大努力通知事务适用于跨平台、跨企业的系统间业务交互;异步确保型事务更适用于同网络体系的内部服务交付。
      从消息层面说:最大努力通知事务需要主动推送并提供多档次时间的重试机制来保证数据的通知;而异步确保型事务只需要消息消费者主动去消费。
      从数据层面说:最大努力通知事务还需额外的定期校验机制对数据进行兜底,保证数据的最终一致性;而异步确保型事务只需保证消息的可靠投递即可,自身无需对数据进行兜底处理。
2.2.4 通知型事务的问题

  通知型事务,是无法解决本地事务执行和消息发送的一致性问题的。因为消息发送是一个网络通信的过程,发送消息的过程就有可能出现发送失败、或者超时的情况。超时有可能发送成功了,有可能发送失败了,消息的发送方是无法确定的,所以此时消息发送方无论是提交事务还是回滚事务,都有可能不一致性出现。
  常规的MQ队列处理流程无法实现消息的一致性。所以,需要借助半消息、本地消息表,保障一致性。
  对于未确认的消息,采用按规则重新投递的方式进行处理。如果允许消息重复发送,那么消费方应该实现业务接方的幂等性设计。

2.2.5 补偿型

  补偿模式使用一个额外的协调服务来协调各个需要保证一致性的业务服务,协调服务按顺序调用各个业务微服务,如果某个业务服务调用异常(包括业务异常和技术异常)就取消之前所有已经调用成功的业务服务。
  补偿模式大致有TCC,和Saga两种细分的方案:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

2.2.6 TCC 事务模型

  TCC 分布式事务模型包括三部分:

1.主业务服务:主业务服务为整个业务活动的发起方,服务的编排者,负责发起并完成整个业务活动。
2. 从业务服务:从业务服务是整个业务活动的参与方,负责提供 TCC 业务操作,实现初步操作(Try)、确认操作(Confirm)、取消操作(Cancel)三个接口,供主业务服务调用。
3. 业务活动管理器:业务活动管理器管理控制整个业务活动,包括记录维护 TCC 全局事务的事务状态和每个从业务服务的子事务状态,并在业务活动提交时调用所有从业务服务的 Confirm 操作,在业务活动取消时调用所有从业务服务的 Cancel 操作。

  TCC 把事务运行过程分成 Try、Confirm / Cancel 两个阶段,每个阶段的逻辑由业务代码控制,避免了长事务,可以获取更高的性能。
  TCC(Try-Confirm-Cancel)分布式事务模型相对于 XA 等传统模型,其特征在于它不依赖资源管理器(RM)对分布式事务的支持,而是通过对业务逻辑的分解来实现分布式事务。
  TCC 模型认为对于业务系统中一个特定的业务逻辑,其对外提供服务时,必须接受一些不确定性,即对业务逻辑初步操作的调用仅是一个临时性操作,调用它的主业务服务保留了后续的取消权。如果主业务服务认为全局事务应该回滚,它会要求取消之前的临时性操作,这就对应从业务服务的取消操作。而当主业务服务认为全局事务应该提交时,它会放弃之前临时性操作的取消权,这对应从业务服务的确认操作。每一个初步操作,最终都会被确认或取消。
  针对一个具体的业务服务,TCC 分布式事务模型需要业务系统提供三段业务逻辑:

  初步操作 Try:完成所有业务检查,预留必须的业务资源。
  确认操作 Confirm:真正执⾏的业务逻辑,不作任何业务检查,只使用Try阶段预留的业务资源。因此,只要 Try 操作成功,Confirm 必须能成功。另外,Confirm 操作需满足幂等性,保证一笔分布式事务有且只能成功一次。
  取消操作 Cancel:释放 Try 阶段预留的业务资源。同样的,Cancel 操作也需要满足幂等性。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  TCC 分布式事务模型示例:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  Try 阶段: 调用 Try 接口,尝试执行业务,完成所有业务检查,预留业务资源。
  Confirm 或 Cancel 阶段: 两者是互斥的,只能进入其中一个,并且都满足幂等性,允许失败重试。
  Confirm 操作: 对业务系统做确认提交,确认执行业务操作,不做其他业务检查,只使用 Try 阶段预留的业务资源。
  Cancel 操作: 在业务执行错误,需要回滚的状态下执行业务取消,释放预留资源。
  Try 阶段失败可以 Cancel ,如果 Confirm 和 Cancel 阶段失败了怎么办?TCC 中会添加事务日志,如果 Confirm 或者 Cancel 阶段出错,则会进行重试,所以这两个阶段需要支持幂等;如果重试失败,则需要人工介入进行恢复和处理等。
【TCC事务模型的要求】
  1、可查询操作:服务操作具有全局唯一的标识,操作唯一的确定的时间。
  2、幂等操作:重复调用多次产生的业务结果与调用一次产生的结果相同。一是通过业务操作实现幂等性,二是系统缓存所有请求与处理的结果,最后是检测到重复请求之后,自动返回之前的处理结果。
  3、TCC操作:Try阶段,尝试执行业务,完成所有业务的检查,实现一致性;预留必须的业务资源,实现准隔离性。Confirm阶段:真正的去执行业务,不做任何检查,仅适用Try阶段预留的业务资源,Confirm操作还要满⾜幂等性。Cancel阶段:取消执行业务,释放Try阶段预留的业务资源,Cancel操作要满足幂等性。TCC与2PC(两阶段提交)协议的区别:TCC位于业务服务层而不是资源层,TCC没有单独准备阶段,Try操作兼备资源操作与准备的能力,TCC中Try操作可以灵活的选择业务资源,锁定粒度。TCC的开发成本比2PC高。实际上TCC也属于两阶段操作,但是TCC不等同于2PC操作。
  4、可补偿操作:Do阶段:真正的执行业务处理,业务处理结果外部可见。Compensate阶段:抵消或者部分撤销正向业务操作的业务结果,补偿操作满足幂等性。约束:补偿操作在业务上可行,由于业务执行结果未隔离或者补偿不完整带来的风险与成本可控。实际上,TCC的Confirm和Cancel操作可以看做是补偿操作。

  • TCC与2PC对比
      TCC其实本质和2PC是差不多的:

  T就是Try,两个C分别是Confirm和Cancel。
  Try就是尝试,请求链路中每个参与者依次执行Try逻辑,如果都成功,就再执行Confirm逻辑,如果有失败,就执行Cancel逻辑。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  在阶段1:

在XA中,各个RM准备提交各自的事务分支,事实上就是准备提交资源的更新操作(insert、delete、update等);而在TCC中,是主业务活动请求(try)各个从业务服务预留资源。

  在阶段2:

XA根据第一阶段每个RM是否都prepare成功,判断是要提交还是回滚。如果都prepare成功,那么就commit每个事务分支,反之则rollback每个事务分支。TCC中,如果在第一阶段所有业务资源都预留成功,那么confirm各个从业务服务,否则取消(cancel)所有从业务服务的资源预留请求。

  • TCC与2PC不同点
      XA是资源层面的分布式事务,强一致性,在两阶段提交的整个过程中,一直会持有资源的锁。基于数据库锁实现,需要数据库支持XA协议,由于在执行事务的全程都需要对相关数据加锁,一般高并发性能会比较差。
      TCC是业务层面的分布式事务,最终一致性,不会一直持有资源的锁,性能较好。但是对微服务的侵入性强,微服务的每个事务都必须实现try、confirm、cancel等3个方法,开发成本高,今后维护改造的成本也高。为了达到事务的一致性要求,try、confirm、cancel接口必须实现幂等性操作由于事务管理器要记录事务日志,必定会损耗一定的性能,并使得整个TCC事务时间拉长。
  • TCC 的使用场景
      TCC是可以解决部分场景下的分布式事务的,但是,它的一个问题在于,需要每个参与者都分别实现Try,Confirm和Cancel接口及逻辑,这对于业务的侵入性是巨大的。
      TCC 方案严重依赖回滚和补偿代码,最终的结果是:回滚代码逻辑复杂,业务代码很难维护。所以,TCC 方案的使用场景较少,但是也有使用的场景。
      如说跟钱打交道的,支付、交易相关的场景,大家会用TCC方案,严格保证分布式事务要么全部成功,要么全部自动回滚,严格保证资金的正确性,保证在资金上不会出现问题。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
2.2.7 SAGA长事务模型

  SAGA可以看做一个异步的、利用队列实现的补偿事务。
  Saga模型是把一个分布式事务拆分为多个本地事务,每个本地事务都有相应的执行模块和补偿模块(对应TCC中的Confirm和Cancel),当Saga事务中任意一个本地事务出错时,可以通过调用相关的补偿方法恢复之前的事务,达到事务最终一致性。
  Saga 模型由三部分组成:

LLT(Long Live Transaction):由⼀个个本地事务组成的事务链。
本地事务:事务链由⼀个个⼦事务(本地事务)组成,LLT =T1+T2+T3+…+Ti。
补偿:每个本地事务 Ti 有对应的补偿 Ci。

  Saga的执行顺序有两种:

T1, T2, T3, …, Tn
T1, T2, …, Tj, Cj,…, C2, C1,其中0 < j < n

  • Saga 两种恢复策略
      1、向后恢复(Backward Recovery):撤销掉之前所有成功子事务。如果任意本地子事务失败,则补偿已完成的事务。如异常情况的执行顺序T1,T2,T3,…Ti,Ci,…C3,C2,C1。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
      2、向前恢复(Forward Recovery):即重试失败的事务,适用于必须要成功的场景,该情况下不需要Ci。执行顺序:T1,T2,…,Tj(失败),Tj(重试),…,Ti。
      向前恢复没有必要提供补偿事务,如果你的业务中,子事务(最终)总会成功,或补偿事务难以定义或不可能,向前恢复更符合你的需求。
  • SAGA模型的解决方案
      SAGA模型的核心思想是,通过某种方案,将分布式事务转化为本地事务,从而降低问题的复杂性。
      如以DB和MQ的场景为例,业务逻辑:1. 向DB中插入一条数据。2. 向MQ中发送一条消息。对应了两种存储端,即DB和MQ,所以,简单的通过本地事务是无法解决的。那么,依照SAGA模型,可以有两种解决方案。
      方案一:半消息模式。RocketMQ新版本中,就支持了这种模式。
      半消息。简单来说,就是在消息上加了一个状态。当发送者第一次将消息放入MQ后,该消息为待确认状态。该状态下,该消息是不能被消费者消费的。发送者必须二次和MQ进行交互,将消息从待确认状态变更为确认状态后,消息才能被消费者消费。待确认状态的消息,就称之为半消息。
      半消息的完整事务逻辑:
  1. 向MQ发送半消息。
  2. 向DB插入数据。
  3. 向MQ发送确认消息。

  MQ引入了一个扫描的机制。即MQ会每隔一段时间,对所有的半消息进行扫描,并就扫描到的存在时间过长的半消息,向发送者进行询问,询问如果得到确认回复,则将消息改为确认状态,如得到失败回复,则将消息删除。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  半消息机制的一个问题是:要求业务方提供查询消息状态接口,对业务方依然有较大的侵入性。
  方案二:本地消息表。在DB中,新增一个消息表,用于存放消息。如下:

  1. 在DB业务表中插入数据。
  2. 在DB消息表中插入数据。
  3. 异步将消息表中的消息发送到MQ,收到ack后,删除消息表中的消息。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  通过上述逻辑,将一个分布式的事务,拆分成两大步。第1和第2,构成了一个本地的事务,从而解决了分布式事务的问题。这种解决方案,不需要业务端提供消息查询接口,只需要稍微修改业务逻辑,侵入性是最小的。

2.3 总体的方案对比

属性 2PC TCC Saga 异步确保型事务 尽最大努力通知
事务一致性
复杂性
业务侵入性
使用局限性
性能
维护成本

三、分布式ID

3.1 数据库自增ID

  基于数据库的自增ID,需要单独使用一个数据库实例,在这个实例中新建一个单独的表:

CREATE DATABASE `SEQID`;
CREATE TABLE SEQID.SEQUENCE_ID (
    id bigint(20) unsigned NOT NULL auto_increment,
    stub char(10) NOT NULL default '',
    PRIMARY KEY (id),
    UNIQUE KEY stub (stub)
) ENGINE=MyISAM;

  可以使用下面的语句生成并获取到一个自增ID:

begin;
replace into SEQUENCE_ID (stub) VALUES ('anyword');
select last_insert_id();
commit;

  stub字段在这里并没有什么特殊的意义,只是为了方便的去插入数据,只有能插入数据才能产生自增id。而对于插入我们用的是replace,replace会先看是否存在stub指定值一样的数据,如果存在则先delete再insert,如果不存在则直接insert。
  这种生成分布式ID的机制,需要一个单独的Mysql实例,虽然可行,但是基于性能与可靠性来考虑的话都不够,业务系统每次需要一个ID时,都需要请求数据库获取,性能低,并且如果此数据库实例下线了,那么将影响所有的业务系统

3.2 数据库多主模式

  如果两个数据库组成一个主从模式集群,正常情况下可以解决数据库可靠性问题,但是如果主库挂掉后,数据没有及时同步到从库,这个时候会出现ID重复的现象。我们可以使用双主模式集群,也就是两个Mysql实例都能单独的生产自增ID,这样能够提高效率,但是如果不经过其他改造的话,这两个Mysql实例很可能会生成同样的ID。需要单独给每个Mysql实例配置不同的起始值和自增步长。
  第一台Mysql实例配置:

set @@auto_increment_offset = 1;   -- 起始值
set @@auto_increment_increment = 2;  -- 步长

  第二台Mysql实例配置:

set @@auto_increment_offset = 2;   -- 起始值
set @@auto_increment_increment = 2;  -- 步长

  经过上面的配置后,这两个Mysql实例生成的id序列如下: mysql1,起始值为1,步长为2。ID生成的序列为:1,3,5,7,9,… mysql2,起始值为2,步长为2,ID生成的序列为:2,4,6,8,10,…
  对于这种生成分布式ID的方案,需要单独新增一个生成分布式ID应用,比如DistributIdService,该应用提供一个接口供业务应用获取ID,业务应用需要一个ID时,通过rpc的方式请求DistributIdService,DistributIdService随机去上面的两个Mysql实例中去获取ID。实行这种方案后,就算其中某一台Mysql实例下线了,也不会影响DistributIdService,DistributIdService仍然可以利用另外一台Mysql来生成ID。
  但是这种方案的扩展性不太好,如果两台Mysql实例不够用,需要新增Mysql实例来提高性能时,这时就会比较麻烦。

3.3 号段模式

  使用号段的方式来获取自增ID,号段可以理解成批量获取,比如DistributIdService从数据库获取ID时,如果能批量获取多个ID并缓存在本地的话,那样将大大提供业务应用获取ID的效率。
  比如DistributIdService每次从数据库获取ID时,就获取一个号段,比如(1,1000],这个范围表示了1000个ID,业务应用在请求DistributIdService提供ID时,DistributIdService只需要在本地从1开始自增并返回即可,而不需要每次都请求数据库,一直到本地自增到1000时,也就是当前号段已经被用完时,才去数据库重新获取下一号段。
  需要对数据库表进行改动:

CREATE TABLE id_generator (
    id int(10) NOT NULL,
    current_max_id bigint(20) NOT NULL COMMENT '当前最大id',
    increment_step int(10) NOT NULL COMMENT '号段的长度',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

  这个数据库表用来记录自增步长以及当前自增ID的最大值(也就是当前已经被申请的号段的最后一个值),因为自增逻辑被移到DistributIdService中去了,所以数据库不需要这部分逻辑了。
  这种方案不再强依赖数据库,就算数据库不可用,那么DistributIdService也能继续支撑一段时间。但是如果DistributIdService重启,会丢失一段ID,导致ID空洞。
  为了提高DistributIdService的高可用,需要做一个集群,业务在请求DistributIdService集群获取ID时,会随机的选择某一个DistributIdService节点进行获取,对每一个DistributIdService节点来说,数据库连接的是同一个数据库,那么可能会产生多个DistributIdService节点同时请求数据库获取号段,那么这个时候需要利用乐观锁来进行控制,比如在数据库表中增加一个version字段,在获取号段时使用如下SQL:

update id_generator 
set current_max_id=#{newMaxId}, version=version+1 
where version = #{version}

  因为newMaxId是DistributIdService中根据oldMaxId+步长算出来的,只要上面的update更新成功了就表示号段获取成功了。
  为了提供数据库层的高可用,需要对数据库使用多主模式进行部署,对于每个数据库来说要保证生成的号段不重复,这就需要利用最开始的思路,再在刚刚的数据库表中增加起始值和步长,比如如果现在是两台Mysql,那么 mysql1将生成号段(1,1001],自增的时候序列为1,3,4,5,7… mysql1将生成号段(2,1002],自增的时候序列为2,4,6,8,10…
  在TinyId中还增加了一步来提高效率,在上面的实现中,ID自增的逻辑是在DistributIdService中实现的,而实际上可以把自增的逻辑转移到业务应用本地,这样对于业务应用来说只需要获取号段,每次自增时不再需要请求调用DistributIdService了。

3.4 雪花算法

  上面的三种方法总的来说是基于自增思想的。
  我们可以换个角度来对分布式ID进行思考,只要能让负责生成分布式ID的每台机器在每毫秒内生成不一样的ID就行了。snowflake是twitter开源的分布式ID生成算法,是一种算法,所以它和上面的三种生成分布式ID机制不太一样,它不依赖数据库。核心思想是:分布式ID固定是一个long型的数字,一个long型占8个字节,也就是64个bit,原始snowflake算法中对于bit的分配如下图:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

  第一个bit位是标识部分,在java中由于long的最高位是符号位,正数是0,负数是1,一般生成的ID为正数,所以固定为0。
  时间戳部分占41bit,这个是毫秒级的时间,一般实现上不会存储当前的时间戳,而是时间戳的差值(当前时间-固定的开始时间),这样可以使产生的ID从更小值开始;41位的时间戳可以使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年。
  工作机器id占10bit,这里比较灵活,比如,可以使用前5位作为数据中心机房标识,后5位作为单机房机器标识,可以部署1024个节点。
  序列号部分占12bit,支持同一毫秒内同一个节点可以生成4096个ID。

  snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。
  根据这个算法的逻辑,只需要将这个算法用Java语言实现出来,封装为一个工具方法,那么各个业务应用可以直接使用该工具方法来获取分布式ID,只需保证每个业务应用有自己的工作机器id即可,而不需要单独去搭建一个获取分布式ID的应用。

  • 优点
      1、不依赖于数据库,灵活方便,且性能优于数据库。
      2、 ID按照时间在单机上是递增的。
  • 缺点
      在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。

3.5 Leaf

  美团的Leaf也是一个分布式ID生成框架。它非常全面,即支持号段模式,也支持snowflake模式。Leaf中的snowflake模式和原始snowflake算法的不同点,也主要在workId的生成,Leaf中workId是基于ZooKeeper的顺序Id来生成的,每个应用在使用Leaf-snowflake时,在启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。

3.6 使用Redis生成ID

  当使用数据库来生成ID性能不够要求的时候,我们可以尝试使用Redis来生成ID。这主要依赖于Redis是单线程的,所以也可以用生成全局唯一的ID。可以用Redis的原子操作 INCR和INCRBY来实现。
  使用Redis来生成分布式ID,其实和利用Mysql自增ID类似,可以利用Redis中的incr命令来实现原子性的自增与返回,比如:

127.0.0.1:6379> set seq_id 1 // 初始化自增ID为1
OK
127.0.0.1:6379> incr seq_id // 增加1,并返回
(integer) 2
127.0.0.1:6379> incr seq_id // 增加1,并返回
(integer) 3

  可以使用Redis集群来获取更高的吞吐量。假如一个集群中有5台Redis。可以初始化每台Redis的值分别是1,2,3,4,5,然后步长都是5。各个Redis生成的ID为:

A:1,6,11,16,21 B:2,7,12,17,22 C:3,8,13,18,23 D:4,9,14,19,24 E:5,10,15,20,25

  这个,随便负载到哪个机确定好,未来很难做修改。但是3-5台服务器基本能够满足器上,都可以获得不同的ID。但是步长和初始值一定需要事先需要了。使用Redis集群也可以方式单点故障的问题。
  另外,比较适合使用Redis来生成每天从0开始的流水号。比如订单号=日期+当日自增长号。可以每天在Redis中生成一个Key,使用INCR进行累加。

  • 优点
      1、不依赖于数据库,灵活方便,且性能优于数据库。
      2、数字ID天然排序,对分页或者需要排序的结果很有帮助。
  • 缺点
      需要编码和配置的工作量比较大。

3.7 UUID

  一般来说全球唯一。

  • 优点
      1、简单,代码方便。
      2、生成ID性能非常好,基本不会有性能问题。
      3、全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。
  • 缺点
      1、没有排序,无法保证趋势递增。
      2、UUID往往是使用字符串存储,查询的效率比较低。
      3、存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。
      4、传输数据量大。
      5、不可读。

四、RPC

  http接口是在接口不多、系统与系统交互较少的情况下,解决信息孤岛初期常使用的一种通信手段;优点就是简单、直接、开发方便。利用现成的http协议进行传输。但是如果是一个大型的网站,内部子系统较多、接口非常多的情况下,RPC框架的好处就显示出来了,首先就是长链接,不必每次通信都要像http一样去3次握手什么的,减少了网络开销;其次就是RPC框架一般都有注册中心,有丰富的监控管理;发布、下线接口、动态扩展等,对调用方来说是无感知、统一化的操作。第三个来说就是安全性。 最后就是对于服务化架构、服务化治理而言,RPC框架是一个强力的支撑。
  socket只是一个简单的网络通信方式,只是创建通信双方的通信通道,而要实现rpc的功能,还需要对其进行封装,以实现更多的功能。
  RPC一般配合netty框架、spring自定义注解来编写轻量级框架,其实netty内部是封装了socket的,较新的jdk的IO一般是NIO,即非阻塞IO,在高并发网站中,RPC的优势会很明显。

  • 什么是RPC
      RPC(Remote Procedure Call Protocol)远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。简言之,RPC使得程序能够像访问本地系统资源一样,去访问远端系统资源。比较关键的一些方面包括:通讯协议、序列化、资源(接口)描述、服务框架、性能、语言支持等。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
      简单的说,RPC就是从一台机器(客户端)上通过参数传递的方式调用另一台机器(服务器)上的一个函数或方法(可以统称为服务)并得到返回的结果。
  • PRC架构组件
      一个基本的RPC架构里面应该至少包含以下4个组件:

  1、客户端(Client):服务调用方(服务消费者)。
  2、客户端存根(Client Stub):存放服务端地址信息,将客户端的请求参数数据信息打包成网络消息,再通过网络传输发送给服务端。
  3、服务端存根(Server Stub):接收客户端发送过来的请求消息并进行解包,然后再调用本地服务进行处理。
  4、服务端(Server):服务的真正提供者。

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  具体调用过程:
  1、服务消费者(client客户端)通过调用本地服务的方式调用需要消费的服务;
  2、客户端存根(client stub)接收到调用请求后负责将方法、入参等信息序列化(组装)成能够进行网络传输的消息体;
  3、客户端存根(client stub)找到远程的服务地址,并且将消息通过网络发送给服务端;
  4、服务端存根(server stub)收到消息后进行解码(反序列化操作);
  5、服务端存根(server stub)根据解码结果调用本地的服务进行相关处理;
  6、本地服务执行具体业务逻辑并将处理结果返回给服务端存根(server stub);
  7、服务端存根(server stub)将返回结果重新打包成消息(序列化)并通过网络发送至消费方;
  8、客户端存根(client stub)接收到消息,并进行解码(反序列化);
  9、 服务消费方得到终结果。

  RPC框架的实现目标则是将上图的第2-10步完好地封装起来,也就是把调用、编码/解码的过程给封装起来,让用户感觉上像调用本地服务一样的调用远程服务。

  • RPC和SOA、SOAP、REST
      REST:可以看着是HTTP协议的一种直接应用,默认基于JSON作为传输格式,使用简单, 学习成本低效率高,但是安全性较低。
      SOAP:一种数据交换协议规范,是一种轻量的、简单的、基于XML的协议的规范。而SOAP可以看着是一个重量级的协议,基于XML、SOAP在安全方面是通过使用XML-Security和XML-Signature两个规范组成了WS-Security来实现安全控制的,当前已经得到了各个厂商的支持 。其优点:易用、灵活、跨语言、跨平台。
      SOA:面向服务架构,它可以根据需求通过网络对松散耦合的粗粒度应用组件进行分布式部署、组合和使用。服务层是SOA的基础,可以直接被应用调用,从而有效控制系统中与软件代理交互的人为依赖性。SOA是一种粗粒度、松耦合服务架构,服务之间通过简单、精确定义接口进行通讯,不涉及底层编程接口和通讯模型。SOA可以看作是B/S模型、XML(标准通用标记语言的子集)/Web Service技术之后的自然延伸。
  • RPC框架需要解决的问题
      1、如何确定客户端和服务端之间的通信协议?
      2、如何更高效地进行网络通信?
      3、服务端提供的服务如何暴露给客户端?
      4、客户端如何发现这些暴露的服务?
      5、如何更高效地对请求对象和响应结果进行序列化和反序列化操作?
  • RPC的实现基础
      1、需要有非常高效的网络通信,比如一般选择Netty作为网络通信框架;
      2、需要有比较高效的序列化框架,比如谷歌的Protobuf序列化框架;
      3、可靠的寻址方式(主要是提供服务的发现),比如可以使用Zookeeper来注册服务等等;
      4、如果是带会话(状态)的RPC调用,还需要有会话和状态保持的功能。

4.1 RPC使用的关键技术

  • 1、动态代理
      生成Client Stub(客户端存根)和Server Stub(服务端存根)的时候需要用到 Java动态代理技术,可以使用JDK提供的原生的动态代理机制,也可以使用开源的:CGLib代理,Javassist字节码生成技术。
  • 2、序列化和反序列化
      在网络中,所有的数据都将会被转化为字节进行传送,所以为了能够使参数对象在网络中进行传输,需要对这些参数进行序列化和反序列化操作。
  • 3、NIO通信
      出于并发性能的考虑,传统的阻塞式 IO 显然不太合适,因此我们需要异步的
    IO,即 NIO。Java 提供了 NIO 的解决方案,Java 7 也提供了更优秀的 NIO.2 支持。可以选择Netty或者MINA来解决NIO数据传输的问题。
  • 4、服务注册中心
      可选:Redis、Zookeeper、Consul 、Etcd。一般使用ZooKeeper提供服务注册与发现功能,解决单点故障以及分布式部署的问题(注册中心)。

4.2 主流RPC框架

  • 1、RMI
      利用java.rmi包实现,基于Java远程方法协议(Java Remote Method Protocol) 和java的原生序列化。
  • 2、Hessian
      一个轻量级的remoting onhttp工具,使用简单的方法提供了RMI的功能。 基于HTTP协议,采用二进制编解码。
  • 3、protobuf-rpc-pro
      一个Java类库,提供了基于 Google 的 Protocol Buffers 协议的远程方法调用的框架。基于 Netty 底层的 NIO 技术。支持 TCP 重用/ keep-alive、SSL加密、RPC 调用取消操作、嵌入式日志等功能。
  • 4、Thrift
      一种可伸缩的跨语言服务的软件框架。它拥有功能强大的代码生成引擎,无缝地支持C + +,C#,Java,Python和PHP和Ruby。thrift允许你定义一个描述文件,描述数据类型和服务接口。依据该文件,编译器方便地生成RPC客户端和服务器通信代码。
  • 5、Avro
      在Thrift已经相当流行的情况下推出Avro的目标不仅是提供一套类似Thrift的通讯中间件,更是要建立一个新的,标准性的云计算的数据交换和存储的Protocol。支持HTTP,TCP两种协议。
  • 6、Dubbo
      Dubbo是 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 Spring框架无缝集成。

4.3 RPC过程

分布式概念,【综合】,分布式,CAP,BASE,分布式事务
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  说两台服务器A,B,一个应用部署在A服务器上,想要调用B服务器上应用提供的函数/方法,由于不在一个内存空间,不能直接调用,需要通过网络来表达调用的语义和传达调用的数据。比如说,A服务器想调用B服务器上的一个方法:
User getUserByName(String userName),其过程:

  • 1、建立通信首先要解决通讯的问题:即A机器想要调用B机器,首先得建立起通信连接
      主要是通过在客户端和服务器之间建立TCP连接,远程过程调用的所有交换的数据都在这个连接里传输。连接可以是按需连接,调用结束后就断掉,也可以是长连接,多个远程过程调用共享同一个连接。
      通常这个连接可以是按需连接(需要调用的时候就先建立连接,调用结束后就立马断掉),也可以是长连接(客户端和服务器建立起连接之后保持长期持有,不管此时有无数据包的发送,可以配合心跳检测机制定期检测建立的连接是否存活有效),多个远程过程调用共享同一个连接。
  • 2、服务寻址要解决寻址的问题
      也就是说,A服务器上的应用怎么告诉底层的RPC框架,如何连接到B服务器(如主机或IP地址)以及特定的端口,方法的名称名称是什么。
      通常情况下我们需要提供B机器(主机名或IP地址)以及特定的端口,然后指定调用的方法或者函数的名称以及入参出参等信息,这样才能完成服务的一个调用。
      可靠的寻址方式(主要是提供服务的发现)是RPC的实现基石,比如可以采用Redis或者Zookeeper来注册服务等等。
      从服务提供者的角度看:当服务提供者启动的时候,需要将自己提供的服务注册到指定的注册中心,以便服务消费者能够通过服务注册中心进行查找;当服务提供者由于各种原因致使提供的服务停止时,需要向注册中心注销停止的服务;服务的提供者需要定期向服务注册中心发送心跳检测,服务注册中心如果一段时间未收到来自服务提供者的心跳后,认为该服务提供者已经停止服务,则将该服务从注册中心上去掉。
      从调用者的角度看:服务的调用者启动的时候根据自己订阅的服务向服务注册中心查找服务提供者的地址等信息;当服务调用者消费的服务上线或者下线的时候,注册中心会告知该服务的调用者;服务调用者下线的时候,则取消订阅。
  • 3、网络传输
      序列化:当A机器上的应用发起一个RPC调用时,调用方法和其入参等信息需要通过底层的网络协议如TCP传输到B机器,由于网络协议是基于二进制的,所有我们传输的参数数据都需要先进行序列化(Serialize)或者编组(marshal)成二进制的形式才能在网络中进行传输。然后通过寻址操作和网络传输将序列化或者编组之后的二进制数据发送给B机器。
      反序列化:当B机器接收到A机器的应用发来的请求之后,又需要对接收到的参数等信息进行反序列化操作(序列化的逆操作),即将二进制信息恢复为内存中的表达方式,然后再找到对应的方法(寻址的一部分)进行本地调用(一般是通过生成代理Proxy去调用,通常会有JDK动态代理、CGLIB动态代理、Javassist生成字节码技术等),之后得到调用的返回值。
  • 4、服务调用
      B机器进行本地调用(通过代理Proxy和反射调用)之后得到了返回值,此时还需要再把返回值发送回A机器,同样也需要经过序列化操作,然后再经过网络传输将二进制数据发送回A机器,而当A机器接收到这些返回值之后,则再次进行反序列化操作,恢复为内存中的表达方式, 后再交给A机器上的应用进行相关处理(一般是业务逻辑处理操作)。通常,经过以上四个步骤之后,一次完整的RPC调用算是完成了,另外可能因为网络抖动等原因需要重试等。

五、分布式锁

5.1 Zookeeper

  基于zookeeper瞬时有序节点实现的分布式锁,其主要逻辑如下。大致思想即为:每个客户端对某个功能加锁时,在zookeeper上的与该功能对应的指定节点的目录下,生成一个唯一的瞬时有序节点。判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  优点:锁安全性高,zk可持久化,且能实时监听获取锁的客户端状态。一旦客户端宕机,则瞬时节点随之消失,zk因而能第一时间释放锁。这也省去了用分布式缓存实现锁的过程中需要加入超时时间判断的这一逻辑。
  缺点:性能开销比较高。因为其需要动态产生、销毁瞬时节点来实现锁功能。所以不太适合直接提供给高并发的场景使用。
  实现:可以直接采用zookeeper第三方库curator即可方便便地实现分布式锁。
  适用场景:对可靠性要求非常高,且并发程度不高的场景下使用。如核心数据的定时全量/增量同步等。

  • 做法1:创建临时节点
      zk 分布式锁,其实可以做的比较简单,就是某个节点尝试创建临时 znode,此时创建成功了就获取了这个锁;这个时候别的客户端来创建锁会失败,只能注册个监听器监听这个锁。释放锁就是删除这个 znode,一旦释放掉就会通知客户端,然后有一个等待着的客户端就可以再次重新加锁。
//ZooKeeperSession
public class ZooKeeperSession {
    private static CountDownLatch connectedSemaphore = new CountDownLatch();
    private ZooKeeper zookeeper;
    private CountDownLatch latch;
    public ZooKeeperSession() {
        try {
           this.zookeeper = new ZooKeeper("192.168.31.187:2181,192.168.31.188");
           try {
                connectedSemaphore.await();
           } catch (InterruptedException e) {
                e.printStackTrace();
           }
           System.out.println("ZooKeeper session established......");
        } catch (Exception e) {
           e.printStackTrace();
        }
    }
    
    //获取分布式锁
    public Boolean acquireDistributedLock(Long productId) {
        String = "/product-lock-" + productId;
        try {
          zookeeper.create(path , "".getBytes(), Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);        
          return true;
        } catch (Exception e) {
            while (true) {
                try {
                    //相当于是给node注册一个监听器,去看看这个监听器是否存在
                    Stat stat = zk.exists( , true);
                    if (stat != null) {
                       this.latch = new CountDownLatch(1);
                       this.latch.await( waitTime, TimeUnit.MILLISECONDS );
                       this.latch = null;
                   }
                   zookeeper.create(path , "".getBytes(), Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);       
                   return true;
                } catch (Exception e) {
                    continue;
                }
            }
        }
        return true;
     }

     //释放掉一个分布式锁
     public void releaseDistributedLock(Long productId) {
         String = "/product-lock-" + productId;
         try {
             zookeeper.delete( , -1);
             System.out.println("release the lock for product[id=" + productId);
         } catch (Exception e) {
             e.printStackTrace();
         }
     }

     //建立 zk session 的 watcher
     private class ZooKeeperWatcher implements Watcher {
          public void process(WatchedEvent event) {
              if (KeeperState.SyncConnected == event.getState()) {
                    connectedSemaphore.countDown();
              }
             if (this.latch != null) {
                 this.latch.countDown();
             }
        }
    }

    //封装单例的静态内部类
    private static class Singleton {
       private static ZooKeeperSession ;
       static {
            instance = new ZooKeeperSession();
       }
       public static ZooKeeperSession getInstance() {
            return instance ;
       }
    }

    //获取单例
    public static ZooKeeperSession getInstance() {
        return Singleton.getInstance();
    }

    //初始化单例的便捷方法
    public static void init() {
       getInstance();
    }
}
  • 方法2:创建临时顺序节点
      如果有一把锁,被多个人给竞争,此时多个人会排队,第一个拿到锁的人会执行,然后释放锁;后面的每个人都会去监听排在自己前面的那个人创建的 node 上,一旦某个人释放了锁,排在自己后面的人就会被 ZooKeeper 给通知,一旦被通知了之后,就 ok 了,自己就获取到了锁,就可以执行代码了。
public class ZooKeeperDistributedLock implements Watcher {
    private ZooKeeper ;
    private String = "/locks";
    private String ;
    private String ;
    private String ;
    private CountDownLatch ;
    private CountDownLatch = new CountDownLatch(1);
    private int = 30000;
    public ZooKeeperDistributedLock(String productId) {
        this.productId = ;
        try {
            String = "192.168.31.187:2181,192.168.31.19:2181");
            zk = new ZooKeeper( , , this);
            connectedLatch.await();
        } catch (IOException ) {
            throw new LockException( );
        } catch (KeeperException ) {
            throw new LockException( );
        } catch (InterruptedException ) {
            throw new LockException( );
        }
   }
   public void process(WatchedEvent event) {
        if ( event.getState() == KeeperState.SyncConnected) {
           connectedLatch.countDown();
           return;
        }
        if (this.latch != null) {
           this.latch.countDown();
        }
    }
    public void acquireDistributedLock() {
       try {
          if (this.tryLock()) {
              return;
          } else {
              waitForLock(waitNode,sessionTimeout );
          }
       } catch (KeeperException e) {
           throw new LockException( );
       } catch (InterruptedException e) {
           throw new LockException( );
       }
   }
   public boolean tryLock() {
        try {
         // 传入进去的locksRoot + “/” + productId
         // 假设productId代表了一个商品id,比如说1
         // locksRoot = locks
         // /locks/10000000000,/locks/10000000001,/locks/10000000002
         lockNode = zk.create(locksRoot + "/" + , new byte[0],Ids.OPEN_ACL_UNSAFE , CreateMode.PERSISTENT);
         // 看看刚创建的节点是不是最小的节点
         // locks:10000000000,10000000001,10000000002
         List<String> = zk.getChildren( locksRoot, false);
         Collections.sort(locks);
         if( lockNode.equals(locksRoot +"/"+ locks.get(0))){
             //如果是最小的节点,则表示取得锁
             return true;
         }
         //如果不是最小的节点,找到比自己小1的节点
         int previousLockIndex = -1;
         for(int i = 0; i< locks.size(); i++) {
             if( lockNode.equals( locksRoot+ "/" + locks.get(i ))) {
                  previousLockIndex = i - 1;
                  break;
              }
          }
          this.waitNode = locks.get(previousLockIndex);
      } catch (KeeperException e) {
          throw new LockException( );
      } catch (InterruptedException e) {
          throw new LockException( );
      }
      return false;
   }
   private boolean waitForLock(String waitNode, long waitTime) throws InterruptedException {
       Stat stat = .exists(locksRoot + "/" + waitNode, true);
       if ( stat!= null) {
           this.latch = new CountDownLatch(1);
           this.latch.await( waitTime, TimeUnit.MILLISECONDS );
           this.latch = null;
       }
       return true;
    }
    public void unlock() {
        try {
            // 删除/locks/10000000000节点
            // 删除/locks/10000000001节点
             System.out.println("unlock " + lockNode);
             zk.delete( lockNode, -1);
             lockNode = null;
             zk.close();
         } catch (InterruptedException ) {
             e.printStackTrace();
         } catch (KeeperException ) {
             e.printStackTrace();
         }
     }
     public class LockException extends RuntimeException {
         private static final long = 1L;
         public LockException(String e) {
              super(e);
         }
         public LockException(Exception e) {
              super(e);
         }
     }
}

5.2 Memcached

  memcached带有add函数,利用add函数的特性即可实现分布式锁。add和set的区别在于:如果多线程并发set,则每个set都会成功,但最后存储的值以最后的set的线程为准。而add的话则相反,add会添加第一个到达的值,并返回true,后续的添加则都会返回false。利用该点即可很轻松地实现分布式锁。
  优点:并发高效。
  缺点:1、memcached采用列入LRU置换策略,所以如果内存不不够,可能导致缓存中的锁信息丢失。2、memcached无法持久化,一旦重启,将导致信息丢失。
  使用场景:高并发场景。需要 1)加上超时时间避免死锁;2)提供足够支撑锁服务的内存空间;3)稳定的集群化管理理。

5.3 Redis

  redis分布式锁,可以结合zk分布式锁高度安全和memcached并发场景下效率很好的优点,其实现方式和memcached类似,采用setnx即可实现。需要注意的是,这里的redis也需要设置超时间。以避免死锁。可以利用jedis客户端实现。
  最普通的实现方式,就是在 Redis 里使用 SET key value [EX seconds] [PX
milliseconds] NX 创建一个 key,这样就算加锁。其中:

  NX :表示只有 key 不存在的时候才会设置成功,如果此时 redis 中存在这个 key ,那么设置失败,返回 nil 。
  EX seconds :设置 key 的过期时间,精确到秒级。意思是 seconds 秒后锁自动释放,别人创建的时候如果发现已经有了就不能加锁了。
  PX milliseconds :同样是设置 key 的过期时间,精确到毫秒级。

5.4 redis 分布式锁和 zk 分布式锁的对比

  redis 分布式锁,其实需要自己不断去尝试获取锁,比较消耗性能。zk 分布式锁,获取不到锁,注册个监听器即可,不需要不断主动尝试获取锁,性能开销较小。
  另外,如果是 Redis 获取锁的那个客户端 出现 bug 挂了,那么只能等待超时时间之后才能释放锁;而 zk 的话,因为创建的是临时 znode,只要客户端挂了,znode 就没了,此时就自动释放锁。

六、单点登录

  单点登录(SSO),指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的系统。简而言之,多个系统,统一登陆。

6.1 单点登录原理

  sso需要一个独立的认证中心,所有子系统都通过认证中心的登录入口进行登录,登录时带上自己的地址,子系统只接受认证中心的授权,授权通过令牌(token)实现,sso认证中心验证用户的用户名密码正确,创建全局会话和token,token作为参数发送给各个子系统,子系统拿到token,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

6.2 单点登录实现方式

  单点登录的实现方案,一般就包含:Cookies,分布式Session、Token。

  • 1、基于Cookie+Redis的单点登录
      最简单的单点登录实现方式,用cookie作为媒介存放用户凭证。 用户登录系统之后,会返回一个加密的cookie,当用户访问子应用的时候会带上这个cookie,授权以解密cookie并进行校验,校验通过后即可登录当前用户。
      redis:key:生成唯一随机值(ip、用户id等);在value:用户数据。
      cookie:把redis里面生成key值放到cookie里面。
      这种方案的交互过程:

  用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数。
  sso认证中心发现用户未登录,将用户引导至登录页面。
  用户输入用户名密码提交登录申请。
  sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌。
  sso认证中心带着令牌跳转会最初的请求地址(系统1)。
  系统1拿到令牌,去sso认证中心校验令牌是否有效。
  sso认证中心校验令牌,返回有效,注册系统1。
  系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源。
  用户访问系统2的受保护资源。
  系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数。
  sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌。
  系统2拿到令牌,去sso认证中心校验令牌是否有效。
  sso认证中心校验令牌,返回有效,注册系统2。
  系统2使用该令牌创建与用户的局部会话,返回受保护资源。

  用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系:

局部会话存在,全局会话一定存在。
全局会话存在,局部会话不一定存在。
全局会话销毁,局部会话必须销毁。

  使用该方式把信任存储在客户端的Cookie中会有两个问题:Cookie不安全、不能跨域实现免登。
  对于第一个问题,通过加密Cookie可以保证安全性,当然这是在源代码不泄露的前提下。如果Cookie的加密算法泄露,攻击者可以通过伪造Cookie伪造成特定用户身份。 对于问题二更是硬伤,所以才有了以下的分布式session方案。

  • 2、分布式session
      1) 用户第一次登录时,将会话信息(用户Id和用户信息),比如以用户Id为Key,写入分布式Session;
      2) 用户再次登录时,获取分布式Session,是否有会话信息,如果没有则调到登录页;
      3) 一般采用Cache中间件实现,建议使用Redis,因此它有持久化功能,方便分布式Session宕机后,可以从持久化存储中加载会话信息;
      4) 存入会话时,可以设置会话保持的时间,比如15分钟,超过后自动超时。
  • 3、页面重定向
      这种方式,是通过父应用和子应用来回重定向中进行通信,实现信息的安全传递。 父应用提供一个GET方式的登录接口,用户通过子应用重定向连接的方式访问这个接口,如果用户还没有登录,则返回一个的登录页面,用户输入账号密码进行登录。如果用户已经登录了,则生成加密的Token,并且重定向到子应用提供的验证Token的接口,通过解密和校验之后,子应用登录当前用户。
  • 4、使用token
      1、在项目某个模块进行登录,登录之后,按照规则生成字符串,把登陆之后用户包含到生成字符串里面,把字符串返回:

1)可以把字符串通过cookie返回。
2)把字符串通过地址栏返回。

  2、再去访问项目其他模块,每次访问在地址栏带着生成的字符串,在访问模块里面获取地址字符串,根据字符串获取用户信息。如果可以获取到就能登录。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

七、保证分布式系统数据一致性的6种方案

  数据一致性的基础理论:

  • 强一致
      当更新操作完成之后,任何多个后续进程或者线程的访问都会返回最新的更新过的值。这种是对用户最友好的,就是用户上一次写什么,下一次就保证能读到什么。根据 CAP 理论,这种实现需要牺牲可用性。
  • 弱一致性
      系统并不保证续进程或者线程的访问都会返回最新的更新过的值。系统在数据写入成功之后,不承诺立即可以读到最新写入的值,也不会具体的承诺多久之后可以读到。
  • 最终一致性
      弱一致性的特定形式。系统保证在没有后续更新的前提下,系统最终返回上一次更新操作的值。

7.1 规避分布式事务

  规避分布式事务——业务整合。
  优点: 解决(规避)了分布式事务。
  缺点: 显而易见,把本来规划拆分好的业务,又耦合到了一起,业务职责不清晰,不利于维护。
  由于这个方法存在明显缺点,通常不建议使用。

7.2 eBay 模式

  此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
  消息日志方案的核心是保证服务接口的幂等性。考虑到网络通讯失败、数据丢包等原因,如果接口不能保证幂等性,数据的唯一性将很难保证。
  将主要修改操作以及更新用户表的消息放在一个本地事务来完成。同时为了避免重复消费用户表消息带来的问题,达到多次重试的幂等性,增加一个更新记录表 updates_applied 来记录已经处理过的消息。
  在第一阶段,通过本地的数据库的事务保障,增加了 transaction 表及消息队列 。
  在第二阶段,分别读出消息队列(但不删除),通过判断更新记录表 updates_applied 来检测相关记录是否被执行,未被执行的记录会修改 user 表,然后增加一条操作记录到 updates_applied,事务执行成功之后再删除队列。
  通过以上方法,达到了分布式系统的最终一致性。

7.3 去哪儿网方案

  分布式事务有两种解决方式:

  • 1、优先使用异步消息
      使用异步消息 Consumer 端需要实现幂等。 幂等有两种方式,一种方式是业务逻辑保证幂等。比如接到支付成功的消息订单状态变成支付完成,如果当前状态是支付完成,则再收到一个支付成功的消息则说明消息重复了,直接作为消息成功处理。
      另外一种方式如果业务逻辑无法保证幂等,则要增加一个去重表或者类似的实现。对于 producer 端在业务数据库的同实例上放一个消息库,发消息和业务操作在同一个本地事务里。发消息的时候消息并不立即发出,而是向消息库插入一条消息记录,然后在事务提交的时候再异步将消息发出,发送消息如果成功则将消息库里的消息删除,如果遇到消息队列服务异常或网络问题,消息没有成功发出那么消息就留在这里了,会有另外一个服务不断地将这些消息扫出重新发送。
  • 2、使用事务记录库
      有的业务不适合异步消息的方式,事务的各个参与方都需要同步的得到结果。这种情况的实现方式其实和上面类似,每个参与方的本地业务库的同实例上面放一个事务记录库。
      比如 A 同步调用 B,C。A 本地事务成功的时候更新本地事务记录状态,B 和 C 同样。如果有一次 A 调用 B 失败了,这个失败可能是 B 真的失败了,也可能是调用超时,实际 B 成功。则由一个中心服务对比三方的事务记录表,做一个最终决定。假设现在三方的事务记录是 A 成功,B 失败,C 成功。那么最终决定有两种方式,根据具体场景:
  1. 重试 B,直到 B 成功,事务记录表里记录了各项调用参数等信息; 2. 执行 A 和 B 的补偿操作(一种可行的补偿方式是回滚)。

  对 b 场景做一个特殊说明:比如 B 是扣库存服务,在第一次调用的时候因为某种原因失败了,但是重试的时候库存已经变为 0,无法重试成功,这个时候只有回滚 A 和 C 了。
  总结起来,其实两种方式的根本原理是类似的,也就是将分布式事务转换为多个本地事务,然后依靠重试等方式达到最终一致性。

7.4 蘑菇街方案

  交易创建的一般性流程:
分布式概念,【综合】,分布式,CAP,BASE,分布式事务
  我们把交易创建流程抽象出一系列可扩展的功能点,每个功能点都可以有多个实现(具体的实现之间有组合/互斥关系)。把各个功能点按照一定流程串起来,就完成了交易创建的过程。

  • 面临的问题
      每个功能点的实现都可能会依赖外部服务。那么如何保证各个服务之间的数据是一致的呢?比如锁定优惠券服务调用超时了,不能确定到底有没有锁券成功,该如何处理?再比如锁券成功了,但是扣减库存失败了,该如何处理?
  • 方案选型
      服务依赖过多,会带来管理复杂性增加和稳定性风险增大的问题。试想如果我们强依赖 10 个服务,9 个都执行成功了,最后一个执行失败了,那么是不是前面 9 个都要回滚掉?这个成本还是非常高的。
      所以在拆分大的流程为多个小的本地事务的前提下,对于非实时、非强一致性的关联业务写入,在本地事务执行成功后,我们选择发消息通知、关联事务异步化执行的方案。
      消息通知往往不能保证 100% 成功;且消息通知后,接收方业务是否能执行成功还是未知数。前者问题可以通过重试解决;后者可以选用事务消息来保证。
      但是事务消息框架本身会给业务代码带来侵入性和复杂性,所以我们选择基于 DB 事件变化通知到 MQ 的方式做系统间解耦,通过订阅方消费 MQ 消息时的 ACK 机制,保证消息一定消费成功,达到最终一致性。由于消息可能会被重发,消息订阅方业务逻辑处理要做好幂等保证。

  所以目前只剩下需要实时同步做、有强一致性要求的业务场景了。在交易创建过程中,锁券和扣减库存是这样的两个典型场景。
  要保证多个系统间数据一致,乍一看,必须要引入分布式事务框架才能解决。但引入非常重的类似二阶段提交分布式事务框架会带来复杂性的急剧上升;在电商领域,绝对的强一致是过于理想化的,我们可以选择准实时的最终一致性。
  我们在交易创建流程中,首先创建一个不可见订单,然后在同步调用锁券和扣减库存时,针对调用异常(失败或者超时),发出废单消息到MQ。如果消息发送失败,本地会做时间阶梯式的异步重试;优惠券系统和库存系统收到消息后,会进行判断是否需要做业务回滚,这样就准实时地保证了多个本地事务的最终一致性。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务

7.5 支付宝方案

  业界常用的还有支付宝的一种 dts 方案,由支付宝在 2PC 的基础上改进而来。
  分布式事务服务 (Distributed Transaction Service, DTS) 是一个分布式事务框架,用来保障在大规模分布式环境下事务的最终一致性。DTS 从架构上分为 xts-client 和 xts-server 两部分,前者是一个嵌入客户端应用的 JAR 包,主要负责事务数据的写入和处理;后者是一个独立的系统,主要负责异常事务的恢复。

  • 核心特性
      传统关系型数据库的事务模型必须遵守 ACID 原则。在单数据库模式下,ACID 模型能有效保障数据的完整性,但是在大规模分布式环境下,一个业务往往会跨越多个数据库,如何保证这多个数据库之间的数据一致性,需要其他行之有效的策略。在 JavaEE 规范中使用 2PC (2 Phase Commit, 两阶段提交) 来处理跨 DB 环境下的事务问题,但是 2PC 是反可伸缩模式,也就是说,在事务处理过程中,参与者需要一直持有资源直到整个分布式事务结束。这样,当业务规模达到千万级以上时,2PC 的局限性就越来越明显,系统可伸缩性会变得很差。基于此,我们采用 BASE 的思想实现了一套类似 2PC 的分布式事务方案,这就是 DTS。DTS在充分保障分布式环境下高可用性、高可靠性的同时兼顾数据一致性的要求,其最大的特点是保证数据最终一致 (Eventually consistent)。
  • DTS 框架有如下特性
      最终一致:事务处理过程中,会有短暂不一致的情况,但通过恢复系统,可以让事务的数据达到最终一致的目标。
      协议简单:DTS 定义了类似 2PC 的标准两阶段接口,业务系统只需要实现对应的接口就可以使用 DTS 的事务功能。
    与 RPC 服务协议无关:在 SOA 架构下,一个或多个 DB 操作往往被包装成一个一个的 Service,Service 与 Service 之间通过 RPC 协议通信。DTS 框架构建在 SOA 架构上,与底层协议无关。
      与底层事务实现无关: DTS 是一个抽象的基于 Service 层的概念,与底层事务实现无关,也就是说在 DTS 的范围内,无论是关系型数据库 MySQL,Oracle,还是 KV 存储 MemCache,或者列存数据库 HBase,只要将对其的操作包装成 DTS 的参与者,就可以接入到 DTS 事务范围内。
  • 分布式事务框架的流程图
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务
      实现:

  一个完整的业务活动由一个主业务服务与若干从业务服务组成。
  主业务服务负责发起并完成整个业务活动。
  从业务服务提供 TCC 型业务操作。
  业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在活动提交时确认所有的两阶段事务的 confirm 操作,在业务活动取消时调用所有两阶段事务的 cancel 操作。”

  与 2PC 协议比较,没有单独的 Prepare 阶段,降低协议成本 -系统故障容忍度高,恢复简单。

7.6 农信网方案

  • 电商业务
      公司的支付部门,通过接入其它第三方支付系统来提供支付服务给业务部门,支付服务是一个基于 Dubbo 的 RPC 服务。
      对于业务部门来说,电商部门的订单支付,需要调用:

1.支付平台的支付接口来处理订单;
2.同时需要调用积分中心的接口,按照业务规则,给用户增加积分。

  从业务规则上需要同时保证业务数据的实时性和一致性,也就是支付成功必须加积分。
  我们采用的方式是同步调用,首先处理本地事务业务。考虑到积分业务比较单一且业务影响低于支付,由积分平台提供增加与回撤接口。
  具体的流程是先调用积分平台增加用户积分,再调用支付平台进行支付处理,如果处理失败,catch 方法调用积分平台的回撤方法,将本次处理的积分订单回撤。
分布式概念,【综合】,分布式,CAP,BASE,分布式事务文章来源地址https://www.toymoban.com/news/detail-804344.html

  • 用户信息变更
      公司的用户信息,统一由用户中心维护,而用户信息的变更需要同步给各业务子系统,业务子系统再根据变更内容,处理各自业务。用户中心作为 MQ 的 producer,添加通知给 MQ。APP Server 订阅该消息,同步本地数据信息,再处理相关业务比如 APP 退出下线等。
      我们采用异步消息通知机制,目前主要使用 ActiveMQ,基于 Virtual Topic 的订阅方式,保证单个业务集群订阅的单次消费。
    分布式概念,【综合】,分布式,CAP,BASE,分布式事务

到了这里,关于分布式概念的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 分布式从ACID、CAP、BASE的理论推进

    ​ 分布式实际上就是单一的本地一体解决方案,在硬件或者资源上不够业务需求,而采取的一种分散式多节点,可以扩容资源的一种解决思路。它研究如何把一个需要非常巨大的计算能力才能解决的问题分成许多小的部分,然后把这些部分分配给多个计算机进行处理,最后把

    2024年02月16日
    浏览(45)
  • 分布式协议与算法——CAP理论、ACID理论、BASE理论

    CAP理论,对分布式系统的特性做了高度抽象,比如抽象成了一致性、可用性和分区容错性,并对特性间的冲突(也就是CAP不可能三角)做了总结。 CAP三指标 CAP理论对分布式系统的特性做了高度抽象,形成了三个指标: 一致性(Consistency) 可用性(Availability) 分区容错性(

    2024年02月14日
    浏览(41)
  • 深入浅出 -- 系统架构之分布式CAP理论和BASE理论

    科技进步离不开理论支撑,而当下大行其道的分布式架构,透过繁荣昌盛表象,底层同样离不开诸多分布式理论撑持。当然,相信诸位在学习分布式相关技术时,必然学到过两个分布式领域中的基础理论,即: CAP与BASE理论 。 当一个从逻辑上被视为整体的系统,拆散到多个节

    2024年04月13日
    浏览(75)
  • 分布式系统中的那些一致性(CAP、BASE、2PC、3PC、Paxos、ZAB、Raft)

    本文介绍 CAP、BASE理论的正确理解、Paxos 算法如何保证一致性及死循环问题、ZAB 协议中原子广播及崩溃恢复以及 Raft 算法的动态演示。 下面还有投票,一起参与进来吧👍 工作过几年的同学,尤其是这几年,大家或多或少都参与过分布式系统的开发,遇到过各式各样“分布式

    2024年02月05日
    浏览(50)
  • 分布式系统概念和设计——(事务与并发控制)

    事务与并发控制 简介 事务的目标是在多个事务访问对象以及服务器面临崩溃的情况下,保证所有由服务器管理的对象始终维持在一个一致的状态上 事务是由客户定义的针对服务器对象的一组操作,组成为一个不可分割的单元,由服务器执行 服务器必须保证整个事务被执行,

    2024年02月07日
    浏览(42)
  • 深入理解高并发下的MySQL与Redis缓存一致性问题(增删改查数据缓存的一致性、Canal、分布式系统CAP定理、BASE理论、强、弱一致性、顺序、线性、因果、最终一致性)

    一些小型项目,或极少有并发的项目,这些策略在无并发情况下,不会有什么问题。 读数据策略:有缓存则读缓存,然后接口返回。没有缓存,查询出数据,载入缓存,然后接口返回。 写数据策略:数据发生了变动,先删除缓存,再更新数据,等下次读取的时候载入缓存,

    2024年03月20日
    浏览(48)
  • 分布式理论基础:CAP定理

    CAP原则又称CAP定理,指的是在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性)这三个基本需求,最多只能同时满足其中的2个。 一致性 :数据在多个副本之间能够保持一致的特性。 可用性 :系统提供的服务一直处于可用的状态

    2024年02月05日
    浏览(45)
  • 分布式系统之CAP定理介绍

      在分布式系统的设计和实现中,CAP定理是一个非常重要的概念。本文将介绍CAP定理的概念、含义和应用。   CAP定理是分布式系统设计中的一个基本原则,它指出,在分布式系统中,一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三个目标无

    2024年02月04日
    浏览(39)
  • 【分布式】1、CAP 理论 | 一致性、可用性、分区容忍性

    是 2002 年证明的定理,原文,内容如下: In a distributed system (a collection of interconnected nodes that share data.), you can only have two out of the following three guarantees across a write/read pair: Consistency, Availability, and Partition Tolerance - one of them must be sacrificed. 在一个分布式系统(指互相连接并共享数据

    2024年02月16日
    浏览(47)
  • 分布式:一文吃透分布式事务和seata事务

    什么是事务 事务是并发控制的单位,是用户定义的一个操作序列。 事务特性 原子性(Atomicity): 事务是数据库的逻辑工作单位,事务中包括的诸操作要么全做,要么全不做。 一致性(Consistency): 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。一致性

    2024年02月07日
    浏览(57)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包