無鎖緩存锭部,每秒 10 萬并發(fā),究竟如何實(shí)現(xiàn)面褐?

> 原文地址 (https://mp.weixin.qq.com/s/BfuRWaB7RDjpGmQbZdmMZw)

有一類業(yè)務(wù)場景:

(1)超高吞吐量拌禾,每秒要處理海量請求;

(2)寫多讀少展哭,大部分請求是對數(shù)據(jù)進(jìn)行修改湃窍,少部分請求對數(shù)據(jù)進(jìn)行讀取匪傍;

這類業(yè)務(wù)您市,有什么實(shí)現(xiàn)技巧么?

接下來役衡,一起聽我從案例入手茵休,娓娓道來。

快狗打車手蝎,場景舉例

(1)司機(jī)地理位置信息會隨時(shí)變化榕莺,可能每幾秒鐘地理位置要修改一次;

(2)用戶打車的時(shí)候查看某個(gè)司機(jī)的地理位置柑船,查詢地理位置的頻率相對較低帽撑;

這里要用到兩個(gè)接口

(1)大量修改司機(jī)信息:

void SetDriverInfo(long driver_id, DriverInfo info);

(2)相對少量查詢司機(jī)信息:

DriverInfo GetDriverInfo(long driver_id);

這一類業(yè)務(wù),一般怎么實(shí)現(xiàn)呢鞍时?

具體到底層的實(shí)現(xiàn)亏拉,往往是一個(gè) Map 內(nèi)存緩存:

(1)查詢 key 定長,例如:司機(jī) ID逆巍;

(2)返回 value 也定長及塘,例如:司機(jī)實(shí)體序列化后的二進(jìn)制串;

即锐极,類似這樣的一個(gè) kv 緩存結(jié)構(gòu):

Map<driver_id, DriverInfo>

這個(gè) kv 內(nèi)存緩存是一個(gè)臨界資源笙僚,對它的并發(fā)訪問,有什么注意事項(xiàng)么灵再?

臨界資源的訪問肋层,需要注意加讀寫鎖亿笤,實(shí)施互斥。

以下栋猖,是加鎖寫入的偽代碼:

void SetDriverInfo(long driver_id, DriverInfo info){

WriteLock (m_lock);

Map<driver_id>= info;

UnWriteLock(m_lock);

}

畫外音:假設(shè) info 已經(jīng)序列化净薛。

以下,是加鎖讀取的偽代碼:

DriverInfo GetDriverInfo(long driver_id){

DriverInfo t;

ReadLock(m_lock);

t= Map<driver_id>;

UnReadLock(m_lock);

return t;

}

當(dāng)吞吐量很高時(shí)蒲拉,上述流程可能存在什么問題肃拜?

假設(shè)快狗打車有 100w 司機(jī)同時(shí)在線,每個(gè)司機(jī)每 5 秒更新一次經(jīng)緯度狀態(tài)雌团,那么每秒就有 20w 次寫并發(fā)操作燃领。

假設(shè)快狗打車日訂單 1000w 個(gè),平均每秒大概也有 300 個(gè)下單锦援,對應(yīng)到查詢并發(fā)量猛蔽,大概每秒 1000 級別的并發(fā)讀操作。

在這樣的吞吐量下(每秒 20w 寫雨涛,1k 讀)枢舶,鎖 m_lock 會成為潛在瓶頸,導(dǎo)致 Map 訪問效率極低替久。

有什么潛在的優(yōu)化方法么?

鎖沖突之所以嚴(yán)重躏尉,是因?yàn)檎麄€(gè) Map 共用一把鎖蚯根,鎖的粒度太粗。

畫外音:可以認(rèn)為是一個(gè)數(shù)據(jù)庫的 “庫級別鎖”胀糜。

是否可能進(jìn)行水平拆分颅拦,來降低鎖沖突呢?

答案是肯定的教藻。

畫外音:類似于數(shù)據(jù)庫里的分庫距帅,把一個(gè)庫鎖變成多個(gè)庫鎖,來提高并發(fā)括堤,降低鎖沖突碌秸。

我們可以把 1 個(gè) Map 水平切分成 N 個(gè) Map:


void SetDriverInfo(long driver_id, DriverInfo info){

i = driver_id % N; // 水平拆分成 N 份,N 個(gè) Map悄窃,N 個(gè)鎖

WriteLock (m_lock[i]); // 鎖第 i 把鎖

Map[i]<driver_id>= info; // 操作第 i 個(gè) Map

UnWriteLock (m_lock[i]); // 解鎖第 i 把鎖

}

