Loading...
墨滴

eZy

2021/10/19  阅读:71  主题:默认主题

(坑)MySQL 锁故障排查案例:使用 Flink 同步数据至 Kafka 时触发

(坑)MySQL 锁故障排查案例:使用 Flink 同步数据至 Kafka 时触发

目录


背景:入坑经历

突然收到开发反馈:线上一数据库有段时间表现为只读状态,即只可以读数据,所有表不能进行写入操作。第一时间登录问题实例,进行了一些针对性测试操作,发现实例已恢复正常。通知开发验证后,也表明业务确实已恢复。虽已恢复,但根困还是要排查定位到。第一时间想到的有两种可能:第一种可能:连接到了从库,从库设置了 read_only,但经过排查,此种可能排除。第二种可能:手动执行了 flush tables ... lock 或 lock tables ... 操作,但与相关人员确认后,这种可能性也被排除。进一步对线上进行排查,大致过程如下:

  • 通过以上流程,先排除了人为误操作可能性。

  • 确认锁的类型大概率是全局锁:通知开发查看应用日志,发现问题时段,只要是涉及表的 DML 操作,都会抛出类似以下锁超时错误信息:

  • 排除逻辑备份任务导致的问题:查看备份相关任务,发现备份行为发生在每天凌晨 5 点,备份日志中也表明备份任务持续时间不长。从 processlist 看,也没有疑似备份相关的 session 存活。

  • 进一步查看 error log,slow log 也未发现可疑操作。

  • 重点查看 ps,sys schema 中与 statment 及 event 相关的性能表/视图,起初没有找到任何有价值的线索,即将放弃之时,从锁事件相关的视图中发现有 flinkcdc 名称的用户。

  • 依靠技术敏感度,到网上搜索 flink cdc 关键词 ,查找到以下内容:

    https://cloud.tencent.com/developer/article/1769765
    https://cloud.tencent.com/document/product/849/52698#.E4.BB.8B.E7.BB.8D

    图释:从文档内容可见,一般使用 flink 且源端数据库为 MySQL 时,debezium.snapshot.locking.mode 参数建议配置为 none。

  • 与开发人员确认线上确实有使用 flink 同步 MySQL 数据至 kafka,且 bezium.snapshot.locking.mode 配置为非 none 值,发生问题期间 flink 也有多次重启行为。

  • 至此基本可以定位到原因:由于 flink 重启期间,从 MySQL 初始化数据至 Kafka,期间需要 flush tables 锁定相关表导致的全局锁出现。

  • 通知开发人员修改 Flink debezium.snapshot.locking.mode 参数值为 none(修改前已评估影响),并建议相关人员将此参数配置规范编入 Flink 部署文档中,被采纳。

关于 debezium.snapshot.locking.mode 这个参数,到底是如何影响 Flink 初始化 MySQL 数据同步行为的呢?请接着往下看。

Flink CDC 初始化关键阶段一撇

Flink CDC 以 MySQL 为同步源时,使用的为 Debezium 的专用 Connector,官方说明参考如下:

Debezium MySQL Connector 快照流程


https://debezium.io/documentation/reference/connectors/mysql.html#mysql-snapshots

图释:在初始化 MySQL 期间,Debezium 将首先尝试获取全局锁,如果不成功则尝试获取表级锁。


使用全局锁初始化处理流程

  • 关键点:获取全局读锁。

  • 启动事务,事务隔离级别配置为 RR。

  • 读取 binlog position。

  • 较耗时步骤(总耗时长与表的数量成正比):读取待同步的表 & 数据库定义信息。

  • 关键点:释放全局锁。

  • 持续捕获 DDL 操作。

  • 最耗时步骤:读取 MySQL 表内行数据,同步所有数据至 Kafka。

  • 提交事务。

  • 记录快照完成时的 binlog 偏移量(CDC 快照点位,后期增量同步用)。


使用表级锁初始化处理流程

  • 关键点:获取表级别锁,表级锁可以一次锁多张表:lock tables a read,b read...。

  • 启动事务,事务隔离级别配置为 RR。

  • 多出步骤:读取表及库的 filters(过滤器)。

  • 读取 binlog position。

  • 较耗时步骤:读取待同步的表 & 数据库定义信息。

  • 持续捕获 DDL 操作。

  • 最耗时步骤:读取 MySQL 表内行数据,同步所有数据至 Kafka。

  • 提交事务。

  • 关键点:释放表级锁。

  • 记录快照完成时的 binlog 偏移量(CDC 快照点位,后期增量同步用)。

经过确认,线上环境的 CDC 同步用数据库账号权限与官方手册一致,所以不存在使用【表级锁定】进行快照初始化的可能性:

https://debezium.io/documentation/reference/connectors/mysql.html#mysql-creating-user

权限清单说明:

另外,由于线上有 20 多个数据库,4000 多张表,就算持有全局锁进行初始化,也可能在获取 库 & 表 的定义信息阶段导致锁持有时间过长。

Debezium MySQL Connector 锁模式指定

由 snapshot.locking.mode 参数控制,Flink 中对应 debezium.snapshot.locking.mode 参数:

https://debezium.io/documentation/reference/connectors/mysql.html#mysql-property-snapshot-locking-mode

