最近随着公司业务快速上升,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命令查看如下图,
本文介绍了一种针对Crtmpserver流媒体服务器的并发性能优化方案,通过采用master-worker架构,实现多进程绑定不同端口,并利用Nginx进行前端代理负载均衡,解决了单进程并发瓶颈。

4327

被折叠的 条评论
为什么被折叠?



