闰秒导致MySQL CPU飙高
偶然发现一台比较空闲的MySQL服务器的CPU利用率比较高,在100%以上, 但是并没有什么连接,非常奇怪。
找耗CPU的线程
由于没有什么连接,所以processlist里面没什么内容。那么就只能看线程了。
top -p `pidof mysqld`
然后输入H,这样可以看到每个线程占用的CPU.
- 17476 0.0
- 17478 0.0
- 17479 0.0
- 17480 0.0
- 17481 0.0
- 17482 0.0
- 17483 0.0
- 17484 0.0
- 17485 0.0
- 17486 0.0
- 17487 0.0
- 17489 61.6
- 17490 74.0
- 17491 0.0
- 17492 0.0
- 17495 0.0
- 17519 0.0
- 25731 0.0
- 25732 0.0
- 25733 0.0
- 25739 0.0
- 25741 0.0
- 25742 0.0
- 25750 0.0
- 25751 0.0
- 25752 0.0
- 25861 0.0
此时,看到有两个线程占用CPU比较高,这时候需要pstack一下mysqld进程, 打印当前进程的堆栈信息,发现了两个占用CPU的线程:
Thread 17 (Thread 0x7f4c566b7700 (LWP 17489)):
#0 0x00000000007e7ed4 in srv_lock_timeout_thread (arg=<optimized out>) at /pb2/build/sb_0-12740893-1405751465.66/mysql-5.5.39/storage/innobase/srv/srv0srv.c:2347
#1 0x00007f4cb63bb806 in start_thread () from /lib64/libpthread.so.0
#2 0x00007f4cb565359d in clone () from /lib64/libc.so.6
#3 0x0000000000000000 in ?? ()
Thread 16 (Thread 0x7f4c564b6700 (LWP 17490)):
#0 sync_array_print_long_waits (waiter=0x7f4c564b5e98, sema=0x7f4c564b5e90) at /pb2/build/sb_0-12740893-1405751465.66/mysql-5.5.39/storage/innobase/sync/sync0arr.c:955
#1 0x00000000007e7b51 in srv_error_monitor_thread (arg=<optimized out>) at /pb2/build/sb_0-12740893-1405751465.66/mysql-5.5.39/storage/innobase/srv/srv0srv.c:2490
#2 0x00007f4cb63bb806 in start_thread () from /lib64/libpthread.so.0
#3 0x00007f4cb565359d in clone () from /lib64/libc.so.6
#4 0x0000000000000000 in ?? ()
找bug
这两个消耗CPU的线程都是后台线程,按理说不应该这么消耗CPU的。找了一下, 在mysql bug里面找到了几乎一样的bug报告:
https://bugs.mysql.com/bug.php?id=65778
官方说这是linux内核bug,由于闰秒导致的,同时也找到了相应的解决方法:
https://blog.mozilla.org/it/2012/06/30/mysql-and-the-leap-second-high-cpu-and-the-fix/
- 重启机器
- date -s "
date
"
闰秒为什么导致CPU飙高?
网上大多给出来的是解决方法,基本上没有人解释为什么会导致CPU飙高。
首先在linux内核上确实是一个bug:Potential fix for leapsecond caused futex related load spikes
http://marc.info/?l=linux-kernel&m=134113577921904
https://lkml.org/lkml/2012/7/1/203
由于对linux内核不了解,这个bug的具体原因我也解释不好,大概的原因是闰秒发生时,CLOCK_REALTIME 相关的变量没有设置正确,导致一些对于CLOCK_REALTIME的调用会出现偏差。 这个bug可以导致的问题就是如果存在基于CLOCK_REALTIME的计时器(timer), 那么计时器就会提前结束。对于次秒级别(小于1s)的计时器,会立刻返回。 这个CLOCK_REALTIME是本地时间,你说的通过NTP同步时间,NTP是会修改这个CLOCK_REALTIME的, 最终系统调用还是调用linux里面的CLOCK_REALTIME。
下面再结合MySQL的代码说明,具体的两个消耗CPU的线程分别是:
srv_error_monitor_thread和srv_lock_timeout_thread。
这两个函数都是后台死循环函数,每隔1s做一次检查。 这就用到了timer,前面提到了,如果timer是次秒级别的,会立刻返回, 就相当于timer的阻塞功能没起到作用。
上面两个函数都调用了如下函数:
os_event_wait_time_low(xxx_event, 1000000, sigcount);
这个函数具体到系统函数调用就是:pthread_cond_timedwait (基于CLOCK_REALTIME) 即等待event 1000000微秒(即1s)返回。刚好针对这个内核bug,对于次秒级的timer,会直接返回。 也就是说,原本每隔1s执行一次的while循环,现在变成了无须等待的直接执行了。