分布式事务的 N 种实现
转自:http://myfjdthink.com/2019/04/26/%E5%88%86%E5%B8%83%E5%BC%8F%E4%BA%8B%E5%8A%A1%E7%9A%84-n-%E7%A7%8D%E5%AE%9E%E7%8E%B0/
需求缘起
在微服务架构中,随着服务的逐步拆分,数据库私有已经成为共识,这也导致所面临的分布式事务问题成为微服务落地过程中一个非常难以逾越的障碍,但是目前尚没有一个完整通用的解决方案。
其实不仅仅是在微服务架构中,随着用户访问量的逐渐上涨,数据库甚至是服务的分片、分区、水平拆分、垂直拆分已经逐渐成为较为常用的提升瓶颈的解决方案,因此越来越多的原子操作变成了跨库甚至是跨服务的事务操作。最终结果是在对高性能、高扩展性,高可用性的追求的道路上,我们开始逐渐放松对一致性的追求,但是在很多场景下,尤其是账务,电商等业务中,不可避免的存在着一致性问题,使得我们不得不去探寻一种机制,用以在分布式环境中保证事务的一致性
引用自 https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
理论基石
ACID 和 BASE
见 https://www.infoq.cn/article/2018/08/rocketmq-4.3-release
见 https://www.txlcn.org/zh-cn/docs/preface.html
2PC
谈到分布式事务,首先要说的就是 2PC(two phase commit)方案,如下图所示:
2PC 把事务的执行分为两个阶段,第一个阶段即 prepare 阶段,这个阶段实际上就是投票阶段,协调者向参与者确认是否可以共同提交,再得到全部参与者的所有回答后,协调者向所有的参与者发布共同提交或者共同回滚的指令,用以保证事务达到一致性。
2PC 是几乎所有分布式事务算法的基础,后续的分布式事务算法几乎都由此改进而来。
需求样例
这里我们定义一个充值需求,后续我们在各个实现中看看如何为该需求实现分布式事务。
Order 和 Account 分别是独立的一个服务,充值完成后,要分别将订单Order 设置为成功以及增加用户余额。
实现1 Seata
介绍 & 框架
Seata(Fescar) is a distributed transaction solution with high performance and ease of use for microservices architecture.
阿里开源,其特点是用一个事务管理器,来管理每个服务的事务,本质上是 2PC(后文会解释) 的一种实现。
Seata 提供了全局的事务管理器
原理
代理 SQL 查询,实现事务管理,类似中间件
实现充值需求
用该方案实现需求的话,就是这样的:
Order 和 Account 都接入 Seata 来代理事务
代码示例
比起自己去实现 2PC,Seata 提供了简化方案,代码实例见 :
实现2 TCC
介绍
TCC(Try-Confirm-Concel) 模型是一种补偿性事务,主要分为 Try:检查、保留资源,Confirm:执行事务,Concel:释放资源三个阶段,如下图所示:
其中,活动管理器记录了全局事务的推进状态以及各子事务的执行状态,负责推进各个子事务共同进行提交或者回滚。同时负责在子事务处理超时后不停重试,重试不成功后转手工处理,用以保证事务的最终一致性。
原理
每个子节点,要实现 TCC 接口,才能被管理。
优点:不依赖 local transaction,可以管理非关系数据库库的服务
缺点:TCC 模式多增加了一个状态,导致在业务开发过程中,复杂度上升,而且协调器与子事务的通信过程增加,状态轮转处理也更为复杂。而且,很多业务是无法补偿的,例如银行卡充值。
实现框架
tx-lcn
LCN distributed transaction framework, compatible with dubbo, spring
cloud and Motan framework, supports various relational databases
https://www.txlcn.org
或者 Seata MT 模式
代码示例
实现充值需求
需要把 Oder.done 和 Account 的余额+ 操作都实现 tcc 接口
可以看出,这样真的很麻烦,能用本地事务的还是尽量用本地事务吧
实现3 事务消息
介绍
以购物场景为例,张三购买物品,账户扣款
100 元的同时,需要保证在下游的会员服务中给该账户增加 100
积分。由于数据库私有,所以导致在实际的操作过程中会出现很多问题,比如先发送消息,可能会因为扣款失败导致账户积分无故增加,如果先执行扣款,则有可能因服务宕机,导致积分不能增加,无论是先发消息还是先执行本地事务,都有可能导致出现数据不一致的结果。
事务消息的本质就是为了解决此类问题,解决本地事务执行与消息发送的原子性问题。
实现框架
Apache RocketMQ™ is an open source distributed messaging and streaming data platform.
原理
- 事务发起方首先发送 prepare 消息到 MQ。
- 在发送 prepare 消息成功后执行本地事务。
- 根据本地事务执行结果返回 commit 或者是 rollback。
- 如果消息是 rollback,MQ 将删除该 prepare 消息不进行下发,如果是 commit 消息,MQ 将会把这个消息发送给 consumer 端。
- 如果执行本地事务过程中,执行端挂掉,或者超时,MQ 将会不停的询问其同组的其它 producer 来获取状态。
- Consumer 端的消费成功机制有 MQ 保证。
优点:对异步操作支持友好
缺点:Producer 端要为 RMQ 实现事务查询接口,导致在业务开发过程中,复杂度上升。
代码示例
// TODO
实现充值需求
通过 MQ,来保障 Order 和 Acount 的两个操作要么一起成功,要么一起失败。
注意一个点,假设 Account 的余额+失败了,这里是无法回滚 Order 的操作的,Account 要保证自己能正确处理消息。
实现4 本地消息表
介绍 & 原理
分布式事务=A系统本地事务 + B系统本地事务 + 消息通知;
准备:
A系统维护一张消息表log1,状态为未执行,
B系统维护2张表,
未完成表log2,
已完成表log3,
消息中间件用两个topic,
topic1是A系统通知B要执行任务了,
topic2是B系统通知A已经完成任务了,
- 用户在A系统里领取优惠券,并往log1插入一条记录
- 由定时任务轮询log1,发消息给B系统
- B系统收到消息后,先检查是否在log3中执行过这条消息,没有的话插入log2表,并进行发短信,发送成功后删除log2的记录,插入log3
- B系统发消息给A系统
- A系统根据id删除这个消息
假设出现网络中断和系统 Crash 等问题时,为了继续执行事务,需要进行重试。重试方式有:
- 定时任务恢复事务的执行,
- 使用 MQ 来传递消息,MQ可以保证消息被正确消费。
优点:简单
缺点:程序会出现执行到一半的状态,重试则要求每个操作需要实现幂等性
注意:分布式系统实现幂等性的时候,记得使用分布式锁,分布式锁详细介绍见文末参考文章
实现充值需求
通过消息表,把断开的事务继续执行下去。
实现5 考拉的方案
介绍 & 原理
考拉的方案,就是使用本地消息表,但是少了两个重要组件(MQ 和 关系型数据库),写起来还是比较辛苦的。
考拉方案有如下特点:
- Order 表承担了消息表功能
- 服务之间使用 http 通信,所以碰到问题要依赖定时任务发布补单重试
- 没有使用关系型数据库,幂等性的实现比较困难。
实现充值需求
难点:
实现幂等性的要求太高,基本要求所有操作都需要实现幂等性,例如更新余额操作,要高效更新,简单的办法是使用乐观锁,但是要同时兼顾幂等性的话,乐观锁就不够用了。
程序在任一一步断开,都需要重新运行起来,补单程序会很难写(简单的业务还好,复杂业务就会混乱了)
改进建议:
- 服务直接使用 mq 通信,服务异常需要重试消费。
- 使用关系型数据库,通过本地事务,可以只程序开始处判断重复,简化幂等性的实现逻辑
实际上就是往上一个实现4上走
总结
我们先对这些实现方案进行一个总结:
基础原理 | 实现 | 优势 | 必要前提 |
---|---|---|---|
2PC | Seata | 简单 | 关系型数据库 |
2PC | TCC | 不依赖关系系数据库 | 实现 TCC 接口 |
2PC | 事务消息 | 高性能 | 实现事务检查接口 |
最终一致性 | 本地消息表 | 去中心化 | 侵入业务,接口需要幂等性 |
各个方案有自己的优劣,实际使用过程中,我们还是需要根据情况来选择不同事务方案来灵活组合。
例如存在服务模块A 、B、 C。A模块是mysql作为数据源的服务,B模块是基于redis作为数据源的服务,C模块是基于mongo作为数据源的服务。若需要解决他们的事务一致性就需要针对不同的节点采用不同的方案,并且统一协调完成分布式事务的处理。
方案:将A模块采用 Seata 模式、B/C采用TCC模式就能完美解决。
分布式事务的 N 种实现的更多相关文章
- 分析 5种分布式事务方案,还是选了阿里的 Seata(原理 + 实战)
好长时间没发文了,最近着实是有点忙,当爹的第 43 天,身心疲惫.这又赶上年底,公司冲 KPI 强制技术部加班到十点,晚上孩子隔两三个小时一醒,基本没睡囫囵觉的机会,天天处于迷糊的状态,孩子还时不时起 ...
- springcloud分布式事务终极探讨
2018阿里云全部产品优惠券(好东东,强烈推荐)领取地址:https://promotion.aliyun.com/ntms/act/ambassador/sharetouser.html?userC ...
- 基于两阶段提交的分布式事务实现(UP-2PC)
引言:分布式事务是分布式数据库的基础性功能,在2017年上海MySQL嘉年华(IMG)和中国数据库大会(DTCC2018)中作者都对银联UPSQL Proxy的分布式事务做了简要介绍,受限于交流形式难 ...
- 分布式事务解决方案汇总:2PC、3PC、消息中间件、TCC、状态机+重试+幂等(转)
数据一致性问题非常多样,下面举一些常见例子.比如在更新数据的时候,先更新了数据库,后更新了缓存,一旦缓存更新失败,此时数据库和缓存数据会不一致.反过来,如果先更新缓存,再更新数据库,一旦缓存更新成功, ...
- 面试被问分布式事务(2PC、3PC、TCC),这样解释没毛病!
整理了一些Java方面的架构.面试资料(微服务.集群.分布式.中间件等),有需要的小伙伴可以关注公众号[程序员内点事],无套路自行领取 更多优选 一口气说出 9种 分布式ID生成方式,面试官有点懵了 ...
- SpringCloud(六)分布式事务
在分布式系统中,分布式事务基本上是绕不开的, 分布式事务是指事务的参与者.支持事务的服务器.资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上 .其实就可以简单理解成在分布式系统中实现事务 ...
- 通过Dapr实现一个简单的基于.net的微服务电商系统(十九)——分布式事务之Saga模式
在之前的系列文章中聊过分布式事务的一种实现方案,即通过在集群中暴露actor服务来实现分布式事务的本地原子化.但是actor服务本身有其特殊性,场景上并不通用.所以今天来讲讲分布式事务实现方案之sag ...
- Spring Cloud Alibaba分布式事务组件 seata 详解(小白都能看懂)
一,什么是事务(本地事务)? 指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行. 简单的说,事务就是并发控制的单位,是用户定义的一个操作序列. 而一个逻辑工作单元要成 ...
- 3种使用MQ实现分布式事务的方式
1.保证消息传递与一致性 1.1生产者确保消息自主性 当生产者发送一条消息时,它必须完成他的所有业务操作. 如下图: 这保证消费者接受到消息时,生产者已处理完毕相关业务,也就是1PC的基础. 1.2 ...
随机推荐
- matlab基础向7-8:画图
1.画直角坐标系的二维图 画直线: x1=[1 2 3]; y1=[4 5 6]; plot(x1,y1);%斜率为1的直线,穿过(1,4)(2,5)(3,6) 画抛物线y=x*x(-3<=x& ...
- docker 空间清理
https://blog.csdn.net/qq_28001193/article/details/79555177 清理之后,重要的是找到原因,如上连接所示,其中一个占空间比较大的是日志文件,除了考 ...
- ansible部署EFK
修改自己不确定的配置文件前,先准备备份,防患于未然!!! Environment:{ 目前测试准备三台2m2g虚拟机 详情概略图见EFK的架构图 https://www.cnblogs.com/se ...
- yolov2
在这篇文章中,作者首先在YOLOv1的基础上提出了改进的YOLOv2,然后提出了一种检测与分类联合训练方法,使用这种联合训练方法在COCO检测数据集(用于检测)和ImageNet分类数据集(用于分类) ...
- C# 按行读取文件 从某行开始取
; FileStream fs = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); u ...
- Codeforces 161.D. Distance in Tree-树分治(点分治,不容斥版)-树上距离为K的点对数量-蜜汁TLE (VK Cup 2012 Round 1)
D. Distance in Tree time limit per test 3 seconds memory limit per test 512 megabytes input standard ...
- JavaScript语法-流程控制语句
一.JavaScript特殊语法 JS特殊语法: 1. 语句以;结尾,如果一行只有一条语句则 ;可以省略 (不建议) 2. 变量的定义使用var关键字,也可以不使用 * 用: 定义的变量是局部变量 * ...
- ln -s 使用
最近开发项目中遇到一个问题,网站上传文件到项目根目录下的upload文件夹,但是每次项目发布都会把upload文件夹删除掉,所以我们需要把upload文件夹放在系统目录下而不是项目根目录下. 访问的时 ...
- Unity2D游戏开发之保卫萝卜
保卫萝卜是2D塔防游戏里边的一个经典案例,这次去开发这个游戏,我们会尽力去实现和原版一样的功能,做好我们可以处理好的每一个游戏细节(比如塔攻击的集火目标优先攻击,与自动搜索,格子的三种处理逻辑,UI的 ...
- php laravel 环境搭建
最近上一个新项目,时间比较紧,为了满足业务需求,没有办法,只有上我大 php 了,找了一个带些基础的数据结构,用的是 laravel 搭建的,然后寻坑就开始了,先是构建 docker 镜像就坑了,然后 ...