[
  {
    "path": "1-常用分布式事务解决方案介绍.md",
    "content": "# 1. 常用分布式事务解决方案\n\n## 1.1 两阶段提交\n\n[一个基于两阶段提交协议的分布式事务框架](https://github.com/codingapi/tx-lcn/)\n\n>二阶段提交(Two-phaseCommit)是指，在计算机网络以及数据库领域内，为了使基于分布式系统架构下的所有节点在进行事务提交时保持一致性而设计的一种算法(Algorithm)。通常，二阶段提交也被称为是一种协议(Protocol))。在分布式系统中，每个节点虽然可以知晓自己的操作时成功或者失败，却无法知道其他节点的操作的成功或失败。当一个事务跨越多个节点时，为了保持事务的ACID特性，需要引入一个作为协调者的组件来统一掌控所有节点(称作参与者)的操作结果并最终指示这些节点是否要把操作结果进行真正的提交(比如将更新后的数据写入磁盘等等)。因此，二阶段提交的算法思路可以概括为：参与者将操作成败通知协调者，再由协调者根据所有参与者的反馈情报决定各参与者是否要提交操作还是中止操作。\n\n- 所谓的两个阶段是指：第一阶段：准备阶段(投票阶段)和第二阶段：提交阶段（执行阶段）。\n\n### 1.1.1 准备阶段\n\n- 事务协调者(事务管理器)给每个参与者(资源管理器)发送Prepare消息，每个参与者要么直接返回失败(如权限验证失败)，要么在本地执行事务，写本地的redo和undo日志，但不提交，到达一种“万事俱备，只欠东风”的状态。\n- 一般讲准备阶段分为以下三个阶段\n    1. 协调者节点向所有参与者节点询问是否可以执行提交操作(vote)，并开始等待各参与者节点的响应。\n    2. 参与者节点执行询问发起为止的所有事务操作，并将Undo信息和Redo信息写入日志。（注意：若成功这里其实每个参与者已经执行了事务操作）\n    3. 各参与者节点响应协调者节点发起的询问。如果参与者节点的事务操作实际执行成功，则它返回一个”同意”消息；如果参与者节点的事务操作实际执行失败，则它返回一个”中止”消息。\n\n### 1.1.2 提交阶段\n\n- 如果协调者收到了参与者的失败消息或者超时，直接给每个参与者发送回滚(Rollback)消息；否则，发送提交(Commit)消息；参与者根据协调者的指令执行提交或者回滚操作，释放所有事务处理过程中使用的锁资源。(注意:必须在最后阶段释放锁资源)\n- 当协调者节点从所有参与者节点获得的相应消息都为”同意”时:\n    1. 协调者节点向所有参与者节点发出”正式提交(commit)”的请求。\n    2. 参与者节点正式完成操作，并释放在整个事务期间内占用的资源。\n    3. 参与者节点向协调者节点发送”完成”消息。\n    4. 协调者节点受到所有参与者节点反馈的”完成”消息后，完成事务。\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A41.png)\n\n- 如果任一参与者节点在第一阶段返回的响应消息为”中止”，或者 协调者节点在第一阶段的询问超时之前无法获取所有参与者节点的响应消息时：\n    1. 协调者节点向所有参与者节点发出”回滚操作(rollback)”的请求。\n    2. 参与者节点利用之前写入的Undo信息执行回滚，并释放在整个事务期间内占用的资源。\n    3. 参与者节点向协调者节点发送”回滚完成”消息。\n    4. 协调者节点受到所有参与者节点反馈的”回滚完成”消息后，取消事务。\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E4%B8%A4%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A42.png)\n\n### 1.1.3 两阶段提交的缺陷\n\n1. 同步阻塞问题。执行过程中，所有参与节点都是事务阻塞型的。当参与者占有公共资源时，其他第三方节点访问公共资源不得不处于阻塞状态。\n2. 单点故障。由于协调者的重要性，一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段，协调者发生故障，那么所有的参与者还都处于锁定事务资源的状态中，而无法继续完成事务操作。（如果是协调者挂掉，可以重新选举一个协调者，但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题）\n3. 数据不一致。在二阶段提交的阶段二中，当协调者向参与者发送commit请求之后，发生了局部网络异常或者在发送commit请求过程中协调者发生了故障，这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。\n4. 二阶段无法解决的问题：协调者再发出commit消息之后宕机，而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者，这条事务的状态也是不确定的，没人知道事务是否被已经提交。\n\n## 1.2 三阶段提交\n\n- 三阶段提交（Three-phase commit），也叫三阶段提交协议（Three-phase commit protocol），是二阶段提交（2PC）的改进版本。\n\n### 1.2.1 与两阶段提交的不同点\n\n1. 引入超时机制。同时在协调者和参与者中都引入超时机制。\n2. 在第一阶段和第二阶段中插入一个准备阶段。保证了在最后提交阶段之前各参与节点的状态是一致的。\n\n>也就是说，除了引入超时机制之外，3PC把2PC的准备阶段再次一分为二，这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。\n\n3. 相对于2PC，3PC主要解决的单点故障问题，并减少阻塞，因为一旦参与者无法及时收到来自协调者的信息之后，他会默认执行commit。而不会一直持有事务资源并处于阻塞状态。但是这种机制也会导致数据一致性问题，因为，由于网络原因，协调者发送的abort响应没有及时被参与者接收到，那么参与者在等待超时之后执行了commit操作。这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。\n\n\n### 1.2.2 CanCommit阶段\n\n- 3PC的CanCommit阶段其实和2PC的准备阶段很像。协调者向参与者发送commit请求，参与者如果可以提交就返回Yes响应，否则返回No响应\n    1.事务询问:协调者向参与者发送CanCommit请求。询问是否可以执行事务提交操作。然后开始等待参与者的响应。\n    2.响应反馈:参与者接到CanCommit请求之后，正常情况下，如果其自身认为可以顺利执行事务，则返回Yes响应，并进入预备状态。否则反馈No\n\n### 1.2.3 PreCommit阶段\n\n- 假如协调者从所有的参与者获得的反馈都是Yes响应，那么就会进行事务的预执行。\n    1. 发送预提交请求:协调者向参与者发送PreCommit请求，并进入Prepared阶段。\n    2. 事务预提交:参与者接收到PreCommit请求后，会执行事务操作，并将undo和redo信息记录到事务日志中。\n    3. 响应反馈:如果参与者成功的执行了事务操作，则返回ACK响应，同时开始等待最终指令。\n- 假如有任何一个参与者向协调者发送了No响应，或者等待超时之后，协调者都没有接到参与者的响应，那么就执行事务的中断。\n    1. 发送中断请求:协调者向所有参与者发送abort请求。\n    2. 中断事务:参与者收到来自协调者的abort请求之后（或超时之后，仍未收到协调者的请求），执行事务的中断。\n\n### 1.2.4 doCommit阶段\n\n- 执行提交\n    1. 发送提交请求:协调接收到参与者发送的ACK响应，那么他将从预提交状态进入到提交状态。并向所有参与者发送doCommit请求。\n    2. 事务提交:参与者接收到doCommit请求之后，执行正式的事务提交。并在完成事务提交之后释放所有事务资源。\n    3. 响应反馈:事务提交完之后，向协调者发送Ack响应。\n    4. 完成事务:协调者接收到所有参与者的ack响应之后，完成事务。\n- 中断事务:协调者没有接收到参与者发送的ACK响应（可能是接受者发送的不是ACK响应，也可能响应超时），那么就会执行中断事务。\n    1. 发送中断请求:协调者向所有参与者发送abort请求\n    2. 事务回滚:参与者接收到abort请求之后，利用其在阶段二记录的undo信息来执行事务的回滚操作，并在完成回滚之后释放所有的事务资源。\n    3. 反馈结果:参与者完成事务回滚之后，向协调者发送ACK消息\n    4. 中断事务:协调者接收到参与者反馈的ACK消息之后，执行事务的中断。\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E4%B8%89%E9%98%B6%E6%AE%B5%E6%8F%90%E4%BA%A4%E5%8D%8F%E8%AE%AE1.png)\n\n>在doCommit阶段，如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时，会在等待超时之后，会继续进行事务的提交。（其实这个应该是基于概率来决定的，当进入第三阶段时，说明参与者在第二阶段已经收到了PreCommit请求，那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前，收到所有参与者的CanCommit响应都是Yes。（一旦参与者收到了PreCommit，意味他知道大家其实都同意修改了）所以，一句话概括就是，当进入第三阶段时，由于网络超时等原因，虽然参与者没有收到commit或者abort响应，但是他有理由相信：成功提交的几率很大。 ）\n\n## 1.3 CAP定理\n\n### 1.3.1 标准分布式事务缺陷\n\n- 效率非常低\n    1. 全局事务方式下，全局事务管理器（TM）通过XA接口使用二阶段提交协议（ 2PC ）与资源层（如数据库）进行交互。使用全局事务，数据被Lock的时间跨整个事务，直到全局事务结束。\n    2. 2PC 是反可伸缩模式，在事务处理过程中，参与者需要一直持有资源直到整个分布式事务结束。这样，当业务规模越来越大的情况下，2PC 的局限性就越来越明显，系统可伸缩性会变得很差。\n    3. 与本地事务相比，XA 协议的系统开销相当大，因而应当慎重考虑是否确实需要分布式事务。而且只有支持 XA 协议的资源才能参与分布式事务。\n\n### 1.3.2 CAP理论详解\n\n- CAP原则是NOSQL数据库的基石。\n- CAP原则又称CAP定理，指的是在一个分布式系统中， Consistency（一致性）、 Availability（可用性）、Partition tolerance（分区容错性），三者不可得兼。\n    1. 一致性（C）：在分布式系统中的所有数据备份，在同一时刻是否同样的值。（等同于所有节点访问同一份最新的数据副本）\n    2. 可用性（A）：在集群中一部分节点故障后，集群整体是否还能响应客户端的读写请求。（对数据更新具备高可用性）\n    3. 分区容忍性（P）：以实际效果而言，分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性，就意味着发生了分区的情况，必须就当前操作在C和A之间做出选择。(容忍网络中断)\n\n## 1.4 BASE理论\n\n>BASE是Basically Available（基本可用）、Soft state（软状态）和Eventually consistent（最终一致性）三个短语的简写，BASE是对CAP中一致性和可用性权衡的结果，其来源于对大规模互联网系统分布式实践的结论，是基于CAP定理逐步演化而来的，其核心思想是即使无法做到强一致性（Strong consistency），**但每个应用都可以根据自身的业务特点，采用适当的方式**来使系统达到最终一致性（Eventual consistency）。接下来我们着重对BASE中的三要素进行详细讲解。\n\n- BA: Basic Availability 基本业务可用性（支持分区失败）:分布式系统在出现不可预知故障的时候，允许损失部分可用性\n    - 响应时间上的损失：正常情况下，一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果，但由于出现异常（比如系统部分机房发生断电或断网故障），查询结果的响应时间增加到了1~2秒。\n    - 功能上的损失：正常情况下，在一个电子商务网站上进行购物，消费者几乎能够顺利地完成每一笔订单，但是在一些节日大促购物高峰的时候，由于消费者的购物行为激增，为了保护购物系统的稳定性，部分消费者可能会被引导到一个降级页面。\n- S: Soft state 柔性状态（状态允许有短时间不同步，异步）\n- E: Eventual consistency 最终一致性（最终数据是一致的，但不是实时一致）\n    - 因果一致性：因果一致性是指，如果进程A在更新完某个数据项后通知了进程B，那么进程B之后对该数据项的访问都应该能够获取到进程A更新后的最新值，并且如果进程B要对该数据项进行更新操作的话，务必基于进程A更新后的最新值，即不能发生丢失更新情况。与此同时，与进程A无因果关系的进程C的数据访问则没有这样的限制。\n    - 读己之所写：读己之所写是指，进程A更新一个数据项之后，它自己总是能够访问到更新过的最新值，而不会看到旧值。也就是说，对于单个数据获取者而言，其读取到的数据一定不会比自己上次写入的值旧。因此，读己之所写也可以看作是一种特殊的因果一致性。\n    - 会话一致性：会话一致性将对系统数据的访问过程框定在了一个会话当中：系统能保证在同一个有效的会话中实现“读己之所写”的一致性，也就是说，执行更新操作之后，客户端能够在同一个会话中始终读取到该数据项的最新值。\n    - 单调读一致性：单调读一致性是指如果一个进程从系统中读取出一个数据项的某个值后，那么系统对于该进程后续的任何数据访问都不应该返回更旧的值。\n    - 单调写一致性：单调写一致性是指，一个系统需要能够保证来自同一个进程的写操作被顺序地执行。\n\n>原子性（A）与持久性（D）必须根本保障\n>为了可用性、性能与降级服务的需要，只有降低一致性( C ) 与 隔离性( I ) 的要求\n\n\n\n## 1.5 柔性事务\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%9F%94%E6%80%A7%E4%BA%8B%E5%8A%A1-%E4%B8%9A%E5%8A%A1%E6%B4%BB%E5%8A%A81.png)\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%9F%94%E6%80%A7%E4%BA%8B%E5%8A%A1-%E4%B8%9A%E5%8A%A1%E6%B4%BB%E5%8A%A8%E4%B8%BE%E4%BE%8B2.png)\n\n### 1.5.1 柔性事务中的服务模式\n\n>服务模式是柔性事务流程中的特殊操作实现（实现上对应业务服务要提供相应模式的功能接口），还不算是某一种柔性事务解决方案。\n\n- 可查询操作\n    - 服务操作的可标识性\n        -  服务操作具有全局唯一标识(可以使用业务单据号/使用系统分配的操作流水号使用操作资源的组合组合标识)\n        -  操作有唯一的、确定的时间\n    - 单笔查询\n        - 使用全局唯一的服务操作标识，查询操作执行结果\n        - 注意状态判断，小心“处理中”的状态\n    - 批量查询\n        - 使用时间区段与(或)一组服务操作的标识，查询一批操作执行结果\n- 幂等操作\n    - 幂等性:f(f(x)) = f(x)\n    - 幂等操作:重复调用多次产生的业务结果与调用一次产生的业务结果相同\n    - 实现方式一:通过业务操作本身实现幂等性\n    - 实现方式二:系统缓存所有请求与处理结果,检测到重复请求之后，自动返回之前的处理结果\n- TCC操作\n    - Try: 尝试执行业务\n        - 完成所有业务检查(一致性)\n        - 预留必须业务资源(准隔离性)\n    - Confirm:确认执行业务\n        - 真正执行业务\n        - 不作任何业务检查\n        - 只使用Try阶段预留的业务资源\n        - Confirm操作要满足幂等性\n    - Cancel: 取消执行业务\n        - 释放Try阶段预留的业务资源\n        - Cancel操作要满足幂等性\n    - 与2PC协议比较\n        - 位于业务服务层而非资源层\n        - 没有单独的准备(Prepare)阶段，Try操作兼备资源操作与准备能力\n        - Try操作可以灵活选择业务资源的锁定粒度(以业务定粒度)\n        - 较高开发成本\n\n>很多人把两阶段型操作等同于两阶段提交协议2PC操作。其实TCC操作也属于两阶段型操作。\n\n- 可补偿操作\n    - do: 真正执行业务\n        - 完成业务处理\n        - 业务执行结果外部可见\n    - compensate:业务补偿\n        - 抵销(或部分抵销)正向业务操作的业务结果\n        - 补偿操作满足幂等性\n    - 约束\n        - 补偿在业务上可行\n        - 由于业务执行结果未隔离、或者补偿不完整带来的风险与成本可控\n\n>TCC操作中的Confirm操作和Cancel操作，其实也可以看作是补偿操作\n\n简而言之，TCC是应用层的2PC(2 Phase Commit, 两阶段提交)，如果你将应用看做资源管理器的话。\n\n详细来说，TCC每项操作需要做的事情如下：Try：尝试执行业务, 完成所有业务检查(一致性)预留必须业务资源(准隔离性); Confirm：确认执行业务, 真正执行业务不做任何业务检查只使用Try阶段预留的业务资源; Cancel：取消执行业务释放Try阶段预留的业务资源.\n\nTCC事务的优点如下：解决了跨应用业务操作的原子性问题，在诸如组合支付、账务拆分场景非常实用。TCC实际上把数据库层的二阶段提交上提到了应用层来实现，对于数据库来说是一阶段提交，规避了数据库层的2PC性能低下问题。 TCC事务的缺点，主要就一个:TCC的Try、Confirm和Cancel操作功能需业务提供，开发成本高。\n\n账务拆分的业务场景如下，分别位于三个不同分库的帐户A、B、C，A和B一起向C转帐共80元：\n\nTry：尝试执行业务。完成所有业务检查(一致性)：检查A、B、C的帐户状态是否正常，帐户A的余额是否不少于30元，帐户B的余额是否不少于50元。预留必须业务资源(准隔离性)：帐户A的冻结金额增加30元，帐户B的冻结金额增加50元，这样就保证不会出现其他并发进程扣减了这两个帐户的余额而导致在后续的真正转帐操作过程中，帐户A和B的可用余额不够的情况。\nConfirm：确认执行业务。真正执行业务：如果Try阶段帐户A、B、C状态正常，且帐户A、B余额够用，则执行帐户A给账户C转账30元、帐户B给账户C转账50元的转帐操作。不做任何业务检查：这时已经不需要做业务检查，Try阶段已经完成了业务检查。只使用Try阶段预留的业务资源：只需要使用Try阶段帐户A和帐户B冻结的金额即可。\nCancel：取消执行业务释放Try阶段预留的业务资源：如果Try阶段部分成功，比如帐户A的余额够用，且冻结相应金额成功，帐户B的余额不够而冻结失败，则需要对帐户A做Cancel操作，将帐户A被冻结的金额解冻掉。 小结：到底要不要使用TCC 到底要不要使用TCC事务，取决于以下几点：是否真正有保证跨应用业务操作的原子性需求。研发上能否投入资源开发相对应的TCC接口。\n\n### 1.5.1 柔性事务解决方案:最终一致性(可靠消息/异步确保型)\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E5%8F%AF%E9%9D%A0%E6%B6%88%E6%81%AF%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A71.png)\n\n- 实现\n    - 业务处理服务在业务事务提交前，向实时消息服务请求发送消息，实时消息服务只记录消息数据，而不真正发送。业务处理服务在业务事务提交后，向实时消息服务确认发送。只有在得到确认发送指令后，实时消息服务才真正发送\n    - 业务处理服务在业务事务回滚后，向实时消息服务取消发送。消息状态确认系统定期找到未确认发送或回滚发送的消息，向业务处理服务询问消息状态，业务处理服务根据消息ID或消息内容确定该消息是否有效\n- 约束\n    - 被动方的处理结果不影响主动方的处理结果， 被动方的消息处理操作是幂等操作\n- 成本\n    - 可靠消息系统建设成本\n    - 一次消息发送需要两次请求，业务处理服务需实现消息状态回查接口\n- 优点、适用范围\n    - 消息数据独立存储、独立伸缩，降低业务系统与消息系统间的耦合\n    - 对最终一致性时间敏感度较高，降低业务被动方实现成本\n- 用到的服务模式:可查询操作、幂等操作\n- 方案特点\n    - 兼容所有实现JMS标准的MQ中间件\n    - 确保业务数据可靠的前提下，实现业务数据的最终一致（理想状态下基本是准实时一致）\n\n### 1.5.2 柔性事务解决方案:TCC(两阶段型/补偿型)\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E5%8F%AF%E9%9D%A0%E6%B6%88%E6%81%AF%E6%9C%80%E7%BB%88%E4%B8%80%E8%87%B4%E6%80%A71.png)\n\n- 实现\n    - 一个完整的业务活动由一个主业务服务与若干从业务服务组成\n    - 主业务服务负责发起并完成整个业务活从业务服务提供TCC型业务操作\n    - 业务活动管理器控制业务活动的一致性，它登记业务活动中的操作， 并在业务活动提交时确认所有的TCC型操作的confirm操作，在业务活动取消时调用所有TCC型操作的cancel操作\n- 成本\n    - 实现TCC操作的成本\n    - 业务活动结束时confirm或cancel操作的执行成本\n    - 业务活动日志成本\n- 适用范围\n    - 强隔离性、严格一致性要求的业务活动\n    - 适用于执行时间较短的业务（比如处理账户、收费等业务）\n- 用到的服务模式:TCC操作、幂等操作、可补偿操作、 可查询操作\n- 方案特点:\n    - 不与具体的服务框架耦合（在RPC架构中通用）\n    - 位于业务服务层，而非资源层\n    - 可以灵活选择业务资源的锁定粒度\n    - TCC里对每个服务资源操作的是本地事务，数据被lock的时间短，可扩展性好（可以说是为独立部署的SOA服务而设计的）\n\n### 1.5.3 柔性事务解决方案:最大努力通知型(定期校对)\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%9C%80%E5%A4%A7%E5%8A%AA%E5%8A%9B%E9%80%9A%E7%9F%A5%E5%9E%8B1.png)\n\n- 实现\n    - 业务活动的主动方，在完成业务处理之后，向业务活动的被动方发送消息，允许消息丢失。\n    - 业务活动的被动方根据定时策略，向业务活动主动方查询，恢复丢失的业务消息。\n- 约束:被动方的处理结果不影响主动方的处理结果\n- 成本:业务查询与校对系统的建设成本\n- 适用范围\n    - 对业务最终一致性的时间敏感度低\n    - 跨企业的业务活动\n- 用到的服务模式:可查询操作\n- 方案特点\n    - 业务活动的主动方在完成业务处理后，向业务活动被动方发送通知消息（允许消息丢失）\n    - 主动方可以设置时间阶梯型通知规则，在通知失败后按规则重复通知，直到通知N次后不再通知\n    - 主动方提供校对查询接口给被动方按需校对查询，用于恢复丢失的业务消息\n- 行业应用案例:银行通知、商户通知等\n\n## 1.6 总结\n\n### 1.6.1.分类总结\n\n- 刚性事务\n    - 全局事务（标准的分布式事务）\n- 柔性事务\n    - 可靠消息最终一致（异步确何型）\n    - TCC （两阶段型、补偿型）\n    - 最大努力通知（非可靠消息 、 定期校对）\n    - 纯补偿型（略）\n\n### 1.6.2.综合使用案例\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/2018-08-27-123943.png)"
  },
  {
    "path": "2-两种基于可靠消息的最终一致性方案介绍.md",
    "content": "# 2. 两种基于可靠消息的最终一致性方案介绍\n\n## 2.1 消息发送的一致性\n\n- 指产生消息的业务动作与消息发送的一致。（也就是说，如果业务操作成功，那么由这个业务操作所产生的消息一定要成功投递出去，否则就丢消息）\n\n### 2.1.1 如何保障消息发送一致性\n\n- 处理方式1\n  - 如果业务操作成功，执行消息发送前应用故障，消息发不出去，导致消息丢失（订单系统与会计系统的数据不一致）;\n  - 如果业务操作成功，应用正常，但消息系统故障或网络故障，也会导致消息发不出去（订单系统与会计系统的数据不一致）;\n\n```java\n/** 支付订单处理 **/\npublic void completeOrder() {\n    // 订单处理（业务操作）\n    orderBiz.process();\n    // 发送会记原始凭证消息（发送消息）\n    sendAccountingVoucherMsg ();\n}\n```\n\n- 处理方式2\n  - 这种情况下，更不可控，消息发出去了，但业务可能会失败（订单系统与会计系统的数据不一致）\n\n```java\n/** 支付订单处理 **/\npublic void completeOrder() {\n    // 发送会记原始凭证消息（发送消息）\n    sendAccountingVoucherMsg ();\n    // 订单处理（业务操作）\n    orderBiz.process();\n}\n```\n\n### 2.1.2 正向流程\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E4%B8%80%E8%87%B4%E6%80%A71.png)\n\n1. 主动方应用先把消息发给消息中间件，消息状态标记为“待确认”；\n2. 消息中间件收到消息后，把消息持久化到消息存储中，但并不向被动方应用投递消息；\n3. 消息中间件返回消息持久化结果（成功/失败），主动方应用根据返回结果进行判断如何进行业务操作处理：\n    - 失败：放弃业务操作处理，结束（必要时向上层返回失败结果）；\n    - 成功：执行业务操作处理；\n4. 业务操作完成后，把业务操作结果（成功/失败）发送给消息中间件；\n5. 消息中间件收到业务操作结果后，根据业务结果进行处理；\n    - 失败：删除消息存储中的消息，结束；\n    - 成功：更新消息存储中的消息状态为“待发送（可发送）”，紧接着执行消息投递；\n6. 前面的正向流程都成功后，向被动方应用投递消息\n\n### 2.1.3 异常流程\n\n- 异常点分析(任何一个环节都可能会出问题)\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E4%B8%80%E8%87%B4%E6%80%A7%E5%BC%82%E5%B8%B8%E7%82%B91.png)\n\n- 从主动方应用的角度Fenix\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E4%B8%80%E8%87%B4%E6%80%A7%E5%BC%82%E5%B8%B8%E7%82%B92.png)\n\n异常状况 | 可能的状态| 一致性\n---|---|---\n预发送消息失败 | 消息未进存储，业务操作未执行（可能的原因：主动方应用、网络、消息中间件、消息存储） | 一致\n预发送消息后，主动方应用没有收到返回消息存储结果 | (1)消息未进存储，业务操作未执行 | 一致\n预发送消息后，主动方应用没有收到返回消息存储结果 | (2)消息已进存储（待确认），业务操作未执行 | 不一致\n收到消息存储成功的返回结果，但未执行业务操作就失败 | 消息已进存储（待确认），业务操作未执行 | 不一致\n\n- 从消息中间件的角度来分析\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/2018-08-27-145530.png)\n\n异常状况 | 可能的状态| 一致性\n---|---|---\n消息中间件没有收到主动方应用的业务操作处理结果 | (1)消息已进存储（待确认），业务操作未执行（或业务操作出错回滚了） | 不一致\n消息中间件没有收到主动方应用的业务操作处理结果| (2)消息已进存储（待确认），业务操作成功 | 不一致\n消息中间件收到业务操作结果（成功/失败），但处理消息存储中的消息状态失败 | (1)消息已进存储（待确认），业务操作未执行（或业务操作出错回滚了） | 不一致\n消息中间件收到业务操作结果（成功/失败），但处理消息存储中的消息状态失败 | (2)消息已进存储（待确认），业务操作成功 | 不一致\n\n- 总结\n\n异常状况 |  一致性 | 异常处理方法\n---|---|---\n消息未进存储，业务操作未执行 | 一致 | 无需处理\n消息已进存储（状态待确认），业务操作未执行| 不一致 |确认业务操作结果，处理消息（删除消息）\n消息已进存储（状态待确认），业务操作成功,但未通知发送| 不一致 |确认业务操作结果，处理消息（更新消息状态，执行消息投递）\n\n- 解决方法:消息中间件进行对主动业务方进行一次查询确认消息的状态\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E4%B8%80%E8%87%B4%E6%80%A7%E5%BC%82%E5%B8%B8%E7%82%B94.png)\n\n## 2.2 消息消费一致性\n\n- 消息消费流程\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/2018-08-27-155905.png)\n\n### 2.2.1 消息消费流程的异常点\n\n- 消息的消费确认流程中，任何一个环节都可能会出问题！\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E8%B4%B9%E5%BC%82%E5%B8%B8%E7%82%B91.png)\n\n### 2.2.2 消息消费流程异常处理\n\n## 2.3 常规MQ处理流程\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97.png)\n\n1. Producer生成消息并发送给MQ（同步、异步）；\n2. MQ接收消息并将消息数据持久化到消息存储（持久化操作为可选配置）；\n3. MQ向Producer返回消息的接收结果（返回值、异常）；\n4. Consumer监听并消费MQ中的消息；\n5. Consumer获取到消息后执行业务处理；\n6. Consumer对已成功消费的消息向MQ进行ACK确认（确认后的消息将从MQ中删除）。\n\n- 队列消息模型的特点：\n    1. 消息生产者将消息发送到Queue中，然后消息消费者监听Queue并接收消息；\n    2. 消息被确认消费以后，就会从Queue中删除，所以消息消费者不会消费到已经被消费的消息；\n    3. Queue支持存在多个消费者，但是对某一个消息而言，只会有一个消费者成功消费。\n\n- 常规MQ队列消息的处理流程无法实现消息发送一致性；\n- 投递消息的流程其实就是消息的消费流程，可细化。\n- 解决方案如下\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%B6%88%E6%81%AF%E5%8F%91%E9%80%81%E4%B8%80%E8%87%B4%E6%80%A72.png)\n\n>常规MQ队列消息的处理流程无法实现消息发送一致性，因此直接使用现成的MQ中间件产品无法实现可靠消息最终一致性的分布式事务解决方案。\n\n## 2.3 消息幂等性\n\n### 2.3.2 消息重复发送的原因\n\n1. 被动方应用接收到消息，业务处理完成后应用出问题，消息中间件不知道消息处理结果，会重新投递消息。\n2. 被动方应用接收到消息，业务处理完成后网络出问题，消息中间件收不到消息处理结果，会重新投递消息。\n3. 被动方应用接收到消息，业务处理时间过长，消息中间件因消息超时未确认，会再次投递消息。\n4. 被动方应用接收到消息，业务处理完成，消息中间件问题导致收不到消息处理结果，消息会重新投递。\n5. 被动方应用接收到消息，业务处理完成，消息中间件收到了消息处理结果，但由于消息存储故障导致消息没能成功确认，消息会再次投递。\n\n### 2.3.3 业务接口的幂等性设计\n\n>约束：被动方应用对于消息的业务处理要实现幂等\n\n- 对于存在同一请求数据会发生重复调用的业务接口，接口的业务逻辑要实现幂等性设计。\n- 在实际的业务应用场景中，业务接口的幂等性设计，常结合可查询操作一起使用。\n  - 支付订单创建：商户编号 + 商户订单号 + 订单状态\n  - 订单更新处理：平台订单号 + 订单状态\n  - 会计系统记账：系统来源 + 请求号\n\n## 2.4 方案一:本地消息服务的设计\n\n### 2.4.1 面临问题\n\n1. 现成MQ中间件不支持消息发送的一致性\n2. 直接改造MQ中间件难度很大\n3. 有什么变通的实现方式?\n\n### 2.4.2 主要流程\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%9C%AC%E5%9C%B0%E6%B6%88%E6%81%AF%E6%9C%8D%E5%8A%A1%E6%96%B9%E6%A1%881.png)\n\n1. 消息存储与业务存储在同一个本地事务中进行,消息存储后设置为待确认状态,并异步将消息发送(注意需要异步发送消息,不要影响主流程).\n2. 通过一定策略不断将待确认的消息重新发送.\n3. 业务方回**收到消息,成功处理业务,并持久化完成后**调主动方接口,通知主动方此消息已经处理完成,主动方将数据库中消息状态改为已发送.\n4. 实现一个消息管理系统,手动处理多次重发失败已死亡的消息.\n\n### 2.4.3 优势\n\n1. 消息实时性较高\n2. 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了对MQ中间件的依赖\n3. 方案轻量容易实现\n\n### 2.4.4 劣势\n\n1. 业务绑定,耦合性强,不通用(如果不想直接被动方应用调用主动方应用接口,也可以使用另外一条队列来通知主动方应用)\n2. 消息数据与业务数据同库,占用业务系统资源\n3. 业务系统在使用关系型数据库的情况下,消息服务性能会受到关系型数据库并发性能的局限\n\n## 2.5 方案二:独立消息服务\n\n### 2.5.1 面临问题\n\n1. 现成MQ中间件不支持消息发送的一致性\n2. 直接改造MQ中间件难度很大\n3. 有什么变通的实现方式?\n\n### 2.5.2 主要流程\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E7%8B%AC%E7%AB%8B%E6%B6%88%E6%81%AF%E6%9C%8D%E5%8A%A11.png)\n\n1. 存储预发送消息(主动方业务执行之前进行,预发送的消息存储后状态为待确认)\n2. 确认并发送消息(主动方业务完成之后,主动方或消息状态确认系统通过此接口将消息变为取消或发送中)\n3. 查询状态确认超时的消息(消息状态确认系统使用)\n4. 确认消息已被成功消费(被动方业务执行完成之后调用,消息队列的ACK可以在业务处理之前返回)\n5. 查询消费确认超时的信息\n\n### 2.5.3 优势\n\n1. 消息服务独立部署,独立维护,独立伸缩\n2. 消息存储可以按需选择不同的数据库来集成实现\n3. 消息服务可以被相同的使用场景共用,降低消息重复建设消息服务的成本\n4. 从应用设计开发的角度实现了消息数据的可靠性,消息数据的可靠性不依赖于MQ中间件,弱化了MQ中间件特性的依赖\n5. 降低了业务系统与系统间的耦合,有利于系统的扩展维护\n\n### 2.5.4 劣势\n\n1. 一次消息需要发送两次请求\n2. 主动方应用系统需要实现业务操作状态校验查询接口"
  },
  {
    "path": "3-基于可靠消息的最终一致性——独立消息服务实现.md",
    "content": "# 3. 最终一致性设计与实现——独立消息服务\n\n## 3.1 整体架构\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E7%8B%AC%E7%AB%8B%E6%B6%88%E6%81%AF%E6%9C%8D%E5%8A%A11.png)\n\n## 3.2 消息服务子系统\n\n### 3.2.1 基本功能\n\n- 存储预发送消息(主动方业务执行之前进行,预发送的消息存储后状态为待确认)\n- 确认并发送消息(主动方业务完成之后,主动方或消息状态确认系统通过此接口将消息变为取消或发送中)\n- 查询状态确认超时的消息(消息状态确认系统使用)\n- 确认消息已被成功消费(被动方业务执行完成之后调用)\n- 查询消费确认超时的信息\n\n### 3.2.2.数据库设计\n\n字段名 | 含义\n---|---\nid | 主键ID\nversion | 版本号\ncuser | 创建人\nmuser | 修改人\nctime | 创建时间\nmtime | 修改时间\nmessage_id | 消息ID\nmessage_body | 消息内容\nmessage_try_times | 消息重试次数\nmessage_queue | 消息队列名\nmessage_dead | 消息是否死亡\nstatus | 消息状态\nremark | 备注\n\n### 3.2.3.业务系统实现\n\n* 预存储消息接口:创建消息,将消息状态初始化为待确认,持久化\n* 确认发送消息接口:将消息状态更改为发送中,将消息发送到MQ,注意这里不将消息状态改为已发送\n* 存储并发送消息接口:直接存储消息并且直接发送(比如支付网关通过消息服务通知其他服务)\n* 直接发送消息接口:透传,消息服务不持久化消息,相当于直接调用MQ\n* 根据id重发消息接口:用于消息恢复子系统或消息管理子系统,通过此接口重新发送接口\n* 将消息标记为死亡接口:用于将消息标记为死亡,不再重发\n* 花样查询消息接口:花样查询各种消息的接口\n* 删除消息接口:业务操作成功和主动方业务失败后用于删除消息\n* 重发某个队列中的全部死亡消息:防止出现被动方应用宕机后消息积压均重发多次后进入死亡状态的结果\n\n\n## 3.3 消息管理子系统\n\n- 主要用来用于手动管理死亡消息,重发等\n\n## 3.4 消息状态确认子系统\n\n### 3.4.1 接口设计\n\n1. 处理待确认的超时消息(注意排序,超时时间越长应该越早处理)\n\n## 3.5 消息恢复子系统\n\n- 主动方调用方业务执行成功,消息服务子系统中消息状态已变成发送中.我们必须保证消息被被动方消费\n\n## 3.6 实时消息服务子系统\n\n- 使用MQ实现\n\n### 3.7 异步确认(防止可补偿流程错误导致主流程回滚)\n\n- 主动业务方流程\n    1. 预发送消息\n    2. 执行业务\n    3. 确认发送(如果这一步超时会回滚前面的业务,但是消息已被发送到消息服务子系统并并持久化)"
  },
  {
    "path": "4-最大努力通知方案.md",
    "content": "# 4. 最大努力通知方案(定期校对)\n\n## 4.1 介绍\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/%E6%9C%80%E5%A4%A7%E5%8A%AA%E5%8A%9B%E9%80%9A%E7%9F%A5%E5%9E%8B1.png)\n\n- 实现\n  - 业务活动的主动方,在完成业务活动处理后,向业务活动被动方发送消息,允许消息丢失\n  - 业务活动的被动方根据定时策略,向业务活动的主动方查询,恢复丢失的业务消息\n- 约束:被动方的业务处理结果不影响主动方的业务处理\n- 成本:业务查询与校对系统建设成本\n- 适用范围\n  - 对时间敏感性较低的业务\n  - 对账\n- 用到的服务模式:可查询操作\n- 方案特点\n  - 业务活动的主动方在完成业务处理后,向业务活动被动方发送通知消息(允许消息丢失)\n  - 主动方可以设置时间阶梯型通知规则,在通知失败后按规则重复通知,直到通知N次后不再通知\n  - 主动方提供校对查询接口给被动方,被动方按需校对查询,用于恢复丢失的业务消息\n- 行业应用案例\n  - 银行通知,商户通知等\n  - 对账文件\n\n## 4.2 设计实现\n\n- 定时任务队列\n\n## 4.3 优化\n\n1. 通知记录/通知日志可视化,手工触发\n2. 考虑吧通知服务做的更通用,通知队列区分,不同队列不同规则等\n3. 保证通知服务的可用性,必要时建立独立的数据库\n4. 要求被动方处理通知接收的业务接口要实现幂等性\n5. 内存调优与流量控制(生产速率不匹配,导致大量消息驻留在消费端内存中)"
  },
  {
    "path": "5-TCC型方案.md",
    "content": "# 5-TCC型方案\n\n## 5.1 介绍\n\n>TCC方案属于两阶段型/补偿型\n\n### 5.1.1 实现\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/TCC1.png)\n\n* 一个完整的业务活动由一个主业务服务与若干从业务服务组成\n* 主业务服务负责发起并完成整个业务活动\n* 从业务服务提供TCC型业务操作\n* 业务活动管理器控制业务活动的一致性,它登记业务活动中的操作,并在业务活动提交时确认所有的TCC型操作的confirm操作,在业务活动取消时调用所有TCC型操作的cancel操作.\n\n### 5.1.2 成本\n\n* 业务活动结束时confirm或cancel操作的执行成本\n* 业务活动日志成本\n\n### 5.1.3 适用范围\n\n* 强隔离性,严格一致性要求的业务活动\n* 适用于执行时间较短的业务,如处理账户,收费等业务\n\n### 5.1.4 用到的服务模式\n\n* TCC操作\n* 幂等操作\n* 可补偿操作\n* 可查询操作\n\n### 5.1.5 方案特点\n\n* 不与具体的服务框架耦合(RPC架构通用)\n* 位于业务服务层,而非资源层\n* 可以灵活选择业务资源的锁定粒度\n* TCC里对每个服务资源操作的是本地事务,数据被lock的时间短,可扩展性好(可以说是为独立部署的SOA服务而设计的)\n\n### 5.1.6 行业应用案例\n\n* 支付宝XTS(蚂蚁金融云的分布式事务服务DTS)\n\n## 5.2 TCC框架\n\n>[GitHub上一个开源的TCC框架实现](https://github.com/changmingxie/tcc-transaction),以此项目为基础讲解TCC框架的实现(主要是在项目源码上添加一些注释)\n\n## 5.3 TCC应用实例\n\n### 5.3.1 业务流程\n\n>以订单处理流程为例:账户扣款->使用红包优惠券->订单状态改变为例\n\n![image](http://clsaa-distributed-transaction-img-bed-1252032169.cossh.myqcloud.com/TCC3.png)\n\n>一个主服务调用从业务服务,被TCC控制的方法都应该具有三种方法:主方法(try方法)/确认方法(confirm方法)/取消方法(cancel方法)\n\n1. 修改支付记录状态\n2. 修改订单状态\n3. 远程调用点券服务\n4. 远程调用积分服务\n5. 若远程调用存在异常\n\n### 5.2.2 使用TCC-TRASACTION示例\n\n```java\n@Service(\"pointAccountService\")\npublic class pointAccountServiceImpl implements pointAccountService{\n\n    private static final Logger LOG = LoggerFactory.getLogger(pointAccountServiceImpl.class);\n\n    @Autowired\n    private pointAccountDao pointAccountDao;\n\n    @Autowired\n    private pointAccountHistoryDao pointAccountHistoryDao;\n\n    @Override\n    public void saveData(pointAccount pointAccount) {\n        pointAccountDao.insert(pointAccount);\n    }\n\n    @Override\n    public void updateData(pointAccount pointAccount) {\n        pointAccountDao.update(pointAccount);\n    }\n\n\n    /**\n     * 积分账户加款 Trying\n     * @param transactionContext\n     * @param userNo\n     * @param pointAmount\n     * @param requestNo\n     * @param bankTrxNo\n     * @param trxType\n     * @param remark\n     * @throws BizException\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @Compensable(confirmMethod = \"confirmCreditToPointAccountTcc\",cancelMethod = \"cancelCreditToPointAccountTcc\")\n    public void creditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {\n\n        LOG.info(\"===>creditToPointAccountTcc TRYING begin\");\n\n        //根据商户编号获取商户积分账户\n        pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);\n        if (pointAccount == null){//如果不存在商户积分账户,创建一条新的积分账户\n            pointAccount = new pointAccount();\n            pointAccount.setBalance(0);\n            pointAccount.setUserNo(userNo);\n            pointAccount.setStatus(PublicEnum.YES.name());\n            pointAccount.setCreateTime(new Date());\n            pointAccount.setId(StringUtil.get32UUID());\n            pointAccountDao.insert(pointAccount);\n        }\n\n        pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);\n        // 幂等判断\n        if ( pointAccountHistory == null ){//防止多次提交\n            pointAccountHistory = new pointAccountHistory();\n            pointAccountHistory.setId(StringUtil.get32UUID());\n            pointAccountHistory.setCreateTime(new Date());\n            pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.TRYING.name());//消息不可用\n            pointAccountHistory.setAmount(pointAmount);///积分账户变动额\n            pointAccountHistory.setBalance(pointAccount.getBalance() + pointAmount);\n            pointAccountHistory.setBankTrxNo(bankTrxNo);//银行流水号\n            pointAccountHistory.setRequestNo(requestNo);//请求号\n            pointAccountHistory.setFundDirection(PointAccountFundDirectionEnum.ADD.name());\n            pointAccountHistory.setTrxType(trxType);\n            pointAccountHistory.setRemark(remark);\n            pointAccountHistory.setUserNo(userNo);\n            pointAccountHistoryDao.insert(pointAccountHistory);\n        }else if (PointAccountHistoryStatusEnum.CANCEL.name().equals(pointAccountHistory.getStatus())){\n            //如果是取消的,有可能是之前的业务出现异常问题而取消,那么重试阶段,再将状态更新为TYING状态,而不是重新创建一条\n            LOG.info(\"之前因为业务问题取消后,又重试的{}\" , pointAccountHistory.getBankTrxNo());\n            pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.TRYING.name());\n            this.pointAccountHistoryDao.update(pointAccountHistory);\n        }\n        //添加一条不可用的积分账户流水\n        LOG.info(\"===>creditToPointAccountTcc TRYING end\");\n    }\n\n    /**\n     * 积分账户增加确认\n     * @param transactionContext\n     * @param userNo\n     * @param pointAmount\n     * @param requestNo\n     * @param bankTrxNo\n     * @param trxType\n     * @param remark\n     * @return\n     * @throws BizException\n     */\n\n    @Transactional(rollbackFor = Exception.class)\n    public void confirmCreditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {\n\n        LOG.info(\"===>confirmCreditToPointAccountTcc begin\");\n        //根据请求号获取账户基本流水\n        pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);\n        // 幂等判断\n        if ( pointAccountHistory == null  || PointAccountHistoryStatusEnum.CONFORM.name().equals(pointAccountHistory.getStatus())){//该笔交易流水已处理过,不需再处理\n            return;\n        }\n\n        pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CONFORM.name());\n        pointAccountHistoryDao.update(pointAccountHistory);\n\n        pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);//获取用户积分账户\n        pointAccount.setBalance(pointAccount.getBalance() + pointAmount);//增加账户余额\n        pointAccountDao.update(pointAccount);\n\n        LOG.info(\"===>confirmCreditToPointAccountTcc end\");\n\n    }\n    /**\n     *积分账户增加回滚\n     * @param transactionContext\n     * @param userNo\n     * @param pointAmount\n     * @param requestNo\n     * @param bankTrxNo\n     * @param trxType\n     * @param remark\n     * @throws BizException\n     */\n    @Transactional(rollbackFor = Exception.class)\n    public void cancelCreditToPointAccountTcc(TransactionContext transactionContext, String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {\n        LOG.info(\"===>cancelCreditToPointAccountTcc begin\");\n        pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);\n        // 幂等判断\n        if ( pointAccountHistory == null  || !PointAccountHistoryStatusEnum.TRYING.name().equals(pointAccountHistory.getStatus())){//该笔交易流水已处理过,不需再处理\n            return;\n        }\n\n        pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CANCEL.name());\n        pointAccountHistoryDao.update(pointAccountHistory);\n        LOG.info(\"===>cancelCreditToPointAccountTcc end\");\n    }\n\n    /**\n     * 积分账户加款 Trying\n     * @param userNo\n     * @param pointAmount\n     * @param requestNo\n     * @param bankTrxNo\n     * @param trxType\n     * @param remark\n     * @throws BizException\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void creditToPointAccount(String userNo, Integer pointAmount, String requestNo, String bankTrxNo, String trxType, String remark) throws BizException {\n\n        //根据商户编号获取商户积分账户\n        pointAccount pointAccount = pointAccountDao.getByUserNo(userNo);\n        if (pointAccount == null){//如果不存在商户积分账户,创建一条新的积分账户\n            pointAccount = new pointAccount();\n            pointAccount.setBalance(0);\n            pointAccount.setUserNo(userNo);\n            pointAccount.setStatus(PublicEnum.YES.name());\n            pointAccount.setCreateTime(new Date());\n            pointAccount.setId(StringUtil.get32UUID());\n            pointAccountDao.insert(pointAccount);\n        }\n\n\n    //添加一条积分历史\n    pointAccountHistory pointAccountHistory = pointAccountHistoryDao.getByRequestNo(requestNo);\n    if ( pointAccountHistory == null ){//防止多次提交\n            pointAccountHistory = new pointAccountHistory();\n            pointAccountHistory.setId(StringUtil.get32UUID());\n            pointAccountHistory.setCreateTime(new Date());\n            pointAccountHistory.setStatus(PointAccountHistoryStatusEnum.CONFORM.name());//可用\n            pointAccountHistory.setAmount(pointAmount);///积分账户变动额\n            pointAccountHistory.setBalance(pointAccount.getBalance() + pointAmount);\n            pointAccountHistory.setBankTrxNo(bankTrxNo);//银行流水号\n            pointAccountHistory.setRequestNo(requestNo);//请求号\n            pointAccountHistory.setFundDirection(PointAccountFundDirectionEnum.ADD.name());\n            pointAccountHistory.setTrxType(trxType);\n            pointAccountHistory.setRemark(remark);\n            pointAccountHistory.setUserNo(userNo);\n            pointAccountHistoryDao.insert(pointAccountHistory);\n        }\n\n        //增加积分账户\n        pointAccount.setBalance(pointAccount.getBalance() + pointAmount);//增加账户余额\n        pointAccountDao.update(pointAccount);\n    }\n\n    @Override\n    public pointAccount getDataById(String id) {\n        return pointAccountDao.getById(id);\n    }\n\n    @Override\n    public PageBean listPage(PageParam pageParam, pointAccount pointAccount) {\n        Map<String, Object> paramMap = new HashMap<String, Object>();\n        return pointAccountDao.listPage(pageParam, paramMap);\n    }\n}\n```"
  },
  {
    "path": "README.md",
    "content": "# 分布式事务解决方案\n\n>笔记阐明了系统中常用的分布式事务解决方案\n\n* 第一节中对分布式系统数据一致性问题产生点进行分析,并对各种分布式事务方案及其涉及的概念进行介绍\n* 第二节中首先对消息发送的一致性与接口的幂等性进行介绍, 然后对基于可靠消息(独立消息实现和本地事务实现)的两种最终一致性实现方案进行介绍.\n* 第三节中对基于可靠消息(本地事务实现)的最终一致性方案的实现方法进行介绍,此方案适用于大部分场景,且系统之间耦合性较低,推荐使用.\n* 第四节中对最大努力通知型方案进行介绍,此方案实现最为简单,只需要生产端实现一个查询接口,消费端根据一定策略定时查询,但数据一致的实时性差.\n* 第五节中介绍了TCC型方案,此方案已经有较为成熟的框架支持:[tcc-transaction](https://github.com/changmingxie/tcc-transaction),可以直接使用,但其源码基本没有注释,在笔记中详细记录了框架的使用方法与源码分析.TCC方案适用于数据实时性要求较高的服务,如:转账充值等.\n\n* [目录](SUMMARY.md)"
  },
  {
    "path": "SUMMARY.md",
    "content": "# Summary\n\n* [1-常用分布式事务解决方案介绍](1-常用分布式事务解决方案介绍.md)\n* [2-基于可靠消息的最终一致性方案介绍](2-基于可靠消息的最终一致性方案介绍.md)\n* [3-最终一致性设计与实现——独立消息服务](3-最终一致性设计与实现——独立消息服务.md)\n* [4-最大努力通知方案(定期校对)](4-最大努力通知方案(定期校对).md)\n* [5-TCC型方案](5-TCC型方案.md)\n"
  }
]