记虎牙视频弹幕JVM故障排查与解决

目录
  1. ✔背景
  2. ✔排查
  3. ✔解决
  4. ✔教训

背景

3月份在做一个虎牙视频弹幕的项目,完成设计和开发后进行功能测试没有什么问题,因为这个项目弹幕的获取和新增都是去调用虎牙那边的thrift服务,压力主要还是在他们那边,加之产品经理着急项目赶紧上线,所有没有进行压力测试,功能测试OK了就赶紧发布了。

项目上线进行一些功能回归测试是没有问题的,一段时间后,测试同学和产品发现线上的弹幕列表没办法获取,新增弹幕也失败。

排查

我赶紧打开浏览器请求一下相关接口,没想到直接超时了。

然后进去服务器,看了一下,并没有明显的报错日志;我在想会不会是java经常crash了,用了

1
ps -ef|grep java

查看了一下,java进程(tomcat)是在的,那为什么接口访问直接超时呢,是不是java进程已经假死了。

我赶紧用jstat看一下java进程的GC情况:

1
jstat -gcutil pid

看了一下,进程基本没有GC发生,Perm space(永久代)占了95%+。

开始,我觉得是不是老年代设得太小导致了,full GC频繁导致的???那为什么不会出现OOME,我把内存都调大了一倍左右,Permentent Space也增加了一倍左右。

然后用ab进行压力测试,发现刚开始就是Young GC比较频繁(这个正常,因为正在压力测试,young GC频繁正常),Full GC并不是非常频繁,Permentent Space 也一直稳定在一半大小以下(因为比之前调大了一倍啦)。

那这TM就奇怪了。。。

。。。。。。

我还是不死心一直怀疑是java进程的内存设置有问题,然后我又用jmap -histo 打印每个class的实例数目,内存占用,类全名信息。

1
jmap -histo pid

看了一下,也没有那个类的数目非常地多,或者占用了非常多的内存。

。。。

从JVM的内存情况实在TMD看不出个所以然,那就只能看一下线程堆栈了,赶紧把线程堆栈打印出来看看。

1
jstack -f pid

终于从线程堆栈可以看出点端倪来。

image

我们可以看出

1
getDanmuList()

这个获取弹幕列表方法阻塞了,接着看一下堆栈里面调用了多少次这个方法:

image

我们可以看出这个方法调用了150次之多,而且这150条线程都处于阻塞状态,这么多次阻塞,直接导致整个web服务器进程假死了。。。

看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public List<DanmuVO> getDanmuList(long vid) {
ThriftClientWrapper<VideoMessageServant.Iface> client = null;
List<DanmuVO> list = new ArrayList<DanmuVO>();
try {
// 取得客户端
client = danmuFactory.createClient();

// 业务逻辑
// ...

} catch (Exception e) {
LOG.error(String.format("获取视频%s弹幕列表失败", vid), e);
}

return list;
}

逻辑主要是获取一个thrift的连接,然后去调用服务化的接口,我们可以清楚地看到代码的最后并没有关闭连接。

因为每次调用服务化接口,需要打开一个连接,但是调用完了并没有关闭,调用一多,连接池就没有连接可用了,后面调用服务化接口的请求就一直在等待,最后导致大量线程阻塞了,直接造成整个tomcat假死了。

解决

代码加上关闭连接的操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public List<DanmuVO> getDanmuList(long vid) {
ThriftClientWrapper<VideoMessageServant.Iface> client = null;
List<DanmuVO> list = new ArrayList<DanmuVO>();
try {
// 取得客户端
client = danmuFactory.createClient();
// 业务逻辑
// ...

} catch (Exception e) {
LOG.error(String.format("获取视频%s弹幕列表失败", vid), e);
}finally {
// 关闭连接
if(client != null){
client.close();
}
}

return list;
}

然后在用ab压测了一下,然后在看一下线程堆栈情况,终于恢复正常了。

教训

  • 无论项目使用人数多少,都要进行压力测试。
  • 应用有外部系统调用,要记得有相关的连接关闭操作。
  • 应用有IO操作,也要有相关的IO关闭操作。
JVM
JVM