如此優(yōu)化讥电,能否提高性能?

(1)一個(gè) Map 變成了 N 個(gè) Map轧抗,每個(gè) Map 的并發(fā)量恩敌,變成了 1/N;

(2)同時(shí)横媚,每個(gè) Map 的數(shù)據(jù)量纠炮,變成了 1/N月趟;

所以理論上,鎖沖突會成平方指數(shù)降低恢口,性能會提升狮斗。

有沒有可能,進(jìn)一步細(xì)化鎖粒度弧蝇,一個(gè)元素一把鎖呢碳褒?

答案也是肯定的。

畫外音:可以認(rèn)為是一個(gè)數(shù)據(jù)庫的 “庫級別鎖”看疗,優(yōu)化為 “行級別鎖”沙峻。

不妨設(shè) driver_id 是遞增生成的,并且假設(shè)內(nèi)存比較大两芳,此時(shí)可以把 Map 優(yōu)化成 Array摔寨,并把鎖的粒度細(xì)化到最細(xì)的,每個(gè)司機(jī)信息一個(gè)鎖:

void SetDriverInfo(long driver_id, DriverInfo info){

index = driver_id;

WriteLock (m_lock[index]); // 超級大內(nèi)存怖辆,一條記錄一個(gè)鎖是复,鎖行鎖

Array[index]= info; //driver_id 就是 Array 下標(biāo)

UnWriteLock (m_lock[index]); // 解鎖行鎖

}

image

這個(gè)方案使得鎖沖突降到了最低,但鎖資源大增竖螃,在數(shù)據(jù)量非常大的情況下淑廊,內(nèi)存往往是裝不下的。

畫外音:數(shù)據(jù)量比較小的時(shí)候特咆,可以一個(gè)元素一把鎖季惩,典型的是連接池,每個(gè)連接用一把鎖表示連接是否可用腻格。

還沒有方法進(jìn)一步降低鎖沖突画拾,提升并發(fā)量呢?

寫多讀少的業(yè)務(wù)菜职,有一種優(yōu)化方案:無鎖緩存青抛,將鎖沖突降低到。

無鎖緩存酬核,可能存在什么問題蜜另?

如果緩存不加鎖,讀寫吞吐量可以達(dá)到極限愁茁,但是多線程對緩存中同一塊定長數(shù)據(jù)進(jìn)行寫操作時(shí)蚕钦,有可能出現(xiàn)不一致的臟數(shù)據(jù)。

這個(gè)方案為了提高性能鹅很,犧牲了一致性嘶居。

讀取時(shí),獲取到了錯(cuò)誤的數(shù)據(jù),是不能接受的邮屁。

畫外音:作為緩存整袁,允許 cache miss,卻不允許讀臟數(shù)據(jù)佑吝。

臟數(shù)據(jù)是如何產(chǎn)生的坐昙?

不加鎖,在多線程并發(fā)寫時(shí)芋忿,可能出現(xiàn)以下情況:

image

(1)線程 1 對緩存進(jìn)行操作炸客,對 key 想要寫入 value1;

(2)線程 2 對緩存進(jìn)行操作戈钢,對 key 想要寫入 value2痹仙;

(3)不加鎖,線程 1 和線程 2 對同一個(gè)定長區(qū)域進(jìn)行一個(gè)并發(fā)的寫操作殉了,可能每個(gè)線程寫成功一半开仰,導(dǎo)致出現(xiàn)臟數(shù)據(jù)產(chǎn)生,最終的結(jié)果即不是 value1 也不是 value2薪铜,而是一個(gè)亂七八糟的不符合預(yù)期的值 value-unexpected众弓;

如何解決上述問題呢?

本質(zhì)上隔箍,這是一個(gè)數(shù)據(jù)完整性問題谓娃。

并發(fā)寫入的數(shù)據(jù)分別是 value1 和 value2,讀出的數(shù)據(jù)是 value-unexpected鞍恢,數(shù)據(jù)被篡改傻粘,這本質(zhì)上是一個(gè)數(shù)據(jù)完整性的問題。

通常如何保證數(shù)據(jù)的完整性呢?

例如:運(yùn)維如何保證,從中控機(jī)分發(fā)到上線機(jī)上的二進(jìn)制沒有被篡改恼蓬?

md5阿趁。

又例如:即時(shí)通訊系統(tǒng)中,如何保證接受方收到的消息瀑志,就是發(fā)送方發(fā)送的消息涩搓?

發(fā)送方除了發(fā)送消息本身,還要發(fā)送消息的簽名劈猪,接收方收到消息后要校驗(yàn)簽名昧甘,以確保消息是完整的,未被篡改战得。

“簽名” 是一種常見的保證數(shù)據(jù)完整性的方案充边。

加入 “簽名” 保證數(shù)據(jù)的完整性之后,讀寫流程需要如何升級?

image

加上簽名之后浇冰,不但緩存要寫入定長 value 本身贬媒,還要寫入定長簽名(例如 16bitCRC 校驗(yàn)):

(1)線程 1 對緩存進(jìn)行操作,對 key 想要寫入 value1肘习,寫入簽名 v1-sign际乘;

(2)線程 2 對緩存進(jìn)行操作,對 key 想要寫入 value2漂佩,寫入簽名 v2-sign脖含;

(3)如果不加鎖,線程 1 和線程 2 對同一個(gè)定長區(qū)域進(jìn)行一個(gè)并發(fā)的寫操作投蝉,可能每個(gè)線程寫成功一半养葵,導(dǎo)致出現(xiàn)臟數(shù)據(jù)產(chǎn)生,最終的結(jié)果即不是 value1 也不是 value2墓拜,而是一個(gè)亂七八糟的不符合預(yù)期的值 value-unexpected港柜,但簽名,一定是 v1-sign 或者 v2-sign 中的任意一個(gè)咳榜;

畫外音:16bit/32bit 的寫可以保證原子性夏醉。

(4)數(shù)據(jù)讀取的時(shí)候,不但要取出 value涌韩,還要像消息接收方收到消息一樣畔柔,校驗(yàn)一下簽名,如果發(fā)現(xiàn)簽名不一致臣樱,緩存則返回 NULL靶擦,即 cache miss;

當(dāng)然雇毫,對應(yīng)到司機(jī)地理位置玄捕,除了內(nèi)存緩存之前,肯定需要 timer 對緩存中的數(shù)據(jù)定期落盤棚放,寫入數(shù)據(jù)庫枚粘,如果 cache miss,可以從數(shù)據(jù)庫中讀取數(shù)據(jù)飘蚯。

巧不巧秒馍迄?

總結(jié)

當(dāng)業(yè)務(wù)滿足:

(1)超高并發(fā)

(2)寫多讀少局骤;

(3)定長 value攀圈;

時(shí),可以用以下方法來提升吞吐量:

(1)水平拆分來降低鎖沖突峦甩;

思路:單庫變多庫赘来。

(2)Map 轉(zhuǎn) Array 的方式來最小化鎖沖突,一條記錄一個(gè)鎖;

思路:庫鎖變行鎖撕捍。

(3)無鎖拿穴,最大化并發(fā);

思路:行鎖變無鎖忧风,完整性與性能的折衷默色。

(4)通過簽名的方式保證數(shù)據(jù)的完整性,實(shí)現(xiàn)無鎖緩存狮腿;

思路:寫時(shí)寫簽名腿宰,讀時(shí)校驗(yàn)簽名。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缘厢,一起剝皮案震驚了整個(gè)濱河市吃度,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌贴硫,老刑警劉巖椿每,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異英遭,居然都是意外死亡间护,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門挖诸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汁尺,“玉大人,你說我怎么就攤上這事多律〕胀唬” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵狼荞,是天一觀的道長辽装。 經(jīng)常有香客問我,道長相味,這世上最難降的妖魔是什么如迟? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮攻走,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘此再。我一直安慰自己昔搂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布输拇。 她就那樣靜靜地躺著摘符,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上逛裤,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天瘩绒,我揣著相機(jī)與錄音,去河邊找鬼带族。 笑死锁荔,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蝙砌。 我是一名探鬼主播阳堕,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼择克!你這毒婦竟也來了恬总?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤肚邢,失蹤者是張志新(化名)和其女友劉穎壹堰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骡湖,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡贱纠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了勺鸦。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片并巍。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖换途,靈堂內(nèi)的尸體忽然破棺而出懊渡,到底是詐尸還是另有隱情,我是刑警寧澤军拟,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布剃执,位于F島的核電站,受9級特大地震影響懈息,放射性物質(zhì)發(fā)生泄漏肾档。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一辫继、第九天 我趴在偏房一處隱蔽的房頂上張望怒见。 院中可真熱鬧,春花似錦姑宽、人聲如沸遣耍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽舵变。三九已至酣溃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間纪隙,已是汗流浹背赊豌。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绵咱,地道東北人碘饼。 一個(gè)月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像麸拄,于是被迫代替她去往敵國和親派昧。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355