首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件架构设计 >

日记系统性能对比分析

2013-10-18 
日志系统性能对比分析一、基础概述日志系统主要负责记录系统运行过程中的行为和数据,这些行为和数据将作为

日志系统性能对比分析
一、基础概述

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

二、系统实现

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

2.1 TCP日志系统(异步)2.1.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, "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, "errmsg:[%d]%s\n", errno, strerror(errno));return -1;}ret = listen(sckid, 20);if(ret < 0){fprintf(stderr, "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, "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, 0777);if(fd < 0){fprintf(stderr, "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, "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.1.2 客户端代码

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

int main(int argc, const char *argv[]){int ret = 0, sckid = -1, idx = 0, len = 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}/* Send log information */for(idx=0; idx<LOOP; idx++){len = snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);ret = write(sckid, buf, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));break;}}close(sckid);return 0;}
2.1.3 测试结果

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

日记系统性能对比分析

图1 TCP日志系统测试结果

2.2 UDP日志系统(异步)2.2.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, "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, "errmsg:[%d]%s\n", errno, strerror(errno));return -1;}fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0777);if(fd < 0){fprintf(stderr, "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, "[%s][%d] errmsg:[%d]%s\n", errno, strerror(errno));return -1;}write(fd, msg, ret);}close(sckid);close(fd);return 0;}
2.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_DGRAM, 0);if(sckid < 0){fprintf(stderr, "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, "errmsg:[%d]%s\n", errno, strerror(errno));return -1;}/* Send log information */for(idx=0; idx<LOOP; idx++){len = snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);ret = write(sckid, buf, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "errmsg:[%d]%s\n", errno, strerror(errno));break;}}close(sckid);return 0;}
2.2.3 测试结果

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

日记系统性能对比分析

图2 UDP日志系统测试结果

2.3 UNIX-TCP日志系统(异步)2.3.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, "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, "errmsg:[%d]%s\n", errno, strerror(errno));return -1;}ret = listen(sckid, 20);if(ret < 0){fprintf(stderr, "[%s][%d]errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d]errmsg:[%d]%s\n", __FILE__, __LINE__, 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, 0777);if(fd < 0){fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}else if(0 == ret){break;}write(fd, buf, ret);}close(clifd);close(fd);return 0;}
2.3.2 客户端代码

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

int main(int argc, const char *argv[]){int ret = 0, sckid = -1, idx = 0, flag = 1, len = 0;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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}/* Send log information */for(idx=0; idx<LOOP; idx++){len = snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);ret = write(sckid, buf, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));break;}}close(sckid);return 0;}
2.3.3 测试结果

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

日记系统性能对比分析

图3 UTCP日志系统测试结果

2.4 UNIX-UDP日志系统(异步)2.4.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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));        return -1;    }    setsockopt(sckid, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));    fd = open("test.log", O_CREAT|O_WRONLY|O_APPEND, 0777);    if(fd < 0)    {        fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));            break;        }        write(fd, msg, ret);    }    close(sckid);    close(fd);    return 0;}
2.4.2 客户端代码

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

int main(int argc, const char *argv[]){int ret = 0, sckid = -1, idx = 0, flag = 1, len = 0;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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}/* Send log information */for(idx=0; idx<LOOP; idx++){len = snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);ret = write(sckid, buf, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));break;}}close(sckid);return 0;}
2.4.3 测试结果

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

日记系统性能对比分析

图4 UUDP日志系统测试结果

2.5 同步日志系统(无锁)2.5.1 代码实现

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

int main(int argc, const char *argv[]){int ret = 0, fd = 0, idx = 0, len = 0;char buf[BUFLEN] = {0};fd = open("test.log", O_CREAT|O_APPEND|O_WRONLY, 0666);if(fd < 0){fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}/* Write log information */for(idx=0; idx<LOOP; idx++){len = snprintf(buf, sizeof(buf), "This is just a test![%d]\n", idx);ret = write(fd, buf, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));break;}}close(fd);return 0;}
2.5.2 测试结果

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

日记系统性能对比分析

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

2.6 同步日志系统(加锁)2.6.1 代码实现

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

int main(int argc, const char *argv[]){int ret = 0, fd = 0, idx = 0, len = 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, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));return -1;}len = 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, len);if(ret < 0){if(EINTR == errno){--idx;continue;}fprintf(stderr, "[%s][%d] errmsg:[%d]%s\n", __FILE__, __LINE__, errno, strerror(errno));break;}close(fd);}return 0;}
2.6.2 测试结果

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

日记系统性能对比分析

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

2.7 其他日志系统

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

三、性能分析

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

        1、同步日志系统(无锁)

        2、UNIX-UDP日志系统

        3、UDP日志系统

        4、UNIX-TCP日志系统

        5、TCP日志系统

        6、同步日志系统(加锁)

    总结:以上6种日志系统中,同步日志系统(无锁)的性能比其他5种日志系统的性能明显优异!但是通过共享内存和消息队列实现的日志系统的系能可能会更加优异!

— 邹祁峰

2013.10.17

热点排行