您的位置:首页 > 其它

日志系统性能对比分析

2013-10-17 18:32 591 查看
作者:邹祁峰

邮箱:Qifeng.zou.job@gmail.com

博客:http://blog.csdn.net/qifengzou

日期:2013.10.17

转载请注明来自"祁峰"的CSDN博客

1 引言

  日志系统主要负责记录系统运行过程中的行为和数据,这些行为和数据将作为系统恢复、错误查找、数据纠正的重要依据,其重要性可见一斑!但是往往对于一个有高性能要求的信息系统而言,日志系统往往又是整个系统的瓶颈所在,针对这个问题,以下为寻找一个更优的日志系统设计方案做一些前期的探索工作。

  以下将对常用的日志系统进行较简单的实现和测试,因采用以下方式的日志系统都是依赖于以下基本的实现过程,因此其基本上能较为准确的反应出各实现方式的性能差异!

2 TCP日志系统[异步]

2.1 服务端代码

-> 主函数代码

  主函数主要负责侦听指定端口,等待接受客户端的连接请求,同时启动子进程与客户端进行交互。[注意:实际应用中可以使用进程池和线程池机制进行完善,但是测试过程中不必过于复杂]

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, clifd = 0, len = 0;
struct sockaddr_in svraddr, cliaddr;

