从一个线上问题分析 binlog 与内部 XA 事务提交过程

栏目: 数据库 · Mysql · 发布时间: 5年前

内容简介:1.   问题业务上新增一条订单记录,用户接收到BinLake拉取的MySQL从库数据消息后,马上根据消息内的订单号去查询同一个MySQL从库,发现有些时候无法查到该条数据,等待大约500ms~1000ms后再去查询数据库,可以查询到该条数据。

从一个线上问题分析 binlog 与内部 XA 事务提交过程

1.   问题

业务上新增一条订单记录,用户接收到BinLake拉取的 MySQL 从库数据消息后,马上根据消息内的订单号去查询同一个MySQL从库,发现有些时候无法查到该条数据,等待大约500ms~1000ms后再去查询数据库,可以查询到该条数据。

注: BinLake为京东商城数据库技术部自研的一套订阅和消费MySQL数据库binlog的组件,本例所描述的问题是业务方希望根据订阅的binlog来获取实时订单等业务消息。

2.  Binlog与内部XA

2.1  XA的概念

XA(分布式事务)规范主要定义了(全局)事务管理器(TM: Transaction Manager)和(局部)资源管理器(RM: Resource Manager)之间的接口。XA为了实现分布式事务,将事务的提交分成了两个阶段:也就是2PC (tow phase commit),XA协议就是通过将事务的提交分为两个阶段来实现分布式事务。

  • 两阶段

1)prepare 阶段

事务管理器向所有涉及到的数据库服务器发出prepare"准备提交"请求,数据库收到请求后执行数据修改和日志记录等处理,处理完成后只是把事务的状态改成"可以提交",然后把结果返回给事务管理器。即:为prepare阶段,TM向RM发出prepare指令,RM进行操作,然后返回成功与否的信息给TM。

2)commit 阶段

事务管理器收到回应后进入第二阶段,如果在第一阶段内有任何一个数据库的操作发生了错误,或者事务管理器收不到某个数据库的回应,则认为事务失败,回撤所有数据库的事务。数据库服务器收不到第二阶段的确认提交请求,也会把"可以提交"的事务回撤。如果第一阶段中所有数据库都提交成功,那么事务管理器向数据库服务器发出"确认提交"请求,数据库服务器把事务的"可以提交"状态改为"提交完成"状态,然后返回应答。即:为事务提交或者回滚阶段,如果TM收到所有RM的成功消息,则TM向RM发出提交指令;不然则发出回滚指令。

  • 外部与内部XA

MySQL中的XA实现分为:外部XA和内部XA。前者是指我们通常意义上的分布式事务实现;后者是指单台MySQL服务器中,Server层作为TM(事务协调者,通常由binlog模块担当),而服务器中的多个数据库实例作为RM,而进行的一种分布式事务,也就是MySQL跨库事务;也就是一个事务涉及到同一条MySQL服务器中的两个innodb数据库(目前似乎只有innodb支持XA)。内部XA也可以用来保证redo和binlog的一致性问题。

2.2.   redo与binlog的一致性问题

我们MySQL为了兼容其它非事务引擎的复制,在server层面引入了 binlog, 它可以记录所有引擎中的修改操作,因而可以对所有的引擎使用复制功能; 然而这种情况会导致redo log与binlog的一致性问题;MySQL通过内部XA机制解决这种一致性的问题。

第一阶段:InnoDB prepare, write/sync redo log;binlog不作任何操作;

第二阶段:包含两步,1> write/sync Binlog; 2> InnoDB commit (commit in memory);

当然在5.6之后引入了组提交的概念,可以在IO性能上进行一些提升,但总体的执行顺序不会改变。

当第二阶段的第1步执行完成之后,binlog已经写入,MySQL会认为事务已经提交并持久化了(在这一步binlog就已经ready并且可以发送给订阅者了)。在这个时刻,就算数据库发生了崩溃,那么重启MySQL之后依然能正确恢复该事务。在这一步之前包含这一步任何操作的失败都会引起事务的rollback。

第二阶段的第2步大部分都是内存操作,比如释放锁,释放mvcc相关的read view等等。MySQL认为这一步不会发生任何错误,一旦发生了错误那就是数据库的崩溃,MySQL自身无法处理。这个阶段没有任何导致事务rollback的逻辑。在程序运行层面,只有这一步完成之后,事务导致变更才能通过API或者客户端查询体现出来。

下面的一张图,说明了MySQL在何时会将binlog发送给订阅者。

从一个线上问题分析 binlog 与内部 XA 事务提交过程                                  

理论上来说,也可以在commit阶段完成之后再将binlog发送给订阅者,但这样会增大主从延迟的风险。

3.    相关代码

4.         int  MYSQL_BIN_LOG::ordered_commit(THD *thd,  bool  all,  bool  skip_commit) {  

5. .....  

6.            // 进入 flush stage   

7. change_stage(thd, Stage_manager::FLUSH_STAGE, thd, NULL, &LOCK_log);  

8. ....  

9.            // 通知底层存储引擎日志刷盘   

10. process_flush_stage_queue(&total_bytes, &do_rotate, &wait_queue);    

11. .....  

12.         // 将各个线程的 binlog cache 写到文件中   

13. flush_cache_to_file(&flush_end_pos);  

14. ....   

15.         // 进入到 Sync stage   

16. change_stage(thd, Stage_manager::SYNC_STAGE, wait_queue, &LOCK_log,  

17. &LOCK_sync));  

