全部产品

性能压测

更新时间:2020-06-30 15:18:04

以下模拟了转账的业务场景,分别用分布式事务的 TCC 模式、SAGA 模式与 FMT 模式实现了转账功能,并对 TCC 模式、SAGA 模式与 FMT 模式进行了压测。

通过本文,您可对 TCC、SAGA、FMT 模式的性能有一个大致了解。

压测模型介绍

如下图所示,“应用 A”对外发布“转账服务”,服务完成“账号 A”向“账号 B”转账的功能。

model

实现流程如下:

  1. “转账服务”内部开启分布式事务。
  2. “参与者 A”负责从“账号 A”扣款,“参与者 A”由“应用 A”提供(与发起方为同一应用)。
  3. “参与者 B”负责向“账号 B”加钱,“参与者 B”服务由“应用 B”提供。

说明:A 的账号数据存储在“数据库 A”中,此数据库仅由“应用 A”访问;B 的账号数据存储“数据库 B”中,此数据库由“应用 B”访问。

实现转账

表结构设计

数据库表 A 结构为:

  1. ## 账户余额表,存储账户余额信息
  2. create table account(
  3. account_no varchar(256) not null comment '账户',
  4. amount DOUBLE comment ‘账户余额’,
  5. freezed_amount DOUBLE comment ‘账户冻结金额,TCC才会用到的字段’,
  6. primary key (account_no)
  7. );
  8. ## 账号操作流水表,记录每一笔分布式事务操作的账号、操作金额、操作类型(扣钱/加钱),TCC模式下才会使用到此表
  9. create table account_transaction(
  10. tx_id varchar(256) not null,
  11. account_no varchar(256) not null,
  12. amount DOUBLE,
  13. type varchar(256) not null,
  14. primary key (tx_id)
  15. );

数据库表 B 结构与数据库表 A 结构相同。

实现 TCC 模式

发起方实现:

  1. @DtxTransaction(bizType="single-transfer-by-tcc")
  2. public boolean transferByTcc(String from, String to, double amount) {
  3. try{
  4. //第一个参与者
  5. boolean ret = firstTccActionRef.prepare_minus(null, from, amount);
  6. if(!ret){
  7. //事务回滚
  8. RuntimeContext.setRollBack();
  9. return false;
  10. }
  11. //第二个参与者
  12. ret = secondTccActionRef.prepare_add(null, to, amount);
  13. if(!ret){
  14. //事务回滚
  15. RuntimeContext.setRollBack();
  16. return false;
  17. }
  18. return ret;
  19. }catch(Throwable t){
  20. throw new RuntimeException(t);
  21. }
  22. }

参与者 A(扣钱)实现:

  1. public interface FirstTccAction {
  2. @TwoPhaseBusinessAction(name = "firstTccAction", commitMethod = "commit", rollbackMethod = "rollback")
  3. public boolean prepare_minus(BusinessActionContext businessActionContext,String accountNo,
  4. double amount);
  5. public boolean commit(BusinessActionContext businessActionContext);
  6. public boolean rollback(BusinessActionContext businessActionContext);
  7. }

参与者 B(加钱)实现:

  1. public interface SecondTccAction {
  2. @TwoPhaseBusinessAction(name = "secondTccAction", commitMethod = "commit", rollbackMethod = "rollback")
  3. public boolean prepare_add(BusinessActionContext businessActionContext,String accountNo, double amount);
  4. public boolean commit(BusinessActionContext businessActionContext);
  5. public boolean rollback(BusinessActionContext businessActionContext);
  6. }

实现 FMT 模式

发起方实现:

  1. @DtxTransaction(bizType="transfer-by-auto")
  2. public boolean transferByAuto(String from, String to, double amount) {
  3. boolean ret = false;
  4. try{
  5. //第一个参与者
  6. ret = firstAutoAction.amount_minus(from, amount);
  7. if(!ret){
  8. //事务回滚
  9. RuntimeContext.setRollBack();
  10. return false;
  11. }
  12. //第二个参与者
  13. ret = secondAutoAction.amount_add(to, amount);
  14. if(!ret){
  15. //事务回滚
  16. RuntimeContext.setRollBack();
  17. return false;
  18. }
  19. return ret;
  20. }catch(Throwable t){
  21. throw new RuntimeException(t);
  22. }
  23. }

