分布式事务解决方案

分布式一致性协议

  • 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: 取消执行业务。
  • 特点
    该模式对代码的嵌入性高,要求每个业务需要写三种步骤的操作。
    该模式对有无本地事务控制都可以支持使用面广。
    数据一致性控制几乎完全由开发者控制,对业务开发难度要求高。

其它

  1. 异步回调模式
  2. 最终一致性模式
  3. 可靠消息模式

代码实现(基于LCN框架4.1.2)

  • 架构图
    在这里插入图片描述
  • 步骤
  1. 由于lcn暂时不支持SpringCloud2.0,因此我们需要把某位大牛改的lcn安装到本地库,打包时将maven-javadoc-plugin插件注掉, 调整redis配置,不然会报错。
  2. 在订单服务中集成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("下单成功!");
	}

}
  1. 在库存服务中集成lcn, 同上。
  2. 配置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;
        }
	}
  1. 初始化测试脚本
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;
  1. 依次启动EurekaServer、两个TxManager、库存服务、订单服务.
  • 测试结果
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值