Yao Yu:談Twitter的百TB級Redis緩存實踐

allowtransparency="true" frameborder="0" scrolling="no" src="http://hits.sinajs.cn/A1/weiboshare.html?url=http%3A%2F%2Fwww.csdn.net%2Farticle%2F2014-09-10%2F2821615-how-twitter-uses-redis-to-scale-105tb-ram-39mm-qps-10000-ins&type=3&count=&appkey=&title=%E5%9C%A8Twitter%E5%B7%A5%E4%BD%9C%E7%9A%84%E6%95%B0%E5%B9%B4%E6%97%B6%E9%97%B4%EF%BC%8CYao%E8%A7%81%E8%AF%81%E4%BA%86%E7%BC%93%E5%AD%98%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%89%A9%E5%B1%95%E4%B9%8B%E8%B7%AF%E2%80%94%E2%80%94%E4%BB%8E1%E4%B8%AA%E9%A1%B9%E7%9B%AE%E5%88%B0%E4%B8%8A%E7%99%BE%E9%A1%B9%E7%9B%AE%E7%9A%84%E4%BD%BF%E7%94%A8%E3%80%82%E4%B8%BA%E4%BA%86%E6%94%AF%E6%92%91%E5%A6%82%E6%AD%A4%E5%BA%9E%E5%A4%A7%E7%9A%84%E7%BC%93%E5%AD%98%E4%BD%93%E7%B3%BB%EF%BC%8CTwitter%E4%BD%BF%E7%94%A8%E4%BA%86%E6%88%90%E5%8D%83%E4%B8%8A%E4%B8%87%E5%8F%B0%E6%9C%8D%E5%8A%A1%E5%99%A8%EF%BC%8C%E5%A4%9A%E4%B8%AA%E9%9B%86%E7%BE%A4%EF%BC%8C%E4%BB%A5%E5%8F%8A%E8%BF%87%E7%99%BETB%E5%86%85%E5%AD%98%E3%80%82&pic=&ralateUid=&language=zh_cn&rnd=1430363719823" width="22" height="16">摘要:在Twitter工作的數年時間,Yao見證了緩存服務的擴展之路——從1個項目到上百項目的使用。爲了支撐如此龐大的緩存體系,Twitter使用了成千上萬臺服務器,多個集羣,以及過百TB內存。

【編者按】文章內容是HighScalability創始人Todd Hoff基於Twitter工程師Yao Yu “Scaling Redis at Twitter”演講的總結。在演講中,Yao從高等級概括了Twitter爲什麼會選擇Redis,及如此規模緩存服務打造的挑戰和途徑。

以下爲譯文:

自2010年,Yao Yu已經效力於Twitter的緩存團隊。而本文主要基於她近日發表的“Scaling Redis at Twitter”演講,主要談Twitter的Redis擴展,同時也不侷限於Redis。從演講中不難發現,Twitter在緩存服務打造上積累了相當豐富的經驗,就如你所想,Twitter使用了大量的緩存。

Timeline服務(一個數據中心)Hybrid List使用情況:

  • 分配40TB左右的內存堆棧
  • 3000萬QPS(query per second)
  • 超過6000個實例

BTtree(一個數據中心)使用狀態:

  • 分配65TB的內存堆棧
  • 900萬QPS
  • 超過4000個實例

下文將會帶你詳細的學習BTree和Hybrid。

幾個值得關注的點:

  • Redis的表現非常不錯,因爲它將大量服務器中未使用的資源整合成有價值的服務。
  • Twitter通過兩個專爲其業務設計的新數據模型來定製化Redis,因此他們獲得了理想的性能,但是也因此受限於舊的代碼,無法迅速添加新特性。因此我(Todd)一直在想,爲什麼他們會使用Redis來做這樣的事情。只是想基於自己數據結構建立一個Timeline服務?Redis真的適合幹這樣的事情?
  • 在網絡飽和之前,分析大量節點上的日誌數據,使用本地的CPU。
  • 如果想獲得非常高的性能,那麼必須做快慢分離,數據永遠都是快的代言,而命令和控制則代表了慢。
  • 當下,Twitter正在使用Mesos作爲作業調度程序以遷移到一個容器環境,這個做法很新穎,因此如何實現是一大看點。當然這個途徑也存在弊端,比如在複雜的運行時環境指定硬件資源的使用限制。
  • 中央集羣管理器來監控集羣。
  • 緩慢的JVM,快速的C,因此他們使用C/C++重寫緩存代理。

