緩存編(轉)

大型web系統數據緩存設計

  1. 前言
    在高訪問量的web系統中,緩存幾乎是離不開的;但是一個適當、高效的緩存方案設計卻並不容易;所以接下來將討論一下應用系統緩存的設計方面應該注意哪些東西,包括緩存的選型、常見緩存系統的特點和數據指標、緩存對象結構設計和失效策略以及緩存對象的壓縮等等,以期讓有需求的同學尤其是初學者能夠快速、系統的瞭解相關知識。

  2. 數據庫的瓶頸
    2.1 數據量
    關係型數據庫的數據量是比較小的,以我們常用的MySQL爲例,單表數據條數一般應該控制在2000w以內,如果業務很複雜的話,可能還要低一些。即便是對於Oracle這些大型商業數據庫來講,其能存儲的數據量也很難滿足一個擁有幾千萬甚至數億用戶的大型互聯網系統。

2.2 TPS
在實際開發中我們經常會發現,關係型數據庫在TPS上的瓶頸往往會比其他瓶頸更容易暴露出來,尤其對於大型web系統,由於每天大量的併發訪問,對數據庫的讀寫性能要求非常高;而傳統的關係型數據庫的處理能力確實捉襟見肘;以我們常用的MySQL數據庫爲例,常規情況下的TPS大概只有1500左右(各種極端場景下另當別論);下圖是MySQL官方所給出的一份測試數據:

而對於一個日均PV千萬的大型網站來講,每個PV所產生的數據庫讀寫量可能要超出幾倍,這種情況下,每天所有的數據讀寫請求量可能遠超出關係型數據的處理能力,更別說在流量峯值的情況下了;所以我們必須要有高效的緩存手段來抵擋住大部分的數據請求!

2.3 響應時間
正常情況下,關係型數據的響應時間是相當不錯的,一般在10ms以內甚至更短,尤其是在配置得當的情況下。但是就如前面所言,我們的需求是不一般的:當擁有幾億條數據,1wTPS的時候,響應時間也要在10ms以內,這幾乎是任何一款關係型數據都無法做到的。
那麼這個問題如何解決呢?最簡單有效的辦法當然是緩存!
3. 緩存系統選型
3.1 緩存的類型
3.1.1 本地緩存
本地緩存可能是大家用的最多的一種緩存方式了,不管是本地內存還是磁盤,其速度快,成本低,在有些場合非常有效;
但是對於web系統的集羣負載均衡結構來說,本地緩存使用起來就比較受限制,因爲當數據庫數據發生變化時,你沒有一個簡單有效的方法去更新本地緩存;然而,你如果在不同的服務器之間去同步本地緩存信息,由於緩存的低時效性和高訪問量的影響,其成本和性能恐怕都是難以接受的。
3.1.2 分佈式緩存
前面提到過,本地緩存的使用很容易讓你的應用服務器帶上“狀態”,這種情況下,數據同步的開銷會比較大;尤其是在集羣環境中更是如此!
分佈式緩存這種東西存在的目的就是爲了提供比RDB更高的TPS和擴展性,同時有幫你承擔了數據同步的痛苦;優秀的分佈式緩存系統有大家所熟知的Memcached、Redis(當然也許你把它看成是NoSQL,但是我個人更願意把分佈式緩存也看成是NoSQL),還有國內阿里自主開發的Tair等;
對比關係型數據庫和緩存存儲,其在讀和寫性能上的差距可謂天壤之別;memcached單節點已經可以做到15w以上的tps、Redis、google的levelDB也有不菲的性能,而實現大規模集羣后,性能可能會更高!
所以,在技術和業務都可以接受的情況下,我們可以儘量把讀寫壓力從數據庫轉移到緩存上,以保護看似強大,其實卻很脆弱的關係型數據庫。

3.1.3 客戶端緩存
這塊很容易被人忽略,客戶端緩存主要是指基於客戶端瀏覽器的緩存方式;由於瀏覽器本身的安全限制,web系統能在客戶端所做的緩存方式非常有限,主要由以下幾種:
a、 瀏覽器cookie;這是使用最多的在客戶端保存數據的方法,大家也都比較熟悉;

b、 瀏覽器本地緩存;很多瀏覽器都提供了本地緩存的接口,但是由於各個瀏覽器的實現有差異,所以這種方式很少被使用;此類方案有chrome的Google Gear,IE的userData、火狐的sessionStorage和globalStorage等;

c、 flash本地存儲;這個也是平時比較常用的緩存方式;相較於cookie,flash緩存基本沒有數量和體積的限制,而且由於基於flash插件,所以也不存在兼容性問題;不過在沒有安裝flash插件的瀏覽器上則無法使用;

d、 html5的本地存儲;鑑於html5越來越普及,再加上其本地存儲功能比較強大,所以在將來的使用場景應該會越來越多。

