一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|JAVA教程|ASP教程|編程技術|正則表達式|C/C++|

服務器之家 - 編程語言 - JAVA教程 - 打通IO棧:一次編譯服務器性能優化實戰

打通IO棧:一次編譯服務器性能優化實戰

2020-10-31 00:22今日頭條Java碼農之路 JAVA教程

隨著企業SDK在多條產品線的廣泛使用,隨著SDK開發人員的增長,每日往SDK提交的補丁量與日俱增,自動化提交代碼檢查的壓力已經明顯超過了通用服務器的負載。于是向公司申請了一臺專用服務器,用于SDK構建檢查。

背景

隨著企業SDK在多條產品線的廣泛使用,隨著SDK開發人員的增長,每日往SDK提交的補丁量與日俱增,自動化提交代碼檢查的壓力已經明顯超過了通用服務器的負載。于是向公司申請了一臺專用服務器,用于SDK構建檢查。

$ cat /proc/cpuinfo | grep ^proccessor | wc -l 

48 

free -h 

             total       used       free     shared    buffers     cached 

Mem:           47G        45G       1.6G        20M       7.7G        25G 

-/+ buffers/cache:        12G        35G 

Swap:           0B         0B         0B 

$ df 

文件系統                          容量  已用  可用 已用% 掛載點 

...... 

/dev/sda1                          98G   14G   81G   15% / 

/dev/vda1                         2.9T  1.8T  986G   65% /home 

這是KVM虛擬的服務器,提供了CPU 48線程,實際可用47G內存,磁盤空間約達到3TB。

由于獨享服務器所有資源,設置了十來個worker并行編譯,從提交補丁到發送編譯結果的速度杠杠的。但是在補丁提交非常多的時候,速度瞬間就慢了下去,一次提交觸發的編譯甚至要1個多小時。通過top看到CPU負載并不高,難道是IO瓶頸?找IT要到了root權限,干起來!

由于認知的局限性,如有考慮不周的地方,希望一起交流學習

整體認識IO棧

如果有完整的IO棧的認識,無疑有助于更細膩的優化IO。循著IO棧從上往下的順序,我們逐層分析可優化的地方。

在網上有Linux完整的IO棧結構圖,但太過完整反而不容易理解。按我的認識,簡化過后的IO棧應該是下圖的模樣。

打通IO棧:一次編譯服務器性能優化實戰
  • 用戶空間:除了用戶自己的APP之外,也隱含了所有的庫,例如常見的C庫。我們常用的IO函數,例如open()/read()/write()是系統調用,由內核直接提供功能實現,而fopen()/fread()/fwrite()則是C庫實現的函數,通過封裝系統調用實現更高級的功能。
  • 虛擬文件系統:屏蔽具體文件系統的差異,向用戶空間提供統一的入口。具體的文件系統通過register_filesystem()向虛擬文件系統注冊掛載鉤子,在用戶掛載具體的文件系統時,通過回調掛載鉤子實現文件系統的初始化。虛擬文件系統提供了inode來記錄文件的元數據,dentry記錄了目錄項。對用戶空間,虛擬文件系統注冊了系統調用,例如SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)注冊了open()的系統調用。
  • 具體的文件系統:文件系統要實現存儲空間的管理,換句話說,其規劃了哪些空間存儲了哪些文件的數據,就像一個個收納盒,A文件保存在這個塊,B文件則放在哪個塊。不同的管理策略以及其提供的不同功能,造就了各式各樣的文件系統。除了類似于vfat、ext4、btrfs等常見的塊設備文件系統之外,還有sysfs、procfs、pstorefs、tempfs等構建在內存上的文件系統,也有yaffs,ubifs等構建在Flash上的文件系統。
  • 頁緩存:可以簡單理解為一片存儲著磁盤數據的內存,不過其內部是以頁為管理單元,常見的頁大小是4K。這片內存的大小不是固定的,每有一筆新的數據,則申請一個新的內存頁。由于內存的性能遠大于磁盤,為了提高IO性能,我們就可以把IO數據緩存在內存,這樣就可以在內存中獲取要的數據,不需要經過磁盤讀寫的漫長的等待。申請內存來緩存數據簡單,如何管理所有的頁緩存以及如何及時回收緩存頁才是精髓。
  • 通用塊層:通用塊層也可以細分為bio層和request層。頁緩存以頁為管理單位,而bio則記錄了磁盤塊與頁之間的關系,一個磁盤塊可以關聯到多個不同的內存頁中,通過submit_bio()提交bio到request層。一個request可以理解為多個bio的集合,把多個地址連續的bio合并成一個request。多個request經過IO調度算法的合并和排序,有序地往下層提交IO請求。
  • 設備驅動與塊設備:不同塊設備有不同的使用協議,而特定的設備驅動則是實現了特定設備需要的協議以正常驅使設備。對塊設備而言,塊設備驅動需要把request解析成一個個設備操作指令,在協議的規范下與塊設備通信來交換數據。

形象點來說,發起一次IO讀請求的過程是怎么樣的呢?

用戶空間通過虛擬文件系統提供的統一的IO系統調用,從用戶態切到內核態。虛擬文件系統通過調用具體文件系統注冊的回調,把需求傳遞到具體的文件系統中。緊接著具體的文件系統根據自己的管理邏輯,換算到具體的磁盤塊地址,從頁緩存尋找塊設備的緩存數據。讀操作一般是同步的,如果在頁緩存沒有緩存數據,則向通用塊層發起一次磁盤讀。通用塊層合并和排序所有進程產生的的IO請求,經過設備驅動從塊設備讀取真正的數據。最后是逐層返回。讀取的數據既拷貝到用戶空間的buffer中,也會在頁緩存中保留一份副本,以便下次快速訪問。

如果 頁緩存沒命中,同步都會一路通到 塊設備,而對于 異步寫,則是把數據放到 頁緩存后返回,由內核回刷進程在合適時候回刷到 塊設備。

根據這個流程,考慮到我沒要到KVM host的權限,我只能著手從Guest端的IO棧做優化,具體包括以下幾個方面:

  • 交換分區(swap)
  • 文件系統(ext4)
  • 頁緩存(Page Cache)
  • Request層(IO調度算法)

由于源碼以及編譯的臨時文件都不大但數量極其多,對隨機IO的要求非常高。要提高隨機IO的性能,在不改變硬件的情況下,需要緩存更多數據,以實現合并更多的IO請求。

咨詢ITer得知,服務器都有備用電源,能確保不會掉電停機。出于這樣的情況,我們可以盡可能優化速度,而不用擔心掉電導致數據丟失問題。

總的來說,優化的核心思路是盡可能多的使用內存緩存數據,盡可能減小不必要的開銷,例如文件系統為了保證數據一致性使用日志造成的開銷。

交換分區

交換分區的存在,可以讓內核在內存壓力大時,把內核認為一些不常用的內存置換到交換分區,以此騰出更多的內存給系統。在物理內存容量不足且運行吃內存的應用時,交換分區的作用效果是非常明顯的。

然而本次優化的服務器反而不應該使用交換分區。為什么呢?服務器總內存達到47G,且服務器除了Jenkins slave進程外沒有大量吃內存的進程。從內存的使用情況來看,絕大部分內存都是被cache/buffer占用,是可丟棄的文件緩存,因此內存是充足的,不需要通過交換分區擴大虛擬內存。

free -h 

             total       used       free     shared    buffers     cached 

Mem:           47G        45G       1.6G        21M        18G        16G 

-/+ buffers/cache:        10G        36G 

交換分區也是磁盤的空間,從交換分區置入置出數據可也是要占用IO資源的,與本次IO優化目的相悖,因此在此服務器中,需要取消swap分區。

查看系統狀態發現,此服務器并沒使能swap。

# cat /proc/swaps  

Filename Type Size Used Priority  

文件系統

用戶發起一次讀寫,經過了虛擬文件系統(VFS)后,交給了實際的文件系統。

首先查詢分區掛載情況:

# mount 

... 

/dev/sda1 on on / type ext4 (rw) 

/dev/vda1 on /home type ext4 (rw) 

... 

此服務器主要有兩個塊設備,分別是 sda和 vda。sda 是常見的 SCSI/IDE 設備,我們個人PC上如果使用的機械硬盤,往往就會是 sda 設備節點。vda 是 virtio 磁盤設備。由于本服務器是 KVM 提供的虛擬機,不管是 sda 還是 vda,其實都是虛擬設備,差別在于前者是完全虛擬化的塊設備,后者是半虛擬化的塊設備。從網上找到的資料來看,使用半虛擬化的設備,可以實現Host與Guest更高效的協作,從而實現更高的性能。在此例子中,sda 作為根文件系統使用,vda 則是用于存儲用戶數據,在編譯時,主要看的是 vda 分區的IO情況。

vda 使用 ext4 文件系統。ext4 是目前常見的Linux上使用的穩定的文件系統,查看其超級塊信息:

# dumpe2fs /dev/vda1 

... 

Filesystem features:      has_journal dir_index ... 

... 

Inode count:              196608000 

Block count:              786431991 

Free inodes:              145220571 

Block size:               4096 

... 

我猜測ITer使用的默認參數格式化的分區,為其分配了塊大小為4K,inode數量達到19660萬個且使能了日志。

塊大小設為4K無可厚非,適用于當前源文件偏小的情況,也沒必要為了更緊湊的空間降低塊大小??臻e inode 達到 14522萬,空閑占比達到 73.86%。當前 74% 的空間使用率,inode只使用了26.14%。一個inode占256B,那么10000萬個inode占用23.84G。inode 實在太多了,造成大量的空間浪費??上В琲node數量在格式化時指定,后期無法修改,當前也不能簡單粗暴地重新格式化。

我們能做什么呢?我們可以從日志和掛載參數著手優化

日志是為了保證掉電時文件系統的一致性,(ordered日志模式下)通過把元數據寫入到日志塊,在寫入數據后再修改元數據。如果此時掉電,通過日志記錄可以回滾文件系統到上一個一致性的狀態,即保證元數據與數據是匹配的。然而上文有說,此服務器有備用電源,不需要擔心掉電,因此完全可以把日志取消掉。

# tune2fs -O ^has_journal /dev/vda1 

tune2fs 1.42.9 (4-Feb-2014) 

The has_journal feature may only be cleared when the filesystem is 

unmounted or mounted read-only

可惜失敗了。由于時刻有任務在執行,不太好直接umount或者-o remount,ro,無法在掛載時取消日志。既然取消不了,咱們就讓日志減少損耗,就需要修改掛載參數了。

ext4掛載參數: data

ext4有3種日志模式,分別是ordered,writeback,journal。他們的差別網上有很多資料,我簡單介紹下:

  • jorunal:把元數據與數據一并寫入到日志塊。性能差不多折半,因為數據寫了兩次,但最安全
  • writeback: 把元數據寫入日志塊,數據不寫入日志塊,但不保證數據先落盤。性能最高,但由于不保證元數據與數據的順序,也是掉電最不安全的
  • ordered:與writeback相似,但會保證數據先落盤,再是元數據。這種性能以保證足夠的安全,這是大多數PC上推薦的默認的模式

在不需要擔心掉電的服務器環境,我們完全可以使用writeback的日志模式,以獲取最高的性能。

# mount -o remount,rw,data=writeback /home 

mount: /home not mounted or bad option 

# dmesg 

[235737.532630] EXT4-fs (vda1): Cannot change data mode on remount 

沮喪,又是不能動態改,干脆寫入到/etc/config,只能寄希望于下次重啟了。

# cat /etc/fstab 

UUID=...  /home  ext4  defaults,rw,data=writeback... 

ext4掛載參數:noatime

Linux上對每個文件都記錄了3個時間戳

時間戳全稱含義atimeaccess time訪問時間,就是最近一次讀的時間mtimedata modified time數據修改時間,就是內容最后一次改動時間ctimestatus change time文件狀態(元數據)的改變時間,比如權限,所有者等

我們編譯執行的Make可以根據修改時間來判斷是否要重新編譯,而atime記錄的訪問時間其實在很多場景下都是多余的。所以,noatime應運而生。不記錄atime可以大量減少讀造成的元數據寫入量,而元數據的寫入往往產生大量的隨機IO。

# mount -o ...noatime... /home 

ext4掛載參數:nobarrier

這主要是決定在日志代碼中是否使用寫屏障(write barrier),對日志提交進行正確的磁盤排序,使易失性磁盤寫緩存可以安全使用,但會帶來一些性能損失。從功能來看,跟writeback和ordered日志模式非常相似。沒研究過這方面的源碼,說不定就是一回事。不管怎么樣,禁用寫屏障毫無疑問能提高寫性能。

# mount -o ...nobarrier... /home 

ext4掛載參數:delalloc

delalloc是 delayed allocation 的縮寫,如果使能,則ext4會延緩申請數據塊直至超時。為什么要延緩申請呢?在inode中采用多級索引的方式記錄了文件數據所在的數據塊編號,如果出現大文件,則會采用 extent 區段的形式,分配一片連續的塊,inode中只需要記錄開始塊號與長度即可,不需要索引記錄所有的塊。這除了減輕inode的壓力之外,連續的塊可以把隨機寫改為順序寫,加快寫性能。連續的塊也符合 局部性原理,在預讀時可以加大命中概率,進而加快讀性能。

# mount -o ...delalloc... /home 

ext4掛載參數:inode_readahead_blks

ext4從inode表中預讀的indoe block最大數量。訪問文件必須經過inode獲取文件信息、數據塊地址。如果需要訪問的inode都在內存中命中,就不需要從磁盤中讀取,毫無疑問能提高讀性能。其默認值是32,表示最大預讀 32 × block_size 即 64K 的inode數據,在內存充足的情況下,我們毫無疑問可以進一步擴大,讓其預讀更多。

# mount -o ...inode_readahead_blks=4096... /home 

ext4掛載參數:journal_async_commit

commit塊可以不等待descriptor塊,直接往磁盤寫。這會加快日志的速度。

# mount -o ...journal_async_commit... /home 

ext4掛載參數:commit

ext4一次緩存多少秒的數據。默認值是5,表示如果此時掉電,你最多丟失5s的數據量。設置更大的數據,就可以緩存更多的數據,相對的掉電也有可能丟失更多的數據。在此服務器不怕掉電的情況,把數值加大可以提高性能。

# mount -o ...commit=1000... /home 

ext4掛載參數匯總

最終在不能umount情況下,我執行的調整掛載參數的命令為:

mount -o remount,rw,noatime,nobarrier,delalloc,inode_readahead_blks=4096,journal_async_commit,commit=1800  /home 

此外,在/etc/fstab中也對應修改過來,避免重啟后優化丟失

# cat /etc/fstab 

UUID=...  /home  ext4  defaults,rw,noatime,nobarrier,delalloc,inode_readahead_blks=4096,journal_async_commit,commit=1800,data=writeback 0 0 

... 

頁緩存

頁緩存在FS與通用塊層之間,其實也可以歸到通用塊層中。為了提高IO性能,減少真實的從磁盤讀寫的次數,Linux內核設計了一層內存緩存,把磁盤數據緩存到內存中。由于內存以4K大小的 頁 為單位管理,磁盤數據也以頁為單位緩存,因此也稱為頁緩存。在每個緩存頁中,都包含了部分磁盤信息的副本。

如果因為之前讀寫過或者被預讀加載進來,要讀取數據剛好在緩存中命中,就可以直接從緩存中讀取,不需要深入到磁盤。不管是同步寫還是異步寫,都會把數據copy到緩存,差別在于異步寫只是copy且把頁標識臟后直接返回,而同步寫還會調用類似fsync()的操作等待回寫,詳細可以看內核函數generic_file_write_iter()。異步寫產生的臟數據會在“合適”的時候被內核工作隊列writeback進程回刷。

那么,什么時候是合適的時候呢?最多能緩存多少數據呢?對此次優化的服務器而言,毫無疑問延遲回刷可以在頻繁的刪改文件中減少寫磁盤次數,緩存更多的數據可以更容易合并隨機IO請求,有助于提升性能。

在/proc/sys/vm中有以下文件與回刷臟數據密切相關:

配置文件功能默認值dirty_background_ratio觸發回刷的臟數據占可用內存的百分比0dirty_background_bytes觸發回刷的臟數據量10dirty_bytes觸發同步寫的臟數據量0dirty_ratio觸發同步寫的臟數據占可用內存的百分比20dirty_expire_centisecs臟數據超時回刷時間(單位:1/100s)3000dirty_writeback_centisecs回刷進程定時喚醒時間(單位:1/100s)500

對上述的配置文件,有幾點要補充的:

  • XXX_ratio 和 XXX_bytes 是同一個配置屬性的不同計算方法,優先級 XXX_bytes > XXX_ratio
  • 可用內存并不是系統所有內存,而是free pages + reclaimable pages
  • 臟數據超時表示內存中數據標識臟一定時間后,下次回刷進程工作時就必須回刷
  • 回刷進程既會定時喚醒,也會在臟數據過多時被動喚醒。

dirty_background_XXX與dirty_XXX的差別在于前者只是喚醒回刷進程,此時應用依然可以異步寫數據到Cache,當臟數據比例繼續增加,觸發dirty_XXX的條件,不再支持應用異步寫。

更完整的功能介紹,可以看內核文檔Documentation/sysctl/vm.txt,也可看我寫的一篇總結博客《Linux 臟數據回刷參數與調優》

對當前的案例而言,我的配置如下:

dirty_background_ratio = 60 

dirty_ratio = 80 

dirty_writeback_centisecs = 6000 

dirty_expire_centisecs = 12000 

這樣的配置有以下特點:

  • 當臟數據達到可用內存的60%時喚醒回刷進程
  • 當臟數據達到可用內存的80%時,應用每一筆數據都必須同步等待
  • 每隔60s喚醒一次回刷進程
  • 內存中臟數據存在時間超過120s則在下一次喚醒時回刷

當然,為了避免重啟后丟失優化結果,我們在/etc/sysctl.conf中寫入:

# cat /etc/sysctl.conf 

... 

vm.dirty_background_ratio = 60 

vm.dirty_ratio = 80 

vm.dirty_expire_centisecs = 12000 

vm.dirty_writeback_centisecs = 6000 

Request層

在異步寫的場景中,當臟頁達到一定比例,就需要通過通用塊層把頁緩存里的數據回刷到磁盤中。bio層記錄了磁盤塊與內存頁之間的關系,在request層把多個物理塊連續的bio合并成一個request,然后根據特定的IO調度算法對系統內所有進程產生的IO請求進行合并、排序。那么都有什么IO調度算法呢?

網上檢索IO調度算法,大量的資料都在描述Deadline,CFQ,NOOP這3種調度算法,卻沒有備注這只是單隊列上適用的調度算法。在最新的代碼上(我分析的代碼版本為 5.7.0),已經完全切換到multi-queue的新架構上了,支持的IO調度算法就成了mq-deadline,BFQ,Kyber,none。

關于不同IO調度算法的優劣,網上有非常多的資料,本文不再累述。

在《Linux-storage-stack-diagram_v4.10》 對 Block Layer 的描述可以形象闡述單隊列與多隊列的差異。

打通IO棧:一次編譯服務器性能優化實戰
打通IO棧:一次編譯服務器性能優化實戰

單隊列的架構,一個塊設備只有一個全局隊列,所有請求都要往這個隊列里面塞,這在多核高并發的情況下,尤其像服務器動則32個核的情況下,為了保證互斥而加的鎖就導致了非常大的開銷。此外,如果磁盤支持多隊列并行處理,單隊列的模型不能充分發揮其優越的性能。

多隊列的架構下,創建了Software queues和Hardware dispatch queues兩級隊列。Software queues是每個CPU core一個隊列,且在其中實現IO調度。由于每個CPU一個單獨隊列,因此不存在鎖競爭問題。Hardware Dispatch Queues的數量跟硬件情況有關,每個磁盤一個隊列,如果磁盤支持并行N個隊列,則也會創建N個隊列。在IO請求從Software queues提交到Hardware Dispatch Queues的過程中是需要加鎖的。理論上,多隊列的架構的效率最差也只是跟單隊列架構持平。

咱們回到當前待優化的服務器,當前使用的是什么IO調度器呢?

# cat /sys/block/vda/queue/scheduler 

none 

# cat /sys/block/sda/queue/scheduler 