重點關注以上技術點,下面一起來看Twitter的Redis使用之道:

爲什麼使用Redis?

  • 使用Redis驅動Timeline,也是Twitter系統內最重要的服務。Timeline是Tweet基於id的一個索引,Home Timeline則是所有Tweet連接起來形成的一個列表。User Timeline則由用戶生成的Tweet組成,它們形成了另外一個列表。
  • 爲什麼使用Redis代替Memcache?主要因爲網絡帶寬問題(Network Bandwidth Problem)和長通用前綴問題(Long Common Prefix Problem)。
  • 網絡帶寬問題
  1. Memcache在Timeline上的表現並沒有Redis好,最大問題發生在fanout(推送)上。
  2. Twitter的讀和寫往往以增量方式進行,雖然每次的更新很少,但是Timeline本身的體積很大。
  3. 當一個Tweet產生時,它會被寫入對應的Timeline中。對於某些數據結構來說,Tweet是組成它的一小部分數據。在讀的時候,小批量Tweet被加載,而在滾輪向下滾動時,則會加載另外的一小批量。
  4. Home Timeline可能會非常大,但是用戶仍然希望在同一個集合中讀取,它可能會包含3000個實體。因此,在性能的優化上,避免數據庫讀取將非常重要。
  5. 增量讀寫使用了一個“讀-改-寫”的過程,因此對Timeline這樣的大型對象進行small reads開銷是非常昂貴的,通常會造成網絡瓶頸。
  6. 在每秒10萬+讀和寫的gigalink上,如果對象的平均大小超過1K,網絡將成爲瓶頸。
  • 長通用前綴問題(其實是兩個問題)
  1. 在數據格式上使用了一個靈活的模式,每個對象都有不同的屬性組成。對每個屬性都建獨立的鍵,這需求給每個屬性單獨發送請求,而不是對所有的屬性都進行緩存。
  2. 使用不同的時間戳跟蹤度量。如果獨立緩存每個度量樣本,那麼長通用前綴會被不停的重複緩存。
  3. 平衡度量和靈活的數據模式,使用分層的key space更令人滿意。
  • 通過CPU使用情況配置專門的集羣。舉個例子,內存鍵值存儲對CPU的利用很低。服務器上1%的CPU時間也許能支撐1K+的RPS,基於不同數據模式會得出不同的結果。
  • Reids的表現非常不錯。它能看到服務器可以做什麼,而不是正在做什麼。對於簡單的鍵值存儲,像Redis這樣的服務在服務器上可能會存在很多的CPU動態餘量。
  • Twitter首次爲Timeline服務配置Redis是在2010年,同時搭載Redis的還有Ads服務。
  • Twitter並沒有使用Redis的磁盤特性。這很大程度因爲在Twitter的系統中,緩存和存儲都在不同的團隊完成,他們會根據自己的使用來定製。也就是,對比Redis,存儲團隊有更好的服務。
  • 熱鍵是一個必須解決的問題,因此Twitter建立一個分層式緩存,客戶緩存會自動的緩存熱鍵。

