程序如何实现“慢启动”效果?

本文记录一个程序优化的case。

背景

这是一个计算任务的调度程序,会并发的RPC调用下游服务,汇总处理结果。

因为下游服务的计算资源非常有限(成本原因),因此调度程序在RPC时控制了请求的并发度,确保不要击垮下游导致超时。

实际上线后,发现每当有1个新的计算任务到来时,调度程序开始工作的瞬时会导致下游服务CPU打满超时,但在此之后的计算压力就在可控范围之内波动了。

分析

虽然程序限制了并发度RPC为30个,但调度启动瞬间(相当于1毫秒)会打出30个并发RPC,导致瞬时过载。

我们可以这样理解:

首先下游是无法承担30个RPC同时CPU计算的(CPU打满了),所以才会出现瞬时的超时。

为什么后续没事了呢?因为请求在网络中要花费一些时间(可以忽略),在下游逻辑中需要一些阻塞I/O的时间是不占用CPU的,这就自然形成了CPU密集时间的错峰,所以就不会打满CPU了。

其实30并发的实际情况是下游CPU使用率也就50%左右,只不过调度启动瞬时因为RPC几乎同时到达并且同时进入CPU密集计算,因此触达了一个CPU峰顶。

下图就是瞬时打满30个RPC并发限制的情况:

优化

因此,调度启动的时候如果能通过“慢启动”的方式逐步放大并发度,让RPC之间自然错峰出行,这样就避开了CPU洪峰。

我心目中的策略是逐步加快放量,一开始放出少量请求,逐步加速放量速度,最后彻底放开。

比如上图中,我期望调度启动后的2秒内总共放行30次RPC,释放的速度需要先慢后快,因此符合二次项函数的曲线:y=ax^2,将x=2000(毫秒)、y=30带入公式,求出系数a。

后续只要带入调度启动后经过的时间x毫秒,就可以得到y为累计允许放量RPC次数,当放量到一定时间之后就停止”慢启动”逻辑,彻底放开速率限制(仍旧保留并发度限制)。

代码示例

调度开始前记录startTime,然后在RPC之前根据y=ax^2计算当前允许的总放量次数y,如果当前放出去的RPC个数requestCounter小于y则允许继续RPC,否则sleep等待时间推移,直到慢启动阶段的时间到达后不再执行if内的逻辑。

如果文章帮助您解决了工作难题,您可以帮我点击屏幕上的任意广告,或者赞助少量费用来支持我的持续创作,谢谢~