noop [deadline] cfq 

這服務器的內核版本是

# uname -r 

3.13.0-170-generic 

查看Linux內核git提交記錄,發現在 3.13.0 的內核版本上還沒有實現適用于多隊列的IO調度算法,且此時還沒完全切到多隊列架構,因此使用單隊列的 sda 設備依然存在傳統的noop,deadline和cfq調度算法,而使用多隊列的 vda 設備(virtio)的IO調度算法只有none。為了使用mq-deadline調度算法把內核升級的風險似乎很大。因此IO調度算法方面沒太多可優化的。

但Request層優化只能這樣了?既然IO調度算法無法優化,我們是否可以修改queue相關的參數?例如加大Request隊列的長度,加大預讀的數據量。

在/sys/block/vda/queue中有兩個可寫的文件nr_requests和read_ahead_kb,前者是配置塊層最大可以申請的request數量,后者是預讀最大的數據量。默認情況下,

nr_request = 128 

read_ahead_kb = 128 

我擴大為

nr_request = 1024 

read_ahead_kb = 512 

優化效果

優化后,在滿負荷的情況下,查看內存使用情況:

# cat /proc/meminfo 

MemTotal:       49459060 kB 

MemFree:         1233512 kB 

Buffers:        12643752 kB 

Cached:         21447280 kB 

Active:         19860928 kB 

Inactive:       16930904 kB 

Active(anon):    2704008 kB 

Inactive(anon):    19004 kB 

Active(file):   17156920 kB 

Inactive(file): 16911900 kB 

... 

Dirty:           7437540 kB 

Writeback:          1456 kB 

可以看到,文件相關內存(Active(file) + Inactive(file) )達到了32.49GB,臟數據達到7.09GB。臟數據量比預期要少,遠沒達到dirty_background_ratio和dirty_ratio設置的閾值。因此,如果需要緩存更多的寫數據,只能延長定時喚醒回刷的時間dirty_writeback_centisecs。這個服務器主要用于編譯SDK,讀的需求遠大于寫,因此緩存更多的臟數據沒太大意義。

我還發現Buffers達到了12G,應該是ext4的inode占用了大量的緩存。如上分析的,此服務器的ext4有大量富余的inode,在緩存的元數據里,無效的inode不知道占比多少。減少inode數量,提高inode利用率,說不定可以提高inode預讀的命中率。

優化后,一次使能8個SDK并行編譯,走完一次完整的編譯流程(包括更新代碼,抓取提交,編譯內核,編譯SDK等),在沒有進入錯誤處理流程的情況下,用時大概13分鐘。

這次的優化就到這里結束了,等后期使用過程如果還有問題再做調整。

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 好吊色永久免费视频大全 | 国产精品视频第一区二区 | 俺去啦最新 | 美女大乳被捏羞羞漫画 | 性xxxx中国老妇506070 | 俄罗斯精品bbw | 亚洲精品一区二区三区在线观看 | 久久久这里有精品999 | 性柔术xxxhd 性派对videos18party | 亚洲 日本 中文字幕 制服 | 亚洲免费色图 | 国产精品视频第一页 | 日本一区二区三区视频在线观看 | 国产午夜亚洲精品理论片不卡 | 亚洲国产美女精品久久久久 | 激情小视频网站 | 韩国最新三级网站在线播放 | 亚洲经典激情春色另类 | 色综合合久久天天综合绕视看 | 亚洲成人一区二区 | sese在线 | 麻豆婷婷 | 日韩网站在线 | 狠狠的撞进去嗯啊h女强男视频 | 双性小说肉 | 亚洲国产综合另类视频 | 亚洲日韩精品欧美一区二区 | 91精品手机国产露脸 | 秋霞午夜视频 | 91精品导航在线观看 | 亚洲激情在线 | 日本一区二区三区久久 | 嫩草影院永久在线一二三四 | 日韩一卡2卡3卡新区网站 | 精品免费视频 | 52zfl宅福利yxpjw | 男女羞羞的视频 | 日韩高清成人毛片不卡 | 韩国帅男同gay网站 韩国三级在线播放 | 紧缚束缚调教丨vk | 国产成人精品免费视频大全五级 |