18.         //binlog fsync 落盘   

19. sync_binlog_file( false )  

20.         // 通知 binlog 发送线程,有新的 binlog 落盘可以发送到订阅者了   

21. update_binlog_end_pos(tmp_thd->get_trans_pos());  

22.         // 进入 commit state  

23. change_stage(thd, Stage_manager::COMMIT_STAGE, final_queue,  

24. leave_mutex_before_commit_stage, &LOCK_commit);  

25.

26. ....  

27.         // 事务状态提交   

28. process_commit_stage_queue(thd, commit_queue);  

29. ....  

30.

31.

其中,在update_binlog_end_pos之后,binlog发送线程就已经可以读取最新的binlog发送给订阅者了。当订阅者收到这些binlog之后如果process_commit_stage_queue因为系统调度等原因还未执行完成,那么订阅者碰巧在此时发起问题中所描述的查询,就会发生查询不到的情况。

下面我们看一下process_commit_stage_queue都做了什么。

在process_commit_stage_queue会分别调用到binlog的commit方法binlog_commit和innodb的commit函数trx_commit_in_memory。

1. static int binlog_commit(handlerton *, THD *,  bool ) {  

2. DBUG_ENTER("binlog_commit");  

3. /* 

4. Nothing to do (any more) on commit. 

5. */  

6. DBUG_RETURN(0);  

7. }  

在binlog_commit中什么也不做,因为跟binlog有关的操作前面都已经做完了。

最后看一下存储引擎innodb的trx_commit_in_memory都干了什么。

1. static void trx_commit_in_memory(  

2. trx_t *trx,       /*!< in/out: transaction */  

3. const mtr_t *mtr, /*!< in: mini-transaction of 

4. trx_write_serialisation_history(), or NULL if 

5. the transaction did not modify anything */  

6. bool serialised)  

7. /*!< in: true if serialisation log was 

8. written */  

9. {  

10. ....  

11.         // 释放锁   

12. lock_trx_release_locks(trx);  

13.

14. ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY));  

15.

16. .....  

17.         // 释放 mvcc 相关的 read view  

18. if (trx->read_only || trx->rsegs.m_redo.rseg == NULL) {  

19. MONITOR_INC(MONITOR_TRX_RO_COMMIT);  

20. if (trx->read_view != NULL) {  

21. trx_sys->mvcc->view_close(trx->read_view,  false );  

22. }  

23.

24. else {  

25. ut_ad(trx->id > 0);  

26. MONITOR_INC(MONITOR_TRX_RW_COMMIT);  

27. }  

28. }  

29. ....  

30.       // 清理 insert 操作相关的 undo log( 注意,此时只有 insert undo 需要清理 )  

31. if (mtr != NULL) {  

32. if (trx->rsegs.m_redo.insert_undo != NULL) {  

33. trx_undo_insert_cleanup(&trx->rsegs.m_redo,  false );  

34. }  

35.

36. if (trx->rsegs.m_noredo.insert_undo != NULL) {  

37. trx_undo_insert_cleanup(&trx->rsegs.m_noredo,  true );  

38. }  

39. }  

这一步完成之后,在运行时刻事务的变更才能被查询到。但需要记住,MySQL在binlog落盘成功后就认为事务的持久化已经完成。

4.总结

在binlog落盘之后,MySQL就会认为事务的持久化已经完成(在这个时刻之后,就算数据库发生了崩溃都可以在重启后正确的恢复该事务)。但是该事务产生的数据变更被别的客户端查询出来还需要在commit全部完成之后。MySQL会在binlog落盘之后会立即将新增的binlog发送给订阅者以尽可能的降低主从延迟。但由于多线程时序等原因,当订阅者在收到该binlog之后立即发起一个查询操作,可能不会查询到任何该事务产生的数据变更(因为此时该事务所处线程可能尚未完成最后的commit步骤)。

如果应用需要根据binlog作为一些业务逻辑的触发点,还是需要考虑引入一些延时重试机制或者重新考虑合适的实现架构。

从一个线上问题分析 binlog 与内部 XA 事务提交过程

从一个线上问题分析 binlog 与内部 XA 事务提交过程

戳原文,更有料!


以上所述就是小编给大家介绍的《从一个线上问题分析 binlog 与内部 XA 事务提交过程》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

心理学与生活

心理学与生活

[美] 理查德·格里格、菲利普·津巴多 / 王垒、王甦 等 / 人民邮电出版社 / 2003-10 / 88.00元

《心理学与生活》是美国斯坦福大学多年来使用的教材,也是在美国许多大学里推广使用的经典教材,被ETS推荐为GRE心理学专项考试的主要参考用书,还是被许多国家大学的“普通心理学”课程选用的教材。这本教科书写作流畅,通俗易懂,深入生活,把心理学理论与知识联系人们的日常生活与工作,使它同样也成为一般大众了解心理学与自己的极好读物。 作为一本包含着丰富的教育思想和独特教学方法的成熟教材,原书中所有元素......一起来看看 《心理学与生活》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具