分布式一致性协议
- XA接口
XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接口。事务管理器控制着全局事务,管理事务生命周期,并协调资源。资源管理器负责控制和管理实际资源(如数据库或JMS队列)
- JTA规范
作为java平台上事务规范JTA(Java Transaction API)也定义了对XA事务的支持. 目前JTA的实现主要由以下几种:
- J2EE容器所提供的JTA实现, 如JBoss
- 独立的JTA实现, 如JOTM,Atomikos.
二阶段提交协议
- 含义
- 表决阶段,所有参与者都将本事务能否成功的信息反馈发给协调者;
- 执行阶段,协调者根据所有参与者的反馈,通知所有参与者,步调一致地在所有分支上提交或者回滚。
- 优点
尽量保证了数据的强一致,实现成本较低,在各大主流数据库都有自己实现,对于 MySQL 是从 5.5 开始支持。
- 缺点
- 单点问题:事务管理器在整个流程中扮演的角色很关键,如果其宕机, 资源管理器就会一直阻塞,导致数据库无法使用。
- 同步阻塞:在准备就绪之后,资源管理器中的资源一直处于阻塞,直到提交完成,释放资源。
- 数据不一致:比如在第二阶段中,假设协调者发出了事务 Commit 的通知,但是因为网络问题该通知仅被一部分参与者所收到并执行了 Commit 操作,其余的参与者则因为没有收到通知一直处于阻塞状态,这时候就产生了数据的不一致性。
- 总结
二阶段提交方案锁定资源时间长,对性能影响很大,基本不适合解决微服务事务问题。
三阶段提交协议
- TCC
- 含义
TCC事务机制相对于传统事务机制(X/Open XA Two-Phase-Commit),其特征在于它不依赖资源管理器(RM)对XA的支持,而是通过对(由业务系统提供的)业务逻辑的调度来实现分布式事务。主要由三步操作,Try: 尝试执行业务、 Confirm:确认执行业务、 Cancel: 取消执行业务。
- 特点
该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
该模式对有无本地事务控制都可以支持使用面广。
数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。
- 异步回调模式
- 最终一致性模式
- 可靠消息模式
代码实现(基于LCN框架4.1.2)
- 架构图
- 步骤
-
由于lcn暂时不支持SpringCloud2.0,因此我们需要把某位大牛改的lcn安装到本地库,打包时将maven-javadoc-plugin插件注掉, 调整redis配置,不然会报错。
-
在订单服务中集成lcn。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102 1// 引入依赖
2<!-- https://mvnrepository.com/artifact/com.codingapi/transaction-springcloud -->
3 <dependency>
4 <groupId>com.codingapi</groupId>
5 <artifactId>transaction-springcloud</artifactId>
6 <version>4.1.2</version>
7 <exclusions>
8 <exclusion>
9 <groupId>org.slf4j</groupId>
10 <artifactId>*</artifactId>
11 </exclusion>
12 </exclusions>
13 </dependency>
14
15 <!-- https://mvnrepository.com/artifact/com.codingapi/tx-plugins-db -->
16 <dependency>
17 <groupId>com.codingapi</groupId>
18 <artifactId>tx-plugins-db</artifactId>
19 <version>4.1.2</version>
20 <exclusions>
21 <exclusion>
22 <groupId>org.slf4j</groupId>
23 <artifactId>*</artifactId>
24 </exclusion>
25 </exclusions>
26 </dependency>
27// 配置lcn
28tm:
29 manager:
30 url: http://lcn.zxkj.com/tx/manager/
31// 添加两个service
32@Service
33public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{
34
35 @Override
36 public String httpGet(String url) {
37 System.out.println("httpGet-start");
38 String res = HttpUtils.get(url);
39 System.out.println("httpGet-end");
40 return res;
41 }
42
43 @Override
44 public String httpPost(String url, String params) {
45 System.out.println("httpPost-start");
46 String res = HttpUtils.post(url,params);
47 System.out.println("httpPost-end");
48 return res;
49 }
50}
51
52@Service
53public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {
54
55 @Value("${tm.manager.url}")
56 private String url;
57
58 @Override
59 public String getTxUrl() {
60 System.out.println("load tm.manager.url ");
61 return url;
62 }
63}
64// 测试用例
65@RestController
66public class IOrderServiceImpl extends BaseApiService implements IOrderService {
67
68 @Autowired
69 private OrderMapper orderMapper;
70
71 @Autowired
72 private StockFeign stockFeign;
73
74 @TxTransaction(isStart = true)
75 @Transactional
76 @GetMapping(value = "/addOrderAndStock")
77 public ResponseBase addOrderAndStock(int i) throws Exception {
78 OrderEntity orderEntity = new OrderEntity();
79 orderEntity.setName("订单1");
80 orderEntity.setOrderCreatetime(new Date());
81 orderEntity.setOrderMoney(300d);
82 orderEntity.setOrderState(0);
83 Long commodityId = 30l;
84 orderEntity.setCommodityId(30l);
85 int orderResult = orderMapper.addOrder(orderEntity);
86 System.out.println("orderResult:" + orderResult);
87 if (orderResult <= 0) {
88 return setResultError("下单失败!");
89 }
90
91 ResponseBase inventoryReduction = stockFeign.inventoryReduction(commodityId);
92 if (inventoryReduction.getRtnCode() != 200) {
93 throw new Exception("调用库存服务接口失败,开始回退订单事务代码");
94 }
95 int reuslt = 1 / i;
96 System.out.println("reuslt:" + reuslt);
97 return setResultSuccess("下单成功!");
98 }
99
100}
101
102
-
在库存服务中集成lcn, 同上。
-
配置nginx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 1 upstream backServer{
2 server 127.0.0.1:8899;
3 server 127.0.0.1:8898;
4 }
5 server {
6 listen 80;
7 server_name lcn.zxkj.com;
8 location / {
9 ### 指定上游服务器负载均衡服务器
10 proxy_pass http://backServer/;
11 ###nginx与上游服务器(真实访问的服务器)超时时间 后端服务器连接的超时时间_发起握手等候响应超时时间
12 proxy_connect_timeout 5s;
13 ###nginx发送给上游服务器(真实访问的服务器)超时时间
14 proxy_send_timeout 5s;
15 ### nginx接受上游服务器(真实访问的服务器)超时时间
16 proxy_read_timeout 5s;
17 index index.html index.htm;
18 }
19 }
20
21
-
初始化测试脚本
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 1CREATE TABLE `stock` (
2 `id` int(11) NOT NULL AUTO_INCREMENT,
3 `commodity_id` int(11) DEFAULT NULL COMMENT '商品ID',
4 `stock` int(11) DEFAULT NULL COMMENT '库存余额',
5 PRIMARY KEY (`id`) USING BTREE
6) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
7
8CREATE TABLE `order` (
9 `id` int(11) NOT NULL AUTO_INCREMENT,
10 `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '订单名称',
11 `order_createtime` datetime(0) DEFAULT NULL COMMENT '下单时间',
12 `order_state` int(11) DEFAULT NULL COMMENT '订单状态 0 已经未支付 1已经支付 2已退单',
13 `order_money` double(10, 0) DEFAULT NULL COMMENT '订单价格',
14 `commodity_id` int(10) DEFAULT NULL COMMENT '商品ID',
15 PRIMARY KEY (`id`) USING BTREE
16) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
17
18
- 依次启动EurekaServer、两个TxManager、库存服务、订单服务.
- 测试结果