MySQL Binlog 技术原理和业务应用案例分析

导语MySQL Binlog 用于记录用户对数据库操作的结构化查询语言 (Structured Query Language , SQL) 语句信息 。是 MySQL 数据库的二进制日志 , 可以使用 mysqlbin 命令查看二进制日志的内容 。爱奇艺在会员订单系统使用到了 MySQL Binlog , 用来实现订单事件驱动 。在使用 Binlog 后在简化系统设计的同时帮助系统提升了可用性和数据一致性 。本文将从实际应用角度出发理解 MySQL 中的相关技术原理 , 从技术原理和工作实践相结合 , 帮助大家以及在相关设计中存在的潜在问题 , 希望能给大家有所帮助和启发 , 共同进步 。作者介绍:作者帆叔目前主要负责爱奇艺会员交易系统的技术和架构工作 , 专注异步编程、服务治理、代码重构等领域 , 热爱技术 , 乐于分享 。
 
背景Binlog 是 MySQL 中一个很重要的日志 , 主要用于 MySQL 主从间的数据同步复制 。正是因为 Binlog 的这项功用 , 它也被用于 MySQL 向其它类型数据库同步数据 , 以及业务流程的事件驱动设计 。通过研究分析 , 我们发现使用 MySQL Binlog 实现事件驱动设计并没有想象中那么简单 , 所以接下来带大家了解 MySQL 的 Binlog、Redo Log、数据更新内部流程 , 并通过对这些技术原理的介绍 , 来分析对业务流程可能造成的问题 , 以及如何避免这些问题 。希望通过本文的解析 , 能够帮助大家了解到 MySQL 的一些原理 , 从而帮助大家能够更顺利地使用 MySQL 这个流行的数据库技术 。
基于 Binlog 的事件驱动首先介绍一下会员订单系统的设计 , 订单系统直接向 MQ 发送消息 , 通过异步消息驱动后续业务流程 , 以实现消息驱动的设计 。大致的业务流程示意图如下:
MySQL Binlog 技术原理和业务应用案例分析

文章插图
【MySQL Binlog 技术原理和业务应用案例分析】 
图 1:直接发送消息的订单事件驱动
这种设计需要保证数据库操作和消息操作的数据一致性 , 即数据保存和消息发送要全部成功或者全部失败 。显然在数据保存前和事务中进行消息发送都是不合适的 。我们是在数据更新操作后 , 数据库事务外发送消息 。如果数据保存成功 , 但消息发送失败 , 支付系统需要重新通知(上图步骤 1) , 直至通知成功 。这种设计虽然实现了功能和对可用性的基本要求 , 但存在如下缺点:
  1. 业务系统直接依赖消息中间件 :消息中间件的故障 , 不仅会影响支付通知的处理也可能影响业务系统上的其它接口 。
  2. 业务系统必须实现可靠的重试 :不论是请求发起方还是请求接收方都必须实现可靠重试才能实现最大努力通知的目标 。
  3. 重试间隔增大会造成业务延迟 :随着重试次数增加 , 每次重试的间隔通常也越来越大 , 这成为 Exponential Backoff(指数级退避) 。这种设计能够让请求接收方的故障处理更加从容 , 避免因密集重试造成请求接收方服务难以恢复 。但这样做可能会使请求接收方在恢复服务之后很长时间后才处理完积压的消息 , 从而造成业务延迟 。我们可以采用类似 Hystrix 的自适应设计 , 在请求接收方服务恢复后回到到正常的请求速率 。但这样的设计显然会复杂许多 。
为了解决上述问题 , 简化技术架构 , 我们采用事件表的设计思想 , 将订单表作为事件表 。通过订阅订单表的 Binlog , 生成订单事件 , 驱动后续业务流程 。在系统架构上 , 业务系统不用直接依赖消息中间件 , 只需专注数据库操作 。而通过引入一个接收 Binlog 的独立的系统 , 将 MySQL 数据变化转换成业务事件驱动后续流程 。具体流程如下:
MySQL Binlog 技术原理和业务应用案例分析

文章插图
 
图 2:基于 Binlog 的订单事件驱动
暗藏问题上文提到 , 虽然基于 Binlog 的订单事件驱动设计存在诸多优点 , 但后来发现其实暗藏问题 。经过实验 , 我们发现偶尔会有订单履约延迟的现象 。
在正常流程中 , 订单履约服务收到订单支付事件后 , 会检查订单状态 , 如果此时订单状态为已支付 , 则进行履约流程的处理 。但对于有履约延迟的订单 , 订单履约服务收到此订单的支付事件后 , 查询数据库发现此订单并非支付状态 。经过调查 , 我们排除了数据并发覆盖问题 , 并且订单状态查询是发生在主库上 , 也不存在主从同步延迟问题 。


推荐阅读