原创 吴就业 164 0 2020-01-18
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/1bd2fed544c5411395c4b6d246fc9819
作者:吴就业
链接:https://wujiuye.com/article/1bd2fed544c5411395c4b6d246fc9819
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
本篇文章写于2020年01月18日,从公众号同步过来(博客搬家),本篇为原创文章。
线上某服务一直运行很稳定,最近突然就cpu
百分百,rpc
远程调用全部失败,并走了mock
逻辑。重启后,一个小时后问题又重现。于是dump
线程栈信息,但不仔细看也看不出什么问题。于是就有了一番排查历程。
前一天,我dump
线程栈信息后发现占用cpu
高的线程是阿里的限流组件sentinel
,并根据线程栈信息知道,在sentinel
控制台会拉取该服务的qps
统计信息。这个接口会读取本地文件内容,因为qps
信息是输出到/root/logs/csp/
目录下的某个文件的,并且是用NIO
方式读取文件。因此我猜测是sentinel
在某种条件下会触发死循环bug
,也就能解释得清楚为什么cpu
会出现百分百的使用率。
带着这个猜测,我调试了一遍源码,也并未复现。于是我将限流功能暂时去掉,以为问题就能解决,但是没想到第二天早上又看到这个问题。于是我先将该服务的其它节点重启,留下一个节点,并将剩下的节点从注册中心中剔除。可没想到的是,注册中心已经没有这个节点了,应该是所有该服务的节点都与注册中心失联了,其它是因为重启之后重新注册到注册中心的。
既然没有注册到注册中心,其它服务调用直接走mock
逻辑也解释得通。继续前一天的进度,我首先是查看文件句柄数是否打开很多,于是使用lsof
查看,果然不出所料。
进程15567
打开的文件句柄数20
多万,因此我更愿意相信是sentinel
读取文件出现bug
的猜测,因为我想起前一天限流功能还没有完全去除,只是去除了限流配置。接着查看这个进程是否打开了/root/logs/csp
目录下的文件(这个目录是从限流组件的源码中找出来的)。
遗憾的是看不到每个文件的打开数量。接着我用lsof -p 进程id
命令查看下,该进程打开的文件句柄信息。
于是我发现打开最多的不是/root/logs/csp
目录下的文件,而是redis
连接。这下只能将限流组件sentinel
有bug
的猜测去掉了,因为昨天去掉配置后,也并没有看到sentinel
线程占用cpu
过高的现象了,只能排除。继续统计redis
的socket
打开数量。
确实对上了,文件句柄数达上限的问题确实是与redis-cluster
有关。于是我使用redis-cli
命令连接上redis
服务,看下是否连接数增多了,是否与该进程持有的文件句柄数对得上。因为前一天我也发现某消息消费服务偶尔出现连接超时的情况。
然而并未发现什么异常,客户端连接数与平常一样。只能悬于此了。于是我又去分析线程栈信息,发现大量的dubbo
线程处于wait
状态,且都是阻塞在归还redis
连接到连接池以及从连接池中申请连接这一步。
还有少数几个dubbo
线程当前是runing
状态,原因是这些线程走了内存缓存。代码中我是用aop
实现内存缓存的,命中内存缓存的不会往下走业务逻辑,也就不会用到redis
。因此这些线程处理的请求是正常的。
那么为什么cpu
会百分百呢?大片的业务线程都已经被阻塞住了,都在wait
状态,到底是什么线程占用如此高的cpu
。于是我才想到用top
命令去查看。
看到两个非业务相关的线程,占用非常高的cpu
,于是又回去琢磨线程栈信息,与top
命令显示的command
列对得上的也就只有两个垃圾回收线程。
于是就是jstat
命令查看gc
垃圾回收情况。
连续使用jstat
命令查看gc
情况,发现每次执行命令,大概是1
秒多,fgc
的次数就加1
了。问题的真相已经非常接近。找到这里,接着就是排查业务代码。
由于使用了内存缓存,会导致full gc
的情况很正常。我首先想到两点,一是缓存数据太多,二是缓存数据清理的定时任务执行失败了。
由于之前该服务一直很稳定,猜测可能是数据格式不正确触发的bug
,导致数据清理失败,越积越多。但是看了代码后,我是加了try
-catch
的,出异常也会移除缓存,只是清理时间间隔比较长,一个小时才清理一次。那么就是数据量暴增的原因。
到此,我就不再往下分析。去掉内存缓存?那就需要加好几台机器来扛下目前的并发数,而且还需要添加Redis节点,想想也是不可能的。所以只能计算业务所需要的内存,提升实例的内存。
通过数据库查询,估算需要缓存的记录数是四百多万,本地写了个测试用例,计算四百多万记录大概消耗700
多m
的内存,因此,只需要将机器的内存稍微提高一点就没有问题了。除非数据量还会暴涨,这也是某个业务功能的实现导致的。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
Unsafe、CAS、AQS是我们了解Java中除synchronized之外的锁必须要掌握的重要知识点。CAS是一个比较和替换的原子操作,AQS的实现强依赖CAS,而在Java中,CAS操作需通过使用Unsafe提供的方法实现。
灰度发布是实现新旧版本平滑过渡的一种发布方式,即让一部分服务更新到新版本,如果这部分服务没有什么问题,再将其它旧版本的服务更新。而实现简单的灰度发布我们可以使用版本号控制,每次发布都更新版本号,新更新的服务就不会调用旧的服务提供者。
我看很多资料在介绍`GC Root`时,并没有说栈帧的操作数栈上引用的对象也是`GC Root`,包括我去翻阅《深入理解Java虚拟机》这本书也是一样。所以我才好奇。
最近看书看到关于`volitale`关键字与`jmm`内存模型的介绍,这个知识点似乎看了好多次,背都能背下来了。但理论性的东西真的很容易忘记,看不到摸不着。于是乎,我上网搜索看底层机器指令的实现,发现不少文章说可以看到`java`编译后的汇编代码,于是了解到`jitwatch`这个工具,从名字上也能看出`jit`编译器监视的意思。
容器化部署就是一次配置到处使用,例如将安装nginx配置nginx这一系列工作制作成一个镜像,在服务器上通过docker拉取镜像并启动容器即可完成nginx的部署。
Dubbo框架的传输层默认使用dubbo协议,这也是一种RPC远程通信协议。学习Dubbo,我们有必要了解dubbo协议长什么样,最好的办法就是从源码中寻找答案。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。