一直在使用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卡死。先这样吧

05-10 05:58