Seata分布式事务详解
Seata分布式事务是SpringCloudAlibaba的核心组件,也是构建分布式事务的基石
Seata
Seata是Simple Extensible Autonomous Transaction Architecture 的简写,是一款阿里开源的分布式事务解决方案。Seata致力于在微服务架构下提供高性能和简单易用的分布式事务服务。
Seata核心组件
Seata事务管理中有三个重要的组件角色,如下图所示:
三个组件相互协作,TC 以 Server 形式独立部署,TM和RM集成在应用中启动。
1.TC (Transaction Coordinator) 事务协调者
TC:维护全局和分支事务的状态,协调全局事务提交或回滚。
2.TM (Transaction Manager) 事务管理器
TM:定义全局事务的范围、开始全局事务、提交或回滚全局事务。
3.RM (Resource Manager) -资源管理器
RM:管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
Seata工作流程
一个典型的seata分布式事务的流程如下:
1.TM向TC请求发起一个全局事务,TC返回一个代表这个全局事务的XID;
2.XID在RPC中传播给每一个调用链中的服务;
3.每个RM拿到XID后向TC发起一个分支事务,TC返回一个代表这个分支事务的XID;
4.RM完成本地分支的业务,提交本地分支,并且报告给TC;
5.全局事务调用链处理完毕,TM根据有无异常向TC发起全局事务的提交或者回滚。
Seata事务模式
Seata将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
XA模式:强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入
TCC模式:最终一致的分阶段事务模式,有业务侵入
SAGA模式:长事务模式,有业务侵入
1.AT 模式
AT 模式基于 支持本地 ACID 事务 的 关系型数据库:
AT模式是Seata默认的工作模式,AT模式是最终一致的分阶段事务模式,无业务侵入。
AT模式机制
-
一阶段 prepare 行为:在本地事务中,一并提交业务数据更新和相应回滚日志记录。
-
二阶段 commit 行为:马上成功结束,自动 异步批量清理回滚日志。
-
二阶段 rollback 行为:通过回滚日志,自动 生成补偿操作,完成数据回滚。
2.TCC模式
所谓 TCC 模式,是指支持把 自定义 的分支事务纳入到全局事务的管理中。
一个分布式的全局事务,整体是 两阶段提交 的模型,全局事务是由若干分支事务组成的,分支事务要满足 两阶段提交 的模型要求,即需要每个分支事务都具备自己的:
-
一阶段 prepare 行为
-
二阶段 commit 或 rollback 行为
如下图所示:
根据两阶段行为模式的不同,将分支事务划分为 Automatic (Branch) Transaction Mode 和 Manual (Branch) Transaction Mode.
TCC 模式,不依赖于底层数据资源的事务支持:
-
一阶段 prepare 行为:调用 自定义 的 prepare 逻辑。
-
二阶段 commit 行为:调用 自定义 的 commit 逻辑。
-
二阶段 rollback 行为:调用 自定义 的 rollback 逻辑。
3.SAGA 模式
在Saga模式中,业务流程中每个参与者都提交本地事务,当出现某一个参与者失败则补偿前面已经成功的参与者,一阶段正向服务和二阶段补偿服务都由业务开发实现。
SAGA模式:长事务模式,有业务侵入。
4.XA模式
XA模式是强一致性分阶段事务模式,牺牲了一定的可用性,无业务侵入。
XA是基于二阶段提交设计的接口标准,对于支持XA的资源管理器,借助Seata框架的XA模式,会使XA方案更简单易用。
RM一阶段的工作:
-
注册分支事务到TC;
-
执行分支业务sql但不提交;
-
报告执行状态到TC;
TC二阶段的工作:
TC检测各分支事务执行状态;
如果都成功,通知所有RM提交事务;
如果有失败,通知所有RM回滚事务;
XA使用前提:需要分支数据库支持XA 事务,应用为 Java应用,且使用JDBC访问数据库。
5.四种模式比较
以上
接口防刷机制
接口被刷指的是同一接口被频繁调用,可能是由于以下原因导致:
- 恶意攻击:攻击者利用自动化脚本或工具对接口进行大量请求,以消耗系统资源、拖慢系统响应速度或达到其他恶意目的。
- 误操作或程序错误:某些情况下,程序错误或误操作可能导致接口被重复调用,例如循环调用或者定时任务配置错误。
Redis 实现接口防刷
Redis是高性能的键值存储系统,常用于缓存和分布式锁等场景。利用Redis可以有效地实现接口防刷功能:
- 计数器:利用Redis的计数器功能,每次接口被调用时增加计数器的值,设定一个时间窗口内的最大调用次数,超过该次数则拒绝请求。
- 分布式锁:利用Redis的分布式锁功能,确保同一时间只有一个请求能够增加计数器的值,防止并发问题导致计数器失效。
拦截器实现接口防刷
在Spring Boot中,可以通过编写拦截器来实现接口防刷的功能:
- 编写拦截器:创建一个实现HandlerInterceptor接口的拦截器类,重写preHandle方法,在该方法中进行接口调用次数的检查,如果超过阈值则拦截请求。
- 配置拦截器:在Spring Boot的配置类中通过addInterceptor方法将拦截器注册到拦截器链中,配置拦截器的拦截路径和排除路径。
分布式ID生成策略
构建分布式系统时,如何对数据进行唯一标识也是一个至关重要的设计。不仅要符合B-tree数据结构以维持查询性能,还要考虑唯一标识的连续性会不会影响系统安全性。在分库分表的情况下,还要避免唯一标识重复且高效等等需要考虑的点。
1、UUID
UUID(Universally Unique Identifier)是基于当前时间、计数器和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。UUID完全可以满足分布式唯一标识,但是在实际应用过程中一般不采用,有几个原因:(如果UUID作为数据库主键,在InnoDB引擎下,UUID的无序性可能会引起数据位置频繁变动,严重影响性能。)
- 存储成本高:UUID太长,16字节128位,以36长度的字符串表示,很多场景不适用。
- 信息不安全:基于MAC地址生成的UUID算法会暴露MAC地址,曾经梅丽莎病毒的制造者就是根据UUID寻找的。
- 不符合MySQL主键要求:MySQL官方有明确的建议,主键要尽量越短越好,因为太长对MySQL索引不利。
2、数据库自增ID
利用MySQL自增的ID,可以达到数据唯一标识。但是分库分表后不能保证整体的ID唯一。为了避免这种情况,有以下两种方式可以解决该问题。
全局主键表
创建全局主键表维护唯一标识,作为ID的输出源可以保证整体ID的唯一。
ID自增步长设置
通过设置MySQL不同实例的主键自增步长为不同值,让分布在不同实例的表数据ID做到不重复,从而保证整体的唯一。但是这种方式的扩展性会是一个非常大的问题。
3、号段模式
号段模式是当下分布式ID生成器的主流实现方式之一。其原理如下:
- 号段模式每次从数据库取出一个号段范围,加载到服务内存中。避免每次生成ID都去访问数据库。
- 当号段ID用完,再次向数据库申请新号段,对max_id字段做一次update操作,新的号段范围是(max_id ,max_id +step]。
- 由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新。
这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。但同样也会存在一些缺点,比如:服务器重启,单点故障会造成ID不连续。
4、Redis INCR
作为共享内存,可以通过Redis的INCR命令来生成全局唯一ID。Redis也有对应的缺点:ID 生成的持久化问题,如果Redis宕机了怎么进行恢复是开发人员需要考虑的。
5、雪花算法
Snowflake,雪花算法是由Twitter开源的分布式ID生成算法,以划分命名空间的方式将64bit位分割成了多个部分,每个部分都有具体的不同含义,在Java中64Bit位的整数是Long类型,所以在Java中Snowflake算法生成的ID就是long来存储的。具体如下:
雪花算法强依赖机器时钟,如果机器上时钟回拨,会导致重复。通常通过记录最后使用时间处理该问题。
6、美团(Leaf)
美团点评分布式ID生成系统。支持号段模式和snowflake算法模式,可以切换使用。
开源项目链接:https://github.com/Meituan-Dianping/Leaf
Leaf详细介绍:https://tech.meituan.com/2017/04/21/mt-leaf.html
7、百度(UidGenerator)
UidGenerator是基于Snowflake算法的。克服了雪花算法的并发限制,单个实例的QPS能超过6000000。需要的环境:JDK8+,MySQL(用于分配WorkerId)。
源码地址:https://github.com/baidu/uid-generator
中文文档地址:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
8、滴滴(TinyID)
Tinyid是滴滴基于美团(Leaf)的号段模式基础上升级而来,不仅支持了数据库多主节点模式,还提供了tinyid-client客户端的接入方式,使用起来更加方便。
开源项目链接:https://github.com/didi/tinyid
总结比较
优点 | 缺点 | |
---|---|---|
UUID | 代码实现简单、没有网络开销,性能好 | 占用空间大、无序 |
数据库自增ID | 利用数据库系统的功能实现,成本小、ID自增有序 | 并发性能受Mysql限制、强依赖DB,当DB异常时整个系统不可用,致命 |
Redis INCR | 性能优于数据库、ID有序 | 解决单点问题带来的数据一致性等问题使得复杂度提高 |
雪花算法 | 不依赖数据库等第三方系统,性能也是非高、可以根据自身业务特性分配bit位,非常灵活 | 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。 |
号段模式 | 数据库的压力小 | 单点故障ID不连续 |
Leaf、Uidgenerator、TinyID | 高性能、高可用、接入简单 | 依赖第三方组件如ZooKeeper、Mysql |