本文是第一個開源項目研究系列,第一個開源項目選擇的是brpc,brpc是百度內最常使用的工業級RPC框架, 有1,000,000+個實例(不包含client)和上千種多種服務, 在百度內叫做"baidu-rpc". 目前只開源C++版本。
ps:為啥選擇brpc,因為算法想要真正在實際中運用,我們還是得要一個高性能實現,而brpc能幫助我們快速解決網絡交互問題,讓我們去實現算法邏輯。
在在線系統中,日誌是非常重要的功能,能幫助我們分析、定位問題,下面會介紹如果讓我們自己設計一個高性能的日誌系統,我們應該怎麼做。
日誌
日誌我們平時都用,如果自己來設計一個日誌框架,需要從哪些角度考慮呢?
我們先來考慮我們使用日誌的話,他應該有哪些最基本的功能?
場景:程序在運行過程中,首先發出要記錄的信息,然後通過約定格式化這些信息,最後再將其輸出到目的地,所以一個日誌框架需要有的基本功能有:
- 日誌記錄
- 格式化
- 輸出地
日誌記錄我們可以看做是一個前端api,而輸出地則是後端,兩者之間經過一個格式化組件進行數據的規範、傳輸。另外在分佈式系統中,日誌的輸出地只有一個,那就是本地磁盤。
在日誌框架的整體設計上,可以抽象為一個多生產者、單消費者的模型。前端通過api不斷寫入日誌,後端有一個消費者對日誌進行輸出,而目的就是本次磁盤。
如果輸出地是磁盤,那就必須要有日誌文件滾動功能,滾動的條件一般有兩個:
1. 日誌大小(每1G)
2. 時間(每隔1小時)
另外,如果我們要寫磁盤,那io就是關鍵,下面介紹下深入介紹下Linux 文件 io。
Linux 標準io
要想了解fwrite,最好的方式就是進行調試,下面是動手環節,大家可以按照下面的步驟,自己動手實驗的,便於理解,有任何問題都可以留言,儘量回答。
下面記錄下如何在開發機器上調試glibc代碼
因為在mac上開發,所以首先得有個Ubuntu鏡像。
1. docker run -it --name="gpp" ubuntu /bin/bash
2. apt-get unpdate && apt-get install -y ubuntu-dev-tools vim
3. docker commit gpp zhuanxuhit/ubuntu:v1
此處安裝 ubuntu-dev-tools 開發者工具。一勞永逸
參考文檔 跟我一起學Docker——搭建編譯環境篇
下一步是啟動了,啟動過程中,因為需要運行gdb,需要在啟動時加上--privileged=true參數,具體可以看:
dockercontainer下gdb無法正常工作的解決辦法
docker run --privileged=true -it -v ~/dev/share:/home/binss --name="gpp" ubuntu /bin/bash
下面是一段簡單的代碼:
gcc -g3 -O0 -gdwarf-4 -ggdb test.c -o test
運行出core,我們通過ulimit -c unlimited在當前文件夾下產生core文件,具體可以看 Linux 下如何產生core文件(core dump設置)
發現是沒有temp文件導致的,創建touch temp後繼續運行。
gdb a.out
> l
> b 5
> r
> s
出現錯誤
_IO_new_fopen (filename=0x5555555547e6 "./temp", mode=0x5555555547e4 "r") at iofopen.c:88
88 iofopen.c: No such file or directory.
這是需要我們去下載glibc源文件
root@28dcf784e1be:/home/binss# ldd a.out
linux-vdso.so.1 (0x00007ffd7b3c1000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f63d95b1000)
/lib64/ld-linux-x86-64.so.2 (0x00007f63d9ba4000)
依賴的是 libc.so.6
直接查看 /lib/x86_64-linux-gnu/libc.so.6
root@28dcf784e1be:/home/binss# ll /lib/x86_64-linux-gnu/libc.so.6
lrwxrwxrwx 1 root root 12 Apr 16 20:14 /lib/x86_64-linux-gnu/libc.so.6 -> libc-2.27.so*
執行 libc-2.27.so
接著我們下載libc的源代碼,需要修改下source.list
具體的源可以查看:https://mirror.tuna.tsinghua.edu.cn/help/ubuntu/,注意打開deb-source,下載源代碼。
安裝完代碼後,我們就可以開始調試了。通過
(gdb) directory /home/binss/glibc-2.27/libio
設置好源代碼搜索目錄,下面開始調試。
通過gdb可以直接定位到fwrite的源碼,源碼如下,位於./libio/iofwrite.c。
設置gdb選項 set print pretty on
先到了iofwrite.c:31
下面我們看下gdb如任調試宏定義,怎麼在gdb中調試宏,可以參照https://sourceware.org/gdb/onlinedocs/gdb/Macros.html
gcc -g3 -O0 -gdwarf-2 test.c
上面我們怎麼知道gdb調試的時候會去加載glibc的debug版本呢?
通過設置 verbose on可以看到gdb會去自動加載符號進來。
最終 _IO_sputn 被化簡為:IO_validate_vtable(const struct _IO_jump_t)-> __xsputn(fp, buf, request)
__xsputn 是 _IO_jump_t 中的指針
(gdb) s
_IO_new_file_xsputn (f=0x555555756260, data=0x7fffffffe67b, n=12) at fileops.c:1220
1220 {
整個調用棧
將新申請到的內存設置到f->_IO_buf_base 中, 最終執行完後,fp中內存數據,我們將數據從用戶空間拷貝到了glibc分配的空間中。
上面我們可以看到,我們將數據是寫入到了glibc的緩衝區中,下面通過fflush,將其寫入到內核緩衝區中。調用函數fflush,gdb調試:
現在調用鏈到了系統調用了
系統調用部分,先上一張圖:
記得大學那塊,自己做嵌入式開發,就是看好多Linux驅動程序編寫。這塊有機會以後專門開個專題來介紹的。
現在總結下目前的進展,我們此次調試的目的是想看下fwrite是怎麼一步一步將數據寫入磁盤的,我們發現要想寫入磁盤,首先我們的數據會從用戶緩衝區中被拷貝到glibc的緩衝區,然後glibc再進行系統調用,將數據寫入到內核緩衝區,然後設備驅動程序再將數據從內核緩衝區寫到設備緩衝區,整個過程可以看下圖:
圖片來自文章:漫談linux文件IO,文中很好的闡述了整個io過程,推薦閱讀。
總結
本文介紹了程序中重要的日誌功能,為了能實現高性能日誌,我們去分析了底層寫入磁盤到底發生了什麼,發現其中一層層的數據拷貝,這些完全都是可以優化的,所以下一篇會去介紹目前高性能庫的做法,歡迎持續關注。
閱讀更多 程序猿的進擊之路 的文章