Hybrid List

  • 爲Redis添加Hybrid List以獲得更可預期的內存性能。
  • Timeline是Tweet ID組成的列表,因此是一堆的整型,每個ID都很小。
  • Redis支持兩個列表類型:ziplist和linklist。Ziplist更具備空間效率,linklist則更加靈活,在雙向鏈表下每個建會佔用兩個指針,對比ID的體積來說,這個開銷非常大。
  • 如果從內存的使用效率上看,ziplists是唯一之選。
  • Ziplist的最大閾值被設置爲Timeline的大小。因此在調整Ziplist的閾值之前,永遠都不會儲存比它更大的Timeline。這同樣意味着一個產品決策,在Timeline中儲存多少個Tweet。
  • 對ziplist做添加和刪除操作效率是非常低的,特別在列表非常大的情況下。從ziplist中使用memmove將數據刪除,這裏必須保證列表仍然是連續的。向ziplist中添加則需要一個內存realloc調用,以保證新實體有足夠的空間。
  • 鑑於Timeline的體積,寫入操作存在潛在的高延時。Timeline在體積上的變化很大,大部分的用戶不會頻繁發送Tweet,因此他們的User Timeline都很小。Home Timeline,特別涉及到那些名流人物則可能很大。當修改一個巨大的Timeline,而內存又被緩存佔滿,通常會進行這個過程:大量小體積timeline將會被驅逐,直到有足夠的連續內存來儲存這個巨大的ziplist。這些內存管理操作將花費很多的時間,因此寫入操作可能存在非常高的潛在延時。
  • 因爲寫入會造成很多Timeline的修改操作,基於需要在內存中擴展Timeline,這裏有很大的可能會產生寫延時陷阱。
  • 基於寫延時帶來的高可變性,很難爲寫入建立SLA。
  • Hybrid List是ziplists組成的linklist,因此存在一個基於所有ziplist最大體積的閾值(以字節爲單位)。以字節爲單位最大程度上是爲了內存效率,它可以幫助分配和解除分配相同體積的內存。當一個列表結束後,空間就被分配給下一個Ziplist。Ziplist並不可回收,直到這個列表爲空。這就意味,通過刪除,你可以讓每個ziplist只包含一個實體。通常情況下,Tweet並不會被全部刪除。
  • 在Hybrid List之前,解決方案是儘快的讓一個大的Timeline過期,這會給其他Timeline節約出內存,但是如果用戶再查看這個Timeline時,開銷是非常大的。

BTree

  • 將BTree添加到Redis是爲了支持分層鍵上的範圍查詢,從而得到一個結果列表。
  • 在Redis中,增加次關鍵字或字段一般通過Hash Map處理,排序數據以執行一個範圍查詢時,sorted set被使用。因爲sorted set只能使用一個double類型的score來排序,所以這裏不能任意指定次級鍵或者名稱來排序。同時,鑑於Hash Map使用的線性搜索,因此它並不適用於存在太多次關鍵字或字段的情況。
  • 通過BSD實現BTree,並將之添加到Redis中。支持鍵查詢和範圍查詢,同時還具備了良好的查詢性能及簡單的代碼。唯一的缺點是BTree並不具備一個良好的內存利用效率,因爲指針將造成大量的元數據開銷。