参与者 A(扣钱)实现:

  1. public class FirstAutoActionImpl implements FirstAutoAction {
  2. ... ...
  3. @Override
  4. public boolean amount_minus(final String accountNo, final double amount) {
  5. try{
  6. return tccFirstActionTransactionTemplateAuto.execute(new TransactionCallback<Boolean>() {
  7. @Override
  8. public Boolean doInTransaction(TransactionStatus status) {
  9. try {
  10. Account account = firstAccountDAOAuto.getAccountForUpdate(accountNo);
  11. if(account == null){
  12. throw new RuntimeException("账号不存在");
  13. }
  14. //扣钱
  15. double newAmount = account.getAmount() - amount;
  16. if (amount < 0) {
  17. throw new RuntimeException("余额不足");
  18. }
  19. account.setAmount(newAmount);
  20. int n = firstAccountDAOAuto.updateAmount(account);
  21. if(n == 1){
  22. return true;
  23. }else{
  24. status.setRollbackOnly();
  25. return false;
  26. }
  27. } catch (Exception e) {
  28. logger.error("amount_minus error", e);
  29. status.setRollbackOnly();
  30. return false;
  31. }
  32. }
  33. });
  34. }catch(Exception e){
  35. logger.error("amount_minus failed", e);
  36. return false;
  37. }
  38. }
  39. }

参与者 B(加钱)实现:

  1. public class SecodeAutoActionImpl implements SecondAutoAction {
  2. ... ...
  3. @Override
  4. public boolean amount_add(final String accountNo, final double amount) {
  5. try{
  6. return tccSecondActionTransactionTemplateAuto.execute(new TransactionCallback<Boolean>() {
  7. @Override
  8. public Boolean doInTransaction(TransactionStatus status) {
  9. try {
  10. Account account = secondAccountDAOAuto.getAccountForUpdate(accountNo);
  11. if(account == null){
  12. throw new RuntimeException("账号不存在");
  13. }
  14. //加钱
  15. double newAmount = account.getAmount() + amount;
  16. account.setAmount(newAmount);
  17. secondAccountDAOAuto.updateAmount(account);
  18. } catch (Exception e) {
  19. logger.error("amount_add error", e);
  20. status.setRollbackOnly();
  21. return false;
  22. }
  23. return true;
  24. }
  25. });
  26. } catch (Exception e) {
  27. logger.error("amount_add failed", e);
  28. return false;
  29. }
  30. }
  31. }

实现 SAGA 模式

使用 SAGA 状态机设计业务流程,如下图所示:

  • amountMinus 是余额扣减服务。
  • amountAdd 是加钱服务。
  • compensateMinus 是余额扣减服务的补偿(冲正)服务,执行的事务就是取消扣减。
  • compensateAdd 是加钱服务的补偿服务,执行的事务是取消加钱。

状态机

发起方实现:

  1. public class TransferServiceImpl implements TransferService {
  2. protected final static Logger logger = LoggerFactory.getLogger(TransferServiceImpl.class);
  3. @Autowired
  4. private StateMachineEngine stateMachineEngine;
  5. @Override
  6. public boolean transferBySaga(String from, String to, BigDecimal amount) {
  7. try {
  8. //生产业务ID
  9. String businessKey = UUID.randomUUID().toString().replaceAll("-", "");
  10. Map<String, Object> params = new HashMap<>(4);
  11. params.put("from", from);//转出帐户
  12. params.put("to", to);//转入帐户
  13. params.put("amount", amount);//转帐金额
  14. StateMachineInstance inst = stateMachineEngine.startWithBusinessKey("transferBySaga", null, businessKey, params);
  15. if (ExecutionStatus.SU.equals(inst.getStatus())
  16. && inst.getCompensationStatus() == null) {
  17. //正向状态为成功, 补偿状态为空(没为触发回滚),则交易成功
  18. return true;
  19. }
  20. else {
  21. //如果执行到了Fail节点会有errorCode和errorMessage
  22. String errorCode = (String) inst.getContext().get(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_CODE);
  23. String errorMessage = (String) inst.getContext().get(DomainConstants.VAR_NAME_STATEMACHINE_ERROR_MSG);
  24. System.out.println("ErrorCode:" + errorCode + ", ErrorMsg:" + errorMessage + ", exception: "+ inst.getException());
  25. return false;
  26. }
  27. } catch (Throwable t) {
  28. logger.error("转账交易执行失败", t);
  29. throw new RuntimeException(t);
  30. }
  31. }
  32. }

