简介

本章我们来看看RTMP服务的auto_push模块。我们知道当一个推流客户端将一路直播流上行推到RTMP直播服务器上,此时由一个worker进程负责接收上行推流数据的。如果我们在nginx.conf文件中配置开启多个worker,那么当有用户进行播放拉流的时候,其创建的socket连接是随机分配到一个worker上的,并不能保证一定是在我们的接收推流的进程上。而auto_push的主要功能是接收推流的进程将其接收到上行数据,再转发给所有的兄弟worker进程一份。这样就可以保证所有的worker进程上都拥有直播上行推流数据,这样当用户播放时,不管是连接到哪一个进程上,都能保证可以获取到下行数据,正常的观看直播。下面我们就来看看RTMP的源码中是如何实现这部分功能的。auto_push在默认情况下是关闭的,需要配置rtmp_auto_push on来打开。还有连接重试功能,通过rtmp_auto_push_reconnect进行配置。最后是本地unix域套接字的目录在rtmp_socket_dir进行配置。

源码分析

我们看到在ngx_rtmp_auto_push_module.c文件中很多函数里面都有NGX_HAVE_UNIX_DOMAIN的宏定义判断,通过这个我们可以知道auto_push功能是只有在Unix/Linux等系统上才起作用,在Windows系统上无效。不过我们的服务器一般都是Linux系统,所以不妨碍我们对其的了解。

首先我们来看ngx_rtmp_auto_push_init_process,在Nginx进程启动的时候调用。

if (ngx_process != NGX_PROCESS_WORKER) {
        return NGX_OK;
    }

通过这段代码我们知道ngx_rtmp_auto_push_init_process是在Worker启动时才会进行下一步的运行逻辑。

next_publish = ngx_rtmp_publish;
    ngx_rtmp_publish = ngx_rtmp_auto_push_publish;

    next_delete_stream = ngx_rtmp_delete_stream;
    ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream;

设置推流和删除流的回调函数,将auto_push模块的推流和删除流函数加入到整体的执行链表中。

/*TODO: clone all RTMP listenings? */
    ls = cycle->listening.elts;
    lss = NULL;
    for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {
        if (ls->handler == ngx_rtmp_init_connection) {
            lss = ls;
            break;
        }
    }

    if (lss == NULL) {
        return NGX_OK;
    }

当前版本的代码实现中,只会对第一个设置的rtmp监听端口服务有效。

。。。。。。
    if (listen(s, NGX_LISTEN_BACKLOG) == -1) {
        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,
                      "listen() to worker_socket, backlog %d failed",
                      NGX_LISTEN_BACKLOG);
        goto sock_error;
    }

在这里是创建一个Unix域套接字并设置相关属性信息,然后绑定和监听新建的Unix域套接字。

ngx_rtmp_auto_push_exit_process
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,
                                                    ngx_rtmp_auto_push_module);
    if (apcf->auto_push == 0) {
        return;
    }
    *ngx_snprintf(path, sizeof(path),
                  "%V/" NGX_RTMP_AUTO_PUSH_SOCKNAME ".%i",
                  &apcf->socket_dir, ngx_process_slot)
         = 0;

    ngx_delete_file(path);

在worker进程退出时,会关闭在启动函数(ngx_rtmp_auto_push_init_process)中创建的Unix域套接字。

ngx_rtmp_auto_push_publish
if (s->auto_pushed || (s->relay && !s->static_relay)) {
        goto next;
    }

判断当前的RTMP会话是否是来自自动转推的,或者当前RTMP会话是否是中继并且不是静态中继。
auto_pushed参数会在初始化RTMP会话时判断是否是Unix域套接字进行设定。

ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
    if (ctx == NULL) {
        ctx = ngx_palloc(s->connection->pool,
                         sizeof(ngx_rtmp_auto_push_ctx_t));
        if (ctx == NULL) {
            goto next;
        }
        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);

    }
    ngx_memzero(ctx, sizeof(*ctx));

