Memcached 是一種眾所周知的、簡單的記憶體快取解決方案。本文描述了 Facebook 如何利用 memcached 作為構建塊來構造和擴充套件一個分散式鍵值儲存支援世界上最大的社交網路。
1.Introduction
一個社交網路(FB)的基礎架構通常需要以下
- 允許實時通訊(近似,允許一定的延遲),
- 動態地,從多個來源聚合內容,
- 能夠訪問和更新非常流行(熱點)共享內容,以及
- 能夠處理大規模使用者請求(每秒數百萬次 request )
傳統web架構難以滿足以上要求,對於計算、網路和I/O需求壓力十分巨大。
為什麼使用 Memcached?
一個頁面請求會產生數以百計的資料庫請求,而這樣的功能只能停止在原型階段,因為實現起來會太慢,代價也太高。在實際應用裡,web 頁面通常都會從 memcached 伺服器獲取數以千計的 key-value 對。
本文包括四個主要貢獻如下
- 描述了 Facebook 的基於 memcached 架構的演變。
- 增強了 memcached,以提高效能並且改進記憶體效率。
- 增加機制,提高大規模系統操作的能力。
- 對生產工作負載賦予了特性。
2 Overview
Pre-memcache
基於以下兩個事實
- 人們通常是消費內容而非創造內容(讀 >> 寫)
- 讀操作從各種來源獲取資料,例如 MySQL 資料庫、HDFS、後端服務等。這就需要靈活的快取策略真對不同來源,進行資料的快取。
Query cache
memcache 的 cache policy 主要如下:
- demand-filled look-aside (read)
- write-invalidate (write)
讀資料時,web server 先嚐試從 memcache 中讀資料,若 cache miss 則從 database 中獲取資料,並寫入到 memcache 中;寫資料時,先更新 database,然後將 memcache 中相應的資料刪除。
Generic cache
Memcached 沒有提供伺服器到伺服器的協同,它僅僅是執行在單機上的一個記憶體雜湊表。接下來描述如何基於 Memcached 構建一個分散式鍵值儲存系統,以勝任在 Facebook 的工作負載下的操作。
在三種不同的規模下出現的問題:
Single front-end cluster
- Read heavy workload
- Wide fanout
- Handling failures
Multiple front-end clusters
- Controlling data replication
- Data consistency
Multiple Regions
- Data consistency
在系統的發展中,我們將這兩個重大的設計目標放在首位:
- 只有已經對使用者或運維產生影響的問題,才值得改變。我們極少考慮範圍有限的最佳化。
- 對陳舊資料的瞬時讀取,其機率和響應度類似,都將作為引數來調整。我們會選擇暴露略微陳舊的資料以便減少後臺儲存服務過高的負載。
3 In a Cluster: Latency and Load
考慮叢集中數以千計的伺服器所帶來的挑戰。在這種規模之下,我們著眼於減少獲取快取時的負載,以及 cache miss 時資料庫的負載。
3.1 Reducing Latency
無論快取是否命中,Memcache 的響應時間對於使用者請求的響應時間來講都是一個重要因素。單個使用者web請求一般包含數百個 Memcache 讀請求。如熱門頁面的載入通常需要從 Memcache 中獲取521個不同的資源。
為了減輕資料庫和其他服務的負擔,我們準備了由數百臺 Memcache 伺服器組成的叢集。資源項透過一致性雜湊存於不同的 Memcache 伺服器中。因此,web 伺服器必須請求多臺 Memcache 伺服器,才能滿足使用者的請求。這也產生了 all-to-all communication 問題,主要有以下兩個影響。
- incast congestion
- a single server to become the bottleneck for many web servers.
資料複製通常可以緩解單伺服器瓶頸的問題,但是這會導致記憶體效率使用低下。減少延遲的方法主要集中在 Memcache 客戶端,每一個 web 伺服器都會執行 Memcache 客戶端。這個客戶端提供一系列功能,包括:序列化、壓縮、請求路由、錯誤處理以及批處理等。
Parallel requests and batching
應用層面上透過並行請求和批處理可以在一定程度上減少延遲。透過構造的資料依賴DAG,最大化並行請求資料資源,同時批次請求(平均24 keys)。
Client-server communication Memcached
伺服器之間不進行通訊,主要在客戶端上改造,客戶端主要提供兩個元件:一個是可以嵌入到應用的庫,一個稱為 Mcrouter 的代理程式。這個代理對外提供Memcached 伺服器的介面,對不同伺服器之間的請求/響應進行路由。客戶端使用 UDP 和 TCP 協議與 Memcached 伺服器通訊。
1.對於讀請求(get),透過 UDP 來減少延遲。允許 web 伺服器中的每個執行緒繞過Mcrouter 直接與 Memcached 伺服器直接通訊,當使用 UDP 通訊時,如果出現丟包或者順序錯誤的情況,直接返回錯誤,不嘗試解決。實際情況中,在峰值負載條件下,觀察Memcache 客戶端約0.25%的請求會被丟棄。其中大約80%是由於延遲或丟包,其餘的是失序交付。客戶端將這類異常作為快取不命中處理,但是 web 伺服器在查詢出資料以後,會跳過更新 Memcached 伺服器這一環節,以便避免對伺服器增添額外的負載(此時網路環境可能負載已經過高)。
2.對於寫請求(delete,set),web 伺服器不與 Memcached 伺服器直接通訊,而是透過與 Mcrouter 建立 TCP 連線,由 Mcrouter 來保持與 memcached server 之間的連線。這種方案一定程度上可以減少維持 TCP 連線、處理網路 I/O 所需的 CPU 以及記憶體資源。
Incast congestion
Memcache 客戶端實現流量控制機制來限制 Incast congestion。使用滑動視窗機制來控制未處理請求的數量。當客戶端收到一個響應的時候,那麼下一個請求就可以傳送了。同TCP 的擁塞控制類似,滑動視窗也是動態改變,請求成功視窗大小就逐漸增加,沒有響應就縮小。
3.2 Reducing Load
使用 Memcache 來減少訪問 DB 讀資料的頻率。當快取沒有命中時,DB 的負載就會升高。下面將描述三種技術,來減少負載。
3.2.1 Leases
引入了一個稱為 leases 的新機制來解決兩個問題:stale sets 和 thundering herds。
stale sets
look-aside 快取策略有機率發生資料不一致的情況。假設兩個 web server,s1 和 s2,需要讀取同一條資料d,其執行順序如下:
- s1 從 memcache 中讀取資料 d,發生 cache miss,從資料庫讀出 d = A;
- DB 中的資料 d 更新為 B;
- s2 從 memcache 中讀取資料 d,發生 cache miss,從資料庫讀出 d = B;
- s2 將 d = B 寫入 memcache 中;
- s1 將 d = A 寫入 memcache 中;
leases 機制簡單來說,當發生 cache miss 時,Memcached 例項給客戶端一個 lease,並將起設定到快取中。lease 是一個64 bit 的 token,與請求的 key 進行繫結。當客戶端寫入資料時需要攜帶這個 lease,Memcached 可以透過這個 lease 來驗證並判斷這個資料是否應該被儲存。同時如果收到了這個key的刪除請求,Memcached 會使之前發放的lease 失效。
因為存在 leases 機制,那麼上面讀取同一條資料d的情況如下:
- s1 從 memcache 中讀取資料 d,發生 cache miss,得到lease L1,從資料庫讀出 d = A;
- DB 中的資料 d 更新為 B,lease L1 失效;
- s2 從 memcache 中讀取資料 d,發生 cache miss,得到lease L2,從資料庫讀出 d = B;
- s2 將 d = B 寫入 memcache 中,lease L2有效,寫入成功;
- s1 將 d = A 寫入 memcache 中,lease L1無效,寫入失敗;
當設值到快取中時,客戶端提供這個租約令牌。透過這個租約令牌,Memcached 可以驗證和判斷是否這個資料應該被儲存,由此仲裁併發寫操作。如果因為收到了對這個資料項的刪除請求,Memcached 使這個租約令牌失效。
thundering herds
當某個特定的主鍵被大量頻繁的讀寫,那麼一次 thundering herds 就發生了。當某個 hot key 過期失效時,大量的讀請求會發生 cahe miss,從而導致資料庫負載增高。leases 機制可以緩解這個問題。Memcached 伺服器可以調節發放 lease 的頻率。預設情況下,同個資料項每10秒只會發放一個 lease,10秒內有請求時,會進行一個特殊響應,客戶端可以選擇等一會,通常情況下,擁有 lease 的客戶端會在幾毫秒內成功的寫入資料。當客戶端重試之後,資料就能正常返回了。
Stale values
在 leases 機制下,透過返回過期的資料,可以進一步減少客戶端的等待時間。也就是說,當 cache miss 發生時,一個讀請求可以返回一個 lease ,也可以返回一份標記為過時的資料。應用可以使用過時的資料繼續進行處理,而不需要等待從資料庫讀取的最新資料。
3.2.2 Memcache Pools
使用 Memcache 做為通用的快取層要求不同的 workloads 共享基礎設施。不同應用的 workloads 可能會互相干擾。FB 將 Memcached 伺服器分割成獨立的池。
• wildcard pool:預設 pool 用來儲存大部分資料
• small pool:儲存訪問頻率高但 cache miss 的成本不高的資料
• large pool:儲存訪問頻率低但 cache miss 的成本高的資料
可以看出,更新頻率高的佔了大部分,所有資料使用同一個 pool 有可能發生高頻率更新的資料將低頻率更新的資料替換掉的情況(LRU)。劃分不同的 pool 來減少這種衝突影響。
3.2.3 Replication Within Pools
在一些池中,使用複製來改善 Memcached 伺服器的延遲和效率。
- the application routinely fetches many keys simultaneously,
- the entire data set fits in one or two memcached servers and
- the request rate is much higher than what a single server can manage.
當以上情況出現時,FB 會選擇在池內複製這一類資料。相比起進一步劃分主鍵空間,FB 更傾向於在例項內進行復制。如果只複製一部分資料,有可能需要透過請求多個例項來獲取,這樣並不會降低單臺例項處理請求的數量。
3.3 Handling Failures
如果無法從 Memcache 中讀取資料,這將會導致後端服務負載增加,對於這種情況,FB考慮一下兩種情況。
- 由於網路或伺服器故障,少量的主機無法連線;
- 叢集內相當大比例的伺服器停機或故障;
如果整個的叢集不得不離線,FB 轉移使用者的 web 請求到別的叢集,這樣將會有效地轉移 Memcache 所有的負載。如果只是少量主機因為網路原因等失聯,依賴於一種自動恢復機制,這不是即時的,通常恢復需要花費幾分鐘時間,但這段期間也有可能會產生連鎖故障。因此 FB 引入了一個機制進一步將後端服務從故障中隔離開來。準備了少量稱作 Gutter 的機器來接管少量故障伺服器的責任。在一個叢集中,Gutter 的數量大約為Memcached 伺服器的1%。
通常來說,每個失敗的請求都會導致對後端儲存的一次存取,潛在地將會使後端過載。使用 Gutter 儲存這些結果,很大部分失敗被轉移到對 Gutter 池的存取,因此減少了後端儲存的負載。在實踐中,Gutter 機制的存在,每天減少了約99%失敗請求,其中10%-25%的失敗轉化為快取命中。如果一臺 Memcached 伺服器整個發生故障,在4分鐘之內,Gutter 池的命中率將會普遍增加到35%,經常會接近50%。因此對於由於故障或者小範圍網路事故造成的一些 Memcached 伺服器不可達的情況,Gutter 將會保護後端儲存免於流量激增。
4 In a Region: Replication
隨著需求的增長,增加更多的機器來部署 Web server 和 Memcached server 來擴充套件叢集,但是單純地擴充套件系統並不能解決所有問題。
- 更多的 Web server 的加入來處理增長的使用者流量,高請求頻率的資料項只會變的更加流行;
- 隨著 Memcached server 的增加,client 和 server 更多的通訊會使得 Incast 擁塞變的更嚴重;
將 Web server 和 Memcached server 分割為多個前端叢集。這些叢集與包含資料庫的儲存叢集一起統稱為 Region。Region 架構同樣也考慮到更小的故障域和易控制的網路配置。我們用資料的複製來換取更獨立的故障域、易控制的網路配置和 incast 擁塞的減少。
4.1 Regional Invalidations
在 Region 中,儲存叢集儲存資料的權威版本,為了滿足使用者的需求就需要將資料複製到前端叢集。儲存叢集負責使快取資料失效來保持前端叢集與權威版本的一致性。
FB 在每一臺資料庫上部署了 Invalidation daemons(mcsqueal)。每個 daemon 檢查資料庫提交的SQL語句,提取任意的刪除命令,並且將刪除命令廣播到 Region 內所有的 Memcached 叢集。
Reducing packet rates
如果 mcsqueal 可以直接聯絡 Memcached 服務,那麼從後端叢集到前端叢集的發包率將會高的無法接受。大量的資料庫中 mcsqueal 和 Memcached server 跨叢集通訊,會產生網路問題(fanout)。
透過指定一組執行 Mcrouter 例項的專用伺服器,Invalidation daemons 批次傳送刪除操作到專用 Mcrouter 伺服器,再由 Mcrouter 就批次操作中分離出單獨的刪除操作,將刪除命令路由到正確的 Memcached 伺服器。
Invalidation via web servers
透過 Web server 廣播刪除命令到所有 Front-end cluster 伺服器更簡單。但這個方法存在兩個問題。
- 因為 Web server 在批處理刪除命令時沒有 mcsqueal 有效率,具有更高的成本;
- 當系統性的無效問題出現時,這種方法會無能為力,比如由於配置錯誤造成的刪除命令錯誤路由;
4.2 Regional Pools
如果使用者的請求被隨機路由到所有可獲得的 Front-end cluster 中,那麼叢集中快取的資料將會大致一致。過度複製資料會使記憶體使用效率降低,特別是對很大的、很少存取的資料項。透過減少副本的數量,使多個前端叢集共享同一個 Memcached 伺服器集合,FB 稱此為 region 池。
型別 A 資料項相比型別 B 來說更大,但是由於存取的頻率很高,所以不把 A 放進 Region pool 中,而 B 的存取頻率和使用的使用者數量都較低,適合放進 Region pool 中。
4.3 Cold Cluster Warmup
由於存在的叢集發生故障或者進行定期的維護,或者增加新的叢集上線,此時快取命中率會很低,大量的 cache miss 會導致後端服務負載增加。一個稱作 Cold Cluster Warmup 的系統可以緩和這種情況,這個系統使 “Cold Cluster”(也就是具有空快取的叢集)中的客戶端從“Warm Cluster”(也就是具有正常快取命中率的叢集)中檢索資料而不是從持久化儲存。這利用到了前面提到的跨叢集的資料複製,使用這個系統可以使冷叢集在幾個小時恢復到滿負載工作能力而不是幾天。
5 Across Regions: Consistency
將資料中心分佈到廣泛的地理位置具有很多優勢。
- 將web伺服器靠近終端使用者可以極大地較少延遲;
- 地理位置多元化可以緩解自然災害和大規模電力故障的影響;
- 新的位置可以提供更便宜的電力和其它經濟上的支援;
FB 透過部署多個 region 來獲得這些優勢。 region 包含一個儲存叢集和多個 front-end 叢集。我們指定一個 region 持有主資料庫,別的 region 包含只讀的副本從庫,依賴 MySQL 的複製機制來保持從庫與主庫的同步。基於這樣的設計,Web server 無論訪問本地 Memcached 伺服器還是本地資料庫副本的延遲都很低。
Writes from a master region
考慮一個 Web server 已完成 Master region 資料庫修改的並試圖使現在過時的資料失效。這種情況可能引發資料的不一致性。在 Master region 內是快取資料失效是安全的。但是,讓 Replica region 中的資料無效可能為時過早,因為主庫的資料更改可能尚未同步到從庫中。接下來對 Replica region 的資料查詢將會與資料庫複製產生競爭,因此增加了將過時資料設定到 Memcache 中的機率。歷史上,在擴充套件到多個 region 之後,FB 實現了上面提過的 mcsqueal 機制。
Writes from a non-master region
現在考慮當複製滯後非常大的時候,使用者從 Replica region 更新資料。如果他最近的改動丟失了,那麼下一個請求將會導致混亂。
- Replica region 中的 web server s1 寫入資料到 master DB;
- s1 將本地 memcache 中的資料刪除;
- Replica region 中的 web server s2 從 memcache 中讀取資料發生 cache miss,從本地 DB 中獲取資料;
- s1 寫入的資料從 master DB 中同步到 replica DB,並透過 mcsqueal 機制將本地 memcache 中的資料刪除;
- web server s2 將其讀到的資料寫入 memcache 中;
只有當資料複製流完成之後才允許從副本資料庫讀取資料並快取。如果沒有這個保障,後續請求將會導致讀取過期資料並且快取。
FB 使用遠端標記 Remote marker 機制來最小化讀取過時資料的機率。出現標記就表明本地副本資料庫中的資料可能是過時的,所以查詢應該重定向到 Master region。
當 Replica region 的 Web server 需要寫入某資料時:
- 在本地 memcache 上打上 remote marker,資料 d 標記為 rd;
- 將 d 寫入到 master DB 中;
- 將 d 從 memcache 中刪除 (rd 標記不刪除)
- 等待 Master DB 將資料同步到 Replica region 中的 replica DB 中;
- Replica region 中的 Replica DB 透過 mcsqueal 刪除本地 memcache 中資料 d 的標記 rd
當 Replica region 的 Web server 想要讀取資料 d 發生 cache miss 時:
- 如果 memcache 中資料 d 帶了 rd,則從 Master DB 中讀取資料;
- 如果 memcache 中資料 d 沒有 rd,則直接從本地的 Replica DB 中讀取資料;
Operational considerations
略
6 Single Server Improvements
All-to-all 的通訊模式意味著單個伺服器可能成為叢集的瓶頸。改進單伺服器快取效能。
6.1 Performance Optimizations
對於單執行緒,使用固定大小的雜湊表的 memcached,FB 做出以下幾點最佳化。
- 允許自動擴充套件雜湊表,以避免查詢時間漂移到O(n);
- 從單執行緒變成多執行緒,透過全域性鎖來保護資料結構;
- 為每個執行緒提供單獨的 UDP 埠,提高通訊效率;
Get Performance
首先研究將原有的多執行緒單鎖的實現替換為細粒度鎖的效益。在傳送包含10個主鍵的 memcached 請求的之前,預先填充了32 byte 的快取資料,然後測量命中的效能。
更細的鎖粒度,意味著更高的效能。由於快取命中時返回內容需要構建和傳輸,而 miss 則不用太多的操作,所以,更多的命中也代表著更多的耗時。
使用UDP代替TCP的效能影響。FB 實驗發現 UDP 實現的效能在單主鍵獲取情況下超出 TCP 實現13%,在 10-key 獲取的情況下超出8%。
6.2 Adaptive Slab Allocator
Memcached 使用 slab 分配器來管理記憶體。分配器將記憶體組織到 slab class 中,每個 slab class 都包含預分配的、大小一致的記憶體塊。Memcached 將項儲存在儘可能小的 slab class 中,可以容納後設資料、鍵和值。
FB 實現了一個適應性的分配器,這個分配器將會週期性的重新平衡 slab 分配來適應當前的工作負載。如果slab class 正在移除資料項,而且如果下一個將要被移除的資料項比其它 slab class 中的最近最少使用的資料項的使用時間多至少20%,那麼就說明這個 slab class 需要更多記憶體。如果找到了一個這樣的 slab class,那麼就將儲存最近最少使用資料項的 slab 釋放,然後轉移到 needy class。
6.3 The Transient Item Cache
因為 memcached 支援過期時間,通常來說,資料項條在它們過期之後仍可以駐留在記憶體中(延時刪除)。 當資料項被請求時或者當它們到達 LRU 的尾端時,memcached 檢查其過期時間後才會進行刪除操作。 這種模式會使得那些偶爾活躍的短期鍵值長期佔據記憶體空間,直到它們到達 LRU 的尾部。
FB 引入一種混合模式,對大多數鍵值使用延時刪除,而對過期的短期鍵值則立即刪除。使用連結串列,按秒索引構建一個緩衝區,每一秒,緩衝區頭部中的所有項都會被清除。
6.4 Software Upgrades
FB 修改了 memcached,將其快取的值和主要資料結構儲存在 System V 共享記憶體區域中,以便資料可以在軟體升級期間保持活躍,從而最大限度地減少中斷。
7 Memcache Workload
在生產環境中執行伺服器上所獲得的資料來描述 memcache 的負載。
7.1 Measurements at the Web Server
Fanout
56%的頁面請求聯絡少於20臺memcached伺服器。按照傳輸量來說,使用者請求傾向於請求小數量的快取資料。然而這個分佈存在一個長尾。對於流行頁面的請求分佈,大部分這樣的請求將會接入超過100臺獨立的伺服器;接入幾百臺memcached伺服器也不是少數。
Response size
中位數(135byte)與平均數(954byte)之間的差值隱含著快取項的大小存在很大差異。大的資料項傾向於儲存資料列表,而小的資料項傾向於儲存單個內容塊。
Latency
測量從 memcache 請求資料的往返延遲,其中包括路由請求和接收響應的成本、網路傳輸時間以及反序列化和解壓縮的成本。在7天內,請求延遲的中位數為333μs,而第75和第95百分位分別為475μs和1.135ms。空閒web server 的端到端延遲中位數為178μs,而p75和p95分別為219μs和374μs。在第95百分位上延遲的巨大差異是由處理資料量大的響應和等待可執行執行緒排程引起的。
7.2 Pool Statistics
- wildcard:預設;
- app:專門設定給特定應用的;
- replicated pool:給存取頻繁的資料的;
- regional pool:很少存取的資料的;
面對不同工作負載,劃分不同的 pool,使用不同的策略進行管理是很有必要的。
7.3 Invalidation Latency
當刪除操作發起後到叢集中 key 失效的延遲,取樣統計如下:
Master region 刪除的延遲相比 Replica region 刪除的延遲要好很多。
8 Related Work & 9 Conclusion
略