导读
x86、arm指令都很多,无论是利用程序员还是数据库内核研发大多时候都不须要对这些指令深刻了解,然而 Pause 指令和数据库操作太严密了,本文通过一次十分乏味的性能优化来引入对 Pause 指令的了解,冀望能够事倍功半地搞清楚 CPU指令集是如何影响你的程序的。
文章分成两大部分,第一局部是 MySQL 集群的一次全表扫描性能优化过程; 第二局部是问题解决后的原理剖析以及Pause指令的前因后果和优缺点以及利用场景剖析。
业务构造
为了解不便做了局部简化:
client -> Tomcat -> LVS -> MySQL(32 个 MySQLD实例集群,每个实例8Core)
场景形容
通过 client 压 Tomcat 和 MySQL 集群(对数据做分库分表),MySQL 集群是32个实例,每个业务 SQL 都须要通过 Tomcat 拆分成 256 个 SQL 发送给 32 个MySQL(每个MySQL上有8个分库),这 256 条下发给 MySQL 的 SQL 不是齐全串行,但也不是齐全并行,有肯定的并行性。
业务 SQL 如下是一个简略的select sum求和,这个 SQL在每个MySQL上都很快(有索引)
SELECT SUM(emp_arr_amt) FROM table_c WHERE INSUTYPE='310' AND Revs_Flag='Z' AND accrym='201910' AND emp_no='1050457';
监控指标阐明
- 后述或者截图中的逻辑RT/QPS是指 client 上看到的Tomcat的 RT 和 QPS;
- RT :response time 申请响应工夫,判断性能瓶颈的惟一指标;
- 物理RT/QPS是指Tomcat看到的MySQL RT 和QPS(这里的 RT 是指达到Tomcat节点网卡的 RT ,所以还蕴含了网络耗费)
问题形容:
通过client压一个Tomcat节点+32个MySQL,QPS大略是430,Tomcat节点CPU跑满,MySQL RT 是0.5ms,减少一个Tomcat节点,QPS大略是700,Tomcat CPU靠近跑满,MySQL RT 是0.6ms,到这里性能根本随着扩容线性减少,是合乎预期的。
持续减少Tomcat节点来横向扩容性能,通过client压三个Tomcat节点+32个MySQL,QPS还是700,Tomcat节点CPU跑不满,MySQL RT 是0.8ms,这就重大不合乎预期了。
性能压测准则:
加并发QPS不再回升阐明到了某个瓶颈,哪个环节RT减少最多瓶颈就在哪里
到这里所有都还是合乎咱们的教训的,看起来就是 MySQL 有瓶颈(RT 减少显著)。
排查 MySQL
现场DBA通过监控看到MySQL CPU不到20%,没有慢查问,并且尝试用client越过所有中间环节间接压其中一个MySQL,能够将 MySQL CPU 跑满,这时的QPS大略是38000(对应下面的场景client QPS为700的时候,单个MySQL上的QPS才跑到6000) 所以排除了MySQL的嫌疑(这个推理不够谨严为前面排查埋下了大坑)。
那么接下来的嫌疑在网络、LVS 等中间环节上。
LVS和网络的嫌疑
首先通过大查问排除了带宽的问题,因为这里都是小包,pps到了72万,很天然想到了网关、LVS的限流之类的pps监控,这台物理机有4个MySQL实例上,pps 9万左右,9*32/4=72万
最终所有网络因素都被排除,外围证据是:做压测的时候重复从 Tomcat 上 ping 前面的MySQL,RT 跟没有压力的时候一样,也阐明了网络没有问题(请思考这个 ping 的作用)。
问题的确认
尝试在Tomcat上关上日志,并将慢 SQL 阈值设置为100ms,这个时候的确能从日志中看到大量MySQL上的慢查问,因为这个SQL须要在Tomcat上做拆分成256个SQL,同时下发,一旦有一个SQL返回慢,整个申请就因为这个短板被连累了。均匀 RT 0.8ms,然而常常有超过100ms的话对整体影响还是很大的。
将Tomcat记录下来的慢查问(Tomcat减少了一个惟一id下发给MySQL)到MySQL日志中查找,果然发现MySQL上的确慢了,所以到这里根本确认是MySQL的问题,终于不必再纠结是否是网络问题了。
同时在Tomcat进行抓包,对网卡上的 RT 进行统计分析:
上是Tomcat上抓到的每个sql的物理RT 平均值,下面是QPS 430的时候, RT 0.6ms,上面是3个server,QPS为700,然而 RT 回升到了0.9ms,根本跟Tomcat监控记录到的物理RT统一。如果MySQL上也有相似抓包计算 RT 工夫的话能够疾速排除网络问题。
网络抓包失去的 RT 数据更容易被所有人承受。尝试过在MySQL上抓包,然而因为LVS模块的起因,进出端口、ip都被批改过,所以没法剖析一个流的响应工夫。
重心再次转向MySQL
这个时候因为问题点根本确认,再去查看MySQL是否有问题的重心都不一样了,不再只是看看CPU和慢查问,这个问题显著更简单一些。
教训:CPU只是影响性能的一个因素,RT 才是后果,要追着 RT 跑,而不是只看 CPU。
通过监控发现MySQL CPU尽管始终不高,然而常常看到running thread飙到100多,很快又降下去了,看起来像是突发性的并发查问申请太多导致了排队期待,每个MySQL实例是8Core的CPU,尝试将MySQL实例扩容到16Core(只是为了验证这个问题),QPS的确能够回升到1000(没有达到现实的1400)。
这是Tomcat上监控到的MySQL状态:
同时在MySQL机器上通过vmstat也能够看到这种飙升:
以上剖析能够清晰看到尽管 MySQL 整体压力不大,然而仿佛会偶然来一波卡顿、running 工作飙升。
像这种短暂突发性的并发流量仿佛监控都很难看到(根本都被均匀掉了),只有一些实时性监控偶然会采集到这种短暂突发性飙升,这也导致了一开始漠视了MySQL。
所以接下来的外围问题就是MySQL为什么会有这种飙升、这种飙升的影响到底是什么?
perf top
间接用 perf 看下 MySQLD 过程,发现 ut_delay 高得不合乎逻辑:
开展看一下,根本是在优化器中做索引命中行数的抉择:
跟间接在 MySQL 命令行中通过 show processlist看到的基本一致:
这是 MySQL 的优化器在对索引进行统计,统计的时候要加锁,thread running 抖动的时候通过 show processlist 看到很多 thread处于 statistics 状态。也就是高并发下加锁影响了 CPU 压不下来同时 RT 激烈减少。
这里ut_delay 耗费了 28% 的 CPU 必定太不失常了,于是将 innodb_spin_wait_delay 从 30 改成 6 后性能立刻下来了,持续减少 Tomcat 节点,QPS也能够线性减少。
耗CPU最高的调用函数栈是…mutex_spin_wait->ut_delay,属于锁期待的逻辑。InnoDB在这里用的是自旋锁,锁期待是通过调用 ut_delay 让 CPU做空循环在等锁的时候不开释CPU从而防止上下文切换,会耗费比拟高的CPU。
最终的性能
调整参数 innodb_spin_wait_delay=6 后在4个Tomcat节点下,并发40时,QPS跑到了1700,物理RT:0.7,逻辑RT:19.6,cpu:90%,这个时候只须要持续扩容 Tomcat 节点的数量就能够减少QPS
再跟调整前比拟一下,innodb_spin_wait_delay=30,并发40时,QPS 500+,物理RT:2.6ms 逻辑RT:72.1ms cpu:37%
再看看调整前压测的时候的vmstat和tsar --cpu,能够看到process running抖动显著
比照批改delay后的process running就很稳固了,即便QPS大了3倍
预先思考和剖析
到这里问题失去了完满解决,然而不禁要问为什么?ut_delay 是怎么工作的? 和 innodb_spin_wait_delay 以及自旋锁的关系?
原理解析既
然调整 innodb_spin_wait_delay 就能解决这个问题,那就要先剖析一下 innodb_spin_wait_delay 的作用
对于 innodb_spin_wait_delay
innodb通过大量的自旋锁(比方 InnoDB mutexes and rw-locks)来用高CPU耗费防止上下文切换,这是自旋锁的正确应用形式,在多核场景下,它们一起自旋抢同一个锁,容易造成cache ping-pong,进而多个CPU核之间会相互使对方缓存局部有效。所以这里innodb通过减少 innodb_spin_wait_delay 和 Pause 配合来缓解cache ping-pong,也就是原本通过CPU 高速自旋抢锁,换成了抢锁失败后 delay一下(Pause)然而不开释CPU,delay 工夫到后持续抢锁,也就是把间断的自旋抢锁转换成了更稠密的点状的抢锁(距离的 delay是个随机数),这样岂但防止了上下文切换也大大减少了cache ping-pong。
自旋锁如何缩小了cache ping-pong
多线程竞争锁的时候,加锁失败的线程会“忙期待”,直到它拿到锁。什么叫“忙期待”呢?它并不意味着始终执行 CAS 函数,而是会与 CPU 紧密配合 ,它通过 CPU 提供的 PAUSE 指令,缩小循环期待时的cache ping-pong和耗电量;对于单核 CPU,忙期待并没有意义,此时它会被动把线程休眠。
X86 PAUSE 指令
X86设计了Pause指令,也就是调用 Pause 指令的代码会抢着 CPU 不开释,然而CPU 会打个盹,比方 10个时钟周期,绝对一次上下文切换是大几千个时钟周期。
这样利用一旦自旋抢锁失败能够先 Pause 一下,只是这个Pause 工夫对于 MySQL 来说还不够久,所以须要减少参数 innodb_spin_wait_delay 来将休息时间放大一些。
在咱们的这个场景下对每个 SQL的 RT 抖动十分敏感(放大256倍),所以过高的 delay 会导致局部SQL RT 变高。函数 ut_delay(ut_rnd_interval(0, srv_spin_wait_delay)) 用来执行这个delay:
/***************************MySQL代码****************************//**Runs an idle loop on CPU. The argument gives the desired delayin microseconds on 100 MHz Pentium + Visual C++.@return dummy value */UNIV_INTERNulintut_delay(ulint delay) //delay 是[0,innodb_spin_wait_delay)之间的一个随机数{ ulint i, j;
UT_LOW_PRIORITY_CPU();
j = 0; for (i = 0; i < delay * 50; i++) { //delay 放大50倍 j += i; UT_RELAX_CPU(); //调用 CPU Pause } UT_RESUME_PRIORITY_CPU(); return(j);}
innodb_spin_wait_delay的默认值为6. spin 期待提早是一个动静全局参数,您能够在MySQL选项文件(my.cnf或my.ini)中指定该参数,或者在运行时应用SET GLOBAL 来批改。在咱们的MySQL配置中默认改成了30,导致了这个问题。
CPU 为什么要有Pause
首先能够看到 Pause指令的作用:
- 防止上下文切换,应用层想要劳动可能会用yield、sleep,这两操作对于CPU来说太重了(随同上下文切换)
- 能给超线程腾出计算能力(HT共享核,然而有独自的寄存器等存储单元,CPU Pause的时候,对应的HT能够占用计算资源),比方同一个core上先跑多个Pause,同时再跑 nop 指令,这时 nop指令的 IPC根本不受Pause的影响
- 节能(CPU能够劳动、然而不让进去),CPU Pause 的时候你从 top 能看到 CPU 100%,然而不耗能。
所以有了 Pause 指令后可能进步超线程的利用率,节能,缩小上下文切换进步自旋锁的效率。
The PAUSE instruction is first introduced for Intel Pentium 4 processor to improve the performance of “spin-wait loop”. The PAUSE instruction is typically used with software threads executing on two logical processors located in the same processor core, waiting for a lock to be released. Such short wait loops tend to last between tens and a few hundreds of cycles. When the wait loop is expected to last for thousands of cycles or more, it is preferable to yield to the operating system by calling one of the OS synchronization API functions, such as WaitForSingleObject on Windows OS.An Intel® processor suffers a severe performance penalty when exiting the loop because it detects a possible memory order violation. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation in most situations. The PAUSE instruction can improve the performance of the processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops”. With Pause instruction, processors are able to avoid the memory order violation and pipeline flush, and reduce power consumption through pipeline stall.
从intel sdm手册以及理论测试验证来看,Pause 指令在执行过程中,根本不占用流水线执行资源。
Skylake 架构的8163 和 Broadwell架构 E5-2682 CPU型号的不同
为什么用得好好的 innodb_spin_wait_delay 参数这次就不行了呢?
这是因为以前业务始终应用的是 E5-2682 CPU,这次用的是新一代架构的 Skylake 8163,那这两款CPU在这里的外围差异是?
在Intel 64-ia-32-architectures-optimization-manual手册中提到:
The latency of the PAUSE instruction in prior generation microarchitectures is about 10 cycles, whereas in Skylake microarchitecture it has been extended to as many as 140 cycles.The PAUSE instruction can improves the performance of processors supporting Intel Hyper-Threading Technology when executing “spin-wait loops” and other routines where one thread is accessing a shared lock or semaphore in a tight polling loop. When executing a spin-wait loop, the processor can suffer a severe performance penalty when exiting the loop because it detects a possible memory order violation and flushes the core processor’s pipeline. The PAUSE instruction provides a hint to the processor that the code sequence is a spin-wait loop. The processor uses this hint to avoid the memory order violation and prevent the pipeline flush. In addition, the PAUSE instruction de-pipelines the spin-wait loop to prevent it from consuming execution resources excessively and consume power needlessly. (See Section 8.10.6.1, “Use the PAUSE Instruction in Spin-Wait Loops,” for more information about using the PAUSE instruction with IA-32 processors supporting Intel Hyper-Threading Technology.)
也就是Skylake架构的CPU的PAUSE指令从之前的10 cycles 改成了 140 cycles。这可是14倍的变动呀。
MySQL 应用 innodb_spin_wait_delay 管制 spin lock等待时间,等待时间工夫从050个Pause到innodb_spin_wait_delay50个Pause。以前 innodb_spin_wait_delay 默认配置30,对于E5-2682 CPU,期待的最长工夫为:30 50 10=15000 cycles,对于2.5GHz的CPU,等待时间为6us。对应计算 Skylake CPU的等待时间:30 50 140=210000 cycles,CPU主频也是2.5GHz,等待时间84us。
E5-2682 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:
Skylake 8163 CPU型号在不同的delay参数和不同并发压力下的写入性能数据:
因为8163的cycles从10改到了140,所以能够看到delay参数对性能的影响更加陡峻。
总结剖析
Intel CPU 架构不同使得 Pause 指令的CPU Cycles不同导致了 MySQL innodb_spin_wait_delay 在 spin lock 失败的时候(此时须要 Pause innodb_spin_wait_delayN)delay更久,使得调用方看到了MySQL更大的 RT ,进而导致 Tomcat Server上业务并发跑不起来,所以最终压力上不去。
在长链路的排查中,细化定位是哪个节点出了问题是最难的,要盯住 RT 而不是 CPU。
欲速则不达,做压测的时候还是要老老实实地从一个并发开始察看QPS、 RT ,而后始终减少压力到压不下来了,再看QPS、 RT 变动,而后确认瓶颈点。
作者:蛰剑
原文链接
本文为阿里云原创内容,未经容许不得转载。