引言
记一次IoT项目中由于日志打印不当导致的OOM,记录整个分析和调优过程。
问题现象
压测某个设备IoT接口,不到1min出现TPS卡顿现象,长时间没响应。如下图:

定位思路
照例三板斧,监控、日志、工具,观察docker容器监控、jvm监控
如下图,使用jstat -gcutil 1 1000 1000命令观察内存gc情况,每秒采样一个点
明显看到Old区很快(几十秒内)达到100%,右侧docker容器也显示重启次数5次

排查日志:
明显看到日志打印异常,vim命令都卡住几秒

因此初步怀疑与日志打印方式不当有关。
观察日志中的打印信息,明显是deviceInfoMap对象打印异常

代码分析
排查代码:

结合代码上下文、以及业务,结合业务分析,该接口是设备登录成功,后端已经返回了全局唯一sysid,作为token凭证,后端会把这个sysid存在一个BidiMap对象里面,后续设备每次交互都需要上送这个sysid,通过sysid查到设备的IMEI号
注:BidiMap是双向映射键值对,它允许通过 key 查找 value,也能通过 value 反向查找 key,且确保键和值都是唯一的(即 key 和 value 都不可重复)。
与hashMap不同,hashMap只能通过 key 查 value,但是BidiMap是双向查找。
排查调用这一段代码的入口,通过设备上送的sysid查询设备的token在内存中是否存在,如果存在则通过sysid返回IMEI,
如下图,

调优分析
从业务上看,这一段代码是想通过设备上送的sysid查询设备的token在内存中是否存在,如果存在则通过sysid返回IMEI,仅仅只是为了获取单个设备的IMEI号,没必要打印整个map对象。
且该map对象的整个内容对于运维和售后也无意义,因此没必要打印整个对象的内容,去掉日志打印即可。
成果检验

经验总结
1、任何性能上的卡顿、重启、长时间的无响应,都可以从监控、日志、工具3个方面排查,作者本人称之为性能分析三板斧。
2、任何性能问题,都需要结合业务、成本、系统稳定性等综合考量,再确定优化方式。
3、尽管暂时解决了问题,但是这种BidiMap明显是存在风险的,因为未来设备可能还会增长。现在的内存资源可以支撑生产上的设备运行,但未来设备再度增长,必然会出现大Map对象一直增长最终导致OOM问题。
思维扩展
此段代码是2019年的项目,早已上线多年,管理层处于系统稳定性考虑,没有进行重构,而是继续沿用这一套sysid写入BidiMap的做法,其实业界目前主流的方式还是加一层Redis等缓存中间件,这样即快又不会引入大map对象导致OOM的问题,还能通过redis自带的过期时间特性,自动管理设备sysid过期。
但是跟管理层沟通以后,上面还是觉得省事儿一点不加Redis,因为这是一个IoT项目,引入redis固然能解决响应快和OOM的问题,但是增加了系统复杂度,需要额外再维护一套redis和相关配套的代码,对于IoT项目来说,不利于稳定性、健壮性。