您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

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

时间:2020-06-29 11:45:17  来源:  作者:

导语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 技术原理和业务应用案例分析「转」

 

图 1:直接发送消息的订单事件驱动

这种设计需要保证数据库操作和消息操作的数据一致性,即数据保存和消息发送要全部成功或者全部失败。显然在数据保存前和事务中进行消息发送都是不合适的。我们是在数据更新操作后,数据库事务外发送消息。如果数据保存成功,但消息发送失败,支付系统需要重新通知(上图步骤 1),直至通知成功。这种设计虽然实现了功能和对可用性的基本要求,但存在如下缺点:

  1. 业务系统直接依赖消息中间件 :消息中间件的故障,不仅会影响支付通知的处理也可能影响业务系统上的其它接口。
  2. 业务系统必须实现可靠的重试 :不论是请求发起方还是请求接收方都必须实现可靠重试才能实现最大努力通知的目标。
  3. 重试间隔增大会造成业务延迟 :随着重试次数增加,每次重试的间隔通常也越来越大,这成为 Exponential Backoff(指数级退避)。这种设计能够让请求接收方的故障处理更加从容,避免因密集重试造成请求接收方服务难以恢复。但这样做可能会使请求接收方在恢复服务之后很长时间后才处理完积压的消息,从而造成业务延迟。我们可以采用类似 Hystrix 的自适应设计,在请求接收方服务恢复后回到到正常的请求速率。但这样的设计显然会复杂许多。

为了解决上述问题,简化技术架构,我们采用事件表的设计思想,将订单表作为事件表。通过订阅订单表的 Binlog,生成订单事件,驱动后续业务流程。在系统架构上,业务系统不用直接依赖消息中间件,只需专注数据库操作。而通过引入一个接收 Binlog 的独立的系统,将 MySQL 数据变化转换成业务事件驱动后续流程。具体流程如下:

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

 

图 2:基于 Binlog 的订单事件驱动

暗藏问题

上文提到,虽然基于 Binlog 的订单事件驱动设计存在诸多优点,但后来发现其实暗藏问题。经过实验,我们发现偶尔会有订单履约延迟的现象。

在正常流程中,订单履约服务收到订单支付事件后,会检查订单状态,如果此时订单状态为已支付,则进行履约流程的处理。但对于有履约延迟的订单,订单履约服务收到此订单的支付事件后,查询数据库发现此订单并非支付状态。经过调查,我们排除了数据并发覆盖问题,并且订单状态查询是发生在主库上,也不存在主从同步延迟问题。

那究竟是什么原因导致业务系统收到根据 Binlog 生成的订单支付事件后,再查询主库得到的订单数据却是未支付状态的?

对于此问题的原因我们先放下不谈,先来看看 MySQL 在更新数据时的内部原理。

MySQL 数据更新相关原理

本节将向大家介绍 MySQL 数据更新相关原理,以及在这一过程中最重要的两种日志:Redo Log 和 Binlog。

Redo Log 和 Binlog

先来介绍 Redo Log 和 Binary Log(Binlog):

  • Redo Log :Redo Log 是 InnoDB 存储引擎提供的一种物理日志结构,用来描述对底层数据页操作的具体内容,主要用于实现 crash-safe,并提升磁盘操作效率。
  • Binlog :Binlog 是 MySQL 本身提供的一种逻辑日志,和具体存储引擎无关,描述的是数据库所执行的 SQL 语句或数据变更情况,主要用于数据复制。

InnoDB 引入 Redo Log 的目的在于实现 crash-safe 和提升数据更新效率。如果 InnoDB 每次数据写操作都要直接持久化到磁盘上的数据页中,那样会大量增加磁盘随机 IO 次数。引入 Redo Log 后,在对数据写操作时,会将部分随机 IO 写变为顺序写。因为磁盘的顺序 IO 效率远高于随机 IO,因此引入 Redo Log 机制有助于提升更新数据时的性能(如何实现 crash-safe 将在下一节介绍)。

下面的表格说明了两种日志的作用和它们的不同:

Redo LogBinlog日志类型物理日志,即数据页中的真实二级制数据,恢复速度快逻辑日志,SQL 语句 (statement) 或数据逻辑变化 (row),恢复速度慢存储格式基于 InnoDB 数据页格式进行存储SQL 语句或数据变化内容用途重做数据页数据复制层级InnoDB 存储引擎层MySQL Server 层记录方式循环写追加写

这时问题来了,现在 MySQL 中存在了两种日志结构:Redo Log 和 Binlog。虽然它们的结构和功能有所不同,但却记录着相同的数据。如何保证这两种日志数据的一致性,以及如何实现 crash-safe 呢?这就引出了两阶段提交设计。

两阶段提交

两阶段提交不是 Redo Log 或 InnoDB 中的设计,而是 MySQL 服务器的设计(但通常说到两阶段提交时都和 Redo Log 放在一起)。因为 MySQL 采用插件化的存储引擎设计,事务提交时,服务器本身和存储引擎都需要提交数据。所以从 MySQL 服务器角度看,其本身就面临着分布式事务问题。

