Hmily
hmily是什么
Hmily 全称 How much I love you . 意为“我多么爱你”,是不是很浪漫~ 作者(称他为宇哥)可能对感情比较专一,故命名为“hmily”。说完它的意义,那么它到底是做啥的呢?官方给的定义如下:Hmily是一款高性能,高可靠,易使用的柔性分布式事务解决方案,目前提供了对dubbo,spring-cloud,motan,grpc等rpc框架的支持,在易用性上提供零侵入性式的 Spring-Boot, Spring-Namespace 快速集成,目标是打造金融级的一体系分布式事务解决方案。当然,上面这个解释是官方定义的,我的理解,它是一款柔性的分布式事务解决方案,性能高不高,不是我说了算,而是得进行压测,后续会出这类压测的文章。
功能
- 高可靠性 :支持分布式场景下,事务异常回滚,超时异常恢复,防止事务悬挂
- 易用性 :提供零侵入性式的
Spring-Boot
,Spring-Namespace
快速与业务系统集成 - 高性能 :去中心化设计,与业务系统完全融合,天然支持集群部署
- 可观测性 :Metrics多项指标性能监控,以及admin管理后台UI展示
- 多种RPC : 支持
Dubbo
,SpringCloud
,Motan
,Sofa-rpc
,brpc
,tars
等知名RPC框架 - 日志存储 : 支持
mysql
,oracle
,mongodb
,redis
,zookeeper
file
等方式,存储方式均使用异步 - 复杂场景 : 支持RPC嵌套调用事务
使用
RPC以springcloud为例,用简单的下单业务进行实验
springcloud demo注册中心使用eureka,eureka配置这里就不在赘述,搭建项目的代码可以去github下,这里也不在多余说
服务: 订单服务,账户服务,库存服务
首先配置这几个服务的配置文件
代码语言:javascript复制hmily:
server:
configMode: local
appName: account-sc/order-sc/inventory-sc
# 如果server.configMode eq local 的时候才会读取到这里的配置信息.
config:
appName: account-sc/order-sc/inventory-sc
# 序列化方式 kryo会高一些
serializer: kryo
#上下文传输 默认使用局部线程
contextTransmittalMode: threadLocal
#定时任务线程池最大数
scheduledThreadMax: 16
#定时恢复事务时间(补偿) 默认60s跑一次
scheduledRecoveryDelay: 60
#定时清除事务日志时间
scheduledCleanDelay: 60
# 定时清理
scheduledPhyDeletedDelay: 600
scheduledInitDelay: 30
#定时恢复事务时间(补偿)
recoverDelayTime: 60
cleanDelayTime: 180
#查询limit
limit: 200
retryMax: 10
bufferSize: 8192
consumerThreads: 16
#开启异步存储
asyncRepository: true
autoSql: true
phyDeleted: true
storeDays: 3
#日志存储方式 本地文件
repository: file
序列化除了kryo,同时还支持以下四种序列化方式
hessian
,jdk
,msgpack
,protobuf
日志存储也可以使用database
etcd
mongodb
redis
zk
这里模拟几个场景:
- 模拟下单付款操作在try阶段时候,账户rpc异常,此时订单状态会回滚,达到数据的一致性(注意:这里模拟的是系统异常,或者rpc异常
- 模拟下单付款操作在try阶段时候,账户rpc超时异常(但是最后自身又成功了),此时订单状态会回滚,账户系统依赖自身的事务日志进行调度恢复,达到数据的一致性(异常指的是超时异常
- 模拟下单付款操作在try阶段时候,库存异常,此时账户系统和订单状态会回滚,达到数据的一致性(注意:这里模拟的是系统异常,或者rpc异常)
- 模拟下单付款操作在try阶段时候,库存超时异常(但是自身最后又成功了),此时账户系统和订单状态会回滚,(库存依赖事务日志进行恢复),达到数据的一致性(异常指的是超时异常)
- 订单支付接口(里模拟的是rpc的嵌套调用 order--> account--> inventory, inventory异常情况
以第一个场景为例进行解析:
模拟下单付款操作在try阶段时候,账户rpc异常,此时订单状态会回滚,达到数据的一致性(注意:这里模拟的是系统异常,或者rpc异常
代码语言:javascript复制@Override
@HmilyTCC(confirmMethod = "confirmOrderStatus", cancelMethod = "cancelOrderStatus")
public String mockPaymentAccountWithTryException(Order order) {
updateOrderStatus(order, OrderStatusEnum.PAYING);
//执行扣款超时 ---> 异常
accountService.mockTryPaymentException(buildAccountDTO(order));
return "success";
}
下方是调用账户扣款服务超时的情况
代码语言:javascript复制 @Override
@HmilyTCC(confirmMethod = "confirm", cancelMethod = "cancel")
public boolean mockWithTryException(AccountDTO accountDTO) {
throw new HmilyRuntimeException("账户扣减异常!");
}
这里会报扣减库存的异常,调用完成之后然后我们再去数据库里面看订单和账户的数据,发现账户金额冻结了1000元,于是在等待了大约20秒的时候发现hmily会通过定时任务进行恢复数据,再一次查看的时候发现余额已经恢复为10000了
于是,最终数据都完成了一致性校验,但是可能会存在延迟的过程,通过查看源码看到它的自我恢复机制是通过用户手动去设置的,因此延迟问题完全可以避免,只不过是牺牲性能来换时间。
代码语言:javascript复制private void selfTccRecovery() {
selfTccRecoveryExecutor
.scheduleWithFixedDelay(() -> {
try {
List<HmilyParticipant> hmilyParticipantList =
hmilyRepository.listHmilyParticipant(acquireDelayData(hmilyConfig.getRecoverDelayTime()), TransTypeEnum.TCC.name(), hmilyConfig.getLimit());
...
//这里是通过方法调用来进行回滚的
if (Objects.isNull(globalHmilyTransaction)) {
tccRecovery(hmilyParticipant.getStatus(), hmilyParticipant);
} else {
tccRecovery(globalHmilyTransaction.getStatus(), hmilyParticipant);
}
}
} catch (Exception e) {
LOGGER.error("hmily scheduled transaction log is error:", e);
}
}, hmilyConfig.getScheduledInitDelay(), hmilyConfig.getScheduledRecoveryDelay(), TimeUnit.SECONDS);
}
剖析它的日志存储模块
1、hmily的日志存储使用的是异步存储的,使用disruptor框架来实现异步的,那么这么做有什么好处?首先想到的是可以提高系统的吞吐量,可以充分利用cpu的性能,而且discruptor是可以实现秒级的到达率,据官网统计,它可以实现一秒几十万的并发,所以应用场景显而易见(秒杀,交易所);其次通过异步可以解耦代码,可以将发布者和实现者区分开。
微服务接入hmily 类图流程,如下图
由此可见,所有操作均在切面上进行,切面贯穿了所有服务之间的联系,以上流程可用时序图表示,如下图所示
图片摘选自 分布式事务
看到上面的流程图,有没有对hmily有一个简单的认识?如果没有,那我的这篇文章就太失败辣!我就得考虑重新编辑咯>
Hmily,曾经有一份真挚的感情摆在我的面前,而我却选择了代码,现在我也不后悔,因为我是年轻人,我讲“码德”!
谢谢您的浏览,我叫奕仁,人人都是产品经理后端开发