参与者 A(扣钱)实现:

  1. public class FirstSagaActionImpl implements FirstSagaAction {
  2. protected final static Logger logger = LoggerFactory.getLogger(FirstSagaActionImpl.class);
  3. //账户dao
  4. private AccountDAO firstAccountDAO;
  5. //业务流水dao
  6. private AccountTransactionDAO firstAccountTransactionDAO;
  7. //事务模版
  8. private TransactionTemplate firstActionTransactionTemplate;
  9. /**
  10. * 扣钱操作
  11. **/
  12. @Override
  13. public boolean amountMinus(final String businessKey, final String accountNo, final BigDecimal amount, final Map<String, Object> extParams) {
  14. try {
  15. if (amount.compareTo(BigDecimal.ZERO) < 0) {
  16. throw new RuntimeException("金额必须大小0");
  17. }
  18. AccountTransaction accountTransaction = firstAccountTransactionDAO.findTransaction(businessKey);
  19. if (accountTransaction != null && Status.SUCCESS.name().equals(accountTransaction.getStatus())) {
  20. //幂等控制: 交易已成功,直接返回成功
  21. return true;
  22. }
  23. return firstActionTransactionTemplate.execute(new TransactionCallback<Boolean>() {
  24. @Override
  25. public Boolean doInTransaction(TransactionStatus status) {
  26. try {
  27. //校验账户余额
  28. Account account = firstAccountDAO.getAccountForUpdate(accountNo);
  29. if (account.getAmount().compareTo(amount) < 0) {
  30. throw new RuntimeException("余额不足");
  31. }
  32. //记录账户操作流水
  33. AccountTransaction accountTransaction = new AccountTransaction();
  34. accountTransaction.setBusinessKey(businessKey);
  35. accountTransaction.setAccountNo(accountNo);
  36. accountTransaction.setAmount(amount);
  37. accountTransaction.setType(Type.MINUS.name());
  38. accountTransaction.setStatus(Status.SUCCESS.name());
  39. firstAccountTransactionDAO.addTransaction(accountTransaction);
  40. //扣钱
  41. BigDecimal amount = account.getAmount().subtract(accountTransaction.getAmount());
  42. if (amount.compareTo(BigDecimal.ZERO) < 0) {
  43. throw new RuntimeException("余额不足");
  44. }
  45. account.setAmount(amount);
  46. firstAccountDAO.updateAmount(account);
  47. } catch (Exception e) {
  48. logger.error("扣钱操作失败", e);
  49. throw new RuntimeException("扣钱操作失败", e);
  50. }
  51. return true;
  52. }
  53. });
  54. } catch (Exception e) {
  55. logger.error("扣钱操作失败", e);
  56. return false;
  57. }
  58. }
  59. /**
  60. * 补偿(冲正)扣钱操作
  61. **/
  62. @Override
  63. public boolean compensateAmountMinus(final String businessKey, final String accountNo) {
  64. AccountTransaction accountTransaction;
  65. try {
  66. accountTransaction = firstAccountTransactionDAO.findTransaction(businessKey);
  67. if (accountTransaction == null) {
  68. //原交易流水不存在, 记录防悬挂(后发先至)流水
  69. accountTransaction = new AccountTransaction();
  70. accountTransaction.setBusinessKey(businessKey);
  71. accountTransaction.setAccountNo(accountNo);
  72. accountTransaction.setType(Type.MINUS.name());
  73. accountTransaction.setStatus(Status.COMPENSATED.name());
  74. firstAccountTransactionDAO.addTransaction(accountTransaction);
  75. return true;
  76. }
  77. if (Status.COMPENSATED.name().equals(accountTransaction.getStatus())) {
  78. //幂等控制: 补偿已成功,直接返回成功
  79. return true;
  80. }
  81. final AccountTransaction accountTransactionFinal = accountTransaction;
  82. return firstActionTransactionTemplate.execute(new TransactionCallback<Boolean>() {
  83. @Override
  84. public Boolean doInTransaction(TransactionStatus status) {
  85. try {
  86. //补偿已扣减金额
  87. Account account = firstAccountDAO.getAccountForUpdate(accountTransactionFinal.getAccountNo());
  88. BigDecimal amount = account.getAmount().add(accountTransactionFinal.getAmount());
  89. account.setAmount(amount);
  90. firstAccountDAO.updateAmount(account);
  91. //更新流水状态
  92. accountTransactionFinal.setStatus(Status.COMPENSATED.name());
  93. firstAccountTransactionDAO.updateTransaction(accountTransactionFinal);
  94. return true;
  95. } catch (Exception e) {
  96. logger.error("扣钱操作补偿失败", e);
  97. status.setRollbackOnly();
  98. return false;
  99. }
  100. }
  101. });
  102. } catch (SQLException e) {
  103. logger.error("扣钱操作补偿失败", e);
  104. return false;
  105. }
  106. }
  107. }

