一直在使用nginx+nginx-rtmp-module做直播和mp4点播(rtmp直播和rtmp点播)
但是最近有一个项目,不定时的就无法点播,重启nginx可以解决,开始几次没在意,后来反复出现,所以开始排查。
出现问题时,nginx是在运行的,但是nginx的Welcome页面打不开,也无法往nginx推流,access和error日志不再写入。使用top命令,发现nginx的work processCPU占用一直是100%。
后来想是不是这个nginx-rtmp-module有Bug,遂重新编译了nginx+nginx-http-flv(这个模块基于nginx-rtmp-module),都使用了最新版,希望能解决,没想到第二天又再次出现同样的问题。
一番谷歌之后,发现还有一个perf命令
#原来不是内置的程序,之前有安装过,使用yum安装
yum -y install perf
#使用方法
perf top -p "nginx work process的pid"
使用命令之后看到主要有两个函数占用了CPU
76.06% nginx [.] ngx_rtmp_mp4_parse
23.70% nginx [.] ngx_rtmp_mp4_parse_trak
看起来应该是点播导致的问题,感觉可能是死循环之类的。遂向http-flv模块作者提了个issue,答曰点播这块没改过,所以去请教arut(nginx-rtmp-module作者),提了个issue(issue #1405)。同时研究了一下 ngx_rtmp_mp4_module.c 里的代码,找到上面两个函数,发现代码里有ngx_log_debug2这样的函数,大概就是写debug日志吧。了解到nginx可以打开debug日志,不过需要重新编译,所以又编译了一个开启debug的nginx,并且配置了相关配置文件。希望通过对应的日志,找到问题是出在哪个位置。
蓝鹅,开启debug后,nginx跑了一天有多,居然还没出问题。arut现在不怎么管这个项目了,issue里只有一个波兰老哥跟我交流,提了一些猜测,没有实质性的意义。
周六到公司加(蹭)班(饭)的时候再上去看,问题终于又出现了,此时产生了47GB的日志(┬_┬),怎么查看又成了问题,tailf tail cat等都要处理半天,能打印的都是无限循环的:
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
我想看出现这个日志之前的日志,百度找了一下,找到了一个UVviewsoft LogViewer,很好,日志下载到Windows机子上,能很快打开,只是滚滚动条也颇费一番力气。最后找到的日志是这样,并且通过再往前的日志找到了触发此问题的视频文件,使用ffmpeg查看这个视频文件,发现此文件只有一个Stream,只有视频流,没有音频流。
2019/04/20 15:18:07 [debug] 21771#0: *74 read: 10, 00007FFDDD60C5D0, 8, 32
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: found moov box
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box unhandled 'mvhd'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: trying track 0
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: too small box: size=-8
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
2019/04/20 15:18:07 [debug] 21771#0: *74 mp4: box 'trak'
mp4: box 'trak'日志来源于ngx_rtmp_mp4_parse函数
代码如下:
ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
{
uint32_t *hdr, tag;
size_t size, nboxes;
ngx_uint_t n;
ngx_rtmp_mp4_box_t *b;
while (pos != last) {
if (pos + 8 > last) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: too small box: size=%i", last - pos);
return NGX_ERROR;
}
hdr = (uint32_t *) pos;
size = ngx_rtmp_r32(hdr[0]);
tag = hdr[1];
if (pos + size > last) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"mp4: too big box '%*s': size=%uz",
4, &tag, size);
return NGX_ERROR;
}
b = ngx_rtmp_mp4_boxes;
nboxes = sizeof(ngx_rtmp_mp4_boxes) / sizeof(ngx_rtmp_mp4_boxes[0]);
for (n = 0; n < nboxes && b->tag != tag; ++n, ++b);
if (n == nboxes) {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: box unhandled '%*s'", 4, &tag);
} else {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: box '%*s'", 4, &tag);
b->handler(s, pos + 8, pos + size);
}
pos += size;
}
return NGX_OK;
}
拉上搞音视频编码的同事分析,认为size不应该等于0,这里应该加个判断,死循环应该出在while这个循环这里。
遂修改如下:
ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last)
{
uint32_t *hdr, tag;
size_t size, nboxes;
ngx_uint_t n;
ngx_rtmp_mp4_box_t *b;
while (pos != last) {
if (pos + 8 > last) {
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: too small box: size=%i", last - pos);
return NGX_ERROR;
}
hdr = (uint32_t *) pos;
size = ngx_rtmp_r32(hdr[0]);
//这里加个判断*****************************
if(0 == size) return NGX_ERROR; //********
//这里加个判断*****************************
tag = hdr[1];
if (pos + size > last) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"mp4: too big box '%*s': size=%uz",
4, &tag, size);
return NGX_ERROR;
}
b = ngx_rtmp_mp4_boxes;
nboxes = sizeof(ngx_rtmp_mp4_boxes) / sizeof(ngx_rtmp_mp4_boxes[0]);
for (n = 0; n < nboxes && b->tag != tag; ++n, ++b);
if (n == nboxes) {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: box unhandled '%*s'", 4, &tag);
} else {
ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
"mp4: box '%*s'", 4, &tag);
b->handler(s, pos + 8, pos + size);
}
pos += size;
}
return NGX_OK;
}
重新编译,再次点播有问题的视频文件,点播失败,但是不会导致nginx卡死。先这样吧