-
Notifications
You must be signed in to change notification settings - Fork 73
分布式事务
实际生产过程中因为硬件故障、网络故障、依赖第三方系统故障给我们带来了很多麻烦。原来的重试补偿功能都是写在各业务模块,增加了很多额外的开发工作量,在加上开发人员水平也不一致也很难全面的考虑各种稳定性问题,所以研发出该模块用于解决微服务业务模块不稳定问题。因业务要求,不允许出现失败回滚场景,该模块只实现了事务补偿。 原理:通过N次重试,跳过执行成功的部分,一直重试失败部分,来达到业务最终执行完成。(N次失败后可以通知人工来进行解决)。
实际场景举例: 用户购买了商品,当微信支付成功后,突然订单模块数据库宕机了。 当数据库修复后,之前丢失的订单能正确处理。
- 支持同步消息与异步消息
- 提供注解方法,使用简单,学习成本低
- 任何需要重试的内容都可以使用,适应性强。
分布式事务算是框架里面比较重的一个模块了,新的模式需要引用一个客户端,然后配置job和存储。
1、在pom.xml中引入依赖模块
<!-- 客户端事务集成模块-->
<dependency>
<groupId>com.hbasesoft.framework</groupId>
<artifactId>framework-tx-integration</artifactId>
<version>${project.parent.version}</version>
</dependency>
<!-- 客户端消息的存储-->
<dependency>
<groupId>com.hbasesoft.framework</groupId>
<artifactId>framework-tx-server-storage-cassandra</artifactId>
<version>${project.parent.version}</version>
</dependency>
2、修改application.yml中配置
project: #项目信息
name: tx-demo-client
server.port: 8939
spring: #应用配置
application:
name: ${project.name}
data:
cassandra: #使用cassandra作为事务消息的存储
keyspace-name: sgp_tx
contactPoints: 127.0.0.1
port: 9042
job:
register:
url: localhost:2181
namespace: ${project.name}
event:
enable: false
3、 在服务的入口增加@Tx注解, 调用远程接口的地方用TxInvokerProxy.invoke(String, TxInvoker)方法进行包裹,这样就可以保证重试的时候跳过已经执行的代码
TestProducter.java
@GetMapping
@Tx // 分布式事务注解,里面支持maxRetryTimes 重试次数(默认重试5次)和retryConfigs重试配置的设置
public synchronized String test(final @RequestParam("id") String id) {
// feClient2Consumer.test为远程方法,如果client2已经执行,下次重试会跳过该方法
String value1 = TxInvokerProxy.invoke("client2", () -> {
return feClient2Consumer.test(id);
});
System.out.println(value1);
// feClient3Consumer.test为远程方法
String value2 = TxInvokerProxy.invoke("client3", () -> {
return feClient3Consumer.test(id);
});
System.out.println(value2);
// 创造随机失败场景
if (new Random().nextInt(NUM_5) == 1) {
throw new RuntimeException();
}
System.out.println(i++ + ":" + id);
return new StringBuilder().append(i).append("client1").append(id).append(':').append(value1).append(':')
.append(value2).toString();
}
针对异步消息framework-message模块,我们增加一些api来支撑这个场景,使用起来很方便
1、在pom.xml中引入额外的依赖模块
<dependency>
<groupId>com.hbasesoft.framework</groupId>
<artifactId>framework-message-tx</artifactId>
<version>${project.parent.version}</version>
</dependency>
2、消费者需要继承com.hbasesoft.framework.message.tx.TxEventLinsener, 该接口是EventLinsener的子类,使用方式完全和EventLinsener一样。
@Component
public class TestEventLinsener implements TxEventLinsener {
private static final int NUM_5 = 5;
private int i = 0;
@Override
public String[] events() {
return new String[] {
"testEvent"
};
}
@Override
public void onEmmit(final String event, final EventData data) {
// 创造随机失败场景
if (new Random().nextInt(NUM_5) == 1) {
throw new RuntimeException();
}
System.out.println(i++ + ":" + data.getMsgId());
}
}
3、生产者使用com.hbasesoft.framework.message.tx.TxEventEmmiter.emmit(String), 当然为了防止重试后消息重复发送,也是需要用TxInvokerProxy.invoke方法进行包裹
@GetMapping
@Tx // 分布式事务注解,里面支持maxRetryTimes 重试次数(默认重试5次)和retryConfigs重试配置的设置
public synchronized String test(final @RequestParam("id") String id) {
// 包裹后,下次重试不会再次触发事件发送
TxInvokerProxy.invoke("client1", () -> {
TxEventEmmiter.emmit("testEvent"); // 事务消息的发送方式
return null;
});
System.out.println("emmit event");
// feClient2Consumer.test为远程方法,如果client2已经执行,下次重试会跳过该方法
String value1 = TxInvokerProxy.invoke("client2", () -> {
return feClient2Consumer.test(id);
});
System.out.println(value1);
// feClient3Consumer.test为远程方法
String value2 = TxInvokerProxy.invoke("client3", () -> {
return feClient3Consumer.test(id);
});
System.out.println(value2);
// 创造随机失败场景
if (new Random().nextInt(NUM_5) == 1) {
throw new RuntimeException();
}
System.out.println(i++ + ":" + id);
return new StringBuilder().append(i).append("client1").append(id).append(':').append(value1).append(':')
.append(value2).toString();
}
Framework属于企业级底层开发框架,集成了log、cache、db、message、rule、tx,每块都以模块形式组织,可以根据项目需要获取模块。我们的初衷是屏蔽项目中各种第三方库之间的版本冲突,打造一套屏蔽底层中间件的全新API,提高项目代码的适配能力。
- framework-common 定义公用的常量、工具类 采用了spring-boot方式启动, 启动类为Application, 也可以支持web方式启动。
- framework-log 分布式集成日志模块,详细的记录了每个方法执行的参数、返回结果、执行时间,可以很方便的排查问题或告警,通过远程接口上传服务器(支持直连服务端,也支持通过kafka发送)
- framework-cache 定义了缓存的获取。 支持注解方式访问缓存, 支持基于Redis的分布式锁
- framework-db 是简单易用的轻量级DAO(Data Access Object)框架,它集成了Hibernate实体维护和Mybaits SQL分离的两大优势,提供了非入侵式API,可以与Hibernate、SpringJdbc等数据库框架很好的集成
- framework-job 定时任务,支持quartz、xxl-job、ElasticJob简单封装的定时器,支持分布式、分片等功能
- framework-message 消息模块,通过简单的api发布和订阅事件, 目前支持kafka、redis、rocketMq
- framework-rule 规则引擎,基于json的轻量级规则引擎, 支持多种插件及扩展, 例如:基于状态机的工作流引擎
- framework-tx 分布式事务,支持各种远程接口、同步异步消息。
- [framework-dependencies] 项目依赖,解决版本包依赖问题
- [framework-shell] 控制台方式提供命令操作,支持自定义各种命令,做各种小工具使用。
- [framework-langchain4j] 对langchain4j的补充,支持国内的大模型,让大家更高效的开发AIGC应用。
jdk1.8请使用framework3.X版本,framework4.X已升级至jdk21版本
Hibernate我用了2年半, 13年下半年去中兴软创用了一年SQL服务(软创内部框架), 14年在京东驻场用了2个月的MyBatis,综合了一下这些项目,各有各的优缺点。例如针对复杂业务SQL,hibernate明显能力不足,简单的功能MyBaties也要弄死人,所以一直在思考一个问题有没有一个框架能扬长避短,把大家的优点都发挥出来。 当时在某网站上看了一个帖子介绍了minidao,思路很新颖,拜读了源码。 从此框架之路走起。( 为什么不直接使用minidao,一是这个项目不火、更新节奏也不快,使用风险较大, 二是软创内部使用的都是自己的框架,连spring都没有,hibernate更不可能,jdk都处于1.4、1.5版本,不可能直接使用minidao )
- 14年7月份左右在软创内部gitlab上发布了第一个版本easydao,主要是结合软创当时的系统框架在其之上封装了一层。
- 14年10月份在github上发布了easydao 剥离掉软创内部框架依赖,使其可以不依赖软创的框架,可以结合spring和hibernate,或者可以单独使用jdbc来使用。
- 15年6月开始framework-0.1版本的设计,数据库已经用的很爽了,但是一个项目不仅仅是数据库,还有很多其他东西, 当时针对的是web项目规划了很多模块,类似于现在的web结构, 做了job可以在线管理,消息、rpc、缓存等等功能
- 16年1月22日正式发布1.0版本
- 16年7月21日发布2.0版本,web模块和jeecg合并单独组成framework-manager, framework专门解决项目底层问题
- 17年9月24日发布3.0版本,升级了spring boot版本至2.0, 去掉了dubbox这个rpc框架,引入spring-cloud框架。前端也放弃了jeecg,基于ant-design-pro 实现的一套web框架(目前还未从项目中分离出来,暂未开源)
- 20年2月4日发布了3.4版本, 增加了framework-tx模块,正式支持分布式事务。
- 23年人工智能比较火,又增加了framework-langchain4j,专门扩展国内的一些大模型。
- 中兴视通网上营业厅项目V1.0
- 咪咕在线客服V1.0
- 中国实践教育平台V2.0
- 大丰科创园微信项目V1.0
- 苏州市总工会微信V1.0
- 佛山港华网上营业厅项目V1.0
- 苏州港华网上营业厅项目V1.0
- 苏州体育局微信活动运营项目V1.0
- 苏州市防汛排涝物资管理系统V1.0
- 港华集团网上营业厅项目
- E网通项目
- 港华紫荆微信项目
- 港华物联网平台