原创做者:胡呈清 黄炎php
前 言mysql
本文是由爱可生运维团队出品的「MySQL专栏」系列文章,内容来自于运维团队一线实战经验,涵盖MySQL各类特性的实践,优化案例,数据库架构,HA,监控等,有扫雷功效。sql
爱可生开源社区持续运营维护的小目标:数据库
每周至少推送一篇高质量技术文章架构
每个月研发团队发布开源组件新版运维
每一年1024开源一款企业级组件源码分析
2019年至少25场社区活动优化
欢迎你们持续关注~ui
MySQL版本:5.7.16,5.7.17,5.7.21this
存在多个半同步从库时,若是参数 rpl_semi_sync_master_wait_for_slave_count=1,启动第1个半同步从库时能够正常启动,启动第2个半同步从库后有很大几率 slave_io_thread 停滞,(复制状态正常,Slave_IO_Running: Yes,Slave_SQL_Running: Yes,可是彻底不一样步主库 binlog )
复现步骤
1. 主库配置参数以下:
rpl_semi_sync_master_wait_for_slave_count = 1 rpl_semi_sync_master_wait_no_slave = OFF rpl_semi_sync_master_enabled = ON rpl_semi_sync_master_wait_point = AFTER_SYNC
2. 启动从库A的半同步复制 start slave,查看从库A复制正常
3. 启动从库B的半同步复制 start slave,查看从库B,复制线程正常,可是不一样步主库 binlog
分析过程
首先弄清楚这个问题 ,须要先结合MySQL其余的一些状态信息,尤为是主库的 dump 线程状态来进行分析:
1. 从库A启动复制后,主库的半同步状态已启动:
show global status like '%semi%'; +--------------------------------------------+-----------+ | Variable_name | Value | +--------------------------------------------+-----------+ | Rpl_semi_sync_master_clients | 1 .... | Rpl_semi_sync_master_status | ON ...
再看主库的 dump 线程,也已经启动:
select * from performance_schema.threads where PROCESSLIST_COMMAND='Binlog Dump GTID'\G *************************** 1. row *************************** THREAD_ID: 21872 NAME: thread/sql/one_connection TYPE: FOREGROUND PROCESSLIST_ID: 21824 PROCESSLIST_USER: universe_op PROCESSLIST_HOST: 172.16.21.5 PROCESSLIST_DB: NULL PROCESSLIST_COMMAND: Binlog Dump GTID PROCESSLIST_TIME: 300 PROCESSLIST_STATE: Master has sent all binlog to slave; waiting for more updates PROCESSLIST_INFO: NULL PARENT_THREAD_ID: NULL ROLE: NULL INSTRUMENTED: YES HISTORY: YES CONNECTION_TYPE: TCP/IP THREAD_OS_ID: 24093
再看主库的 error log,也显示 dump 线程(21824)启动成功,其启动的半同步复制:
2018-05-25T11:21:58.385227+08:00 21824 [Note] Start binlog_dump to master_thread_id(21824) slave_server(1045850818), pos(, 4) 2018-05-25T11:21:58.385267+08:00 21824 [Note] Start semi-sync binlog_dump to slave (server_id: 1045850818), pos(, 4) 2018-05-25T11:21:59.045568+08:00 0 [Note] Semi-sync replication switched ON at (mysql-bin.000005, 81892061)
2. 从库B启动复制后,主库的半同步状态,仍是只有1个半同步从库Rpl_semi_sync_master_clients=1:
show global status like '%semi%'; +--------------------------------------------+-----------+ | Variable_name | Value | +--------------------------------------------+-----------+ | Rpl_semi_sync_master_clients | 1 ... | Rpl_semi_sync_master_status | ON ...
再看主库的 dump 线程,这时有3个 dump 线程,可是新起的那两个一直为 starting 状态:
再看主库的 error log,21847 这个新的 dump 线程一直没起来,直到1分钟以后从库 retry ( Connect_Retry 和 Master_Retry_Count 相关),主库上又启动了1个 dump 线程 21850,仍是起不来,而且 21847 这个僵尸线程还停不掉:
2018-05-25T11:31:59.586214+08:00 21847 [Note] Start binlog_dump to master_thread_id(21847) slave_server(873074711), pos(, 4) 2018-05-25T11:32:59.642278+08:00 21850 [Note] While initializing dump thread for slave with UUID <f4958715-5ef3-11e8-9271-0242ac101506>, found a zombie dump thread with the same UUID. Master is killing the zombie dump thread(21847). 2018-05-25T11:32:59.642452+08:00 21850 [Note] Start binlog_dump to master_thread_id(21850) slave_server(873074711), pos(, 4)
**3. 到这里咱们能够知道,从库B slave_io_thread 停滞的根本缘由是由于主库上对应的 dump 线程启动不了。**如何进一步分析线程调用状况?推荐使用 gstack 或者 pstack(实为gstack软链)来查看线程调用栈,其用法很简单:gstack <process-id>
4. 看主库的 gstack,能够看到 24102 线程(旧的复制 dump 线程)堆栈:
能够看到 24966 线程(新的复制 dump 线程)堆栈:
两线程都在等待 Ack_Receiver 的锁,而线程 21875 在持有锁,等待select:
Thread 15 (Thread 0x7f0bce7fc700 (LWP 21875)): #0 0x00007f0c028c9bd3 in select () from /lib64/libc.so.6 #1 0x00007f0be7589070 in Ack_receiver::run (this=0x7f0be778dae0 <ack_receiver>) at /export/home/pb2/build/sb_0-19016729-1464157976.67/mysql-5.7.13/plugin/semisync/semisync_master_ack_receiver.cc:261 #2 0x00007f0be75893f9 in ack_receive_handler (arg=0x7f0be778dae0 <ack_receiver>) at /export/home/pb2/build/sb_0-19016729-1464157976.67/mysql-5.7.13/plugin/semisync/semisync_master_ack_receiver.cc:34 #3 0x00000000011cf5f4 in pfs_spawn_thread (arg=0x2d68f00) at /export/home/pb2/build/sb_0-19016729-1464157976.67/mysql-5.7.13/storage/perfschema/pfs.cc:2188 #4 0x00007f0c03c08dc5 in start_thread () from /lib64/libpthread.so.0 #5 0x00007f0c028d276d in clone () from /lib64/libc.so.6
理论上 select 不该hang, Ack_receiver 中的逻辑也不会死循环,请教公司大神黄炎进行一波源码分析。
5. semisync_master_ack_receiver.cc 的如下代码造成了对互斥锁的抢占, 饿死了其余竞争者:
void Ack_receiver::run() { ... while(1) { mysql_mutex_lock(&m_mutex); ... select(...); ... mysql_mutex_unlock(&m_mutex); } ... }
在 mysql_mutex_unlock 调用后,应适当增长其余线程的调度机会。
试验: 在 mysql_mutex_unlock 调用后增长 sched_yield();,可验证问题现象消失。
从库 slave_io_thread 停滞的根本缘由是主库对应的 dump thread 启动不了;
rpl_semi_sync_master_wait_for_slave_count=1 时,启动第一个半同步后,主库 ack_receiver 线程会不断的循环判断收到的 ack 数量是否 >= rpl_semi_sync_master_wait_for_slave_count,此时判断为 true,ack_receiver基本不会空闲一直持有锁。此时启动第2个半同步,对应主库要启动第2个 dump thread,启动 dump thread 要等待 ack_receiver 锁释放,一直等不到,因此第2个 dump thread 启动不了。
相信各位DBA同窗看了后会很震惊,“什么?竟然还有这样的bug...”,**这里要说明一点,****这个bug 触发是有概率的,可是概率又很大。**这个问题已经由我司大神提交了 bug 和 patch:https://bugs.mysql.com/bug.php?id=89370,加上本人提交SR后时不时的催一催,官方终于确认修复在 5.7.23(官方最终另有修复方法,没采纳这个 patch)。
最后或许会有疑问“既然是几率,有没有办法下降几率呢?”,尤为是不具有及时升级版本条件的同窗,欢迎社区交流群(669663113)讨论~