1. 程式人生 > >日誌系統性能對比分析

日誌系統性能對比分析

               


1 引言

  日誌系統主要負責記錄系統執行過程中的行為和資料,這些行為和資料將作為系統恢復、錯誤查詢、資料糾正的重要依據,其重要性可見一斑!但是往往對於一個有高效能要求的資訊系統而言,日誌系統往往又是整個系統的瓶頸所在,針對這個問題,以下為尋找一個更優的日誌系統設計方案做一些前期的探索工作。

  以下將對常用的日誌系統進行較簡單的實現和測試,因採用以下方式的日誌系統都是依賴於以下基本的實現過程,因此其基本上能較為準確的反應出各實現方式的效能差異!


2 TCP日誌系統[非同步]

2.1 服務端程式碼

-> 主函式程式碼

  主函式主要負責偵聽指定埠,等待接受客戶端的連線請求,同時啟動子程序與客戶端進行互動。[注意:實際應用中可以使用程序池和執行緒池機制進行完善,但是測試過程中不必過於複雜]

int main(int argc, const char *argv[])int ret = 0, sckid = -1
, clifd = 0, len = 0struct 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 = -1char 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 = 0struct 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條日誌的測試結果如下圖所示:[注:請關注紅線區域的系統呼叫情況]

LOG-TCP

圖1 TCP日誌系統測試結果


3 UDP日誌系統[非同步]

3.1 服務端程式碼

  此模組負責繫結指定埠,並接收UDP客戶端傳送過來的資料,並將資料寫入到指定的日誌檔案中。[注:在實際的應用過程中,可以引入執行緒池機制進行完善]

int main(int argc, const char *argv[])int ret = 0, sckid = -1, fd = 0, len = 0char 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 = 0struct 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條日誌的測試結果如下圖所示:[注:請關注紅線區域的系統呼叫情況]

LOG-UDP

圖2 UDP日誌系統測試結果


4 U-TCP日誌系統[非同步]

4.1 服務端程式碼

-> 主函式程式碼

  主函式主要負責繫結指定檔案,並等待接收客戶端的連線請求,再啟動子程序處理與客戶端的互動![注:實際應用中可使用程序池或執行緒池機制進行完善]

int main(int argc, const char *argv[])int ret = 0, sckid = -1, clifd = 0, len = 0, flag = 1struct 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 = -1char 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 = 1struct 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條日誌的測試結果如下圖所示:[注:請注意紅線區域的系統呼叫情況]

LOG-UTCP

圖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 = 1struct 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條日誌的測試結果如下圖所示:[注:請注意紅線區域的系統呼叫情況]

LOG-UUDP

圖4 U-UDP日誌系統測試結果


6 同步日誌系統[無鎖]

6.1 程式碼實現

  該函式是開啟檔案後,直接將日誌寫入指定檔案中。[注:此日誌系統適合在日誌檔案不共用的系統中]

int main(int argc, const char *argv[])int ret = 0, fd = 0, idx = 0char 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條日誌的測試結果如下圖所示:[注:請注意紅線區域的系統呼叫情況]

LOG-UNLOCK

圖5 同步日誌系統(無鎖)測試結果


7 同步日誌系統[加鎖]

7.1 程式碼實現

  該函式開啟檔案後,再往檔案中寫入日誌資訊之前,需要加鎖並重新調整檔案流的位置![注:此日誌系統適合在日誌檔案共用的系統中]

int main(int argc, const char *argv[])int ret = 0, fd = 0, idx = 0char 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