参与者 B(加钱)实现:

  1. public class SecondSagaActionImpl implements SecondSagaAction {
  2. protected final static Logger logger = LoggerFactory.getLogger(SecondSagaActionImpl.class);
  3. private AccountDAO secondAccountDAO;
  4. private AccountTransactionDAO secondAccountTransactionDAO;
  5. private TransactionTemplate secondActionTransactionTemplate;
  6. /**
  7. * 加钱操作
  8. **/
  9. @Override
  10. public boolean amountAdd(final String businessKey, final String accountNo, final BigDecimal amount, final Map<String, Object> extParams) {
  11. try {
  12. if (amount.compareTo(BigDecimal.ZERO) < 0) {
  13. throw new RuntimeException("金额必须大于0");
  14. }
  15. AccountTransaction accountTransaction = secondAccountTransactionDAO.findTransaction(businessKey);
  16. if (accountTransaction != null && Status.SUCCESS.name().equals(accountTransaction.getStatus())) {
  17. //幂等控制: 交易已成功,直接返回成功
  18. return true;
  19. }
  20. return secondActionTransactionTemplate.execute(new TransactionCallback<Boolean>() {
  21. @Override
  22. public Boolean doInTransaction(TransactionStatus status) {
  23. try {
  24. //记录账户操作流水
  25. AccountTransaction accountTransaction = new AccountTransaction();
  26. accountTransaction.setBusinessKey(businessKey);
  27. accountTransaction.setAccountNo(accountNo);
  28. accountTransaction.setAmount(amount);
  29. accountTransaction.setType(Type.ADD.name());
  30. accountTransaction.setStatus(Status.SUCCESS.name());
  31. secondAccountTransactionDAO.addTransaction(accountTransaction);
  32. Account account = secondAccountDAO.getAccountForUpdate(accountNo);
  33. //加钱
  34. BigDecimal amount = account.getAmount().add(accountTransaction.getAmount());
  35. account.setAmount(amount);
  36. secondAccountDAO.updateAmount(account);
  37. } catch (Exception e) {
  38. logger.error("加钱操作失败", e);
  39. throw new RuntimeException("加钱操作失败", e);
  40. }
  41. return true;
  42. }
  43. });
  44. } catch (Exception e) {
  45. logger.error("加钱操作失败", e);
  46. return false;
  47. }
  48. }
  49. /**
  50. * 补偿(冲正)加钱操作
  51. **/
  52. @Override
  53. public boolean compensateAmountAdd(final String businessKey, final String accountNo) {
  54. AccountTransaction accountTransaction;
  55. try {
  56. accountTransaction = secondAccountTransactionDAO.findTransaction(businessKey);
  57. if (accountTransaction == null) {
  58. //防悬挂: 原交流流水不存在, 记录防悬挂(后发先至)流水
  59. accountTransaction = new AccountTransaction();
  60. accountTransaction.setBusinessKey(businessKey);
  61. accountTransaction.setAccountNo(accountNo);
  62. accountTransaction.setType(Type.ADD.name());
  63. accountTransaction.setStatus(Status.COMPENSATED.name());
  64. secondAccountTransactionDAO.addTransaction(accountTransaction);
  65. //允许空补偿: 返回补偿成功
  66. return true;
  67. }
  68. if (Status.COMPENSATED.name().equals(accountTransaction.getStatus())) {
  69. //幂等控制: 补偿已成功,直接返回成功
  70. return true;
  71. }
  72. final AccountTransaction accountTransactionFinal = accountTransaction;
  73. return secondActionTransactionTemplate.execute(new TransactionCallback<Boolean>() {
  74. @Override
  75. public Boolean doInTransaction(TransactionStatus status) {
  76. try {
  77. //扣回已加金额
  78. Account account = secondAccountDAO.getAccountForUpdate(accountTransactionFinal.getAccountNo());
  79. BigDecimal amount = account.getAmount().subtract(accountTransactionFinal.getAmount());
  80. if(amount.compareTo(BigDecimal.ZERO) < 0){
  81. throw new RuntimeException("余额不足, 请工人核对");
  82. }
  83. account.setAmount(amount);
  84. secondAccountDAO.updateAmount(account);
  85. //更新流水状态
  86. accountTransactionFinal.setStatus(Status.COMPENSATED.name());
  87. secondAccountTransactionDAO.updateTransaction(accountTransactionFinal);
  88. return true;
  89. } catch (Exception e) {
  90. logger.error("加钱操作补偿失败", e);
  91. status.setRollbackOnly();
  92. return false;
  93. }
  94. }
  95. });
  96. } catch (SQLException e) {
  97. logger.error("加钱操作补偿失败", e);
  98. return false;
  99. }
  100. }
  101. }

