一,分布式出现的原因
出现分布式事务的两个原因,其中有个原因是因为微服务过多。太多团队一个人维护几个微服务,太多团队过度设计,搞得所有人疲劳不堪,而微服务过多就会引出分布式事务,这个时候我不会建议你去采用下面任何一种方案,而是请把需要事务的微服务聚合成一个单机服务,使用数据库的本地事务。因为不论任何一种方案都会增加你系统的复杂度,这样的成本实在是太高了,千万不要因为追求某些设计,而引入不必要的成本和复杂度。
二,业界提供的解决方案
业界目前主流的分布式事务解决方案主要有:多阶段提交方案(2PC、3PC)、补偿事务(TCC)和消息事务(主要是RocketMQ,基本思想也是多阶段提交方案,其他消息队列中间件并没有实现分布式事务)。这些方案的原理在此处不展开,目前网络中相应资料比较多,小结一下它们的特点:
多阶段提交方案:常见的有二阶段和三阶段提交事务,需要额外的资源管理器来协调事务,数据一致性强,但是实现方案比较复杂,对性能的牺牲比较大(主要是需要对资源锁定,等待所有事务提交才能解锁),不适用于高并发的场景,目前比较知名的有阿里开源的fescar。
补偿事务:一般也叫TCC,因为每个事务操作都需要提供三个操作尝试(Try)、确认(Confirm)和补偿/撤销(Cancel),数据一致性的强度比多阶段提交方案低,但是实现的复杂度会有所降低,比较明显的缺陷是每个业务事务需要实现三组操作,有可能出现过多的补偿方案的代码;另外有很多场景TCC是不合适的。
消息事务:这里只谈RocketMQ的实现,一个事务的执行流程包括:发送预消息、执行本地事务、确认消息发送成功。它的消息中间件存储了下游无法消费成功的消息,并且不断重试推送下游消费消息,而生产者(上游)需要提供一个check接口,用于检查成功发送预消息但是未确认最终消息发送状态的事务的状态。
三,SpringCloud集成tx-lcn分布式处理框架
本文集成github上较为流行的tx-lcn分布式处理框架,它是基于redis的一种补偿型处理方案
1.导入tx-lcn分布式处理框架所需要的jar
<!-- 分布式事物 -->
<dependency>
<groupId>com.github.wxiaoqi</groupId>
<artifactId>transaction-springcloud</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.wxiaoqi</groupId>
<artifactId>tx-plugins-db</artifactId>
<version>${lcn.last.version}</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
2.编写TxManagerHttpRequestService和TxManagerTxUrlService的实现类
@Service
public class TxManagerHttpRequestBiz 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 TxManagerTxUrlBiz implements TxManagerTxUrlService {
@Value("${tm.manager.url}")
private String url;
@Override
public String getTxUrl() {
System.out.println("load tm.manager.url ");
return url;
}
}
创建分布式事务拦截器
通过aop拦截指定方法
/**
分布式事务拦截器
**/
@Aspect
@Component
public class TxTransactionInterceptor implements Ordered {
@Override
public int getOrder() {
return 1;
}
@Autowired
private TxManagerInterceptor txManagerInterceptor;
@Around("execution(* com.meishi.sales.service.impl.*Impl.*(..))")
public Object around(ProceedingJoinPoint point) throws Throwable {
return txManagerInterceptor.around(point);
}
}
3.开启分布式事务
# tx-manager地址
tm:
manager:
url: http://192.168.3.9:8899/tx/manager/
此时架构搭建完成
四,使用分布式事务
只需要在需要使用的方法上加上@Transactional和 @TxTransaction注解即可
@Override
@Transactional
@TxTransaction
public Object deleteBill(String id) {
log.info("客户还信---删除单据>>>" + id);
if (StringUtils.isBlank(id)) {
return ResponseUtil.fail("ID不能为空!");
}
CreditReturn entity = selectById(id);
if (entity == null) {
return ResponseUtil.fail("未找到对应ID的数据>>>ID>>" + id);
}
deleteById(id);
//删除任务
oaFeignApi.deleteTask(entity.getBillNo());
return ResponseUtil.ok();
}
@Override
@Transactional
public Object addBill(CreditReturnDTO dto) {
//判断本次还信金额是否小于未还信金额
if (NumberUtil.isLess(NumberUtil.sub(dto.getCreditAmount(), dto.getReturnAmount()), dto.getThisReturnAmount())) {
return ResponseUtil.fail("本次还信金额不能大于未还信金额");
}
//判断本次还信金额是否小于客户的可用金额
BigDecimal bigDecimal = capitalAccountFeignApi.queryCustomerBalance(dto.getCustomerCode(), BaseContextHandler.getTenantID());
if (NumberUtil.isLess(bigDecimal, dto.getThisReturnAmount())) {
return ResponseUtil.fail("本次还信金额不能大于客户账户的可用金额");
}
log.info("客户还信---保存单据>>>" + dto.toString());
CreditReturn entity = new ModelMapper().map(dto, CreditReturn.class);
//只有第一次启动流程
if (StringUtils.isBlank(entity.getId())) {
entity.setBillNo(findMaxCode());
entity.setBillStatus(BillStatusConstant.BILL_STATE_LZ);
EntityUtils.setCreatAndUpdatInfo(entity);
insert(entity);
//获取任务判断是否要走流程
TaskCommon task = oaFeignApi.getTask(BaseContextHandler.getTenantID(), billPrefix);
if (task != null) {
//启动流程 (必传参数>>>billId-单据ID billNo-单据号 user-操作人)
task.setUser(BaseContextHandler.getUserID());
task.setBillId(entity.getId());
task.setBillNo(entity.getBillNo());
oaFeignApi.startTask(task);
} else {
endBill(entity);
}
} else {
if (selectById(entity.getId()).getBillStatus().equals(BillStatusConstant.BILL_STATE_LZ)) {
return ResponseUtil.fail("该单据已流转,不能再进行编辑操作!");
}
entity.setBillStatus(BillStatusConstant.BILL_STATE_LZ);
EntityUtils.setUpdatedInfo(entity);
updateById(entity);
}
return ResponseUtil.ok();
}