只需5分钟,深刻理解本地事务状态表方案|分布式事务系列(四)

news/2025/2/16 2:24:44/

之前我们已经讲过了最基础的CAP、BASE理论,然后介绍了强一致性方案XA、2PC和3PC,然后详细讲述了TCC在生产中的应用场景和原理。本文继续讲解最终一致性方案——本地事务状态表方案。

点击上方“后端开发技术”,选择“设为星标” ,优质资源及时送达

本地事务状态表

本地事务状态表的方案中主要有三种角色:调用方、被调用方、定时任务。

我们都知道,分布式事务之所以很难保证一致性,但是本地事务却可以,就是因为本地事务是存储引擎层面去保证的。比如 MySQL 的InnoDB 引擎中,他的底层用了 RedoLog 和 UndoLog 以及锁等机制确保了这一点。这里对此不再深入作答,具体可以看往期文章。

所以现在思路是这样:我们要利用存储引擎可以保证事务一致性,这就让我们想到了为每一次远程服务的调用生成一条数据,在数据库落盘,利用本地事务的特点来保证一致性。每一条数据都代表一次RPC调用,然后利用重试机制来保证最终一致性。

用一个案例思考

我先不给详细过程,而是带你实际推理一下,掌握学习方法更重要。

这里使用支付和订单项目来举例,本地服务是支付项目,在支付完成后需要使用RPC通知订单支付结果成功,订单更新订单状态。

正常调用流程如下:

35acc061fde1a4edd1379c0dabc9a5d8.png

看起来流程没有毛病,余额如果通知订单失败,本地事务回滚就是了。余额会加上去,订单也没有支付成功。

订单接口超时怎么办?

但是,一个远程连接有有来和去两个过程,如果余额扣减成功,并且订单服务已经收到支付成功的信息,但是订单本地状态更新为支付成功后,接口响应超时,没有成功返回给支付服务怎么办?

这就造成了余额本地事务回退,金额没有扣减,但是订单状态成功的情况。

3a462ee33041d21ad87bfd55d8030d06.png

本地事务状态表核心流程

为了解决数据不一致的情况,根据文章一开始我们提到的BASE理论,以及利用本地事务来保证数据一致的思路。

因为订单接口有可能超时,网络问题我们无法从编码层面解决,但是可以做到有效的补偿。根据BASE理论,一次调用不能一致,那我多次调用不断尝试不就可以了嘛,总不能每次都超时。

所以这就涉及到两个问题:

  1. 订单接口(被调用方)需要支持幂等操作。

  2. 需要有一种机制让系统知道该重试哪些操作,并且这个机制需要保证和余额(被调用方)数据的一致性。

第一个问题很好解决,可以通过订单ID利用唯一键或者Redis来保证支付行为不会被重复处理。

第二个问题提到了一致性和数据记录,那我们直接保存一条本地事务数据到表里就可以了,保存时候记录订单接口的必要参数信息,以及调用成功状态,默认是未成功。

因为要保证本地事务状态表中的数据和余额(被调用方)一致,那我们就需要把事务提交的时机提前到RPC调用前。

因为此时还没有真正发起RPC调用,所以对于表中记录的数据,可以单独启动一个定时任务不断重试。如果通知订单(被调用方)成功就更改状态为已通知,这样以后定时任务就不会再读取这条记录了。如果还不成功就保持状态不变,继续等待下一次尝试。

d98a291991e803d422b6853ffc4d51f4.png

这里的定时任务不断重试体现了最终一致性的思想。

优缺点

当然,本地事务状态表也优缺点并存,具体使用需要根据场景。

优点:

  1. 性能还不错,业务之间没有强依赖。

  2. 实现了最终一致性,适合对数据不一致有一定容忍度的系统。

缺点:

  1. 有一定的业务入侵性。

  2. 每个系统需要有自己的本地事务状态表,难通用。

  3. 实效性并不是很好。

但是,如上这些缺点其实有一些解决的办法。

定时任务不及时怎么办

笔者自己实现了一套简单的本地事务表框架,主要解决了两个问题:业务入侵度高和实效性。

8a0ceaa298122954f8433552f9f3645b.png

这里不说具体代码了,核心思想是:

  1. 将每个需要利用本地事务表方案的接口和参数保存在ThreadLocal中,并且保存状态表数据到MySQL。

  2. 将每个需要利用本地事务表方案的方法用一个自定义注解去做切面。

  3. 利用TransactionSynchronization接口自己实现了commit,afterCompletion等方法,在事务结束的时候读取ThreadLocal中的内容,以做到及时调用对应RPC。

