日誌系統性能對比分析
- 作者:鄒祁峰
- 郵箱:[email protected]
- 部落格: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% |
02 | U-UPD非同步日誌 | 1.58 | +143.7% |
03 | UDP非同步日誌 | 1.70 | +126.5% |
04 | U-TCP非同步日誌 | 2.10 | +83.3% |
05 | TCP非同步日誌 | 2.10 | +83.3% |
06 | 同步日誌[加鎖] | 3.85 | 0% (參照t0) |
表1 效能排序
總結:以上6種日誌系統中,同步日誌系統(無鎖)的效能比其他5種日誌系統的效能明顯優異,而使用加鎖的日誌系統性能明顯比其他的差很多!
注意:使用共享記憶體的日誌快取+無鎖機制+同步機制+SVR程序的日誌系統的效能在同步日誌系統[無鎖]的基礎上提高150%以上,關於此日誌系統的設計我將在後續的博文中給出設計思路。
再分享一下我老師大神的人工智慧教程吧。零基礎!通俗易懂!風趣幽默!還帶黃段子!希望你也加入到我們人工智慧的隊伍中來!https://blog.csdn.net/jiangjunshow