/* Create socket */
sckid = socket(AF_INET, SOCK_STREAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Bind port */
bzero(&svraddr, sizeof(svraddr));

svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(PORT);

ret = bind(sckid, (struct sockaddr *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

ret = listen(sckid, 20);
if(ret < 0)
{
fprintf(stderr, "Listen failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Receive client connection */
while(1)
{
memset(&cliaddr, 0, sizeof(cliaddr));

len = sizeof(cliaddr);
clifd = accept(sckid, (struct sockaddr *)&cliaddr, &len);

ret = fork();
if(ret < 0)
{
fprintf(stderr, "Fork failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}
if(0 == ret)
{
close(sckid);
recv_msg(clifd);
exit(1);
}
}

close(sckid);

return 0;
}

-> 接收代码

  此函数由服务端子进程调用,用于接收客户端发送的日志信息,并将信息写入指定的日志文件中!

int recv_msg(int clifd)
{
int ret = 0, fd = -1;
char buf[1024] = {0};

fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

while(1)
{
memset(buf, 0, sizeof(buf));

ret = read(clifd, buf, sizeof(buf) - 1);
if(ret < 0)
{
if(EINTR == errno)
{
continue;
}

fprintf(stderr, "Read failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}
else if(0 == ret)
{
break;
}

write(fd, buf, ret);
}

close(clifd);
close(fd);

return 0;
}

2.2 客户端代码

  此函数负责连接至远程服务端,并将日志信息发送至远程服务端!

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, idx = 0;
struct sockaddr_in server;
char buf[BUFLEN] = {0};

memset(&server, 0, sizeof(server));

/* Create socket */
sckid = socket(AF_INET, SOCK_STREAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Connect to server */
server.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
server.sin_port = htons(PORT);

ret = connect(sckid, (void *)&server, sizeof(server));
if(ret < 0)
{
fprintf(stderr, "Connect failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
for(idx=0; idx<LOOP; idx++)
{
snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

ret = write(sckid, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
}

close(sckid);

return 0;
}

2.3 测试结果

  撰写100w条日志的测试结果如下图所示:[注:请关注红线区域的系统调用情况]



图1 TCP日志系统测试结果

3 UDP日志系统[异步]

3.1 服务端代码

  此模块负责绑定指定端口,并接收UDP客户端发送过来的数据,并将数据写入到指定的日志文件中。[注:在实际的应用过程中,可以引入线程池机制进行完善]

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, fd = 0, len = 0;
char msg[BUFLEN] = {0};
struct sockaddr_in svraddr, fromaddr;

/* Create socket */
sckid = socket(AF_INET, SOCK_DGRAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Bind port */
bzero(&svraddr, sizeof(svraddr));

svraddr.sin_family = AF_INET;
svraddr.sin_addr.s_addr = htonl(INADDR_ANY);
svraddr.sin_port = htons(PORT);

ret = bind(sckid, (struct sockaddr *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Receive log information */
while(1)
{
memset(&fromaddr, 0, sizeof(fromaddr));

len = sizeof(fromaddr);

ret = recvfrom(sckid, msg, sizeof(msg), 0, (struct sockaddr *)&fromaddr, &len);
if(ret < 0)
{
if(EINTR == ret)
{
continue;
}
fprintf(stderr, "Recvfrom failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

write(fd, msg, ret);
}

close(sckid);
close(fd);

return 0;
}

3.2 客户端代码

  客户端代码主要将日志信息发送到服务端指定端口!

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, idx = 0;
struct sockaddr_in server;
char buf[BUFLEN] = {0};

memset(&server, 0, sizeof(server));

/* Create socket */
sckid = socket(AF_INET, SOCK_DGRAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Connect to server */
server.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &server.sin_addr);
server.sin_port = htons(PORT);

ret = connect(sckid, (void *)&server, sizeof(server));
if(ret < 0)
{
fprintf(stderr, "Connect failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
for(idx=0; idx<LOOP; idx++)
{
snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

ret = write(sckid, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
}

close(sckid);

return 0;
}

3.3 测试结果

  撰写100w条日志的测试结果如下图所示:[注:请关注红线区域的系统调用情况]



图2 UDP日志系统测试结果

4 U-TCP日志系统[异步]

4.1 服务端代码

-> 主函数代码

  主函数主要负责绑定指定文件,并等待接收客户端的连接请求,再启动子进程处理与客户端的交互![注:实际应用中可使用进程池或线程池机制进行完善]

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, clifd = 0, len = 0, flag = 1;
struct sockaddr_un svraddr, cliaddr;

/* Create socket */
sckid = socket(AF_UNIX, SOCK_STREAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

/* Bind port */
bzero(&svraddr, sizeof(svraddr));

svraddr.sun_family = AF_UNIX;
snprintf(svraddr.sun_path, sizeof(svraddr.sun_path), "%s", SVRPATH);

ret = bind(sckid, (struct sockaddr *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

ret = listen(sckid, 20);
if(ret < 0)
{
fprintf(stderr, "Listen failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
while(1)
{
memset(&cliaddr, 0, sizeof(cliaddr));

len = sizeof(cliaddr);
clifd = accept(sckid, (struct sockaddr *)&cliaddr, &len);

ret = fork();
if(ret < 0)
{
fprintf(stderr, "Fork failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}
else if(0 == ret)
{
close(sckid);
recv_msg(clifd);
exit(1);
}
}

close(sckid);

return 0;
}

-> 接收代码

  此函数被子进程调用,主要负责接收客户端的日志信息,并将信息写入到指定的日志文件中!

int recv_msg(int clifd)
{
int ret = 0, fd = -1;
char buf[1024] = {0};

fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

while(1)
{
memset(buf, 0, sizeof(buf));

ret = read(clifd, buf, sizeof(buf) - 1);
if(ret < 0)
{
if(EINTR == errno)
{
continue;
}

fprintf(stderr, "Read failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}
else if(0 == ret)
{
break;
}

write(fd, buf, ret);
}

close(clifd);
close(fd);

return 0;
}

4.2 客户端代码

  此代码主要负责侦听指定文件,同时将日志信息发送到服务端!

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, idx = 0, flag = 1;
struct sockaddr_un cliaddr, svraddr;
char buf[BUFLEN] = {0};

memset(&cliaddr, 0, sizeof(cliaddr));
memset(&svraddr, 0, sizeof(svraddr));

/* Create socket */
sckid = socket(AF_UNIX, SOCK_STREAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

/* Bind file */
cliaddr.sun_family = AF_UNIX;
snprintf(cliaddr.sun_path, sizeof(cliaddr.sun_path), "%s", CLI_LSN);

ret = bind(sckid, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Connect to server */
svraddr.sun_family = AF_UNIX;
snprintf(svraddr.sun_path, sizeof(svraddr.sun_path), "%s", SVR_LSN);

ret = connect(sckid, (void *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Connect failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
for(idx=0; idx<LOOP; idx++)
{
snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

ret = write(sckid, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
}

close(sckid);

return 0;
}

4.3 测试结果

  客户端写100W条日志的测试结果如下图所示:[注:请注意红线区域的系统调用情况]



图3 U-TCP日志系统测试结果

5 U-UDP日志系统[异步]

5.1 服务端代码

  此代码负责侦听指定文件,同时接受客户端发送过来的数据,再将信息写入指定日志文件中。[注:实际实现过程,可使用线程池进行完善]

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, len = 0, flag = 1, fd = -1;
struct sockaddr_un svraddr, fromaddr;
char msg[MSGLEN] = {0};

/* Create socket */
sckid = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

/* Bind port */
bzero(&svraddr, sizeof(svraddr));

svraddr.sun_family = AF_UNIX;
snprintf(svraddr.sun_path, sizeof(svraddr.sun_path), "%s", SVRPATH);

ret = bind(sckid, (struct sockaddr *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
while(1)
{
len = sizeof(fromaddr);
memset(&fromaddr, 0, sizeof(fromaddr));
fromaddr.sun_family = AF_UNIX;

ret = recvfrom(sckid, msg, sizeof(msg), 0, (struct sockaddr *)&fromaddr, &len);
if(ret < 0)
{
if(EINTR == errno)
{
continue;
}

fprintf(stderr, "Recvfrom failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}

write(fd, msg, ret);
}

close(sckid);
close(fd);

return 0;
}

5.2 客户端代码

  客户端代码侦听指定文件后,再将日志信息发送到服务端!

int main(int argc, const char *argv[])
{
int ret = 0, sckid = -1, idx = 0, flag = 1;
struct sockaddr_un cliaddr, svraddr;
char buf[BUFLEN] = {0};

memset(&cliaddr, 0, sizeof(cliaddr));
memset(&svraddr, 0, sizeof(svraddr));

/* Create socket */
sckid = socket(AF_UNIX, SOCK_DGRAM, 0);
if(sckid < 0)
{
fprintf(stderr, "Socket failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));

/* Bind file */
cliaddr.sun_family = AF_UNIX;
snprintf(cliaddr.sun_path, sizeof(cliaddr.sun_path), "%s", CLI_LSN);

ret = bind(sckid, (struct sockaddr *)&cliaddr, sizeof(cliaddr));
if(ret < 0)
{
fprintf(stderr, "Bind failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Connect to server */
svraddr.sun_family = AF_UNIX;
snprintf(svraddr.sun_path, sizeof(svraddr.sun_path), "%s", SVR_LSN);

ret = connect(sckid, (void *)&svraddr, sizeof(svraddr));
if(ret < 0)
{
fprintf(stderr, "Connect failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Send log information */
for(idx=0; idx<LOOP; idx++)
{
snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

ret = write(sckid, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
}

close(sckid);

return 0;
}

5.3 测试结果

  客户端写100W条日志的测试结果如下图所示:[注:请注意红线区域的系统调用情况]



图4 U-UDP日志系统测试结果

6 同步日志系统[无锁]

6.1 代码实现

  该函数是打开文件后,直接将日志写入指定文件中。[注:此日志系统适合在日志文件不共用的系统中]

int main(int argc, const char *argv[])
{
int ret = 0, fd = 0, idx = 0;
char buf[BUFLEN] = {0};

fd = open("test.log", O_CREAT|O_APPEND|O_WRONLY, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

/* Write log information */
for(idx=0; idx<LOOP; idx++)
{
snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

ret = write(fd, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
}

close(fd);

return 0;
}

6.2 测试结果

  客户端写100W条日志的测试结果如下图所示:[注:请注意红线区域的系统调用情况]



图5 同步日志系统(无锁)测试结果

7 同步日志系统[加锁]

7.1 代码实现

  该函数打开文件后,再往文件中写入日志信息之前,需要加锁并重新调整文件流的位置![注:此日志系统适合在日志文件共用的系统中]

int main(int argc, const char *argv[])
{
int ret = 0, fd = 0, idx = 0;
char buf[BUFLEN] = {0};

for(idx=0; idx<LOOP; idx++)
{
fd = open("test.log", O_CREAT|O_APPEND|O_WRONLY, 0666);
if(fd < 0)
{
fprintf(stderr, "Open failed! errmsg:[%d]%s\n", errno, strerror(errno));
return -1;
}

snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);

lockf(fd, F_LOCK, 0);
lseek(fd, 0, SEEK_END);

ret = write(fd, buf, strlen(buf));
if(ret < 0)
{
if(EINTR == errno)
{
--idx;
continue;
}

fprintf(stderr, "Write failed! errmsg:[%d]%s\n", errno, strerror(errno));
break;
}
close(fd);
}

return 0;
}

7.2 测试结果

  客户端写100W条日志的测试结果如下图所示:[注:请注意红线区域的系统调用情况]



图6 同步日志系统(加锁)测试结果

8 其他日志系统

  其他日志系统包括使用共享内存、消息队列等等方式实现的日志系统,因其过程相对较为复杂,在此不做实现!感兴趣的可以自己去实现,并对比一下各自的性能情况!

9 性能分析

  以上系统调用的结果是通过strace -c ./proc-name进行统计的,通过对比可知性能排序如下所示:(依次递减)

名次日志系统时间提高

(t1/t0)
性能对比

(t0-t1)/t1
01同步日志[无锁]1

(参照t0)
+285%
02U-UPD异步日志1.58+143.7%
03UDP异步日志1.70+126.5%
04U-TCP异步日志2.10+83.3%
05TCP异步日志2.10+83.3%
06同步日志[加锁]3.850%

(参照t0)
表1  性能排序
  总结:以上6种日志系统中,同步日志系统(无锁)的性能比其他5种日志系统的性能明显优异,而使用加锁的日志系统性能明显比其他的差很多!

  注意:使用共享内存的日志缓存+无锁机制+同步机制+SVR进程的日志系统的性能在同步日志系统[无锁]的基础上提高150%以上,关于此日志系统的设计我将在后续的博文中给出设计思路。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: