分布式对应的是单体架构,早期单体架构是非常流行的,如下
但随着业务的复杂度提高,逐渐演变出了分布式服务,互相协作,每个服务负责不同的业务,如下
服务与服务之间需要远程协作才能完成事务,这种分布式环境下由不同的服务之间通过远程协作完成的事务称之为分布式事务,如创建订单扣减库存事务。
在分布式架构下,每个节点只知晓自己操作的失败或者成功,无法得知其他节点的状态。当一个事务跨多个节点时,为了保持事务的原子性与一致性,而引入一个协调者来统一掌控所有参与者的操作结果,并指示它们是否要把操作结果进行真正的提交或者回滚(rollback)。典型的场景就是微服务架构下微服务之间通过远程服务调用完成事务操作。
1、Seata概念
Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。
Seata特点:
- 对业务无侵入:即减少技术架构上的微服务化所带来的分布式事务问题对业务的侵入;
- 高性能:减少分布式事务解决方案所带来的性能消耗;
Seata 支持四种事务模式:
- Seata AT 模式
- Seata TCC 模式
- Seata Saga 模式
- Seata XA 模式
Seata 中三个核心概念:
- TC (Transaction Coordinator) 事务协调者:维护全局和分支事务的状态,驱动全局事务提交或回滚。
- TM (Transaction Manager) 事务管理器:定义全局事务的范围,开始全局事务、提交或回滚全局事务。
- RM ( Resource Manager ) 资源管理器:管理分支事务处理的资源( Resource ),与 TC 交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
其中,TC 为单独部署的 Server 服务端,TM 和 RM 为嵌入到应用中的 Client 客户端。
2、搭建Seata服务端
Seata的协调者其实就是阿里开源的一个服务,其下载地址:
https://github.com/seata/seata/releases
此处使用的版本是1.3.0,这个工具在windows或linux上部署的差别不大,此处部署在windows上。
首先下载1.3.0版本的zip压缩包,然后解压到指定目录。
①创建TC所需要的表
创建名为seata的数据库,找到seata-1.3.0源码的script\server\db这个目录,将会看到以下SQL文件:
此处选中mysql数据库,执行sql脚本即可,创建了3张表:
Server 端的 DB tables 创建完成之后,你还得为每个微服务背后的数据库创建一个特殊的表,叫做 undo_log,在 Seata 的 AT 模式下,Seata Server 发起一个 Rollback 指令后,微服务作为 Client 端要负责执行一段 Rollback 脚本,这个脚本所要执行的回滚逻辑就保存在 undo_log 中,undo_log 的建表语句可以从资源文件目录下的 client.sql 文件中找到。
②在conf目录中配置两个地方
a.首先配置 file.conf 文件
b.再配置 registry.conf 文件
file.conf 中配置 TC 的存储模式,TC 的存储模式有三种:
- file:适合单机模式,全局事务会话信息在内存中读写,并持久化本地文件;
- db:适合集群模式,全局事务会话信息通过 db 共享,相对性能差点;
- redis:适合集群模式,全局事务会话信息通过 redis 共享,相对性能好点,但是要注意,redis 模式在 Seata-Server 1.3 及以上版本支持,性能较高,不过存在事务信息丢失的风险,所以需要开发者提前配置适合当前场景的 redis 持久化配置;
下面库存储模式采用数据库方式,file.conf文件具体配置如下:
registry.conf 主要配置 Seata 的注册中心和配置中心。找到seata-server-1.3.0\seata\conf这个目录,其中有一个registry.conf文件,其中配置了TC的注册中心和配置中心。默认的注册中心是file形式,此处需要改成Nacos形式。
下面注册中心采用Nacos,registry.conf文件具体配置如下:
在seata-1.3.0\script\config-center中有一个config.txt文件,其中就是TC所需要的全部配置,修改如下:
在seata-1.3.0\script\config-center\nacos中有一个脚本nacos-config.sh则是将config.txt中的全部配置自动推送到nacos中,运行下面命令(windows可以使用git bash运行,git在windows环境自行下载安装即可):
# -h 主机,你可以使用localhost,-p 端口号 你可以使用8848,-t 命名空间ID,-u 用户名,-p 密码
$ sh nacos-config.sh -h 127.0.0.1 -p 8848 -g SEATA_GROUP -u nacos -w nacos
推送成功则可以在Nacos中查询到所有的配置,如下
注意sh所在路径不能有空格,否则报错。
启动即可,在nacos中查看如下
3、CAP原则
CAP原则又叫CAP定理,同时又被称作布鲁尔定理(Brewer’s theorem),指的是在一个分布式系统中,不可能同时满足以下三点。
- 一致性Consistency:指强一致性,在写操作完成后开始的任何读操作都必须返回该值,或者后续写操作的结果;
- 可用性Availability:指高可用,每次向未崩溃的节点发送请求,总能保证收到响应数据(允许不是最新数据)
- 分区容忍性Partition Tolerance:分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务,也就是说,服务器A和B发送给对方的任何消息都是可以放弃的,也就是说A和B可能因为各种意外情况,导致无法成功进行同步,分布式系统要能容忍这种情况。除非整个网络环境都发生了故障。
在分布式系统中,必须满足CAP中的P,此时只能在C和A之间做出取舍,若是选择了CA,舍弃了P,说白了就是一个单体架构。
4、Base理论
BASE理论是对CAP中的一致性和可用性进行一个权衡的结果,理论的核心思想就是:我们无法做到强一致,但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性。
- BA(Basic Available)基本可用:整个系统在某些不可抗力的情况下,仍然能够保证“可用性”,即一定时间内仍然能够返回一个明确的结果。这里是属于基本可用;
- S(Soft State)柔性状态:允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统不同节点的数据副本之间进行数据同步的过程存在延时;
- E(Eventual Consisstency)最终一致性:同一数据的不同副本的状态,可以不需要实时一致,但一定要保证经过一定时间后仍然是一致的;
5、Seata实例
以电商系统为例介绍一下如何编码实现分布式事务。
用户购买商品完成下单的业务逻辑,整个业务逻辑有2个微服务提供支持:
- 订单服务:购买商品创建订单;
- 商品服务:对给定的商品扣减数量;
搭建product商品服务
①pom依赖
<!-- seata -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
注意依赖的版本与服务的版本保持一致。
②创建数据库,新建undo_log:回滚日志表,这是Seata要求必须有的,每个业务库都应该创建一个,SQL如下:
CREATE TABLE `undo_log` (
`branch_id` bigint(20) NOT NULL COMMENT 'branch transaction id',
`xid` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'global transaction id',
`context` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT 'undo_log context,such as serialization',
`rollback_info` longblob NOT NULL COMMENT 'rollback info',
`log_status` int(11) NOT NULL COMMENT '0:normal status,1:defense status',
`log_created` datetime(6) NOT NULL COMMENT 'create datetime',
`log_modified` datetime(6) NOT NULL COMMENT 'modify datetime',
UNIQUE INDEX `ux_undo_log`(`xid`, `branch_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'AT transaction mode undo table' ROW_FORMAT = Compact;
③bootstrap.yml中配置seata
# 客户端seata的相关配置
seata:
## 是否开启seata,默认true
enabled: true
application-id: ${spring.application.name}
## seata事务组的名称,一定要和config.tx(nacos)中配置的相同
tx-service-group: ${spring.application.name}-tx-group
## 配置中心的配置
config:
## 使用类型nacos
type: nacos
## nacos作为配置中心的相关配置,需要和server在同一个注册中心下
nacos:
## 命名空间,需要server端(registry和config)、nacos配置client端(registry和config)保持一致
namespace:
## 地址
server-addr: localhost:8848
## 组, 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
## 用户名和密码
username: nacos
password: nacos
registry:
type: nacos
nacos:
## 这里的名字一定要和seata服务端中的名称相同,默认是seata-server
application: seata-server
## 需要server端(registry和config)、nacos配置client端(registry和config)保持一致
group: SEATA_GROUP
namespace:
username: nacos
password: nacos
server-addr: localhost:8848
④分布式事务
@GlobalTransactional
@Override
public void addOrderLogsAndAddBrand(Long orderId, Integer orderStatus, String user, String detail) {
log.info("添加订单操作日志,orderId={},detail={}", orderId, detail);
OmsOrderLog orderLog = new OmsOrderLog();
orderLog.setDetail(detail);
orderLog.setOrderId(orderId);
orderLog.setOrderStatus(orderStatus);
orderLog.setUser(user);
boolean save = this.save(orderLog);
log.info("下单:" + save);
Object deduct = productFeignClient.deduct();
log.info("扣减:" + deduct.toString());
}
搭建order订单服务同product商品服务。
6、测试
先启动Nacos注册中心,然后启动Seata Server,最后启动Order、Product微服务。
测试下单接口服务,生成订单的同时进行扣减库存,当扣减库存失败时,分布式事务进行回滚,数据库中的数据会保持原样。
分布式事务正常时如下:
分布式事务异常时如下:
将扣减库存的接口手动设置休眠2min,这样肯定超时了,下单操作则整体回滚,数据库中数据未变,则说明分布式事务成功了。
第一个操作对应的库操作先增加一条记录,第二个库执行异常未进行库操作,导致分布式事务异常,进行回滚,第一个操作对应的库中新增加的记录又被删除了。