知识卡片
Akka日志组件的瞬时峰值坑
内容
雪球IM系统最初的设计目标是提供聊天功能(Netty加自定义网络协议、每个在线Client对应Akka的一个actor、Client在线时用推模式、支持单账号多端同步),但移动互联网时代除了微信QQ之外几乎所有IM都转型成了推送通道,核心指标变成了瞬间峰值性能,原有架构在很多地方就不太合适了,因此做了一系列优化:分配更多资源(专门的推送账号actor池)、精简业务逻辑(重复消息只存ID、实时提醒内容不推给历史设备、不更新非活跃设备的session列表)、把拉黑等无法精简的业务逻辑迁移到本地缓存、以及优化代码(异步加密存储、去除不合理的Akka使用)。其中最有价值的一个具体教训是Akka自带的log adapter踩过的坑:Akka内部用一个专门的actor来处理所有的log event stream,这套机制平时运转正常,但当瞬间峰值到来时,这个event stream会一下子被堵上百万条log,导致GC颠簸非常严重——因为所有日志事件都要排队等这一个actor处理,日志处理速度跟不上日志产生速度,积压的日志对象本身又不断产生GC压力,形成恶性循环。最终的解决办法是绕过Akka自带的log adapter,直接使用logback的appender——优化后线上记录显示,5万/秒(主动限速)的推送持续3分钟,p99性能指标没有明显变化。这条教训揭示的一般性原理是:框架自带的某个子组件(这里是日志),即便在正常负载下工作良好,也可能在其内部设计上暗藏一个隐蔽的串行瓶颈(这里是”所有日志都排队给单个actor处理”),这类瓶颈只有在真正的峰值流量冲击下才会暴露出来;遇到框架自带组件在极端场景下失效的情况,绕过它、直接使用更底层或更专用的实现,往往比试图在框架层面调优更直接有效。