但是还有问题,每次都需要根据不同的事件类型,写不同的业务逻辑,并且还需要在手动写保存到任务表的逻辑,依旧有业务入侵性,还是有点繁琐。

总结

简单总结下流程:

  1. 调用方在本地事务中保存一条待调用状态的数据到本地事务状态表中。

  2. 本地事务提交后马上调用RPC,如果成功就更新状态,如果不成功那就不管。

  3. 在后台启动一个定时任务,定期扫描本地事务状态表中未调用完成的数据,不断尝试调用。如果成功就更新状态,如果失败那就等待下次调用。在失败重试一定次数后触发告警人工介入处理。

对于本地事务表方案,你能做出自己设计的框架吗?如何解决他存在的问题?

加入讨论群是升职加薪第一步!

回复:加群

71286472ea9f1601b2f3a17ac36d6ac9.jpeg

点赞是一种美德,如对您有帮助,欢迎评论和分享,感谢阅读!

面试官:会SQL调优,那你知道索引合并吗?|金三银四系列

2023-03-21

15c1238d2633526a1d4c4cc17b342cb8.jpeg

CAP、BASE理论真的很重要!|分布式事务系列(一)

2023-03-17

a4c822c8a090c62652c07efdf3646115.jpeg

面试官:你是如何预防多线程死锁的?|金三银四系列

2023-03-16

a901211292f66bd5efb19f9d7fd4025c.jpeg

http://www.ppmy.cn/news/55190.html

相关文章

DIY可视化必看教程 FLEX组件使用,教大家如何布局界面

DIY可视化必看教程FLEX组件使用 水平布局实现、两端对齐 1、拖个FLEX组件过来,排列方向改为水平。 2、拖个文件内容组件进去、栅格化到0 3、复制多一个文本内容组件 4、修改FLEX组件显示对齐方式 5、图标对齐 6、修改FLEX组件对齐方式 7、修改中间占位大&#xff0…

线程池~~

文章目录 线程池线程池实现API、参数说明线程池处理Runnable任务线程池处理Callable任务Executors工具类实现线程池定时器Timer定时器ScheduledExecutorService定时器 并发和并行线程的生命周期 线程池 线程池实现API、参数说明 线程池处理Runnable任务 线程池处理Callable任务…

FreeRTOS任务的创建(动态方法和静态方法)

文章目录 前言一、FreeRTOS任务基本概念二、动态创建任务三、静态创建任务四、静态创建任务和动态创建任务的区别五、任务的删除总结 前言 本篇文章将介绍FreeRTOS任务的创建(动态方法和静态方法),了解什么是任务和任务的具体创建方法。 一…

Java多线程,可以吊打面试官(一)

线程和进程 1. 一个Java程序(进程) 就是一个大工场,一个线程就是一个工人; 2. 单核CPU:工厂只有老板一人干活;单核多线程:老板这一分钟模拟工人a干A活,下一分钟模拟工人b干B活; 3. 多核CPU&…

用python制作俄罗斯方块

代码如下,可以直接运行: import random, pygame, sys, ctypes from pygame.locals import *FPS 60 CELL_SIZE 20 CELLS_WIDE 16 CELLS_HIGH 24GRID [] for x in range(CELLS_WIDE):GRID.append([None] * CELLS_HIGH)WHITE (255, 255, 255) BLACK …

【Unity入门】19.定时调用Invoke

【Unity入门】定时调用Invoke 大家好,我是Lampard~~ 欢迎来到Unity入门系列博客,所学知识来自B站阿发老师~感谢 (一)计时器 (1)Invoke 单词调用 计时器我们并不陌生,在cocos上有着schedule类是…

Docker安装在Linux系统上(纯步骤)

Docker安装在Linux系统上操作步骤 本文章只有操作步骤,没有原理解释,只是用来提醒自己安装步骤 下面是docker官网,也有安装详情 https://docs.docker.com/engine/install/centos/ 安装分为四步走 我使用的是CentOS7版本,下面命令…

【AI实战】微小目标检测模型SSPNet--训练环境从零开始搭建

【AI实战】微小目标检测模型SSPNet--训练环境从零开始搭建 SSPNet介绍环境搭建安装依赖参考 SSPNet介绍 SSPNet: Scale Selection Pyramid Network for Tiny Person Detection from UAV Images 官方连接 https://github.com/MingboHong/SSPNetarxiv https://arxiv.org/abs/210…