Crtmpserver的多进程实现方案

本文介绍了一种针对Crtmpserver流媒体服务器的并发性能优化方案,通过采用master-worker架构,实现多进程绑定不同端口,并利用Nginx进行前端代理负载均衡,解决了单进程并发瓶颈。

    最近随着公司业务快速上升,Crtmpserver作为流媒体服务器的并发性能已经阻碍了业务进一步发展,Crtmpserver使用单进程实现,所以很简单地,想到了使用多进程的方式改造Crtmpserver,我从这几个方面进行修改:

    (1)采用经典的master-work架构,Crtmpserver master进程负责拉起work进程并监控work进程,不同于Nginx的work进程从master进程继承接收句柄然后每个work进程竞争accept锁的方式,我很简单地让每个Crtmpserver work进程绑定不同的端口,第一个work进程绑定1936端口,后面的work进程绑定的端口依次增加。

    核心逻辑函数RunDaemon()如下,master进程负责的事情很简单,拉起work进程后就一直wait。

void RunDaemon()

{
    int     fd;
    int     index;
    int     rtmpport;
    int     sWaitStatus;
    int     sExitStatus;
    int     nChildExitCount;
    pid_t   pid;
    pid_t   ppid; 
    ssize_t nLen;
    time_t  tChildExit;
    time_t  tChildStart;    
    bool    bNeedFork;
    bool    bStartProxy;
    char    pReadBuf[1024];
    char    pArgBuf[1024];
    char    pPidBuf[32];
    const char *args[64];
    string  pidStr;
    Variant subprocesses;
    vector<int> portlist;


    if (g_bWorkStartByMaster) {
        return;
    }
    
    fflush(NULL);


    pid = fork(); 


    if (pid < 0) {    
        SAFATAL(RTMP_MAIN_SID, "run daemon failed: fork() error:%s", strerror(errno));
        exit(1);
    }


    if (pid > 0) {
        exit(0);
    }


    fd = open("/dev/null", O_RDWR);


    if (fd < 0) {
        fclose(stdin);
        fclose(stdout);
        fclose(stderr);


    } else {
        dup2(fd, STDIN_FILENO);
        dup2(fd, STDOUT_FILENO);
        dup2(fd, STDERR_FILENO);


        if (STDIN_FILENO != fd && STDOUT_FILENO != fd && STDERR_FILENO != fd) {
            close(fd);
        }
    }


    umask(0);


    if (setsid() < 0) {
        SAFATAL(RTMP_MAIN_SID, "run daemon failed: setsid() error:%s", strerror(errno));
        exit(1);
    }


    if (g_bIsWorkProcess) {
        return;
    }


    WritePidFile(getpid(), g_pCrsPidFile);


    bStartProxy = true;
    portlist.clear();
    for (index = 0; index < g_lWorkProcessNumber; index++) {
        portlist.push_back(rtmpListenPort+index+1);  //1936 begin 
    }


    #define PROXY_FLAG 0XABCD
    
    for (nChildExitCount = 0; ; ) {


        tChildStart = time(NULL);


        for (index =0; index < (int)portlist.size(); index++) {
            
            pid = fork(); 


            if (pid < 0) {    
                SAFATAL(RTMP_MAIN_SID, "run daemon failed: fork() error:%s", strerror(errno));
                exit(1);
            }


            if (0 == pid) {


                //if parent die, the sub process will be killed
                prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
                            
                nLen = readlink("/proc/self/exe", pReadBuf, 1024);


                if (nLen < 0) {
                    SAFATAL(RTMP_MAIN_SID, "run daemon failed: readlink() error:%s", 
                            strerror(errno));
                    exit(1);
                }


                if (nLen >= 1024) {
                    SAFATAL(RTMP_MAIN_SID, "run daemon failed: readlink too large:%ld",
                            nLen);
                    exit(1);
                }


                pReadBuf[nLen] = '\0';


                args[0] = strdup(pReadBuf);
                args[1] = "-w";
                sprintf(pArgBuf, "%d", portlist[index]);
                args[2] = "-p";
                args[3] = strdup(pArgBuf);
                args[4] = "-c";
                args[5] = STR(g_pConfigFile);                


                if (g_bIsRunInCdn) {
                    args[6] = strdup("-r");
                    args[7] = NULL;
                }else {
                    args[6] = NULL;
                }
                
                execv(pReadBuf, (char *const *)args);


                /* can not get here */ 
                SAFATAL(RTMP_MAIN_SID, "run daemon failed: execv() error:%s", 
                        strerror(errno));
                exit(1);
            }else if (pid > 0) {
                sprintf(pPidBuf, "%d", pid);
                pidStr = string(pPidBuf);
                subprocesses[pidStr]["starttime"] = tChildStart;
                subprocesses[pidStr]["rtmpport"] = portlist[index];
            }
        }        


        if (g_bProxyEnable && bStartProxy) {
            pid = StartProxy();
            sprintf(pPidBuf, "%d", pid);
            pidStr = string(pPidBuf);
            subprocesses[pidStr]["starttime"] = tChildStart;
            subprocesses[pidStr]["rtmpport"] = PROXY_FLAG;               
        }
                
        if (0 == subprocesses.MapSize()) {
            SAFATAL(RTMP_MAIN_SID, "all work processes run over");
            exit(1);
        }
        
        portlist.clear();
     
        ppid = getpid();
        
        pid = waitpid(-1, &sWaitStatus, 0);


        sprintf(pPidBuf, "%d", pid);
        pidStr = string(pPidBuf);
        rtmpport = subprocesses[pidStr]["rtmpport"];
        tChildStart = subprocesses[pidStr]["starttime"];
        subprocesses.RemoveKey(pidStr);


        tChildExit = time(NULL);


        nChildExitCount = (tChildExit - tChildStart < 10) ? nChildExitCount + 1 : 0;
  
        if (nChildExitCount > 10) {
            SAFATAL(RTMP_MAIN_SID, "program child process exited too rapidly, not fork again.");
            continue;
        }
        
        bNeedFork = false;
        
        if (WIFEXITED(sWaitStatus)) {


            sExitStatus = WEXITSTATUS(sWaitStatus);


            SAFATAL(RTMP_MAIN_SID, "child process:%d exited, status:%d",
                    pid, sExitStatus);
            
            if (0 == sExitStatus) {
                bNeedFork = false;   //exit normally, not need fork again 
            } else {
                bNeedFork = true; 
            }
            
        } else if (WIFSIGNALED(sWaitStatus)) {
        
            sExitStatus = WTERMSIG(sWaitStatus);


            SAFATAL(RTMP_MAIN_SID, "child process:%d exited, signal:%d",
                    pid, sExitStatus);
                    
            if (SIGINT == sExitStatus || SIGTERM == sExitStatus) {
                bNeedFork = false;  //these two signal not need fork again
            } else {
                bNeedFork = true; 
            }
       
        } else {
            SAFATAL(RTMP_MAIN_SID, "child process:%d exited, unknown reason", pid);
            bNeedFork = true;
        }


        if (!bNeedFork) {
            continue;
        }


        if (PROXY_FLAG == rtmpport) {
            bStartProxy = true;
        } else { 
            portlist.push_back(rtmpport);
        }
        
        usleep(500000);
    }


    return;
}


    (2)使用Nginx作为前端1935端口代理,将客户端请求分发到各个work进程处理。




(3) Crtmpserver配置文件修改

     每个work进程读取相同的配置文件,但不少配置肯定需要分端口处理,比如日志文件路径,需要修改为带端口形式,fileName="/usr/local/rtmp/debug/" .. rtmpPort 。

    修改相应代码,在读取配置文件ReadLuaFile()函数中,解析配置文件前,增加如下两行即可,

    lua_pushnumber(pLuaState, rtmpPort);
    lua_setglobal(pLuaState, "rtmpPort");


最终的进程关系如下图,



通过ps aux命令查看如下图,



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值