为解决此问题,MySQL 引入了两阶段提交。在两阶段提交过程中,Redo Log 会有两次操作:Prepare 和 Commit。而 Binlog 写操作则夹在 Redo Log 的 Prepare 和 Commit 操作之间。我们可以设想一下不同失败场景下两阶段提交的设计是如何保证数据一致的:

  1. Redo Log Prepare 成功,在写 Binlog 前崩溃:在故障恢复后事务就会回滚。这样 Redo Log 和 Binlog 的内容还是一致的。这种情况比较简单,比较复杂的是下一种情况,即在写 Binlog 和 Redo Log Commit 中间崩溃时,MySQL 是如何处理的?
  2. 在写 Binlog 之后,但 Redo Log 还没有 Commit 之前崩溃
  • 如果 Redo Log 有 Commit 标识,说明 Redo Log 其实已经 Commit 成功。这时直接提交事务;
  • 如果 Redo Log 没有 Commit 标识,则使用 XID(事务 ID)查询 Binlog 相应日志,并检查日志的完整。如果 Binlog 是完整的,则提交事务,否则回滚;

如何判断 Binlog 是否完整?简单来说 Statement 格式的 Binlog 最后有 Commit,或 Row 格式的 Binlog 有 XID Event,那 Binlog 就是完整的。

MySQL 数据更新流程

接下来看一下 MySQL 执行器和 InnoDB 存储引擎在执行简单 update 语句 update t set n = n + 1 where id = 2 时的流程(因为此例只执行单条更新语句,所以其自身就是一个事务)。

  1. 执行器先找引擎取 ID=2 这一行。ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。
  2. 执行器拿到引擎给的行数据,把这个值加上 1,比如原来是 N,现在就是 N+1,得到新的一行数据,再调用引擎接口写入这行新数据。
  3. 引擎将这行新数据更新到内存中。然后将对内存数据页的更新内容记录在 Redo Log Buffer 中(这里不详细介绍 Redo Log Buffer。只需知道对 Redo Log 的操作并不会直接写在文件上,而是先记录在内存中,然后在特定时刻才会写入磁盘)。此时完成了数据更新操作。
  4. 接下来要进行事务提交的操作。事务提交时,Redo Log 被标记为 Prepare 状态。通常此时,Redo Log 会从 Buffer 写入磁盘(innodb_flush_log_at_trx_commit,值为 1 时,每次提交事务 Redo Log 都会写入磁盘)。然后 InnoDB 告知执行器执行完成,可以提交事务。
  5. 执行器生成本次操作的 Binlog,并把 Binlog 写入磁盘。
  6. 执行器调用引擎的提交事务接口,引擎把刚刚写入的 Redo Log 改成提交 Commit 状态,更新完成。
MySQL Binlog 技术原理和业务应用案例分析「转」

 

图中描述了 update 语句执行过程中 MySQL 执行器、InnoDB,以及 Binlog、Redo Log 交互过程(图中深绿底色的是 MySQL 执行器负责的阶段,浅绿底色是 InnoDB 负责的阶段)

问题解析

从上面对 MySQL 原理的介绍我们得知,写 Binlog 发生在事务提交阶段,但是 MySQL 因为在 Server 层和存储引擎层都引入了不同的日志结构,从而引入了两阶段提交。Binlog 的写入发生在存储引擎真正提交事务之前,这导致理论上通过 Binlog 同步数据的系统(MySQL 从库、其它数据库或业务系统)有可能早于 MySQL 主库使最新提交的数据生效。

所以上面提到的订单履约服务在收到基于 Binlog 的订单支付事件后却查到相应订单是未支付的,原因很可能是订单履约服务在查询数据时,订单支付数据更新操作在 MySQL 内部尚未彻底完成事务的提交。

我们通过开发验证程序重现了这一现象。验证程序接收到事务提交完成后的完整 Binlog 时会再次在 MySQL 主库上查询对应的记录,结果会有一定概览获得事务提交前的数据。

另外经过了解,也有同行反映遇到过从库早于主库看到数据提交的问题。

问题的解决方法

在了解问题背后的原因之后,我们需要思考如何解决此问题。目前解决此问题有两个方法:重试和直接使用 Binlog 数据。

重试这种做法简单粗暴,既然问题原因是 Binlog 早于事务提交,那等一下再重试查询自然就解决了。但在实践中,需要考虑重试的实现方法、以及是否会因为重试过多甚至无限重试导致服务异常。对于重试的实现,可使用的方法有线程 Sleep 大法和消息重投等方式。线程 Sleep 大法通常是不被推荐的,因为它会导致线程利用率降低,甚至导致服务无法响应。但考虑到本次问题出现概率较低,我们认为线程 Sleep 大法是可以使用的,并且此方式简单易行,可用于问题的快速修复。

第二种重试方式是消息重投,比如 RocketMQ 中 Consumer 返回
ConsumeConcurrentlyStatus.RECONSUME_LATER 即可触发消息重投。但这种重试方法成本较前一种方法高,另外重试间隔也相对较大,对时间敏感的业务影响也较大,因此是否采用此方法需从业务和技术两个角度综合考虑。

