分布式事务解决方案
程序员文章站
2022-03-15 18:16:14
...
分布式一致性协议
- 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。
// 引入依赖
<!-- https://mvnrepository.com/artifact/com.codingapi/transaction-springcloud -->
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- https://mvnrepository.com/artifact/com.codingapi/tx-plugins-db -->
<dependency>
<groupId>com.codingapi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>4.1.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
// 配置lcn
tm:
manager:
url: http://lcn.zxkj.com/tx/manager/
// 添加两个service
@Service
public class TxManagerHttpRequestServiceImpl implements TxManagerHttpRequestService{
@Override
public String httpGet(String url) {
System.out.println("httpGet-start");
String res = HttpUtils.get(url);
System.out.println("httpGet-end");
return res;
}
@Override
public String httpPost(String url, String params) {
System.out.println("httpPost-start");
String res = HttpUtils.post(url,params);
System.out.println("httpPost-end");
return res;
}
}
@Service
public class TxManagerTxUrlServiceImpl implements TxManagerTxUrlService {
@Value("${tm.manager.url}")
private String url;
@Override
public String getTxUrl() {
System.out.println("load tm.manager.url ");
return url;
}
}
// 测试用例
@RestController
public class IOrderServiceImpl extends BaseApiService implements IOrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private StockFeign stockFeign;
@TxTransaction(isStart = true)
@Transactional
@GetMapping(value = "/addOrderAndStock")
public ResponseBase addOrderAndStock(int i) throws Exception {
OrderEntity orderEntity = new OrderEntity();
orderEntity.setName("订单1");
orderEntity.setOrderCreatetime(new Date());
orderEntity.setOrderMoney(300d);
orderEntity.setOrderState(0);
Long commodityId = 30l;
orderEntity.setCommodityId(30l);
int orderResult = orderMapper.addOrder(orderEntity);
System.out.println("orderResult:" + orderResult);
if (orderResult <= 0) {
return setResultError("下单失败!");
}
ResponseBase inventoryReduction = stockFeign.inventoryReduction(commodityId);
if (inventoryReduction.getRtnCode() != 200) {
throw new Exception("调用库存服务接口失败,开始回退订单事务代码");
}
int reuslt = 1 / i;
System.out.println("reuslt:" + reuslt);
return setResultSuccess("下单成功!");
}
}
- 在库存服务中集成lcn, 同上。
- 配置nginx
upstream backServer{
server 127.0.0.1:8899;
server 127.0.0.1:8898;
}
server {
listen 80;
server_name lcn.zxkj.com;
location / {
### 指定上游服务器负载均衡服务器
proxy_pass http://backServer/;
###nginx与上游服务器(真实访问的服务器)超时时间 后端服务器连接的超时时间_发起握手等候响应超时时间
proxy_connect_timeout 5s;
###nginx发送给上游服务器(真实访问的服务器)超时时间
proxy_send_timeout 5s;
### nginx接受上游服务器(真实访问的服务器)超时时间
proxy_read_timeout 5s;
index index.html index.htm;
}
}
- 初始化测试脚本
CREATE TABLE `stock` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_id` int(11) DEFAULT NULL COMMENT '商品ID',
`stock` int(11) DEFAULT NULL COMMENT '库存余额',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
CREATE TABLE `order` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL COMMENT '订单名称',
`order_createtime` datetime(0) DEFAULT NULL COMMENT '下单时间',
`order_state` int(11) DEFAULT NULL COMMENT '订单状态 0 已经未支付 1已经支付 2已退单',
`order_money` double(10, 0) DEFAULT NULL COMMENT '订单价格',
`commodity_id` int(10) DEFAULT NULL COMMENT '商品ID',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 17 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
- 依次启动EurekaServer、两个TxManager、库存服务、订单服务.
- 测试结果
上一篇: MIT 6.824 lab2 Raft