前言
随着互联网公司的业务的不断拓展,数据量以及用户的不断增多,越来越多的公司的系统架构从最开始的单系统架构转为了多系统间的分布式架构,这种方式能够大大减少系统间的耦合性,同时也能缓解系统压力;但是同时也带来了系统间数据不一致的问题,在接口的调用和数据的传输写入等操作中,往往很难保证事务的可靠性。
通常分布式事务有以下几种解决方案:
1.基于数据库XA/JTA协议的方式(需要数据库厂商支持;JAVA组件有atomikos等);
2.异步校对数据的方式(支付宝、微信支付主动查询支付状态、对账的形式);
3.基于可靠消息(MQ)的解决方案;
4.TCC编程式解决方案;
其中基于MQ的事务解决方案适用于异步场景,且通用性较强,拓展性较高,本文将使用中间件RabbitMQ来解决分布式事务的问题。
事务问题的产生
1. 业务场景
以外卖配送场景为例,这里将系统简化为两个系统,一个为订单系统,该系统记录了用户下单的信息;另一个系统为运单系统,记录了订单的一系列配送信息。
2. 问题产生
此时将会产生一个问题,两个系统都为独立的系统,数据也分别存储于不同的数据库,在用户下单后,需要将订单系统的这份下单数据存储一份数据到运单系统,然而因为是跨系统的接口调用,使用事务注解@Transactional能否解决数据一致性的问题呢?
3. 示例代码
3.1.订单数据库中的订单表:
CREATE TABLE `table_order` (
`order_id` varchar(100) NOT NULL COMMENT '订单号',
`user_id` varchar(255) DEFAULT NULL COMMENT '用户编号',
`order_content` varchar(255) DEFAULT NULL COMMENT '订单内容(买了哪些东西,送货地址)',
`create_time` datetime NOT NULL COMMENT '创建时间',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2.运单数据库中的运单表:
CREATE TABLE `table_dispatch` (
`order_id` varchar(100) NOT NULL COMMENT '订单编号',
`dispatch_seq` varchar(255) DEFAULT NULL COMMENT '调度流水号',
`dispatch_status` varchar(255) DEFAULT NULL COMMENT '调度状态',
`dispatch_content` varchar(255) DEFAULT NULL COMMENT '调度内容(送餐员,路线)',
PRIMARY KEY (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.3 运单系统代码:
3.3.1 运单系统 http API 向外提供接口:
/**
* 运单系统http API
*/
@RestController
@RequestMapping("/dispatch-api")
public class DispatchController {
@Autowired
private DispatchService dispatchService;
// 下订单后,添加调度信息
@GetMapping("/dispatch")
public String lock(String orderId) throws Exception {
Thread.sleep(3000L); // 此处模拟业务耗时,接口调用者会认为超时
dispatchService.dispatch(orderId); // 将外卖订单分配给送餐小哥
return "ok";
}
}