图释:对应 4 种锁模式,以下从锁的影响范围大小升序列出

  • none:不锁定,适用于快照期间表结构无修改的场景,如果是 MyISAM 引擎的表(不支持事务)还是会锁定(很好理解,和 Percona XtraBackup 一样的行为)。
  • minimal_percona:加备份锁,只适用于 Percona Mysql 分支版。
  • minimal:只在获取表/库结构定义信息期间锁定(默认配置)。
  • extended:快照期间持续锁定。

结论(包含避坑指南)

运维规范:对 Flink 运维人员

  • 部署规范:部署 Flink 时,没有特殊要求的情况下,debezium.snapshot.locking.mode 指定为 none。
  • 初始化同步规范:Flink CDC 初始化操作应与 MySQL 版本发布操作错开,即:避开可能有 DDL 操作的时段。

运维规范:对 MySQL DBA

  • 用户创建规范:用户名尽量能够体现业务特征,如此次故障排查期间,在我即将放弃之时,用户名就提供了关键线索。杜绝使用类似 app 这样的通用名称,而是类似 flink_cdc01,flink_cdc02 这样的名称,MySQL 8.0 也可以给用户添加 comment。

  • 诊断规范(通用临时审计规范):如果没有发现带 flink cdc 字样的用户名,此次故障可能就成为了一个未解之迷。但可确定的是大概率是执行了 lock tables 或 flush tables 导致的,可以临时开启审计进行定位,开启时审计范围仅指定这两类语句即可(不建议直接开启 general log 进行排查,生成的 log 数据量太大),以下是基于 Mcafee 审计插件的操作步骤示例:

    # 下载页面(注意下载的版本要兼容,目前此插件支持 percona、mariadb 及 官方社区版):https://github.com/mcafee/mysql-audit/releases
    # 部署说明:https://github.com/mcafee/mysql-audit/wiki/Installation
    # 配置说明:https://github.com/mcafee/mysql-audit/wiki/Configuration

    mysql> show global variables like '%plugin%dir%';
    +---------------+--------------------------+
    | Variable_name | Value                    |
    +---------------+--------------------------+
    | plugin_dir    | /usr/lib64/mysql/plugin/ |
    +---------------+--------------------------+
    1 row in set (0.01 sec)

    # unzip audit-plugin-mysql-5.7-1.1.9-974-linux-x86_64.zip
    # cp audit-plugin-mysql-5.7-1.1.9-974/lib/libaudit_plugin.so /usr/lib64/mysql/plugin/.


    # chown mysql:mysql /usr/lib64/mysql/plugin/libaudit_plugin.so

    mysql> INSTALL PLUGIN AUDIT SONAME 'libaudit_plugin.so';
    Query OK, 0 rows affected (1.86 sec)


    mysql> set global audit_record_cmds='lock_tables,flush';

    mysql> set global audit_json_file=on;
    # cat /var/lib/mysql/mysql-audit.json
    {"msg-type":"activity","date":"1632901274269","thread-id":"28","query-id":"348","user":"root","priv_user":"root","ip":"","host":"localhost","_os":"Linux","_client_name":"libmysql","_pid":"1323","_client_version":"5.7.34","_platform":"x86_64","program_name":"mysql","pid":"1323","os_user":"root","appname":"mysql","status":"0","cmd":"lock_tables","objects":[{"db":"testdb","name":"b","obj_type":"TABLE"}],"query":"lock table b read"}
    {"msg-type":"activity","date":"1632901278995","thread-id":"28","query-id":"349","user":"root","priv_user":"root","ip":"","host":"localhost","_os":"Linux","_client_name":"libmysql","_pid":"1323","_client_version":"5.7.34","_platform":"x86_64","program_name":"mysql","pid":"1323","os_user":"root","appname":"mysql","status":"1192","cmd":"flush","query":"flush tables with read lock"}

    定位到问题后关闭之:

    mysql> set global audit_json_file=off;

碎碎念(可跳过)

给软件部署人员的几点建议

软件部署前,如果你对这个软件不了解,尽量去网上多搜索一些参考资料,优先看一下官方文档中的 quick start 部分,不建议上来就翻 reference,毕竟在所谓“敏捷开发”的时代中,大家都没有时间去细翻那些个大部头。quick start 阅读完过后再多翻看一些国内相对比较权威站点的相关文章,或许就有一些最佳实践(避坑指南)供你参考。待交付过后,再利用空闲时间对相关的知识点进行补充。例如像我一样,以 debezium.snapshot.locking.mode 这个参数为切入点,可以对 Flink 及其低层技术栈 Debezium 进行进一步的了解。

至此,正文结束,负责软件部署的同学们加油!!!

参考资料

链接性参考源都就近放在相关内容附近,以代码块的形式。

给自己挖坑(伏笔/预告)

  • MySQL 审计方案建议(未填)。

作者:eZy_1990(ixdba/linora)

来源:公众号 - 悟空的数橘窟私房菜(ID:wkDB007)

关于作者:DBA 一枚,09 年开始接触数据库,早年间从事 Oracle DBA 一职,目前专注于开源数据库领域,混过很多家公司,也玩过很多种数据库。

其他说明:不保证全文没有任何错漏之处,如有不妥不吝赐教。

eZy

2021/10/19  阅读:71  主题:默认主题

作者介绍

eZy