硬件环境

本场景使用的硬件配置如下:

说明:RDS 为 MySql 5.6 数据库。

hardware

压测样本数据策略

用户数据规模(账号数量):5000 > 5000,以验证在不同业务数据规模的场景下,分布式事务的 TCC 模式、SAGA 模式和 FMT 模式的性能表现。

压测模式 A 组账号数 B 组账号数
TCC,FMT,SAGA 5000 5000

压测结果

如下图所示,用户数据规模(A > B)在 5000 > 5000 的场景下,TCC、SAGA、FMT 模式的 TPS 与 Response Time 数据如下:

说明

  • TCC(总)为 TCC 模式下 TPS 总数,TCC(成)为 TCC 模式下成功 TPS 总数;
  • FMT(总) 为 FMT 模式下 TPS 总数,FMT(成)为 FMT 模式下成功 TPS 总数;
  • SAGA(总) 为 SAGA 模式下 TPS 总数,SAGA(成)为 SAGA 模式下成功 TPS 总数。
  • A 组转账至 B 组(5000 > 5000)TPS

    用户量(A 组转账至 B 组) TPS
    A 组:5000
    B 组:5000
    并发数 TCC(总) TCC(成) FMT(总) FMT(成) SAGA(总) SAGA(成)
    1 50 50 40 40 48 48
    5 250 250 200 200 240 240
    10 460 460 380 380 450 450
    20 800 800 695 690 780 780
    50 1450 1450 1220 1200 1300 1300
    100 1500 1500 1550 1500 1480 1480
    200 1500 1500 1600 1450 1550 1550
    500 1600 1600 1500 1400 1580 1580
    700 1500 1500 1500 1300 1550 1550

    5000-tps

  • A 组转账至 B 组(5000 > 5000)Response Time

    Response Time(成功)
    并发数 TCC FMT SAGA
    1 19 26 21
    5 21 25 21
    10 23 27 23
    20 27 30 26
    50 35 45 38
    100 70 75 68
    200 135 160 129
    500 225 380 316
    700 490 520 451

    5000-rt