- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
最近项目中遇到一个场景。
为了减少单库的数据量,系统采用了分库的方式,分为1个主库和N个分库。
现在,在分库中的A表,需要收敛成一个汇总的数据,并写入主库中的B表。需要保证分库更改A表的处理状态和插入主库B表两个动作具有原子性,那么,这就涉及到了跨库的分布式事务的一致性问题。
经过一番学习了解,由于该场景是采用定时任务的方式完成,不要求实时的强一致性,最后参考了本地消息表的方式,保证事务的最终一致性。
可以通过这篇文章了解一下分布式事务,包括本地消息表。
摘取其中关于本地消息表的案例讲解。
本地消息表这个方案最初是ebay提出的 ebay的完整方案https://queue.acm.org/detail.cfm?id=1394128。
此方案的核心是将需要分布式处理的任务通过消息日志的方式来异步执行。消息日志可以存储到本地文本、数据库或消息队列,再通过业务规则自动或人工发起重试。人工重试更多的是应用于支付场景,通过对账系统对事后问题的处理。
对于本地消息队列来说核心是把大事务转变为小事务。还是举上面用100元去买一瓶水的例子。
1.当你扣钱的时候,你需要在你扣钱的服务器上新增加一个本地消息表,你需要把你扣钱和写入减去水的库存到本地消息表放入同一个事务(依靠数据库本地事务保证一致性。
2.这个时候有个定时任务去轮询这个本地事务表,把没有发送的消息,扔给商品库存服务器,叫他减去水的库存,到达商品服务器之后这个时候得先写入这个服务器的事务表,然后进行扣减,扣减成功后,更新事务表中的状态。
3.商品服务器通过定时任务扫描消息表或者直接通知扣钱服务器,扣钱服务器本地消息表进行状态更新。
4.针对一些异常情况,定时扫描未成功处理的消息,进行重新发送,在商品服务器接到消息之后,首先判断是否是重复的,如果已经接收,在判断是否执行,如果执行在马上又进行通知事务,如果未执行,需要重新执行需要由业务保证幂等,也就是不会多扣一瓶水。
本地消息队列是BASE理论,是最终一致模型,适用于对一致性要求不高的。实现这个模型时需要注意重试的幂等。
在本地消息表方案的基础上,本案例可以再简化。
本案例写入分库和主库的操作,是在同一个定时任务里,没有使用消息中间件异步解耦,因此,可以把上文图中的kafka省略。或者理解为,定时任务已经起到了kafka的作用,在分库写完消息后,就把消息通知了主库。
因此可以得出如下方案:
第1步,在分库中更新A表数据的状态为处理中(类比上图中的写业务数据),并在分库中的未提交日志表写入一条记录(类比上图中的写消息数据),要求开启事务,保证原子性;
第2步,在主库中插入或更新B表的数据(类比上图中的写业务数据),并在主库记录A表数据ID和B表数据ID的关系,用于后续判断事务成功与否。本步骤是同一个定时任务的操作,因此省略了消息中间件传递消息的环节;
第3步,在分库中更新A表的数据状态为处理成功,并将对应的分库的未提交日志表删除。
以上3步对应第2大点案例的前3步,都需要开启事务保证原子性。
那么对于异常的场景,需要有一个事务协调器进行事后问题的处理。本例采用一个补偿定时任务作为这个协调器。
补偿任务会扫描分库中的未提交日志表,若表为空,说明事务要么不存在,要么是成功的,不用处理;否则表示有事务异常的场景,需要细化分析是在哪一步失败了。
根据分库未提交日志表的信息,在主库查找A表数据ID和B表数据ID的关系,以该关系是否正常写入作为事务成功或失败的标识。若存在,说明事务是成功的,是在第3步失败了,那么重做第3步即可;否则,说明事务是失败的,可以选择回滚或者重新发起事务(即重做第2和第3步。本例因为是简单的收敛,不考虑回滚,所以是重新发起事务)。
整体流程图如下:
进一步思考,我发现这个方案与MySql InnoDB的bin log和redo log的写入相似,其采用两阶段提交的方式,即2PC。
第一阶段,写入redo log并置于prepare阶段,不写bin log;
第二阶段,即commit阶段,redo log和bin log同时提交。
在故障恢复过程中,以bin log是否完整写入为标准,判定事务是否真正提交,并进行对应的提交或回滚操作。
之所以我觉得两个方案相似,是因为可以这样看:把第1步看作是prepare阶段,把第2和第3步看是commit阶段,在故障恢复中以其中一个作为事务成功判断标识(关系表 vs bin log完整写入)。
不知道这样的理解是否正确,还望大牛不吝赐教。
这个问题在这里已经有了答案: Oracle: merging two different queries into one, LIKE & IN (1 个回答) 8年前关闭。 我有以下代码: case
我查阅过此页面:http://dev.mysql.com/doc/refman/5.1/en/case.html以及这个,但无法获得一个简单的程序来工作...... 更新:为了明确我想要做什么:我想从
有什么办法可以优化下面的查询吗? SELECT DATE_FORMAT(a.duedate,'%d-%b-%y') AS dte, duedate, SUM(CASE WHEN (typeofnoti
我进退两难,以下 SQL 查询的结果是什么以及它是如何工作的: SELECT ... CASE WHEN (a.FIELD=1 AND b.FIELD=2) THEN 1 WHEN
问题:输入年,月,打印对应年月的日历。 示例: 问题分析: 1,首先1970年是Unix系统诞生的时间,1970年成为Unix的元年,1970年1月1号是星期四,现在大多的手机的日历功能只能显
**摘要:**介绍了Angular中依赖注入是如何查找依赖,如何配置提供商,如何用限定和过滤作用的装饰器拿到想要的实例,进一步通过N个案例分析如何结合依赖注入的知识点来解决开发编程中会遇到的问题。 本
我想拥有自动伴侣类apply case 类的构造函数来为我执行隐式转换,但无法弄清楚如何这样做。我到处搜索,我能找到的最接近的答案是 this问题(我将解释为什么它不是我在下面寻找的)。 我有一个看起
您好,我已经浏览了“多列案例”问题,但没有看到与此相同的内容,所以我想我应该问一下。 基本上我有两个我想要连接的表(都是子查询的结果)。它们具有相同的列名称。如果我加入他们的 ID 和 SELECT
我发现了一些类型推断的非直觉行为。因此,语义等效代码的工作方式不同,具体取决于编译器推断出的有关函数返回类型的信息。当您在最小单元测试中重现此案例时,或多或少会清楚发生了什么。但我担心在编写框架代码时
CREATE TABLE test ( sts_id int , [status1] int , [status2] int , [status3] int , [status4] int ) INS
我有以下声明: SELECT Dag AS Dag, CASE Jaar WHEN 2013 THEN Levering END AS '2013', CASE
我想做的是为所有高于平均时间、平均时间和低于平均时间的游乐设施获取平均tip_portion。所以返回3行。当我运行它时,它显示: ERROR: missing FROM-clause entry
我正在尝试设置一个包含以下字段的报告: 非常需要报告来显示日期、该日期内的总记录(因此我按日期分组),然后按小时计算 12 小时工作日(从上午 8 点到晚上 8 点)我需要计算记录在这些时间内出现的时
我有这个查询 SELECT users.name FROM users LEFT JOIN weapon_stats ON users.id = weapon_stats.zp_id WHERE we
我正在尝试按收视率等级获取不同视频的计数。我有下表: vid_id views 1 6 1 10 1 900 2 850 2 125000
假设我有一个如下所示的 SQL 语句: select supplier, case when platform in (5,6) then 'mobile' when p
我有一个表测试 TestNumber (int primary key) InactiveBitwise (int) 我执行以下命令: UPDATE tests SET CASE WH
我有一个像这样的表(name=expense): id amount date 1 -1687 2014-01-02 00:00:00.0 2 11000 2014-01-02 0
我有一个 multimap 定义 typedef std::pair au_pair; //vertices typedef std::pair acq_pair; //ch qlty specifi
我有一个有点像枚举的类,它的每个实例都有一个唯一的 int 值,该值从 0 开始并在每个新实例时递增。 class MyEnumLikeClass { static int NextId =
我是一名优秀的程序员,十分优秀!