一、简介
在分布式中,如下有 order 和 stock 是两个系统,调用方服务(order)调用完被调用方服务(stock)的接口后,发生异常,调用方(order)的事务回滚了,但是被调用(stock)方的事务没有回滚,这就是分布式事务问题,例如下图:
CPA 和 Base 理论是解决分布式事务的基本理论
CPA
一致性(C)
在分布式系统中的所有数据备份,在同一时刻是否同样的值。(等同于所有节点访问同一份最新的数据副本)
分区容错性(P)
以实际效果而言,分区相当于对通信的时限要求,也就是网络是否出现问题。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况,P是无法避免的,所以必须就当前操作在C和A之间做出选择。
可用性(A)
在集群中一部分节点故障后,集群整体是否还能响应客户端的读写请求。(对数据更新具备高可用性)
Base理论
Basically Available(基本可用)
基本可用是指分布式系统在出现不可预知故障的时候,允许损失部分可用性,但这绝不等价于系统不可用。比如:
(1) 响应时间上的损失。正常情况下,一个在线搜索引擎需要在0.5秒之内返回给用户相应的查询结果,但由于出现故障,查询结果的响应时间增加了1~2秒。
(2) 系统功能上的损失:正常情况下,在一个电子商务网站上进行购物的时候,消费者几乎能够顺利完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面
Soft state(软状态)
软状态指允许系统中的数据存在中间状态,并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
Eventually consistent(最终一致性)
最终一致性强调的是所有的数据副本,在经过一段时间的同步之后,最终都能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
关于一致性,主要区别为
弱一致性
:允许数据短暂的不一致
强一致性
:数据必须是一致的,不能出现脏读
最终一致性
:最终的结果一定要正确一致
二、LCN 分布式事务解决方案
LCN 框架是分布式事务解决方案,他是事务的协调者,具体流程图如下
图片来自:http://www.txlcn.org/zh-cn/docs/principle/control.html
这里我们以 3 个服务演示,事务管理者 lcn ,订单服务 order,库存服务 stock。用户创建订单成功后,会先在订单数据库创建订单数据,然后再去库存数据库把相应的产品 ID减一,如果成功,两个数据库都发生改变,如果任意一方的操作失败,整个事务都将回滚。
在这三者之间,LCN 是协调者 ,发起者是 order,参与者是 stock
流程和原理简单解释
1、发起者、参与者、协调者保持长连接
2、发起方向协调者申请事务ID
3、发起者把分组 ID 传给参与者
4、如果参与者出现异常,就会抛给发起者,发起者把异常会告诉协调者
5、协调者通知所有参与者回滚
1、创建事务管理者服务
这里代码不会贴全,完整实现文末有连接
(1) 添加核心依赖
<dependency>
<groupId>com.codingapi.txlcn</groupId>
<artifactId>txlcn-tm</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
(2) 启动类添加注解
@EnableTransactionManagerServer
@SpringBootApplication
@EnableTransactionManagerServer
public class TxlcnTmServiceApplication {
public static void main(String[] args) {
SpringApplication.run(TxlcnTmServiceApplication.class, args);
}
}
(2) 添加配置文件
spring.application.name=tx-manager
# 后台管理
server.port=8899
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/tx-manager?characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=roof
mybatis.configuration.map-underscore-to-camel-case=true
mybatis.configuration.use-generated-keys=true
tx-lcn.logger.driver-class-name=${spring.datasource.driver-class-name}
tx-lcn.logger.jdbc-url=${spring.datasource.url}
tx-lcn.logger.username=${spring.datasource.username}
tx-lcn.logger.password=${spring.datasource.password}
# TxManager Host Ip
tx-lcn.manager.host=127.0.0.1
# TxClient连接请求端口
tx-lcn.manager.port=9999
# 心跳检测时间(ms)
tx-lcn.manager.heart-time=15000
# 分布式事务执行总时间
tx-lcn.manager.dtx-time=30000
#参数延迟删除时间单位ms
tx-lcn.message.netty.attr-delay-time=10000
tx-lcn.manager.concurrent-level=128
# 开启日志
tx-lcn.logger.enabled=false
logging.level.com.codingapi=debug
#redis 主机
spring.redis.host=127.0.0.1
spring.redis.password=
#redis 端口
spring.redis.port=16379
tx-lcn.manager.admin-key=codingapi
(4) 导入数据库
详情见文章后面的代码连接
(5) LCN 还提供了一个管理界面,我们可以通过配置的管理端口访问
http://127.0.0.1:8899/admin/index.html#/TxClient
2、创建订单服务和库存服务
关键示例代码如下
订单服务
这里用户在调用订单服务后,就会进行远程调用库存服务减库存,但是如果现在产生了异常,按照原来的情况,订单服务的事务会回滚,但是库存服务的事务并不会回滚。
但是我们提供了事务管理者,库存服务在执行了数据库操作后,还不会提交事务,需要等待事务管理者通知是否需要提交,库存服务抛出了异常后,事务管理者就会通知所有服务进行回滚,只有全部服务执行成功,事务管理者才会通知所有服务提交事务。
@RequestMapping(value = "/order/{goodsId}")
@Transactional
@LcnTransaction
public List<Order> addUser(@PathVariable("goodsId")Integer id) {
indexMapper.addOrder(id);
//添加订单,调用库存减库存
restTemplate.getForObject("http://localhost:8883/stock/"+id, String.class);
int a = 1/0;
return indexMapper.getOrders();
}
库存服务
@RequestMapping(value = "/stock/{id}")
@Transactional
@LcnTransaction //分布式事务注解
public List<Stock> addUser(@PathVariable("id") Integer id) {
stockMapper.updateStock(id);
return stockMapper.getStocks();
}
源代码下载地址
包括依赖,数据库,完整实现代码
https://github.com/huangliangyun/Spring-Cloud-Finchley/tree/master/16-spring-cloud-lcn
—— 完
最后,感谢您阅读我的文章,如果觉得对您有帮助,麻烦点点赞吧!
ABOUT
公众号:【星尘Pro】
github:https://github.com/huangliangyun
参考资料
http://www.txlcn.org/zh-cn/docs/demo/env.html
https://github.com/codingapi/tx-lcn