除了考虑用何种方式重试,还要考虑 ABA 问题,即状态变化按照 A->B->A 的方式进行。业务系统期待的状态是 B,但实际可能没办法再变成 B 了。因此在用重试解决此问题之前,需要先排除业务系统存在 ABA 问题的可能。对于状态 ABA 问题,可用状态机等方式解决,这里不再展开讨论。

除了重试,另一种方法就是直接使用 Binlog。因为 Binlog (row 格式) 直接反映了数据的变化情况,其中可以记录事务提交涉及到的完整数据,因此可直接用作业务处理。这样还可以降低数据库 QPS。如果是新设计的系统,我认为这样做法比较理想。但对于已有系统,这种方式改动可能较大,是否采用需权衡成本和收益。

本文转载自公众号爱奇艺技术产品团队(ID:iQIYI-TP)。

原文链接

https://mp.weixin.qq.com/s?__biz=MzI0MjczMjM2NA==&mid=2247486912&idx=2&sn=8aa949ed12d68424b2df3a1016373db7&chksm=e97691e3de0118f5cbf6deeffb88a0b8fcc5c1936c31d4660f16257cec2bc31c77f435ecaa8e&scene=27#wechat_redirect



Tags:MySQL Binlog 技术   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
导语MySQL Binlog 用于记录用户对数据库操作的结构化查询语言 (Structured Query Language,SQL) 语句信息。是 MySQL 数据库的二进制日志,可以使用 mysqlbin 命令查看二进制日...【详细内容】
2020-06-29  Tags: MySQL Binlog 技术  点击:(65)  评论:(0)  加入收藏
▌简易百科推荐
作者:雷文霆 爱可生华东交付服务部 DBA 成员,主要负责Mysql故障处理及相关技术支持。爱好看书,电影。座右铭,每一个不曾起舞的日子,都是对生命的辜负。 本文来源:原创投稿 *爱可生...【详细内容】
2021-12-24  爱可生    Tags:MySQL   点击:(7)  评论:(0)  加入收藏
生成间隙(gap)锁、临键(next-key)锁的前提条件 是在 RR 隔离级别下。有关Mysql记录锁、间隙(gap)锁、临键锁(next-key)锁的一些理论知识之前有写过,详细内容可以看这篇文章...【详细内容】
2021-12-14  python数据分析    Tags:MySQL记录锁   点击:(18)  评论:(0)  加入收藏
binlog 基本认识 MySQL的二进制日志可以说是MySQL最重要的日志了,它记录了所有的DDL和DML(除了数据查询语句)语句,以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二...【详细内容】
2021-12-14  linux上的码农    Tags:mysql   点击:(13)  评论:(0)  加入收藏
为查询优化你的查询 大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查...【详细内容】
2021-12-09  元宇宙iwemeta    Tags:mysql   点击:(15)  评论:(0)  加入收藏
测试的目的和原因,公司有很多程序员,每个程序员对数据库和表结构都有自己的理解。而且每个程序员的理解往往是以效率考虑。既然都是为了效率考虑,那么我就来测试一下究竟哪种使...【详细内容】
2021-12-08  吴彬的分享    Tags:Mysql数据库   点击:(14)  评论:(0)  加入收藏
当你们考虑项目并发的时候,我在部署环境,当你们在纠结使用ArrayList还是LinkedArrayList的时候,我还是在部署环境。所以啊,技术不止境,我在部环境。今天这篇文章缕一下在同一台服...【详细内容】
2021-12-08  秃头码哥    Tags:MySQL数据库   点击:(17)  评论:(0)  加入收藏
对于数据分析来说,MySQL使用最多的是查询,比如对数据进行排序、分组、去重、汇总及字符串匹配等,如果查询的数据涉及多个表,还需要要对表进行连接,本文就来说说MySQL中常用的查询...【详细内容】
2021-12-06  笨鸟学数据分析    Tags:MySQL   点击:(21)  评论:(0)  加入收藏
在学习SQL语句之前,首先需要区分几个概念,我们常说的数据库是指数据库软件,例如MySQL、Oracle、SQL Server等,而本文提到的数据库是指数据库软件中的一个个用于存储数据的容器。...【详细内容】
2021-11-24  笨鸟学数据分析    Tags:SQL语句   点击:(23)  评论:(0)  加入收藏
概述以前参加过一个库存系统,由于其业务复杂性,搞了很多个应用来支撑。这样的话一份库存数据就有可能同时有多个应用来修改库存数据。比如说,有定时任务域xx.cron,和SystemA域...【详细内容】
2021-11-05  Java云海    Tags:分布式锁   点击:(31)  评论:(0)  加入收藏
MySQL的进阶查询 一、 按关键字排序 使用ORDERBY语句来实现排序排序可针对一个或多个字段ASC:升序,默认排序方式 【升序是从小到大】DESC:降序 【降序是从大到小】ORDER BY的...【详细内容】
2021-11-05  Java热点    Tags:SQL语句   点击:(28)  评论:(0)  加入收藏
最新更新
栏目热门
栏目头条