由於大部分的web應用都會盡量做到無狀態,以方便線性擴容,所以我們能使用的除了後端存儲(DB、NoSQL、分佈式文件系統、CDN等)外,就只剩前端的客戶端緩存了。
對客戶端存儲的合理使用,原本每天幾千萬甚至上億的接口調用,一下就可能降到了每天幾百萬甚至更少,而且即便是用戶更換瀏覽器,或者緩存丟失需要重新訪問服務器,由於隨機性比較強,請求分散,給服務器的壓力也很小!在此基礎上,再加上合理的緩存過期時間,就可以在數據準確和性能上做一個很好的折衷。
3.1.4 數據庫緩存
這裏主要是指數據庫的查詢緩存,大部分數據庫都是會提供,每種數據庫的具體實現細節也會有所差異,不過基本的原理就是用查詢語句的hash值做key,對結果集進行緩存;如果利用的好,可以很大的提高數據庫的查詢效率!數據庫的其他一些緩存將在後邊介紹。

3.2 選型指標
現在可供我們選擇使用的(僞)分佈式緩存系統不要太多,比如使用廣泛的Memcached、最近炒得火熱的Redis等;這裏前面加個僞字,意思是想說,有些所謂的分佈式緩存其實仍是以單機的思維去做的,不能算是真正的分佈式緩存(你覺得只實現個主從複製能算分佈式麼?)。
既然有這麼多的系統可用,那麼我們在選擇的時候,就要有一定的標準和方法。只有有了標準,才能衡量一個系統時好時壞,或者適不適合,選擇就基本有了方向。
下邊幾點是我個人覺的應該考慮的幾個點(其實大部分系統選型都是這麼考慮的,並非只有緩存系統):

3.2.1 容量
廢話,容量當然是越大越好了,這還用說麼,有100G我幹嘛還要用10G?其實這麼說總要考慮一下成本啦,目前一臺普通的PC Server內存128G已經算是大的了,再大的話不管是從硬件還是從軟件方面,管理的成本都會增加。單機來講,比如說主板的插槽數量,服務器散熱、操作系統的內存分配、回收、碎片管理等等都會限制內存卡的容量;即便使用多機的話,大量內存的採購也是很費money的!
有詩云:山不在高,有仙則名;所以內存也不在多,夠用就好!每個系統在初期規劃的時候,都會大致計算一下所要消耗的緩存空間,這主要取決於你要緩存的對象數量和單個對象的大小。一般來說,你可以採用對象屬性在內存中的存儲長度簡單加和的方法來計算單個對象的體積,再乘以緩存對象的數量和預期增長(當然,這裏邊有一個熱點數據的問題,這裏就不細討論了),大概得出需要使用的緩存空間;之後就可以按照這個指標去申請緩存空間或搭建緩存系統了。

3.2.2 併發量
這裏說併發量,其實還不如說是QPS更貼切一些,因爲我們的緩存不是直接面向用戶的,而只面向應用的,所以肯定不會有那個高的併發訪問(當然,多個系統共用一套緩存那就另當別論了);所以我們關心的是一個緩存系統平均每秒能夠承受多少的訪問量。
我們之所以需要緩存系統,就是要它在關鍵時刻能抗住我們的數據訪問量的;所以,緩存系統能夠支撐的併發量是一個非常重要的指標,如果它的性能還不如關係型數據庫,那我們就沒有使用的必要了。
對於淘寶的系統來說,我們不妨按照下邊的方案來估算併發量:
QPS = 日PV × 讀寫次數/PV ÷ (8 × 60 × 60)
這裏我們是按照一天8個小時來計算的,這個值基於一個互聯網站點的訪問規律得出的,當然,如果你不同意這個值,可以自己定義。
在估算訪問量的時候,我們不得不考慮一個峯值的問題,尤其是像淘寶、京東這樣大型的電商網站,經常會因爲一些大的促銷活動而使PV、UV衝到平時的幾倍甚至幾十倍,這也正是緩存系統發揮作用的關鍵時刻;倍受矚目的12306在站點優化過程中也大量的引入了緩存(內存文件系統)來提升性能。
在計算出平均值之後,再乘以一個峯值係數,基本就可以得出你的緩存系統需要承受的最高QPS,一般情況下,這個係數定在10以內是合理的。

3.2.3 響應時間
響應時間當然也是必要的,如果一個緩存系統慢的跟蝸牛一樣,甚至直接就超時了,那和我們使用MySQL也沒啥區別了。
一般來說,要求一個緩存系統在1ms或2ms之內返回數據是不過分的,當然前提是你的數據不會太大;如果想更快的話,那你就有點過分了,除非你是用的本地緩存;因爲一般而言,在大型IDC內部,一個TCP迴環(不攜帶業務數據)差不多就要消耗掉0.2ms至0.5ms。
大部分的緩存系統,由於是基於內存,所以響應時間都很短,但是問題一般會出現在數據量和QPS變大之後,由於內存管理策略、數據查找方式、I/O模型、業務場景等方面的差異,響應時間可能會差異很多,所以對於QPS和響應時間這兩項指標,還要靠上線前充分的性能測試來進一步確認,不能只單純的依賴官方的測試結果。

3.2.4 使用成本
一般分佈式緩存系統會包括服務端和客戶端兩部分,所以其使用成本上也要分爲兩個部分來講;
首先服務端,優秀的系統要是能夠方便部署和方便運維的,不需要高端硬件、不需要複雜的環境配置、不能有過多的依賴條件,同時還要穩定、易維護;
而對於客戶端的使用成本來說,更關係到程序員的開發效率和代碼維護成本,基本有三點:單一的依賴、簡潔的配置和人性化的API。
另外有一點要提的是,不管是服務端還是客戶端,豐富的文檔和技術支持也是必不可少的。

3.2.5 擴展性
緩存系統的擴展性是指在空間不足的性情況,能夠通過增加機器等方式快速的在線擴容。這也是能夠支撐業務系統快速發展的一個重要因素。
一般來講,分佈式緩存的負載均衡策略有兩種,一種是在客戶端來做,另外一種就是在服務端來做。

客戶端負載均衡
在客戶端來做負載均衡的,諸如前面我們提到的Memcached、Redis等,一般都是通過特定Hash算法將key對應的value映射到固定的緩存服務器上去,這樣的做法最大的好處就是簡單,不管是自己實現一個映射功能還是使用第三方的擴展,都很容易;但由此而來的一個問題是我們無法做到failover。比如說某一臺Memcached服務器掛掉了,但是客戶端還會傻不啦嘰的繼續請求該服務器,從而導致大量的線程超時;當然,因此而造成的數據丟失是另外一回事了。要想解決,簡單的可能只改改改代碼或者配置文件就ok了,但是像Java這種就蛋疼了,有可能還需要重啓所有應用以便讓變更能夠生效。
如果線上緩存容量不夠了,要增加一些服務器,也有同樣的問題;而且由於hash算法的改變,還要遷移對應的數據到正確的服務器上去。

服務端負載均衡
如果在服務端來做負載均衡,那麼我們前面提到的failover的問題就很好解決了;客戶端能夠訪問的所有的緩存服務器的ip和端口都會事先從一箇中心配置服務器上獲取,同時客戶端會和中心配置服務器保持一種有效的通信機制(長連接或者HeartBeat),能夠使後端緩存服務器的ip和端口變更即時的通知到客戶端,這樣,一旦後端服務器發生故障時可以很快的通知到客戶端改變hash策略,到新的服務器上去存取數據。
但這樣做會帶來另外一個問題,就是中心配置服務器會成爲一個單點。解決辦法就將中心配置服務器由一臺變爲多臺,採用雙機stand by方式或者zookeeper等方式,這樣可用性也會大大提高。

3.2.6 容災
我們使用緩存系統的初衷就是當數據請求量很大,數據庫無法承受的情況,能夠通過緩存來抵擋住大部分的請求流量,所以一旦緩存服務器發生故障,而緩存系統又沒有一個很好的容災措施的話,所有或部分的請求將會直接壓倒數據庫上,這可能會直接導致DB崩潰。
並不是所有的緩存系統都具有容災特性的,所以我們在選擇的時候,一定要根據自己的業務需求,對緩存數據的依賴程度來決定是否需要緩存系統的容災特性。

3.3 常見分佈式緩存系統比較
3.3.1 Memcached
Memcached嚴格的說還不能算是一個分佈式緩存系統,個人更傾向於將其看成一個單機的緩存系統,所以從這方面講其容量上是有限制的;但由於Memcached的開源,其訪問協議也都是公開的,所以目前有很多第三方的客戶端或擴展,在一定程度上對Memcached的集羣擴展做了支持,但是大部分都只是做了一個簡單Hash或者一致性Hash。
由於Memcached內部通過固定大小的chunk鏈的方式去管理內存數據,分配和回收效率很高,所以其讀寫性能也非常高;官方給出的數據,64KB對象的情況下,單機QPS可達到15w以上。
Memcached集羣的不同機器之間是相互獨立的,沒有數據方面的通信,所以也不具備failover的能力,在發生數據傾斜的時候也無法自動調整。
Memcached的多語言支持非常好,目前可支持C/C++、Java、C#、PHP、Python、Perl、Ruby等常用語言,也有大量的文檔和示例代碼可供參考,而且其穩定性也經過了長期的檢驗,應該說比較適合於中小型系統和初學者使用的緩存系統。

3.3.2 Redis
Redis也是眼下比較流行的一個緩存系統,在國內外很多互聯網公司都在使用(新浪微博就是個典型的例子),很多人把Redis看成是Memcached的替代品。
下面就簡單介紹下Redis的一些特性;
Redis除了像Memcached那樣支持普通的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章