owenzhang的博客

滚雪球

字数统计: 1.6k阅读时长: 5 min
2011/10/18
loading

案例
基本情况
如下图,进程A是一个单进程系统,通过udp套接字接收前端请求进行处理。在处理过程中,需要访问后端系统B,是同步的方式访问后端系统B,根据后端系统B的SLA,超时时间设置是100ms。前端用户请求的超时时间是1s。

进程A的时序是:
Step1: 从socket接收缓冲区接收用户请求
Step2: 进行本地逻辑处理
Step3: 发送请求到后端系统B
Step4: 等待后端系统B返回
Step5: 接收后端系统B的应答
Step6: 应答前端用户,回到step1处理下一个请求

正常情况下的负载
正常情况下
1、前端请求报文大小约100Bytes。前端请求的峰值每分钟1800次,即峰值每秒30次。
2、后端系统B并行能力较高,每秒可以处理10000次以上,绝大多数请求处理时延在20ms内。
3、进程A在处理请求的时候,主要时延是在等待后端系统B,其他本地运算耗时非常少,小于1ms
这个时候,我们可以看出,系统工作良好,因为处理时延在20ms内,每秒进程A每秒中可以处理50个请求,足以将用户每秒峰值30个请求及时处理完。


导火索
某天,后端系统B进行了新特性发布,由于内部逻辑变复杂,导致每个请求处理时延从20ms延长至50ms(每秒最多可以处理20个请求),根据sla的100ms超时时间,这个时延仍然在正常范围内。当用户请求达到峰值时间点时,灾难出现了,用户每次操作都是“服务器超时无响应”,整个服务不可用。


过载分析
当后端系统B处理时延延长至50ms的时候,进程A每秒只能处理20个请求(1s / 50ms = 20 )。小于正常情况下的用户请求峰值30次/s。这个时候操作失败的用户往往会重试,我们观察到前端用户请求增加了6倍以上,达到200次/s,是进程A最大处理能力(20次/s)的10倍!
这个时候为什么所有用户发现操作都是失败的呢? 为什么不是1/10的用户发现操作能成功呢? 因为请求量和处理能力之间巨大的差异使得5.6s内就迅速填满了socket接收缓冲区(平均能缓存1000个请求,1000/(200-20)=5.6s),并且该缓冲区将一直保持满的状态。这意味着,一个请求被追加到缓冲区里后,要等待50s(缓存1000个请求,每秒处理20个,需要50s)后才能被进程A取出来处理,这个时候用户早就看到操作超时了。换句话说,进程A每次处理的请求,都已经是50s以前产生的,进程A一直在做无用功。雪球产生了。

启示

  • 每个系统,自己的最大处理能力是多少要做到清清楚楚。例如案例一中的前端进程A,他的最大处理能力不是50次/s,也不是20次/s,而是10次/s。因为它是单进程同步的访问后端B,且访问后端B的超时时间是100ms,所以他的处理能力就是1S/100ms=10次/s。而平时处理能力表现为50次/s,只是运气好。

  • 当过载发生时,该拒绝的请求(1、超出整个系统处理能力范围的;2、已经超时的无效请求)越早拒绝越好。

  • 当雪球发生了,直接清空雪球队列(例如重启进程可以清空socket 缓冲区)可能是快速恢复的有效方法。

拒绝请求越早越好的实现一般有几种:
(1)频率限制法
应该在系统的入口处加上频率限制,超过频率限制的请求直接拒绝。频率限制的一般做法是:设置一个sum_req记录单位时间内的请求数量,来一个请求sum_req++,比较sum_req和单位时间的最大处理数量,小于继续处理,大于继续处理,超过单位时间时,sum_req清零。那么这个“单位时间”设置为多少是合理的呢?对上面的案例,前端的超时是1s,我觉得A上的频率控制的单位时间至少应该是1s(频率检查的单位时间设置为前端每个请求的超时时间——在socket缓冲区能放下的前提下)(更小的话更精确),如果A的频率检查的单位时间超过前端超时的时间,前端的请求不能得到及时的服务也不能立即发现,仍然会出现滚雪球的现象。

(2)打时间戳法
在该系统每个机器上新增一个进程:interface进程。Interface进程能够快速的从socket缓冲区中取得请求,打上当前时间戳,压入channel。业务处理进程从channel中获取请求和该请求的时间戳,如果发现时间戳早于当前时间减去超时时间(即已经超时,处理也没有意义),就直接丢弃该请求,或者应答一个失败报文。
Channel是一个先进先出的通信方式,可以是socket,也可以是共享内存、消息队列、或者管道,不限。
Socket缓冲区要设置合理,如果过大,导致interface进程都需要处理长时间才能清空该队列,就不合适了。建议的大小上限是:缓存住超时时间内interface进程能够处理掉的请求个数(注意考虑网络通讯中的元数据)。

(3)暴力法
直接改小内核的socket缓冲区大小。雪球其实是服务了大量已经超时的请求,及时清空服务队列是消灭雪球的方法。如果socket缓冲区比较小,就相当于服务队列比较短,原来应该排队的请求会直接被丢包,表现为机器丢包。这种方法其实不怎么友好,被丢弃的包不能立即被通知前端是超过频率限制了,前端只能等待超时。

CATALOG