转载:
SpringCloud中国社区
http://springcloud.cn/view/374
前言
分布式事务对微服务开发者而言是既想努力避免又无法完全回避的蛋疼问题。通过使用分布式事务处理框架可以很大程度上解决分布式事务所带来的事务性能、可靠性问题,以及引入的编码复杂性。本文由海信HICS技术团队压测提供,目前国内主要的开源分布式事务框架框架包括:
框架名称 | GitHub地址 | star数量 |
---|---|---|
社区开源项目dts | https://github.com/venusteam/dts | 111 |
tcc-transaction | https://github.com/changmingxie/tcc-transaction | 2192 |
Hmily | https://github.com/yu199195/hmily | 1196 |
ByteTCC | https://github.com/liuyangming/ByteTCC | 1137 |
myth | https://github.com/yu199195/myth | 894 |
EasyTransaction | https://github.com/QNJR-GROUP/EasyTransaction | 805 |
tx-lcn | https://github.com/codingapi/tx-lcn/ | 802 |
我们响应springcloud社区的号召,计划对上框架的非功能对比测试,希望对大家日后的选型有所帮助。
本文是此测试的第一轮,本轮对不使用消息中间件的分布式事务处理模式进行了测试(包括TCC、广义上的2PC,3PC等),测试框架包括:
测试硬件环境
应用服务器规格均为(2核4G,实例规格ecs.c5.large)
mysql数据库规格为(RDS配置为2核4G,实例规格rds.mysql.s2.large,MySQL5.7高可用版)
ECS主机与RDS均在同一内网环境下
测试业务场景
本测试场景涉及三个服务:
- 订单服务
- 支付服务
- 库存服务
用户请求订单服务创建订单,订单服务作为事务发起者,发起分布式事务,订单服务分别请求库存服务与支付服务完成相应操作,从而完成一个分布式事务。
- 新增订单信息
- 新增订单明细信息
- 新增/更新商品统计信息
- 查询商品信息
- 查询商品品类信息
- 查询更新商品统计信息
- 新增支付信息
- 新增支付优惠信息
- 新增/更新支付统计信息
- 查询支付统计信息
- 新增库存变动信息
- 更新库存信息
- 查询库存信息
测试说明
- 针对上面的框架进行性能测试,对于性能较好的框架会进一步进行事务可靠性测试
- 商品库模拟了1000种商品
- 测试软件使用 Apache JMeter,模拟在30到200个线程情况下,测试各个框架的平均响应时间与平均TPS数值。一般而言,平均响应时间越低,平均TPS数值越高,则性能越好。
性能测试
分布式事务测试结果概要
先上性能测试结果,有时间的同学可以看详细的测试过程
名次 | 框架名称 | 最高平均TPS |
---|---|---|
对照组 | 本地事务 | 850.2 |
1 | LCN | 554.6 |
2 | EasyTransaction | 346.4 |
3 | Hmily | 132.3 |
4 | tcc-transaction | 127.7 |
测试参考基准
先将所有的分布式事务逻辑以及数据库操作在一个单体服务的本地事务中完成做为本次后续性能测试的对照基准。
我们测试了在30、50、100、150、200个线程下,本地事务情况下的TPS测试结果如下:
线程数 | 平均TPS | 平均响应时间(ms) |
---|---|---|
30线程 | 850.2 | 35 |
50线程 | 847.1 | 58 |
100线程 | 845.2 | 117 |
150线程 | 828.2 | 178 |
200线程 | 828.4 | 236 |
基准测试性能瓶颈在数据库上,数据库的CPU占用达90%以上。
tcc-transaction
首先,测试star最多的tcc-transaction,本次测试使用1.2.x分支。
tcc-transaction在GitHub上有完整的使用说明文档,并且提供了基于dubbo、http等方式的demo,对于使用者来说比较友好。
tcc-transaction在使用上通过注解的形式编写分布式事务的confirm与cancel代码,具有代码入侵性较小的特点。
对于tcc-transaction,测试其在30、50、100个线程下的表现,测试结果如下:
线程数 | 平均TPS | 平均响应时间(ms) |
---|---|---|
30线程 | 125.6 | 236 |
50线程 | 127.7 | 387 |
100线程 | 125.5 | 785 |
分析发现tcc-transaction的性能压力主要集中在数据库上,一个分布式事务涉及至少四次框架层面的数据库操作,一次插入、至少两次更新、一次删除,由下图可以看出操作耗时大约在15ms,对比其他框架的1~2ms可以说是响应时间非常慢了。其中操作耗时长的表中均有多个长字段,包括一个长度是8000、类型为VARBINARY的字段,且更新与插入的比例高于其他框架的事务操作比例。
此外数据库出入网络流量在120TPS时,已达到28M/s,带宽占用高达224Mbps。即使数据库服务器性能提高,在千兆网络环境下,带宽占用将成为瓶颈。
Hmily
然后,测试star数第二的Hmily。
Hmily有作者自己博客上的源码解析文章,还有录制的环境搭建和源码详解的视频,框架支持dubbo、motan、springcloud多种RPC框架,作者也给出了以上RPC框架的demo代码,对于开发者来说,了解源码与使用框架都非常方便。
说明
- 测试demo基于框架自带的Spring Cloud demo改造,运行依赖Eureka
对于Hmily,测试其在50、100、200线程下的表现,测试结果如下:
线程数 | 平均TPS | 平均响应时间(ms) |
---|---|---|
50线程 | 132.3 | 377 |
100线程 | 123.5 | 806 |
200线程 | 130.4 | 1530 |
通过工具分析发现,Hmily在压力测试中,线程大部分时间处于阻塞状态,多数线程都处于等待锁的状态中。
通过追踪处于阻塞状态线程的调用方法栈,注意到StarterHmilyTransactionHandler类中有一个可重入锁,这个锁加在了confirm阶段,这个加锁对于性能产生了影响,导致多数的线程处于阻塞的状态。confirm加锁能够防止发生错乱,但是对性能却产生较大的影响,建议作者优化confirm加锁的方式,提高框架的性能。
ByteTCC
接下来是仅比Hmily少59个star而屈居第三位的BtyeTCC。
ByteTCC在有使用方法的文档,也提供了dubbo、springcloud两种RPC框架的demo,开发者入门比较方便。
然而在上周测试ByteTCC时,框架提供的springcloud demo却不能正常运行,由于测试时间比较紧张的关系,没有时间基于ByteTCC框架自行开发测试demo,因此ByteTCC的测试暂时搁置,希望ByteTCC作者更新一下示例demo,使得demo更加易用。
EasyTransaction
EasyTransaction基于TCC、补偿以及消息队列等多种形式实现了分布式事务,不同的实现形式可以在同一个分布式事务中使用,开发者可以选择自己适用的场景开发。但是,EasyTransaction示例代码只有单元测试代码,demo代码不及以上几个框架,对于开发者不够易用。EasyTransaction对于代码的入侵性比较大,不及其他框架方便。
说明
- 测试demo基于单元测试代码使用Spring Cloud自行开发,运行依赖Eureka
- 运行EasyTransaction框架依赖ZooKeeper、Kafka
- 仅测试EasyTransaction框架的TCC场景
对于EasyTransaction,测试其在30、50、100、150线程下的表现,测试结果如下:
线程数 | 平均TPS | 平均响应时间(ms) |
---|---|---|
30线程 | 334.5 | 89 |
50线程 | 346.4 | 143 |
100线程 | 346.4 | 286 |
150线程 | 345.1 | 426 |
EasyTransaction在简单的性能测试中未发现明显的性能问题,其远程调用采用了多线程的处理方式,较大的提升了框架的性能。但在测试中发现框架存在两个问题:
- 即使只使用TCC,在框架启动时也会检查是否部署了消息中间件。
- 虽然有远程调用超时处理的注解,但未实现。
LCN
最后是star数最少的LCN。
LCN有比较全面的文档,包括原理介绍、使用说明、视频讲解,演示demo等,而且LCN对于业务代码的入侵性极小,开发者使用起来也比较方便。不过LCN需要单独部署txmanager服务。
说明
- 测试demo基于框架自带的Spring Cloud demo改造,运行依赖Eureka
- 运行LCN框架依赖tx-manager
对于LCN,测试其在50、100、150线程下的表现,测试结果如下:
线程数 | 平均TPS | 平均响应时间(ms) |
---|---|---|
50线程 | 455.3 | 109 |
100线程 | 530.2 | 188 |
150线程 | 554.6 | 269 |
在简单的性能测试中未发现明显的性能问题。在A性能场景中表现最好。
为进一步评估LCN和EasyTransaction的性能,我们又对订单集中在单一商品的场景进行了测试,以了解框架在访问集中在某个业务实体的情况下的性能表现。
对于EasyTransaction与LCN,我们增加了单个商品的测试,就是说所有请求都会更新一条商品的库存数据。
采用50个线程,最终测试结果如下:
单个商品TPS | 一千个商品TPS | 下降比率 | |
---|---|---|---|
LCN | 118.5 | 455.3 | 73.9% |
EasyTransaction | 166.2 | 346.4 | 52.0% |
可以看到由于LCN的事务处理机制导致事务完成前会锁定所有的分布式资源,导致在此场景下性能衰减比较大。
可靠性测试
可靠性测试选取性能测试中表现较好的LCN与EasyTransaction进行。可靠性测试以压测的形式,最终评估压测过程中的全部订单数据、支付数据、库存数据是否与实际完成的交易结果保持一致。
可靠性测试场景说明
在可靠性测试中会通过一定的规则抛出异常的方式来制造事务异常,来验证事务异常场景下,分布式事务的数据最终一致性。
异常规则如下:
- 在支付服务中对于总价格为100的数据抛出异常
- 在库存服务中对于商品ID为100的数据抛出异常
商品价格价格为1-10000之间随机,商品ID为1-1000之间随机,购买数量也通过一定的规则随机产生。
可靠性测试总发送100万个请求,记录所有成功请求的参数中的商品价格以及商品个数等信息,最终在查询三个服务各自数据库中的数据,看销售金额以及售出商品个数与请求参数总的结果是否一致。
EasyTransaction
统计记录的所有成功请求的数据,总商品价格是3000172719,查询订单服务的数据库中总销售金额为3000172719,在支付服务数据库中扣除销售金额为100的错误数据后,销售金额也为3000172719,三者一致。
统计记录的所有成功请求的数据,总购买数量1799058,查询订单服务的数据库中商品总售出数量是1799058。在库存服务数据库1000种商品总扣减的库存数也是1799058,三者一致。
LCN
统计记录的所有成功请求的数据,总商品价格是4375046335,查询订单服务的数据库中总销售金额为4375046335,在支付服务数据库中销售金额也为4375046335,三者一致。
统计记录的所有成功请求的数据,总购买数量2624655,查询订单服务的数据库中商品总售出数量是2624655。在库存服务数据库1000种商品总扣减的库存数也是2624655,三者一致。
测试通过统计订单数据库、支付数据库、库存数据库的数据,来检测分布式事务最终一致性。经过测试发现EasyTransaction与LCN都能可靠的完成分布式事务,达到最终一致性。
总结
本次测试中,性能方面表现亮眼的是LCN和EasyTransaction,对tcc-transaction和Hmily来说,在性能优化方面还有很大的改进空间;在可靠性方面,LCN与EasyTransaction都表现不错。
LCN总结
tx-lcn是LCN(lock,cancel,notify)的事务模式的实现
- 性能优秀
- 可靠性强
- 代码入侵性小
在本次对比的所有框架中,LCN实现的分布式事务处理模式,编码复杂性和入侵代码量最低。
- 需额外部署tx-manager服务节点。
- 由于需要lock资源这种处理方式,如果集中更新某几个热门商品时,LCN的性能衰减量大于TCC模式。
- 服务超时时,会造成其他服务的资源被锁住,比如支付服务超时过程中,相关商品库存会一直无法操作。
EasyTransaction总结
基于Spring事务为基础,实现了多种分布式事务处理模式并提供扩展接口(本次仅对TCC模式进行了测试)
优点
- 针对远程调用采用多线程并发处理的模式,性能优化较好
- 可靠性强
- 整合简单,无需额外部署事务管理节点
缺点
- 对比其他框架,入侵性和编码量是偏大的
- RPC请求超时处理尚未实现
tcc-transaction总结
实现TCC模式
优点
通过注解的形式声明TCC的的confirm与cancel方法,代码入侵性较小。
缺点
- 对数据的性能压力太大,操作响应时间长,带宽占用高,成为了性能瓶颈。
- 分布式事务中RPC请求是串行处理,请求响应时间长
Hmily总结
实现TCC模式
优点
通过注解的形式声明TCC的的confirm与cancel方法,代码入侵性较小。
缺点
- 多线程的锁机制有待优化。
- 分布式事务中RPC请求串行处理,请求的响应时间长
ByteTCC总结
实现TCC模式
很遗憾,因为demo代码的问题再加上我们时间有限,本次未能对此框架完成有效测试。希望作者有空改善一下demo代码的问题,我们会在后续补充本框架的测试结果。
分布式事务与本地事务对比
经过测试,使用以上几种框架实现分布式事务的性能比本地事务的性能要差的多,从这一结果可以看出不通过消息队列实现的分布式事务处理性能都远不如本地事务,从这个层面也进一步说明了微服务设计中应尽量避免分布式事务的必要性。
在下一轮测试中我们将测试基于消息中间件的异步化会分布式事务的性能方面会有怎样的改善。