-
-
[原创]一起由redis故障导致的内存暴涨分析-linux .net core,sos+LLDB环境
-
发表于: 2024-4-26 11:49 2378
-
前方销烟再起╮(╯▽╰)╭
dump内存一份
1、先用PerfView打开dump看看打字情况
从dump内存概要以及火焰图上卦象上看,是由于System.Private.CoreLib!Text.StringBuilder调用较多,占用大量系统资源,高达92%+。
由于这次环境是linux .net core,windbg是用不上了,只能用lldb了,其配置也不复杂,网上教程一大把,相信咱们混迹看雪的基本都能轻松解决,环境搭建在此略过
LLDB分析dump对象占用统计如下:
执行dumpheap -stat
从LLDB中统计的卦象来看,其最大占用为char类型,位居榜首。
再执行dumpheap -mt <mt表地址> -min 0n10000,来查看大于10000byte的对象信息,如下
进一步查看该方法表中的char类型详情,发现大量大小为16024byte的char[]。
高达3.9G内存占用,与运维监控基本一致。
任意抽取一个char[]查看对象信息,执行dumpobj <对象地址>:
执行lldb命令查看内存:memory read -c 128 <对象地址>
卦象上显示为一堆垃圾字符,其值为一堆FF(-1):
由于该对象处于GEN2代,不排除已被GC回收内存导致,但是如果为GC回收,内存中数据应该为随机产生,基本不会用FF来填充内存区域。
执行gcroot <对象地址>查看该char[]对象的引用关系,如下:
可见非常冗长的调用关系,全部被System.Private.CoreLib!Text.StringBuilder霸占,多大16w+,这里与火焰图分析一致,。
从引用关系上可看出,该引用来自于918号线程所调用的CSRedis.Internal.IO.RedisReader.ReadLine(),切换至该线程,
执行clrstack查看调用栈,如下图:
(这里稍微说明下,gcroot上看到线程号,需要执行sos Threads命令来对照线程号,比如这里我们在gcroot上看到线程号是Thread 918
需要到此处找到OSID 918 对应的DBG号,再用thread select <DBG号>切换线程
)
如上图,从调用栈上可看到的确调用CSRedis.Internal.IO.RedisReader.ReadLine()方法,同时说明该方法,可能存在问题。
找到该方法所在module:
执行name2ee *!<方法名>
执行dumpmodule <Module地址>
找到路径,拷贝出该DLL反编译查看
如下:
其中通过ReadByte()方法读取,继续跟进该方法:
从这个方法的调用关系,以及代码,已大致明白产生大量FF的char[]的病根:
由于某些原因,发生网络故障,redis故障无响应等,导致socket通信产生错误,以至于
该方法执行失败返回-1,由于在ReadLine()方法中没有对其进行判断处理,直接丢进stringBuilder,导致内存暴增。
老夫随后在开源GITHUG上提交该问题,以及修复方案:
https://github.com/2881099/csredis
目前作者已将老夫代码并该项目主分支。
处理方式也很简单,增加相关判断即可:
告一段落,等待作者更新版本了。╮(╯▽╰)╭