高并发场景下 disk io 引发的高时延问题

Posted qcrao

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了高并发场景下 disk io 引发的高时延问题相关的知识,希望对你有一定的参考价值。

该系统属于长连接消息推送业务,某节假日推送消息的流量突增几倍,瞬时出现比平日多出几倍的消息量等待下推。

事后,发现生产消息的业务服务端因为某 bug,把大量消息堆积在内存里,在一段时间后,突发性的发送大量消息到推送系统。但由于流量保护器的上限较高,当前未触发熔断和限流,所以消息依然在流转。

消息系统不能简单地进行削峰填谷式的排队处理,因为很容易造成消息的耗时长尾,所以在不触发流量保护器的前提下,需要进行的并发并行地去流转消息。

下图是我司长连接消息推送系统的简单架构图,问题出在下游的消息生产方业务端。

其实更重要的原因是前些日子公司做成本优化,把一个可用区里的一波机器从物理机迁移到阿里云内,机器的配置也做了阉割。突然想起曹春晖大佬的一句话:

没钱做优化,有钱加机器。

这样两个问题加起来,导致消息时延从 100ms 干到 3s 左右,通过监控看到高时延问题持续 10 来分钟。

分析问题

造成消息推送的时延飙高,通常来说有几种情况,要么 cpu 负载高?要么 redis 时延高?要么消费rocketmq 慢?或者哪个关键函数处理慢 ?

通过监控图表得知,load 正常,且网络 io 方面都不慢,但两个关键函数都发生了处理延迟的现象,这两个函数内除处理 redis 和 mq 的网络 io 操作外,基本是纯业务组合的逻辑,讲道理不会慢成这个德行。

询问基础运维的同学得知,当时该几个主机出现了磁盘 iops 剧烈抖动, iowait 也随之飙高。

但问题来了,大家都知道通常来说 Linux 下的读写都有使用 buffer io,写数据是先写到 page buffer 里,然后由内核的 kworker/flush 线程将 dirty pages 刷入磁盘,但当脏写率超过阈值 dirty_ratio 时,业务中的 write 会被堵塞住,被动触发进行同步刷盘。

推送系统的日志已经是 INFO 级别了,虽然日志经过特殊编码,空间看似很小,但消息的流程依旧复杂,不能不记录,每次扯皮的时候都依赖这些链路日志来甩锅。

阿里云主机普通云盘的 io 性能差强人意,以前在物理机部署时,真没出现这问题。????

解决思路

通过监控的趋势可分析出,随着消息的突增造成的抖动,我们只需要解决抖动就好了。上面有说,虽然是 buffer io 写日志,但随着大量脏数据的产生,来不及刷盘还是会阻塞 write 调用的。

解决方法很简单,异步写不就行了!!!

  • 实例化一个 ringbuffer 结构,该 ringbuffer 的本质就是一个环形的 []byte 数组,可使用 Lock Free 提高读写性能;

  • 为了避免 OOM, 需要限定最大的字节数;为了调和空间利用率及性能,支持扩缩容;缩容不要太频繁,可设定一个空闲时间;

  • 抽象 log 的写接口,把待写入的数据塞入到ringbuffer里;

  • 启动一个协程去消费 ringbuffer 的数据,写入到日志文件里;

    • 当 ringbuffer 为空时,进行休眠百个毫秒;

    • 当 ringbuffer 满了时,直接覆盖写入。

这个靠谱么?我以前做分布式行情推送系统也是异步写日志,据我所知,像 WhatsApp、腾讯 QQ 和广发证券也是异步写日志。对于低延迟的服务来说,disk io 造成的时延也是很恐怖的。

覆盖日志,被覆盖的日志呢?异步写日志,那 Crash 了呢?首先线上我们会预设最大 ringbuffer 为 200MB,200MB 足够支撑长时间的日志的缓冲。如果缓冲区满了,说明这期间并发量着实太大,覆盖就覆盖了,毕竟系统稳定性和保留日志,你要哪个?

Crash 造成异步日志丢失?针对日志做个 metrics,超过一定的阈值才开启异步日志。但我采用的是跟广发证券一样的策略,不管不顾,丢了就丢了。如果正常关闭,退出前可将阻塞日志缓冲刷新完毕。如果 Crash 情况,丢了就丢了,因为 golang 的 panic 会打印到 stderr。

另外 Golang 的垃圾回收器 GC 对于 ringbuffer 这类整块 []byte 结构来说,扫描很是友好。Ringbuffer 开到 1G 进行测试,GC 的 Latency 指标趋势无异常。

至于异步日志的 golang 代码,我暂时不分享给大家了,不是因为多抠门,而是因为公司内部的 log 库耦合了一些东西,真心懒得抽离,但异步日志的实现思路就是这么一回事。

结论

如有全量详细日志的打印需求,建议分两组 ringbuffer 缓冲区,一个用作 debug 输出,一个用作其他 level 的输出,好处在于互不影响。还有就是做好缓冲区的监控。

下面是我们集群中的北京阿里云可用区集群,高峰期消息的推送量不低,但消息的延迟稳定在 100ms 以内。


让技术也有温度!关注我,一起成长~

资料分享,关注公众号回复指令:

  • 回复【加群】,和大佬们一起成长。

  • 回复【000】,下载一线大厂简历模板。

  • 回复【001】, 送你 Go 开源电子书。

以上是关于高并发场景下 disk io 引发的高时延问题的主要内容,如果未能解决你的问题,请参考以下文章

大话程序猿眼里的高并发(下)

5G机遇 | 如何解决在核心场景的高并发超低延迟需求?

C++实现的高并发内存池

用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景?

架构师眼中的高并发架构

架构师眼中的高并发架构