知识卡片

MappedByteBuffer在I/O瓶颈下意外变慢:客户端"能异步的都要异步"的教训来源

普通读书笔记卡

内容

CAT客户端曾用MappedByteBuffer这个文件内存映射类,来本地存储当前消息ID自增计数这样一个只有几个字节的小对象——单独测试时这个类的性能表现非常高,团队按常理判断,存储这么小的数据量,理论上不应该有任何性能问题。但在真实线上场景下,团队发现很多业务线程都阻塞在这个操作上,排查后才明白:MappedByteBuffer的性能表现依赖于底层I/O是否顺畅,一旦系统整体的I/O出现瓶颈(比如磁盘压力大),即使写入的数据量再小,这个类的操作也会跟着变得很慢——因为它本质上是把文件映射到内存,读写操作最终依然要和底层文件系统打交道,当I/O本身成为瓶颈时,这层映射并不能让操作绕开这个瓶颈。这个案例最终的解法是把这个操作异步化,团队由此提炼出一条更普遍的客户端设计原则:客户端代码应当尽可能地把所有可能存在时间延迟的操作都异步化——不只是网络传输,序列化、任何涉及本地I/O的操作都要纳入这条原则。这个教训揭示了一个容易被忽视的陷阱:一项技术在孤立的基准测试中表现优异,不代表它在生产环境的真实负载下、在系统整体资源出现瓶颈(哪怕瓶颈发生在看似无关的其他环节,比如磁盘I/O)时依然能保持这份优异——性能测试的”数据量小、操作简单”这类局部特征,并不能免除它对底层共享资源(这里是I/O子系统)的依赖,一旦这个共享资源本身出现瓶颈,所有依赖它的操作,无论体量多小,都会被拖慢。客户端类库(尤其是嵌入到别人核心业务流程里的旁路组件,如监控SDK)尤其需要对这类”表面无害、实际依赖共享瓶颈资源”的操作保持警惕,默认把它们异步化,避免同步阻塞污染主业务的执行路径。

参考来源

- 位置:《高可用架构(第1卷)》第5章《运维保障》"5.2 深度剖析开源分布式监控CAT"节,"5.2.3 客户端设计"(源文件:_epub-src/OEBPS/Text/Chapter5_2_4.xhtml) - 结论依据:原文说明"客户端使用了MappedByteBuffer这个类……对它进行测试后,结果证明它的性能非常高……在一次线上场景下,很多业务线程都block在这个上面,结果发现当I/O存在瓶颈时,MappedByteBuffer这个类的使用也会变得很慢。后来的优化就是把这个操作异步化,所以客户端需要尽可能地异步化",直接支撑本卡片结论。 - 原始内容:客户端使用了MappedByteBuffer这个类,比类是一个文件内存映射,对它进行测试后,结果证明它的性能非常高……在一次线上场景下,很多业务线程都block在这个上面,结果发现当I/O存在瓶颈时,MappedByteBuffer这个类的使用也会变得很慢。后来的优化就是把这个操作异步化。