Xupeng's blog

圆外之大,心向往之

Context switch 和 TLB shootdowns

memcache 缓存大对象失效,因此某个涉及4万条记录的查询每次都会把压力直接施加到 MySQL,致使 MySQL 服务器的 load 在几分钟内上升到 60+。

问题的解决当然是找出引发问题的查询,临时屏蔽发起该查询的页面,解决缓存失效的问题后再恢复,在此不多说。

在 MySQL 服务器 load 飙升时,有几个有趣的现象:

  1. 85% 以上的 CPU(16个核) 时间花费在 system/kernel 态
  2. 有 80 个左右 MySQL 查询处于 sending data 状态,每个查询持续 20s 左右
  3. 内网流量 99.xMbps,但始终未突破 100Mbps
  4. Context switch 78k/s
  5. TLB shootdowns 135k/s

查阅了一些资料,基本弄清楚了因果关系,先整理和翻译几段 wiki,解释一下 Context switch 和 TLB shootdowns:

  • Context switch(上下文切换):  简单说来,如果可运行的线程数大于CPU的数量,那么OS最终会强行换出正在执行的线程,从而使其他线程能够使用CPU。这会引起上下文切换,它会保存当前运行线程的执行上下文,并重建新调入线程的执行上下文。
  • TLB(Translation Lookaside Buffer) shootdowns:TLB是虚拟内存地址到物理内存地址的映射表,有软件TLB和硬件TLB, x86架构使用硬件TLB。当虚拟内存地址到物理内存地址的映射发生变化时,TLB会被刷新,就产生了TLB shootdowns中断,对于x86架构来说是硬件中断。

发生上下文切换时,TLB 中的部分条目会失效,不同 TLB 实现会有不同做法,x86 会清掉整个 TLB,而有的实现可以做到选择性地只 flush 部分条目。

那么因果关系应该是这样:

  1. memcache 失效,有很大结果集的查询直接施压到了 MySQL 上
  2. 80 个左右的活动 MySQL 查询长时间处于 sending data 状态,CPU 不停地在各个 MySQL 线程间切换(意味着有大量的上下文切换)
  3. 频繁的上下文切换导致频繁的 TLB 失效,引发大量的 TLB shootdowns 中断
  4. 于是大量的 CPU 时间耗费在处理中断上,CPU 的 run 队列等待了 60 个左右可运行线程,load 飙升到 60+

由 MySQL 所产生的 99.xMbps 网络流量比较奇怪,不确定是否是因为网卡的中断分布不均(事发当时所有的网卡中断全部由第一个 CPU 核处理),因此在 CPU 忙于处理上下文切换、TLB shootdowns 中断以及其他中断时,网络吞吐量也受到了极大影响,但是这个接近 100Mbps 但又不超过 100Mbps 的数值的确蹊跷。

目前还没有想到有什么好的办法能够避免类似的情况再发生,Facebook 的 patch 可以限制并发的查询数,应该可以缓解这类问题带来的压力,不过现在看来还不太成熟,在正式采用 Facebook 的 patch 并确认有效之前,可以依靠监控来及时发现问题,并尽快采取措施。

Comments