集羣管理

  • 爲每個目的建立單獨的集羣,每個集羣部署了1個以上的Redis實例。如果一個數據集的大小大於單Redis實例可以支撐的極限,或者單Redis實例並不能提供足夠的吞吐量,key space需要被分割,數據則會橫跨一組實例在多個分片上保存,路由器將會爲key選擇應該保存的數據分片。
  • 集羣管理是Redis不會被撐爆的首要原因。既然可以使用集羣,那麼沒理由不將所有的用例轉移到Redis上。
  • Redis集羣運營起來並不輕鬆。基於頻繁的更新操作,人們使用Redis,但是許多Redis運營並不是冪等的。比如,網絡故障可能會引起重試,從而在一定程度上破壞數據的完整性。
  • Redis集羣支持集中管理者操控全局。使用memcache,許多集羣使用了一個基於一致性哈希的客戶端解決方案。如果出現非一致性數據,只能順其自然。爲了提供更好的服務,集羣需要一個功能負責檢測哪個分片發生問題,並且重新進行操作來保證同步。在足夠長的時間後,緩存應該被清理。在Redis中破壞數據是不容易被發現的,當有一個列表,它缺失了一個部分,這個問題很難被描述清楚。
  • 在建立Redis集羣上,Twitter做過了很多嘗試。Twemproxy,最初並不是在Twitter內部使用,它被建立用於服務Twemcache,隨後添加了對Redis的支持。同時,還針對代理類型路由建立了兩個額外的解決方案,其中一個與Timeline服務有關,但不是通用的;另一個則是專爲Timeline設計的通用解決方案,提供了集羣管理、複製和分片修復。
  • 集羣中存在3個選擇,服務器相互間通信以達成一個協議:集羣狀態;使用一個代理;或者是當客戶端數量達到閾值時做一個客戶端方面的集羣管理。
  • 沒有做服務器方面的優化,因爲一直以保持服務器簡單、透明和快速爲理念。
  • 並沒有通過客戶端,因爲改變不容易被推廣。在Twitter,1個緩存集羣大約爲100個項目使用。如果在一個客戶端中做改變必須推進到100個客戶端,這花費的時間可能以年計算。快速迭代意味着客戶端不能放任何代碼。
  • 使用一個代理模式路由途徑以及分片主要基於兩個原因。首先,緩存服務必須是個高性能服務。如果你想獲得高性能,就必須分離快快慢路徑,快對應着數據路徑,而慢則對應了命令行和控制路徑。其次,如果將集羣管理混合到服務器中,將增加Redis編碼的複雜度,從而造成一個有狀態的服務,如果你想給管理代碼打補丁或者升級,有狀態的服務你必須要重啓,從而可能會導致丟失一部分數據,滾動重啓集羣是非常痛苦的。
  • 使用代理途徑的另一個原因是在客戶端和服務器之間插入一個新的網絡躍點。關於在添加額外的網絡躍點上,Profiling 揭露了業內普遍存在的一個謠言。最起碼在Twitter的系統中,Redis服務器產生的延時不會超過0.5毫秒。在Twiiter,大部分的後端系統都基於Java,並使用Finagle做相互間的交互。在通過Finagle後,延時增加到10毫秒。因此附加的網絡躍點並不是問題,問題的本身在於JVM。除了JVM之外,你可以做任何的事情,當然除了添加又一個JVM。
  • 代理的失敗並不會增加困擾。在數據路徑上,引入一個代理層並不意味着帶來額外開銷。客戶端不用去關心需要連接的代理。如果因爲代理故障而造成的超時,客戶端只需要隨便選擇一個其他的代理。在代理等級不會發生任何分片,它們同樣是無狀態的。爲了擴展吞吐量可以添加更多的代理,代價是額外的成本。代理層只會因爲轉發被分配資源。集羣管理、分片以及集羣狀態監視都不是代理負責的範圍。代理之間並不需要保持一致。
  • Twitter中存在實例同時打開10萬個連接的情況,服務器也並不會因此發生故障。在Twitter,服務器沒理由去關閉一個連接,讓它一直打開有助於改善延時。
  • 緩存集羣被用作後備緩存(look-aside cache)。緩存本身不會負責數據的補充,客戶端負責取得一個丟失的鍵並進行緩存。如果一個節點發生故障,分片會被轉移到另一個節點。對故障恢復後的服務器進行同步以保證數據的一致性,所有這些都通過集羣管理者完成。在讓一個集羣更容易理解上,中央觀點確實非常重要。
  • 測試使用C++來編寫代理。C++代理帶來了一個顯著的性能提升,隨後代理層都使用了C和C++。

