文章目录
- 1 分布式事务问题
- 1.1 本地事务
- 1.2 分布式事务
- 1.3 创建分布式事务演示案例
- 2 理论基础
- 2.1 CAP定理
- 2.2 BASE理论
- 2.3 解决分布式事务的思路
- 2.4 Seata
1 分布式事务问题
1.1 本地事务
本地事务,也就是传统的单机事务,它必须要满足以下四个原则:
1.2 分布式事务
分布式事务,就是指不是在单个服务或单个数据库架构下产生的事务。例如:
- 跨数据源的分布式事务
- 跨服务的分布式事务
- 跨数据源且跨服务的综合分布式事务
在数据库水平拆分、服务垂直拆分之后,一个业务操作通常要跨多个数据库、服务才能完成。
例如电商行业中比较常见的下单付款案例,就包括创建新订单、扣减商品库存、从用户账户余额扣除金额等几个行为,而这些行为需要访问三个不同的微服务和三个不同的数据库:
创建新订单、扣减商品库存、从用户账户余额扣除金额在每一个服务和数据库内都是一个本地事务,可以保证ACID原则。
把这三个行为看做一个"业务",保证“业务”的原子性,要么所有操作全部成功,要么全部失败,不允许出现部分成功部分失败的现象,这就是分布式系统下的事务。
此时ACID难以满足,这是分布式事务要解决的问题。
1.3 创建分布式事务演示案例
- 1)创建数据库,名为jingd,然后分别创建用户表t_account、订单表t_order、库存表t_storage
-- 创建数据库
create database jingd;
use jingd;
-- 创建用户表
CREATE TABLE `t_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`money` int(11) UNSIGNED NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- 用户ID为 user202103032042012 的用户余额为 1000
INSERT INTO `t_account` VALUES (1, 'user202103032042012', 1000);
-- 创建订单表
CREATE TABLE `t_order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) NULL DEFAULT 0,
`money` int(11) NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- 创建库存表
CREATE TABLE `t_storage` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`count` int(11) UNSIGNED NULL DEFAULT 0,
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `commodity_code`(`commodity_code`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = COMPACT;
-- 商品编号为 100202003032041 的商品库存有 10 件
INSERT INTO `t_storage` VALUES (1, '100202003032041', 10);
- 2)创建微服务的结构如下
- jingd-demo:父工程,负责管理项目依赖。
- jd-account-service:账户服务,负责管理用户的资金,提供扣减余额的接口。
- jd-storage-service:库存服务,负责管理商品库存,提供扣减库存的接口。
- jd-order-service:订单服务,负责管理订单,提供创建订单的接口。
需要特别注意的是,jd-order-service
订单服务创建订单时,需要通过Feign调用account-service
和storage-service
服务的接口:
启动后,三个微服务均注册到nacos中:
并提供了相对应的接口:
- 3)测试创建订单
初始化数据库后,商品编号为100202003032041
的商品库存有10件,用户ID为user202103032042012
的用户余额为1000。
调用创建订单接口,假设该用户买了15件商品,共花费500元:
查看日志可以发现,由于库存不足,创建订单失败了:
但扣减余额却成功了:
这就违背了“创建订单”业务的原子性,出现了分布式业务问题。
2 理论基础
2.1 CAP定理
1998年,加州大学的计算机科学家 Eric Brewer 提出,分布式系统有三个指标:
- Consistency(一致性)
- Availability(可用性)
- Partition tolerance (分区容错性)
Eric Brewer 认为,这三个指标不可能同时做到,这个结论就叫做 CAP 定理。
- CAP指标的矛盾
一致性是指,用户访问分布式系统中的任意节点,得到的数据必须一致的。
例如jd-account-service
账户服务包含两个节点account-01
和account-02
,它们的初始数据是一致的。
当创建订单时,可能修改了account-01
节点的数据,此时两个节点的数据就产生了差异。而要想保住一致性,就必须实现account-01
节点到account-02
节点的数据同步。
可用性是指,用户访问集群中的任意健康节点,必须能得到响应,而不是超时或拒绝。
而分区容错是指,由于网络故障或其它原因导致的分布式系统部分节点与其它节点失去连接时,系统可以形成独立分区对这些故障节点进行隔离,使整个系统仍然可以持续对外提供服务。
-
如果此时要保证数据一致性,就必须等待网络恢复,完成数据同步后,整个集群才对外提供服务。但这段时间里,服务处于阻塞状态,可用性受到影响。
-
如果此时要保证服务可用性,就不能等待网络恢复,那不同节点之间就会出现数据不一致,一致性受到影响。
-
也就是说,在P一定会出现的情况下,A和C之间只能实现一个。
2.2 BASE理论
BASE理论是对CAP的一种解决思路,包含三个思想:
- Basically Available (基本可用):分布式系统在出现故障时,允许损失部分可用性,即保证核心可用。
- Soft State(软状态):在一定时间内,允许出现中间状态,比如临时的不一致状态。
- Eventually Consistent(最终一致性):虽然无法保证强一致性,但是在软状态结束后,最终达到数据一致。
2.3 解决分布式事务的思路
分布式事务最大的问题是各个子事务的一致性问题,因此借鉴CAP定理和BASE理论,有两种解决思路:
- AP模式:各子事务分别执行和提交,允许出现结果不一致,然后采用弥补措施恢复数据即可,实现最终一致。
- CP模式:各个子事务执行后互相等待,同时提交,同时回滚,达成强一致。但事务等待过程中,处于弱可用状态。
但不管是哪一种模式,都需要在子系统事务之间互相通讯,协调事务状态,也就是需要一个事务协调者(TC):
2.4 Seata
Seata是2019年1月份蚂蚁金服和阿里巴巴共同开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务,为用户打造一站式的分布式解决方案。
它在分布式系统中有三个重要的角色:
- TC (Transaction Coordinator) - 事务协调者
- TM (Transaction Manager) - 事务管理器
- RM (Resource Manager) - 资源管理器
…
本节完,更多内容请查阅分类专栏:微服务学习笔记
感兴趣的读者还可以查阅我的另外几个专栏:
- SpringBoot源码解读与原理分析
- MyBatis3源码深度解析
- Redis从入门到精通
- MyBatisPlus详解
- SpringCloud学习笔记