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 发送给订阅者.
理论上来说, 也可以在 commit 阶段完成之后再将 binlog 发送给订阅者, 但这样会增大主从延迟的风险.
3. 相关代码
来源: http://blog.51cto.com/wangwei007/2323844