數據洞察

  • 當有調用顯示緩存系統失效,而大多數緩存都是正常時,這通常因爲客戶端的配置錯誤,或者它們請求的鍵過多從而導致濫用緩存,當然也可能因爲對同一個鍵多次請求造成服務器或鏈接飽和。
  • 如果通知某個人正在濫用系統,他們很可能會要求出示證據。哪個鍵?哪個分片的問題?什麼樣的流量導致了這個問題?因此你需要做足夠的度量和分析,從而將證據展示給客戶。
  • 面向服務的架構並不會給帶來問題隔離或者是自動debug,必須對組成系統的每個組件保持足夠的能見度。
  • 決定在緩存上獲得洞察力。緩存使用C編寫所以足夠快速,因此它可以能其他組件所不能,提供足夠的數據,而其他服務不能爲每個請求都提供數據。
  • 可以實現爲每條命令單獨建立日誌。在10萬QPS時,緩存可以記錄下所有發生的事情。
  • 避免鎖和阻塞,特別不能因爲磁盤寫入而造成阻塞。
  • 在每秒100請求和每條日誌消息100字節的情況下,每臺服務器每秒會記錄10MB的數據。當問題發生時,這些數據傳輸將造成很大的網絡開銷,大約佔10%的帶寬,這種開銷完全不允許。
  • 預計算服務器上的日誌以減少開銷。設想是已經清楚需要被計算的內容,一個進程負責讀取日誌,計算並生成一個摘要信息,然後只定期發送主機的摘要信息。對比原始數據,這個體積將微不足道。
  • 這些雲計算數據通過Storm聚合和存儲,並在上面建立可視化系統,你可以基於這些建立容量規劃。因爲每條日誌都可以被捕獲,所以你可以做許多事情。
  • 對於運營來說,洞察力非常重要。如果出現丟包現象,通常情況下是熱鍵或者是流量峯值導致。

對Redis的希望清單

  • 顯式的內存管理。
  • Deployable(Lua)Scripts。
  • 多線程,可以簡化集羣管理。Twitter有很多高性能服務器,每個主機都擁有100GB以上的內存以及大量的CPU。爲了使用一個服務器的所有能力,需要在實體主機商開啓許多Redis實例。通過多線程減少需要啓動實例數量,從而更容易的進行管理。

學到的知識

  • 可預見的規模需求。集羣越大、客戶越多,你越希望你的服務更可預知和確定。如果只有一個用戶和一個問題,你可以迅速的找到並解決這個問題。但是如果你有70個客戶,你完全忙不過來。
  • 如果你需要在許多分片上推廣某個更新,一個分片的速度問題就可能導致全局問題。
  • Twitter正在向container環境發展,使用了Mesos作爲作業調度器,調度器用來給請求分配CPU、內存等資源數量。當作業佔用的資源高於請求時,監視器會直接將它終止。在容器的環境下,Redis會產生一個問題。Redis引入了外部存儲碎片,這意味着你要使用更多內存來存儲同樣的數據。如果不想作業被終止,必須設計一個緩衝的區間。你可能會認爲內存碎片率設定在5%就足矣,但是我更願意多分配10%,甚至是20%的空間作爲緩衝。或者在我認爲每臺主機連接數可能會達到5000時,我將給系統分配支撐1萬個連接數的內存,結果會造成很大的浪費。對於當下大多數低延時服務來說,Mesos都不太適合,因此這些作業會與其他作業隔離。
  • 在運行時清楚資源使用是非常重要的在一個大型集羣中,總會有問題產生。雖然你一直認爲系統很安全,但是事情還是以意料之外的方式發生了。當下很少出現某臺機器完全崩潰的情況,比如,在達到10GB的內存上限後,在有空閒內存之前,請求都會被拒絕,造成的後果僅是一小部分請求不能獲得自己所需的內存資源,無傷大雅。而垃圾回收問題則是非常麻煩的,它可能降低系統的流量,當下這個問題已經困擾了很多機構。
  • 用數據說話。在計算到磁盤和計算到網絡之前,查看相對網絡速度、CPU速度計磁盤速度是非常有意義的,比如,節點被推送到中央監視服務之前查看的日誌綜述。除此之外,Redis中的LUA也是給數據提供計算的途徑。
  • LUA當下還沒有在Redis生產環境中實現響應式腳本意味着服務提供商不能保證他們的SLA,一個被加載的腳本可以做任何事情,因此沒有服務提供商會因爲添加一些代碼鋌而走險去破壞SLA。但是對於部署模型來說,意義重大,它將允許代碼評審和基準測試,以清晰的計算資源使用和性能。
  • Redis作爲下一個高性能流處理平臺。當下已經擁有pub-sub和scripting,沒什麼不可以的。

原文鏈接: How Twitter Uses Redis to Scale - 105TB RAM, 39MM QPS,10,000+ Instances(編譯/童陽  責編/仲浩)

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