12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
ADADADADAD
mysql数据库 时间:2024-12-24 19:11:27
作者:文/会员上传
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
12-09
导语MySQL Binlog用于记录用户对数据库操作的结构化查询语言(Structured Query Language,SQL)语句信息。是MySQL数据库的二进制日志,可以使用mysqlbin命令查看二进制日志的内
以下为本文的正文内容,内容仅供参考!本站为公益性网站,复制本文以及下载DOC文档全部免费。
导语
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 发送消息,通过异步消息驱动后续业务流程,以实现消息驱动的设计。大致的业务流程示意图如下:
图1:直接发送消息的订单事件驱动
图2:基于 Binlog 的订单事件驱动
暗 藏 问 题
上文提到,虽然基于 Binlog 的订单事件驱动设计存在诸多优点,但后来发现其实暗藏问题。经过实验,我们发现偶尔会有订单履约延迟的现象。
在正常流程中,订单履约服务收到订单支付事件后,会检查订单状态,如果此时订单状态为已支付,则进行履约流程的处理。但对于有履约延迟的订单,订单履约服务收到此订单的支付事件后,查询数据库发现此订单并非支付状态。经过调查,我们排除了数据并发覆盖问题,并且订单状态查询是发生在主库上,也不存在主从同步延迟问题。
那究竟是什么原因导致业务系统收到根据 Binlog 生成的订单支付事件后,再查询主库得到的订单数据却是未支付状态的?
对于此问题的原因我们先放下不谈,先来看看 MySQL 在更新数据时的内部原理。
Redo Log
Binlog
日志类型
物理日志,即数据页中的真实二级制数据,恢复速度快
逻辑日志,SQL 语句 (statement) 或数据逻辑变化 (row),恢复速度慢
存储格式
基于 InnoDB 数据页格式进行存储
SQL 语句或数据变化内容
用途
重做数据页
数据复制
层级
InnoDB 存储引擎层
MySQL Server 层
记录方式
循环写
追加写
图中描述了 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。如果是新设计的系统,我认为这样做法比较理想。但对于已有系统,这种方式改动可能较大,是否采用需权衡成本和收益。
招聘信息
爱奇艺会员开发团队诚招 Java 资深工程师/技术专家。会员业务是爱奇艺核心业务之一,我们致力于通过技术手段服务核心业务,研发通用化、高可用的业务系统,同时我们也需要擅长如数据库、服务治理、MQ 等技术的人才。欢迎感兴趣的同学发送简历至:luodi@qiyi.com(邮件标题请注明:会员开发)
11-20
11-19
11-20
11-20
11-20
11-19
11-20
11-20
11-19
11-20
11-19
11-19
11-19
11-19
11-19
11-19