设置当前RTMP会话的auto_push对应的ctx。在RTMP的每个模块中我们都可以看到会设置对应模块的ctx。

ctx->push_evt.data = s;
    ctx->push_evt.log = s->connection->log;
    ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect;

    ctx->slots = ngx_pcalloc(s->connection->pool,
                             sizeof(ngx_int_t) * NGX_MAX_PROCESSES);
    if (ctx->slots == NULL) {
        goto next;
    }

    ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
    ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));

    ngx_rtmp_auto_push_reconnect(&ctx->push_evt);

最后设置ctx对应的时间属性,并开始真正的转推连接。

ngx_rtmp_auto_push_delete_stream 删除自动转推流
apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,
                                                    ngx_rtmp_auto_push_module);
    if (apcf->auto_push == 0) {
        goto next;
    }

非auto_push的,直接跳过。

ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);
    if (ctx) {
        if (ctx->push_evt.timer_set) {
            ngx_del_timer(&ctx->push_evt);
        }
        goto next;
    }

如果RTMP会话对应的auto_push模块ctx已经设置推流定时器,那么久删除推流定时器,并跳到最后。

/* skip non-relays & publishers */
    rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
    if (rctx == NULL ||
        rctx->tag != &ngx_rtmp_auto_push_module ||
        rctx->publish == NULL)
    {
        goto next;
    }

判断当前的RTMP会话是否是中继会话、如果是中继会话但是并不是auto_push会话或者不是publish会话,则直接跳到最后。

slot = (ngx_process_t *) rctx->data - &ngx_processes[0];

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "auto_push: disconnect slot=%i app='%V' name='%V'",
                   slot, &rctx->app, &rctx->name);

    pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,
                                   ngx_rtmp_auto_push_index_module);
    if (pctx == NULL) {
        goto next;
    }

    pctx->slots[slot] = 0;

    /* push reconnect */
    if (!pctx->push_evt.timer_set) {
        ngx_add_timer(&pctx->push_evt, apcf->push_reconnect);
    }

最后找的需要被删除的的RTMP会话所在的slot位置,找到中继的publish会话,并添加push_evt定时器,用于重新建立auto_push的自动转推RTMP会话。

ngx_rtmp_auto_push_reconnect

最后我们来看本模块中最重要的一个函数ngx_rtmp_auto_push_reconnect,它是push_evt定时器的回调函数,它的主要作用就是建立同各个兄弟worker进程的RTMP中继会话。

for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
......
    if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) {
        *slot = 1;
        npushed++;
        continue;
    }
......
 }

我们看到第一个for循环中,会设置中继会话的对端信息,即at,然后调用ngx_rtmp_relay_push,创建向其他兄弟woker进程推流的中继RTMP会话。

if (ccf->worker_processes == npushed + 1) {
        return;
    }

    /* several workers failed */

    slot = ctx->slots;

    for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {
        pid = ngx_processes[n].pid;

        if (n == ngx_process_slot || *slot == 1 ||
            pid == 0 || pid == NGX_INVALID_PID)
        {
            continue;
        }

        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "auto_push: connect failed: slot=%i pid=%P name='%s'",
                      n, pid, ctx->name);
    }

    if (!ctx->push_evt.timer_set) {
        ngx_add_timer(&ctx->push_evt, apcf->push_reconnect);
    }

这个首先是判断第一个for循环中,是否和每个兄弟worker进程创建auto_push中继会话成功,如果不成功,第二个for循环会打印出来所有没有建立成功的auto push会话信息,然后是添加push_evt定时器,用于下一次重试auto push建立中继会话。

小结

通过本章的介绍,我们知道了RTMP auto push模块的实际运行原理,即进程间转发。这就带来一个问题:每个进程上面的流都是一样的,也就是同一个直播流被负责了N份,对于现在的多核服务器来说,并不能真正有效的利用多核的并行效率。所以这个模块只适合作为demo项目或者增进对RTMP服务的理解使用,是不建议在线上开启auto push模块的。至于如何在线上开启多个worker以充分利用服务器的每个内核,我们将在后面的章节进行介绍。