原文連結
還有個與之類似的是buffer。這裡就談談buffer和cache。
那麼他們到底是用來幹什麼的呢?其實他們就是在兩個相對獨立的系統之間的一個中介層,用來避免這兩個系統之間不必要的互動和不不必要的或者重複的同步。同步,你懂的,不同數量級系統之間的同步,你也懂的。比如記憶體和磁碟之間,比如應用和資料庫之間。buffer針對寫,cache針對讀。
這篇文章先來看看Linux裡面系統的那些cache和buffer(至於硬體裡面的一些cache如cpu指令緩衝這裡就不談了)。
首先是檔案IO
對於寫操作通常我們會遇到兩個兩個緩衝 (buffer):
一個是核心緩衝。
當我們調用write寫檔案時,write返回之後其實內容並沒有立刻寫到硬碟上,而是寫到了核心的緩衝中。什麼時候寫到磁碟?核心有一套刷緩衝的機制。這樣做有很明顯的好處,比如我們調用1次write寫1kb和調用1k次write每次寫1b的資料,所花的時間是差不多的。後者所花的使用者態/核心態切換時間多些,但是寫磁碟的次數卻是一樣的。這樣就大大提高了效率。
另外一個是glibc維護的使用者態緩衝。
這個緩衝又是用來幹什麼的呢?核心和硬碟是兩個相對獨立的系統,核心緩衝在這兩個之間避免了很多不必要的同步。那麼同樣,核心和使用者程式也是兩個相對獨立的系統,每次系統調用也是要花代價的。所以上面1次write寫1kb和調用1k次write每次寫1b的資料的例子,前後兩種方法還是有差距的,差距就在於後者需要做1k此使用者態和核心態的切換。所以,glibc在使用者態上又做了一個緩衝。當我們調用glibc提供的printf輸出的時候,並沒有直接映射到一次write系統調用,而是存在了glibc管理的緩衝中,當條件滿足時(下面會說上面時候滿足)再調用一次write,把使用者態的緩衝寫到核心態去。所以,調用1此printf到檔案1kb字元和1k此print每次1個字元,所花的時間就真差不多了。
反過來,對於讀,我們有緩衝(cache)
用read讀檔案,其實就是讀的核心的緩衝。當核心緩衝中沒有的時候,會從磁碟讀些內容到緩衝,然後從緩衝返回給使用者。 注意,在某些情況下我們還可以做進一步的最佳化。考慮一下,我們從頭到尾讀檔案,唯讀1次1kb和讀1k次每次1b的時間是不是也差不多呢?這就需要核心在 read的時候事先讀1kb的內容到緩衝才行(這叫:read ahead)。而核心有不知道我們後續的read調用是從頭到尾順序讀還是胡亂讀,如果是隨機讀那麼read ahead就會適得其反。還好posix規定了posix_fadvise()系統調用,讓我們告訴核心,我們讀檔案的方式。用這個調用告訴核心我們是順 序讀。read的效能就上來了。爽啊。
下面列舉一下glibc預設的緩衝的行為(可以通過setvbuf修改):
如果檔案是stderr,則預設是沒緩衝,每次printf對應一次write。
如同檔案是終端,則預設是行緩衝,每當遇到換行的時候write一次
如果檔案對應的是磁碟檔案,則預設是全緩衝,當緩衝區滿或者檔案關閉時會write。
當然,任何時候,你都可以調用fflush()來強制使用者態的緩衝寫到核心緩衝中。
好,聊完了檔案緩衝,下面聊聊socket的緩衝。
對!socket也是有緩衝的。同樣的例子我們應用到socket上:1次send 1kb和1k次send每次1b。哪個費時費力呢?顯然是後者,因為如果我們知道,IP包頭,TCP包頭都是要花頻寬的。如果一個大大的IP包中只有1位元組的資料,顯然是大大的浪費了頻寬。所以,John Nagle提出了Nagle演算法,將這些淩亂的小包緩衝起來,集齊N個組成一個大包,再發出去。這樣就大大提高了網路效率。
當然,這樣做也是有負面作用的,當我們應用對網路的即時性要求比較高的時候,可能會因為這個機制而增加了網路延遲(畢竟要等集齊了才發嘛)。這時還是用setsockopt+TCP_NODELAY參數把這個最佳化禁用了吧。
還有嗎?當然,cache和buffer無處不在。記憶體管理中到處有cache。就那使用者空間來說,glibc中的ptmalloc2,google的TCMalloc都有cache的存在。由於這部分複雜度極高,後面有機會再分析吧。這篇文章就這樣吧。
談了一些Linux系統層面的cache和buffer。這裡主要談談應用程式層面的那些cache。相比系統層面的cache集中在IO上,應用程式層面的cache就顯得五花八門了。就從WEB說起吧。
web緩衝對於伺服器和用戶端都是不可或缺的。
對於web伺服器來說,緩衝是非常重要的東西,它可以大大的增加並發,縮短相應時間,減少伺服器負荷。原理很簡單。因為對於一個URL來說,很短時間內它的內容變化其實是不大的,如果每次請求都要伺服器算一遍就顯得太浪費了。所以web緩衝就是放在web伺服器前面的一個代理,它接收使用者請求,並向後端請求,在返迴響應的時候將這些響應緩衝起來,遇到請求時不經過伺服器計算直接返迴響應,從大大提高響應速度,尤其是在請求量很大的時候。web緩衝代表作是squid和varnish。
對於用戶端的瀏覽器來說緩衝同樣不可或缺。甚至在HTTP協議中都為緩衝提供了支援,HTTP返回碼304 Not Modified就是告訴瀏覽器,你要的內容沒變,用你緩衝中的吧。在HTTP協議之外,瀏覽器自己也會做許多的緩衝,對於圖片啊,js什麼的,短時間內的請求就自作主張直接不去遠程取了,會大大減少請求量,從而節省大量的時間,使用者需要速度。況且瀏覽器和伺服器本是同根生嘛,不必相互煎熬。所以有時候你得強制重新整理。
與web伺服器緊密相關的就是資料庫了。
在資料庫系統中,緩衝同樣無處不在。因為同樣的道理,對同一個sql查詢來說,在某些條件下(比如它查的表自上次查詢後都沒更新過)它的結果就應該和上次查詢一樣的。於是mysql提供了query cache。也有些架構提供了緩衝功能,比如hibernate。這都是讀緩衝,目的在於讀很多,而寫比較少的時候提高讀的效能,如果寫很多,而讀比較少的話這類緩衝就沒什麼用了。於是,在一些情況下我們希望可以為資料庫引入寫緩衝。典型的是主鍵查詢和更新。於是出現了kv資料庫(比如memcached)。可以提供基於主鍵查詢的讀寫緩衝。這對於提高資料系統的整體效能是極其重要的。
其他五花八門的cache還有那些呢?
比如dns緩衝,dns緩衝同樣存在於用戶端和dns伺服器中,與web緩衝的原理是一樣的。將dns的解析結果緩衝起來。
比如arp緩衝,將arp的結果緩衝起來。
甚至,連編譯系統也引入了緩衝比如ccache,比如visual studio中的pch(先行編譯頭)機制。
最後,我想說的是:cache is king! cache is everywhere!