原文出處:http://weibo.com/p/1001643872772421209951
作者:賴佳俊@LierD
微博作為目前最大的中文社交媒體平臺,擁有著上億的日活用戶。我們每天都會面臨各種非常具有挑戰(zhàn)性的業(yè)界技術難題。其中最具挑戰(zhàn)性的幾類問題是:
海量數(shù)據(jù)存儲。微博總量已經(jīng)超過千億數(shù)據(jù)。海量數(shù)據(jù)的存取是一個非常大的技術挑戰(zhàn)
巨大的并發(fā)請求。主feed接口的設計需要面對4倍于日常請求峰值的情況。
我們在解決上述的幾類挑戰(zhàn)中,結合自身的業(yè)務場景以及實際請求情況,設計出了高容量、可擴展、具有穩(wěn)定性保障等特點的服務架構。本文主要結合微博平臺的業(yè)務場景以及個人對分布式緩存的使用經(jīng)驗和體會來做說明介紹。
文章主要從以下幾個方面來展開:
(1)緩存介紹,包括緩存的引入,緩存內(nèi)存分配策略,以及淘汰算法等基本要點說明
(2)分布式緩存架構,主要結合平臺緩存使用中碰到的問題以及一些考慮點,說明緩存系統(tǒng)的變遷。
在傳統(tǒng)的后端架構中,由于請求量以及響應時間要求不高,我們經(jīng)常采用單一的db的結構。如下圖1?所示,應用服務器直接存取DB。這種架構簡單,但也存在著如圖中所描述的問題,即DB存在性能瓶頸,隨著請求量的增加,單DB無法繼續(xù)穩(wěn)定提供服務。
對于請求量不大的場景,我們可以通過對DB進行讀寫分離、一主多從、硬件升級(SSD)等方式提升系統(tǒng)的承載能力以及冗余能力,但這幾種提升方式存在著以下幾個缺陷:
(1)性能提升有限。很難得到量級上的提升,而大部分互聯(lián)網(wǎng)產(chǎn)品直接面對著千萬級用戶訪問,單一使用db的結構,難以達到性能要求
(2)成本高昂,為了達到N倍的承載能力的提升,需要至少N倍以上DB服務器
圖1?傳統(tǒng)服務架構
?? ?那么我們是否有更好的方式來做呢?
?? ?相信大家在學習操作系統(tǒng)的時候,一定看過如圖2?類似的一組數(shù)據(jù):
圖2?各類存儲介質(zhì)的訪問速度
從圖2中可以看到,一次內(nèi)存尋址的時間大概在100ns,順序從內(nèi)存中獲取1MB數(shù)據(jù)的時間大概在250000ns。對應的,我們可以看到一次磁盤尋址以及順序讀取磁盤的速度大概在千萬ns級別。這里我們可以得出一個結論,也即內(nèi)存數(shù)據(jù)的獲取速度大概在磁盤的獲取速度的兩個數(shù)量級。也因此我們可以通過引入緩存中間件,來提高系統(tǒng)整體的承載能力,原有的單層db結構也可以變?yōu)槿鐖D3所示的緩存+db結構。
圖3 DB+緩存
通過在應用服務與DB中間引入緩存層,我們可以得到如下三個好處:
(1)讀取速度得到提升
(2)系統(tǒng)擴展能力得到大幅增強。我們可以通過加緩存,來讓系統(tǒng)的承載能力提升
(3)總成本下降,單臺緩存即可承擔原來的多臺DB的請求量,大大節(jié)省了機器成本
常見的緩存服務有本地緩存,memcached,redis等。他們各有自己的特點,本文以下內(nèi)容主要是結合微博對于memcached一些使用來做講解
1.2.1 Memcached介紹
Memcached?是一個高性能的分布式內(nèi)存對象緩存系統(tǒng),廣泛應用于動態(tài)Web應用,以減輕數(shù)據(jù)庫負載。它通過在內(nèi)存中緩存數(shù)據(jù)和對象來減少讀取數(shù)據(jù)庫的次數(shù),從而提高網(wǎng)站的訪問速度。
Memcached都有哪些特點呢?
本身是一個kv存儲系統(tǒng)。存儲的時候,需要指定key以及對應的value;獲取的時候,只能通過key獲取
協(xié)議簡單,只支持簡單的命令如:set、get、cas、delete、stats、incr、decr等,而無類似于mysql的select、join等復雜的sql命令。所有的命令以及數(shù)據(jù)都是以文本的形式傳遞,這也就意味著mc的協(xié)議無需特殊的客戶端解析工具。我們可以通過在終端telnet進行命令傳遞以及數(shù)據(jù)獲取。
支持設定過期時間
1.2.2 Memcached的安裝以及使用
Memcached的安裝可以通過在官網(wǎng)上下載源碼的方式安裝:http://memcached.org/
相關安裝教程可以參考官網(wǎng)安裝步驟,親測有效~:http://code.google.com/p/memcached/wiki/NewInstallFromSource
安裝完成后,可以在linux終端執(zhí)行以下命令啟動?./memcached -l 11211
新啟一個終端,敲入命令telnet localhost 11211,即進入命令行模式
下面我們一起來試試Memcached hello world調(diào)用:
圖4 memcached helloworld
因本文篇幅有限,具體命令不再展開描述,大家如果感興趣可以通過官網(wǎng)的鏈接:http://code.google.com/p/memcached/wiki/NewCommands??了解。
1.2.3 Memcached內(nèi)存分配原理介紹
掌握Memcached的安裝、使用命令,其實對大部分的同學來說已經(jīng)足以開展相關開發(fā)工作了。但當碰到一些線上問題的時候,單純的會用Memcached是無法快速、合理的分析問題所在的。所以接下來我們將介紹Memcached的內(nèi)存分配管理原理。
Memcached默認情況下采用了名為Slab Allocator的機制分配、管理內(nèi)存。Slab Allocator的基本原理是按照預先規(guī)定的大小,將分配的內(nèi)存分割成特定長度的塊, 以完全解決內(nèi)存碎片問題。
在介紹memcached的內(nèi)存分配原理之前,需要跟大家說明以下幾個關鍵的名詞的概念:
item
一個待存儲的元素,按字節(jié)計算大小,可以理解為一個物品
Chunk
用于緩存item的內(nèi)存空間。可以理解為一個儲物格
Slab Class
特定大小的chunk的組。可以理解為儲物格按大小進行分類,如80B作為一類,96B作為一類....
Page
分配給Slab class的內(nèi)存空間,默認是1MB。分配給Slab之后根據(jù)slab class的大小切分成chunk??梢岳斫鉃橐粋€page是一個固定大小的柜子,上面可以按slab class進行分割,一個柜子只能按一個slab class進行分割。柜子上的格子數(shù)為柜子大小/?儲物格的大小
介紹完上述的幾個基本概念后,我們可以來看看mc在分配內(nèi)存的時候是怎么處理的。
圖5 memcached?初始化示意圖
如圖5為一個memcached示例在啟動的時候,可以指定的一些參數(shù),初始大小為slab class的起始大小,增長因子為下一個slab class是初始因子的倍數(shù)。如圖中所示,初始大小為80B,增長因子為1.5。則mc在啟動后,會按下圖生成slab class表。
圖6 slab分布圖
完成初始后,當某一個請求到來的時候——如圖中所示由一個123B大小的元素希望找到存儲空間,memcached會通過slab class表找到最合適的slab class:比元素大的最少的那個,在圖中場景下為180B,即使所需的空間只要123B。
?? ? ? ?此時Memcached示例并沒有分配任何的空間給180B的slab進行管理。所以為了能讓請求的元素能存儲上,Memcached實例會分配1?個page給180這個slab(在默認情況下為1MB實際內(nèi)存)。
圖 7 page分配圖
180B slab class在獲取到1MB的空間后,會按照自己的大小對page進行分隔,也即1MB/180=5828個具體的存儲空間(chunk)。此時,123B的請求就可以被存儲起來了。
隨著時間的慢慢推移,memcached的內(nèi)存空間會逐步被分配完,如下圖8所示:
圖 8?內(nèi)存slab分配圖
我們可以看到,memcached劃分給每個slab的page數(shù)是不均等的,存在部分的slab是可能一個page都分配不到的。
假設所有的內(nèi)存都分配完,同時每個slab內(nèi)部的chunk也都分配完了。此時又來了一個新的元素123B,那么就會觸發(fā)memcached的淘汰機制了。
memcached首先會查看180B的slab是否存在過期的元素,如果存在,則先清理部分,預留空位。如果180B這個slab的數(shù)據(jù)都比較熱(沒有過期),則按LRU進行淘汰。需要注意的是,淘汰是在slab內(nèi)部進行的,也即在上面的場景中只有180Bslab內(nèi)部進行淘汰剔除,對于其他的slab,是沒有受到影響的。memcached也不會回收比較空余的其他slab的page。也即一個page被分配給某個slab后,他將一直被這個slab所占用,永遠無法被mc回收,直到memcached重啟。
這個特性被稱為Memcached的鈣化問題:Memcached在線上跑了一段時間后,內(nèi)存按原始訪問模式分配內(nèi)存。當訪問模式變更后,原有的分配模式可能導致緩存頻繁出現(xiàn)數(shù)據(jù)剔除問題。最典型的場景即為內(nèi)存尚有空余,但一直有數(shù)據(jù)被剔除,命中率一直上不去。對于這種情況,解決方法為重啟緩存。
1.2.4?小結
上面兩節(jié)中我們主體介紹了Memcached的使用以及內(nèi)部的一些內(nèi)存分布原理。相信大家在自己動手操作過后,會有一個較深的影響。在實際的使用中,除了關注緩存本身的一個使用外,我們還會有其他一些關注點。如高可用、擴展性等,這些關注點的實現(xiàn)并不是單純依靠單一服務節(jié)點即可。為了能夠滿足上述的幾個關注點,我們需要引入分布式緩存結構。
考慮之前在緩存引入小節(jié)中所描述的,我們在原有的單層db結構中引入了緩存memcached:
圖 9 DB+緩存
在這種單實例緩存架構下,隨著業(yè)務規(guī)模的不斷增長,我們發(fā)現(xiàn)存在如下幾個問題:
(1)容量問題
單一服務節(jié)點無法突破單機內(nèi)存上限。目前微博數(shù)據(jù)已經(jīng)超過千億數(shù)據(jù),雖然無需所有數(shù)據(jù)都放入緩存當中,但在保證一定的命中率的情況下(注:微博核心緩存命中率需要達到99%)緩存部分數(shù)據(jù),也需要以百GB甚至是TB為單位的內(nèi)存容量。而單臺服務器的內(nèi)存總有上限(目前微博使用的常用服務器內(nèi)存上限128G),也即單實例緩存無法滿足緩存所有數(shù)據(jù)的需要
(2)服務高可用(HA)
在單實例場景下,緩存如果因為網(wǎng)絡波動、機器故障等原因不可用,原來由緩存承擔的請求會全部落到DB上,最終系統(tǒng)會如同雪崩般,全部crash。
(3)擴展問題
單一服務節(jié)點無法突破單實例請求峰值上限。微博業(yè)務是存在熱點突發(fā)事件的,也即隨時會有可能出現(xiàn)數(shù)倍于日常請求峰值的情況。雖然我們在做資源的評估的時候,會確保資源的冗余度足夠滿足請求翻倍(一般為2~4倍)的情況。但我們也需要后端資源是可以線性擴展的
下面我們也會針對上面的幾個問題,一步一步說明相應的解決方案。
微博線上服務都是由多個服務端節(jié)點存儲數(shù)據(jù)。由于memcached本身不支持分布式,所以需要客戶端或者中間層代理實現(xiàn)分布式節(jié)點獲取。在今天的描述中,為了簡化問題,會主要以客戶端實現(xiàn)分布式為主,即應用服務器(客戶端)根據(jù)內(nèi)部的算法,選擇特定的緩存節(jié)點,存取數(shù)據(jù)。
2.2.1 數(shù)據(jù)分片(Sharding)
前面提到單實例memcached主要會有以下幾個問題:
(1)請求量請求量超過單端口可承受極限
(2)數(shù)據(jù)容量超過單實例內(nèi)存容量。
為了解決以上兩個問題,我們的做法是從單端口實例擴展為一組實例。一組memcached由多個memcached實例組成,每個實例上只緩存數(shù)據(jù)總量的一部分數(shù)據(jù),客戶端在請求的時候,需要決定從哪個memcached中獲取數(shù)據(jù)(hash)。
常見的hash算法可以使用:
(1)取模hash
圖 10?取模hash
如圖10所示,客戶端根據(jù)請求的key,做取模,最終根據(jù)結果選擇對應的memcached節(jié)點。這種方式實現(xiàn)簡單。易于理解,缺點在于加減節(jié)點的時候,會造成較大的震蕩:每加、減一個節(jié)點,hash方式全部改變,整體命中率會下降的非??臁?/p>
(1)一致性hash
一致性哈希的算法描述可以參考
https://zh.wikipedia.org/wiki/%E4%B8%80%E8%87%B4%E5%93%88%E5%B8%8C
一致性哈希算法的主要優(yōu)點在于加減節(jié)點,對于服務整體的震蕩較小。并且在某個節(jié)點crash后,可以將后續(xù)請求,轉移至另一個節(jié)點中,具備一定的防單點特性。
2.2.2?主從雙層結構
?? ?從單臺實例增加到一組緩存后,我們可以解決單端口容量、訪問量不足的問題,但是如果出現(xiàn)某一臺緩存掛了的情況。請求依然會落到后端的DB上。
上面也提到了一致性hash的算法,可以通過一致性hash的方式,來減少損失。
但基于一致性哈希策略的分布式實現(xiàn)在微博業(yè)務場景下也存在一些問題:
(1)微博線上業(yè)務對緩存命中率要求高。某臺緩存掛了,會導致緩存整體命中率下降(即使一致性hash會在一定時間后將數(shù)據(jù)重新種到另一個節(jié)點上),對于命中率要求在99%以上的Feed流核心業(yè)務場景來說,命中率的下降是難以接受的
(2)一致性hash存在請求漂移的情況,假設某一段時間服務因網(wǎng)絡因素訪問某個服務節(jié)點失敗,則在這時候,會將數(shù)據(jù)的更新、獲取都遷移到下一個節(jié)點上。后續(xù)網(wǎng)絡恢復后,應用服務探測到服務節(jié)點可用,則繼續(xù)從原服務節(jié)點中獲取數(shù)據(jù),這就導致了在故障期間所做的更新操作,對于原服務節(jié)點不可見了
目前我們對于這種單點問題主要是通過引入主從緩存結構來解決的。主從結構示意圖如下圖11所示:
圖 11?主從雙層結構緩存
服務端在上行邏輯中,進行雙寫操作——由應用服務負責更新master、slave數(shù)據(jù)。
下行獲取數(shù)據(jù),先獲取master數(shù)據(jù),當master返回空,或者無法取到數(shù)據(jù)的時候,訪問slave。
在這種模式下,為了避免兩份數(shù)據(jù)帶來的不一致問題,需要以master數(shù)據(jù)為準。即如果有更新數(shù)據(jù)操作,需要從master中獲取數(shù)據(jù),再對master進行cas更新。更新成功后,才更新slave。如果cas多次后都失敗,則對master、slave進行delete操作,后續(xù)讓請求穿透回種即可。
2.2.3?橫向線性擴展
?? ? ? ?在雙層結構下,我們可以很好的解決單點問題,即某一個節(jié)點如果crash了,請求可以被slave承接住,請求不會直接落在DB上。
?? ? ? 但這種架構仍然存在一些問題:
(1)帶寬問題。由于存在熱點訪問的情況,線上經(jīng)常出現(xiàn)單個服務節(jié)點的帶寬跑滿的情況。
(2)請求量問題。隨著業(yè)務的不斷發(fā)展,并發(fā)請求數(shù)超過了單個節(jié)點的機器上限。數(shù)據(jù)分片、雙層結構都不能解決這種問題。
上面的兩個問題,其實總結起來是如何快速橫向擴展系統(tǒng)的支撐能力。對于這個問題,我們的解決思路為增加數(shù)據(jù)的副本數(shù)。即讓數(shù)據(jù)副本存在于多個節(jié)點中,從而平攤原本落在一個節(jié)點的請求。
從我們經(jīng)驗來看,對于線性擴展,可以在原來的master上引入一層L1層緩存。整體示意圖如12所示:
圖12?采用L1的緩存架構
上行操作需要對L1進行多寫。寫緩存的順序為master-slave-L1(所有),寫失敗則進行delete操作,后續(xù)由穿透請求進行回種。
L1可以由多組緩存組成,每組緩存相互獨立。應用服務在獲取數(shù)據(jù)的時候,先從L1中選取一組資源,然后再進行hash選取特定節(jié)點。對于multiget的場景也是先選取一組緩存,然后才對這組緩存進行multiget操作。如果L1獲取不到數(shù)據(jù),再依次獲取master、slave數(shù)據(jù)。獲取成功,則回種到L1中。
在采用L1的模式中,數(shù)據(jù)也是以master中數(shù)據(jù)為主的。即如果有更新數(shù)據(jù)的需要,需要從master中獲取數(shù)據(jù)原本,再進行cas更新。如果cas更新成功,才同時更新slave、L1資源。如果對master的操作失敗,則進行delete all操作,讓后續(xù)請求穿透回種。
當線上流量、請求量達到一個水位的時候,我們會進行L1的擴容——增加一組、或幾組L1緩存,從而提升系統(tǒng)整體的承載能力。此時系統(tǒng)的整體響應請求量是可以做到線性擴展的。
可以看到,雙層結構下,slave作為主的備份存在。假設線上master緩存命中率為99%,則落在slave上的請求只有1%,并且這1%的請求都是很偏、很少人訪問到的??梢韵胂?,在這種情況下,如果master真的出現(xiàn)問題,請求全部落在slave上,slave也是沒有任何數(shù)據(jù)可供訪問的。Slave作為防單點措施是失敗的。
引入L1后,slave過冷并沒有被解決,同時,由于master被放置到L1之下,也遇到了slave的問題,master的數(shù)據(jù)也存在過冷的風險。為了解決上面的問題,我們在線上配置的時候,會將整組slave做為L1的一組資源進行配置,讓slave以L1的身份承擔部分的熱請求。同時為了解決master過冷的問題,我們也會讓應用服務在選擇L1的時候有一定的概率落空,從而讓master作為L1邏輯分組,去承擔部分熱請求。整體結構圖如圖13所示:
圖13 slave、master同時作為L1架構
本文主要結合目前微博平臺的線上業(yè)務場景,根據(jù)個人對memcached的使用的經(jīng)驗,以及分布式架構情況做了介紹。行文倉促,肯定有很多細節(jié)需要繼續(xù)完善,如果有問題或者建議,可以微博私信?@微博平臺架構?或講師?@LierD?一起繼續(xù)探討。
隨著微博業(yè)務的蓬勃發(fā)展,相信還會有更多的技術挑戰(zhàn)在等著我們?nèi)ソ鉀Q,去征服。我們也希望看到本文的小伙伴以及有志于做一些不一樣的互聯(lián)網(wǎng)產(chǎn)品的小伙伴們,能夠加入我們,跟我們一起去攀登技術高峰~
新兵訓練營簡介
微博平臺新兵訓練營活動是微博平臺內(nèi)部組織的針對新入職同學的團隊融入培訓課程,目標是團隊融入,包括人的融入,氛圍融入,技術融入。當前已經(jīng)進行4期活動,很多學員迅速成長為平臺技術骨干。
具體課程包括《環(huán)境與工具》《分布式緩存介紹》《海量數(shù)據(jù)存儲基礎》《平臺RPC框架介紹》《平臺Web框架》《編寫優(yōu)雅代碼》《一次服務上線》《Feed架構介紹》《unread架構介紹》
微博平臺是非常注重團隊成員融入與成長的團隊,在這里有人幫你融入,有人和你一起成長,也歡迎小伙伴們加入微博平臺,歡迎私信咨詢。
講師簡介
?? ? ? ? 賴佳?。ㄎ⒉╆欠Q:@LierD?),2013年7月畢業(yè)于哈爾濱工業(yè)大學后,加入新浪微博工作至今,擔任系統(tǒng)研發(fā)工程師。曾先后參與微博Feed流優(yōu)化、后端服務穩(wěn)定性建設等項目。關注jvm調(diào)優(yōu),微服務,分布式存儲系統(tǒng)。微博平臺新兵訓練營二期學員.
?? 業(yè)余愛好三國殺,dota,喜歡自己做飯下廚希望能在微博的平臺上認識到更多優(yōu)秀的小伙伴,一起交流,一起成長
更多建議: