蘑菇街中间件分布式事物 的设计与实现
的设计与实现)
感言
离开中间件团队,带大数据团队已经有一年半时间了,感觉之前做的有些东西还是值得记录一下的,对于分布式事务,网上介绍的很多,但是都偏概念性介绍,对于真正的实现细节,好像还真没看到有介绍的,那么这里就介绍一下当时我们自己的做的 分布式事务中间件的具体实现吧
什么是分布式事务?
什么是事务
事务其实就是一系列指令的集合。
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节
单机事务
单个数据库,执行多个操作,保证事务一致性。
由Mysql提供的事务机制保证
分布式事务
多个数据库,执行多个操作,保证事务一致性。
可以由中间件Raptor保证
分布式事务产生背景
在当前如火如荼的互联网浪潮下,如何应对海量数据、高并发成为大家面临的普遍难题。广大IT公司从以往的集中式网站架构,纷纷转向分布式的网站架构,随之而来的就是进行数据库拆分和应用拆分,如何在跨数据库、跨应用保证数据操作和业务操作的一致性、原子性,又成为需要解决的新的问题
分布式事务需求来源
跨数据库
数据库拆分(水平、垂直)带来的分布式事务 -> 保证跨库操作的原子性
由单个业务方直接在多个数据库上执行操作。
跨应用
应用拆分带来的分布式事务 -> 保证跨应用业务操作的原子性
多个业务方跨服务调用,并分别在各自数据库上执行操作
分布式事务方案
TCC
优点:性能损耗比较低。
缺点:业务方接入成本非常大,需要自己将业务场景拆分为 try-commit-cancel ,并自己编写 try-commit-cancel 代码
2PC
优点:业务方接入成本非常低,只需要在原有代码上添加一个 注解即可
缺点:性能会有损耗,有单点故障。
总评:性能方面可以优化,单点故障也可以解决
最终选择
跟业务方交流后,业务方也希望有一个低成本的接入,那么我们优先做2PC分布式事务,并且对2PC方案进行优化,降低RT,解决单点故障。
- 后续有需要求可以再考虑做TCC
2PC方案
协议参与者
在两阶段提交协议中,系统一般包含两类角色:一类为事务协调者(coordinator),另一类为事务参与者(participants,cohorts或workers),
事务参与者
在两阶段提交协议中,系统一般包含两类角色:一类为事务协调者(coordinator),另一类为事务参与者(participants,cohorts或workers)
事务协调者
一个远端的事务中心,用于协调各个事务参与者,开始事务,决策事事务,统一进行 提交 / 回滚 事务 等操作
请求阶段(commit-request phase)
在请求阶段,事务参与者会执行各自的SQL,然后将执行结果,告知协调者自己的决策:准备提交(事务参与者本地作业执行成功)或 准备回滚(本地作业执行故障)。
提交阶段(commit phase)
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或回滚。
当且仅当所有的参与者同意提交事务协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者回滚事务。
参与者在接收到协调者发来的消息后将执行响应的操作。
2PC方案-缺点
与事务中心交互较频繁
分布式事务需要多次与事务中心交互,会有多次请求。这样肯定会增加方法执行的RT,并会拉长事务周期
同步阻塞问题
执行过程中,所有参与节点都是事务阻塞型的,当参数者update某一条数据时,数据库会添加写锁,这时候,如果有别的请求update此条数据 就会等待前一次事务释放锁,由于分布式事务的存在,会将事务周期拉长,导致等待锁释放的时间比较长,所以在高并发的情况下,对热点数据使用分布式事务,会导致RT变长
单点故障
由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
数据不一致问题
在二阶段提交的阶段二中,当协调者向参与者发送commit请求之后,发生了局部网络异常或者在发送commit请求过程中协调者发生了故障,这回导致只有一部分参与者接受到了commit请求。
而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。
2PC方案-优化
降低与事务中心交互的RT
异步化:将这些都请求都采用异步的方式,不影响主流程,但是这样又会产生一个问题,如果异步的请求失败了怎么办? 追加本地事务表,如果由于异步请求造成的 事务中心的数据不一致的问题,那么 验证 本地事务表 事务是否一致。
解决同步阻塞和高并发的问题
降低RT:参考方案1
提供TCC模式:如果业务方大量并发请求热点数据,又对性能要求非常高,那么可以提供TCC模式(后续可以提供此模式)
事务中心故障时高可用
异步化:所有的与事务中心的交互,都是异步的,不影响主流程
决策转移:事务决策由事务中心转移倒事务发起方
故障处理:如果与事务中心通信失败,重试后,仍然失败,则根据当前SQL执行结果,自行决策,并更新本地事务状态,后续等中心恢复后,check 本地事务表的 状态一致性,如果状态不一致,告警,人工修复。
数据一致性保证
如果二阶段的提交阶段中,部分commit成功,部分commit失败,那么会造成事务中心 事务表 事务状态不一致的情况, 如果发现 此情况,则 继续 检查 本地事务表,的事务状态是否一致,如果 一致 则 OK,如果不一致,则人工告警处理,进行数据回退或订正。
分布式事务需求来源
非跨应用分布式事务
需求来源
1.由于数据水平或垂直拆分,单个应用 一次请求 需要访问多个分片库
2.单个应用 一次请求需要 访问 多个数据源
3.并保证多个物理库的数据一致性
架构图
时序图
跨应用分布式事务
需求来源
1.由于微服务架构,通过RPC(远程)调用访问不同的应用
2.再由不同的应用,访问不同的数据库
3.并且保证多个物理库的数据一致性
架构图
时序图
代码实现方案
方案说明
1.Spring中已经支持了单机事务的实现,通过注解方式进行事务拦截,事务判定,事务执行
2.为了保持用户的使用习惯,Raptor分布式事务的使用方式与Spring单机事务的基本一致
3.通过重写Spring事务管理器来实现对Raptor分布式事务的支持
1. 重写Spring事务管理器,使其支持多数据源事务
2. 重写Spring事务管理器,使其支持多Connection绑定,以及 多Connection提交/回滚
3. 不支持嵌套事务,如果遇到会抛异常
Spring单机事务流程图
Raptor分布式事务流程图
数据一致性&稳定性
数据一致性
1绝大部分场景下,都可以进行正确的事务决策,全部回滚 或 全部提交。
2.提交阶段,如果部分commit成功,部分commit失败(提交阶段失败的概率非常低),事务中心会发现事务不一致,再检查本地事务表,如果发现仍然不一致,则告警,人工介入处理
稳定性
1.异步上报事务中心的消息丢失:在这种情况下,事务中心会误以为数据不一致性,此时再去检查本地事务表,如果发现本事事务表状态一致,则忽略。
2.事务中心部分不可用:事务中心是一个无状态的集群,部分机器不可用时,不影响整体可用性,所以整个集群挂掉的概率非常低
3.事务中心集群不可用:由于与事务中心的交互都是异步的,当事务中心集群不可用时,不影响主流程,根据事务发起方的决策结果,统一进行commit/rollback, 等事务中心恢复后,对本地事务表进行chek,如果发现事务不一致的情况下,进行告警,人工介入处理
分布式事务-接入方式
默认数据源
使用raptor-support自动创建数据源的时候
自定义数据源
通过配置 自定义数据源时的时候
分布式事务本地事务表
无论哪种使用场景,都需要先在所有分库创建本地事务表,线下自行创建,线上找DBA创建
CREATE TABLE `raptor_distributed_transaction_info` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`created` bigint(20) NOT NULL DEFAULT '0' COMMENT '创建时间',
`updated` bigint(20) NOT NULL DEFAULT '0' COMMENT '修改时间',
`xid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '全局事务ID',
`bid` bigint(20) unsigned NOT NULL DEFAULT '0' COMMENT '分支事务ID',
`exeSql` text NOT NULL COMMENT '执行的sql',
`sts` int(11) NOT NULL DEFAULT '0' COMMENT '状态',
`appName` varchar(64) NOT NULL DEFAULT '' COMMENT '应用名',
`schemaName` varchar(64) NOT NULL DEFAULT '' COMMENT '逻辑数据源名称',
`groupName` varchar(64) NOT NULL DEFAULT '' COMMENT '分组数据源名称',
`exeIp` varchar(32) NOT NULL DEFAULT '' COMMENT '执行事务的机器IP(不是DB)',
PRIMARY KEY (`id`),
KEY `idx_xid` (`xid`),
KEY `idx_bid` (`bid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='事务日志表';
POM依赖
<dependency>
<groupId>com.mogujie.raptor</groupId>
<artifactId>raptor-support-spring-orm</artifactId>
<version>1.5.2-alpha</version>
</dependency>
<dependency>
<groupId>com.mogujie.raptor</groupId>
<artifactId>raptor-client</artifactId>
<version>0.15.2-alpha</version>
</dependency>
XML配置
需要在Spring配置文件中启动事务拦截器
此方式下自动创建分布式事务管理器,并且beanId为: raptorDistributed
<!-- 采用@Transactional注解方式使用事务 -->
<tx:annotation-driven mode="proxy"/>
<aop:aspectj-autoproxy proxy-target-class="true"/>
默认数据源使用分布式事务-代码样例
需要在分布式事务方法上追加注解
指定分布式事务处理器为 raptorDistributed
@Transactional(value = "raptorDistributed")
public void updateOnNestedDistributedTransactionSuccess(Provider record, TradeOrder query) {
providerManager.offLivingVersion(record);
int i = tradeOrderDao.update(query);
}
自定义据源使用分布式事务-代码样例
需要手动创建事务管理器,并注入自定义数据源
<bean id="appDistributedTransactionManager" class="com.mogujie.raptor.support.spring.transaction.RaptorDataSourceTransactionManager">
<constructor-arg name="dataSource" >
<list>
<ref bean="mgjDataSource"></ref>
<ref bean="mgj1DataSource"></ref>
</list>
</constructor-arg>
<qualifier value="appDistributed"/>
</bean>
需要在分布式事务方法上追加注解
指定分布式事务处理器为 添加的事务管理
@Transactional(value = "appDistributed")
public void updateOnNestedDistributedTransactionSuccess(Provider record, TradeOrder query) {
providerManager.offLivingVersion(record);
int i = tradeOrderDao.update(query);
}
提交回滚机制
1.与Spring单机事务机制保持一致
2.当有异常抛出时,认为执行失败,全部回滚
3.当正常执行完毕时,认为执行成功 全部提交
4.如果内部有异常,但是被try-catch 住,没有抛出来,则扔认为是正常结束,全部提交