一直在使用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 <https://github.com/arut/nginx-rtmp-module/issues/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卡死。先这样吧