圖解:美團(tuán)大規(guī)模KV存儲挑戰(zhàn)與架構(gòu)實(shí)踐

0 評論 516 瀏覽 0 收藏 49 分鐘

本文為演講內(nèi)容的整理。文章主要分為四個(gè)部分:第一部分介紹了美團(tuán) KV 存儲發(fā)展歷程;第二部分分享了內(nèi)存 KV Squirrel 挑戰(zhàn)和架構(gòu)實(shí)踐;第三部分闡述了持久化 KV Cellar 挑戰(zhàn)和架構(gòu)實(shí)踐;最后一部分介紹了未來的發(fā)展規(guī)劃。希望這些內(nèi)容能對大家有所幫助或啟發(fā)。

KV 存儲作為美團(tuán)一項(xiàng)重要的在線存儲服務(wù),承載了在線服務(wù)每天萬億級的請求量,并且保持著 99.995% 的服務(wù)可用性。在 DataFunSummit 2023 數(shù)據(jù)基礎(chǔ)架構(gòu)峰會上,我們分享了《美團(tuán)大規(guī)模 KV 存儲挑戰(zhàn)與架構(gòu)實(shí)踐》。

1 美團(tuán) KV 存儲發(fā)展歷程

2 大規(guī)模 KV 存儲的挑戰(zhàn)

3 內(nèi)存 KV Squirrel 挑戰(zhàn)和架構(gòu)實(shí)踐

3.1 Squirrel水平擴(kuò)展的挑戰(zhàn)

3.2 Gossip優(yōu)化

3.3 Squirrel 垂直擴(kuò)展的挑戰(zhàn)

3.4 forkless RDB

3.5 工作多線程

3.6 Squirrel可用性的挑戰(zhàn)

3.7 兩機(jī)房容災(zāi)

3.8 跨地域容災(zāi)

3.9 雙向同步?jīng)_突自動解決

4 持久化 KV Cellar 挑戰(zhàn)和架構(gòu)實(shí)踐

4.1 Cellar垂直擴(kuò)展的挑戰(zhàn)

4.2 Bulkload 數(shù)據(jù)導(dǎo)入

4.3 線程調(diào)度模型優(yōu)化

4.4 線程RTC模型改造

4.5 內(nèi)存引擎無鎖化

4.6 Cellar可用性的挑戰(zhàn)

4.7 雙向同步?jīng)_突自動解決

5 發(fā)展規(guī)劃和業(yè)界趨勢

1 美團(tuán) KV 存儲發(fā)展歷程

上圖就是美團(tuán)第一代的分布式 KV 存儲的架構(gòu),可能很多公司都經(jīng)歷過這個(gè)階段。

在客戶端內(nèi)做一致性哈希,然后在后端部署上很多 Memcached 實(shí)例,這樣就實(shí)現(xiàn)了最基本的 KV 存儲分布式設(shè)計(jì)。但這樣的設(shè)計(jì)存在很明顯的問題:比如在宕機(jī)摘除節(jié)點(diǎn)會時(shí)丟失數(shù)據(jù);此外,在緩存空間不夠需要擴(kuò)容時(shí),一致性哈希也會丟失一些數(shù)據(jù),這樣會給業(yè)務(wù)的開發(fā)帶來很大的困擾。

隨著 Redis 項(xiàng)目的成熟,美團(tuán)也引入了 Redis 來解決我們上面提到的問題,進(jìn)而演進(jìn)出來上圖這樣一個(gè)架構(gòu)??梢钥吹剑蛻舳诉€是一樣,使用一致性哈希算法,在服務(wù)器端變成了 Redis 組成的主從結(jié)構(gòu)。當(dāng)任何一個(gè)節(jié)點(diǎn)宕機(jī),我們可以通過 Redis 哨兵完成 failover,實(shí)現(xiàn)高可用。但有,還一個(gè)問題還是沒有解決,如果擴(kuò)縮容的話,一致性哈希仍然會丟失數(shù)據(jù)。

這時(shí)我們發(fā)現(xiàn)業(yè)界有一個(gè)比較成熟的開源 KV 存儲:也就是阿里巴巴的 Tair 。2014年,我們把 Tair 引入到技術(shù)內(nèi)部,去滿足業(yè)務(wù) KV 存儲方面的需求。

Tair 開源版本的架構(gòu)主要是三部分:最下邊的是存儲節(jié)點(diǎn),存儲節(jié)點(diǎn)會上報(bào)心跳到它的中心節(jié)點(diǎn),中心節(jié)點(diǎn)內(nèi)部設(shè)有兩個(gè)配置管理節(jié)點(diǎn),會監(jiān)控所有的存儲節(jié)點(diǎn)。如果有任何存儲節(jié)點(diǎn)宕機(jī)或者擴(kuò)容之類的行為,它會做集群拓?fù)涞闹匦聵?gòu)建。客戶端啟動的時(shí)候,它會直接從中心節(jié)點(diǎn)引入一個(gè)路由表,這個(gè)路由表簡單來說就是一個(gè)集群的數(shù)據(jù)分布圖,客戶端根據(jù)路由表直接去存儲節(jié)點(diǎn)讀寫。之前我們 KV 遇到的擴(kuò)容丟數(shù)據(jù)問題,它也有數(shù)據(jù)遷移機(jī)制來保證數(shù)據(jù)的完整性。

但是在使用的過程中,我們還遇到了一些其他問題,比如:它的中心節(jié)點(diǎn)雖然是主備高可用的,但它沒有分布式仲裁之類的機(jī)制,所以在網(wǎng)絡(luò)分割的情況下,它是有可能發(fā)生“腦裂”的,這種情況也給我們的業(yè)務(wù)造成過比較大的影響。在容災(zāi)擴(kuò)容的時(shí)候,遇到過數(shù)據(jù)遷移影響業(yè)務(wù)可用性的問題。

另外,我們之前用過 Redis ,業(yè)務(wù)會發(fā)現(xiàn) Redis 的數(shù)據(jù)結(jié)構(gòu)特別豐富,而 Tair 還不支持這些數(shù)據(jù)結(jié)構(gòu)。雖然我們用 Tair 解決了一些問題,但是 Tair 同樣也無法完全滿足我們的業(yè)務(wù)需求。于是,我們認(rèn)識到在美團(tuán)這樣一個(gè)業(yè)務(wù)規(guī)模大、復(fù)雜度高的場景下,很難有開源系統(tǒng)能很好滿足我們的需求。所以,我們決定在已應(yīng)用的開源系統(tǒng)之上進(jìn)行自研。

時(shí)值 2015 年, Redis 社區(qū)正式發(fā)布了它的集群版本 Redis Cluster。所以,我們緊跟社區(qū)步伐,并結(jié)合內(nèi)部需求做了很多自研功能,進(jìn)而演進(jìn)出本文要介紹的全內(nèi)存、高吞吐、低延遲的 KV 存儲 Squirrel。另外,我們基于 Tair,加入了很多美團(tuán)自研的功能,演進(jìn)出本文要介紹的持久化、大容量、數(shù)據(jù)高可靠的 KV 存儲 Cellar 。

Redis 社區(qū)一直都很活躍,所以,Squirrel 的迭代是自研和社區(qū)并重,自研功能設(shè)計(jì)上也會盡量與社區(qū)架構(gòu)兼容。Tair 開源版本已經(jīng)多年沒有更新,所以,Cellar 的迭代完全靠自研。后續(xù)內(nèi)容上大家也能看到,因?yàn)檫@方面的不同,Cellar 和 Squirrel 在解決同樣問題時(shí)可能會選取不同的方案。

這兩個(gè)存儲其實(shí)都是 KV 存儲領(lǐng)域的解決方案。實(shí)際應(yīng)用上,如果業(yè)務(wù)的數(shù)據(jù)量小,對延遲敏感,建議用 Squirrel ;如果數(shù)據(jù)量大,對延遲不是特別敏感,我們建議用成本更低的 Cellar 。

2 大規(guī)模 KV 存儲的挑戰(zhàn)

大規(guī)模KV 存儲的業(yè)務(wù)挑戰(zhàn)主要有兩點(diǎn):

一個(gè)是擴(kuò)展性。隨著業(yè)務(wù)規(guī)模持續(xù)變大,業(yè)務(wù)會要求使用容量更大的集群。這個(gè)容量包括兩方面,一方面是數(shù)據(jù)量,還有一方面是調(diào)用量。擴(kuò)展容量,最常見的方法就是把集群水平擴(kuò)展到更多的節(jié)點(diǎn),但是當(dāng)集群節(jié)點(diǎn)數(shù)達(dá)到一定規(guī)模后,再想擴(kuò)展新節(jié)點(diǎn)也會遇到很多困難,這是擴(kuò)展性上的第一個(gè)挑戰(zhàn)。

還有一個(gè)問題是有些業(yè)務(wù)場景的調(diào)用容量是無法隨著集群水平擴(kuò)展而擴(kuò)展的。比如,很多業(yè)務(wù)會使用 mget 進(jìn)行批量讀取。但隨著集群節(jié)點(diǎn)數(shù)的增加,由于“木桶效應(yīng)”,整個(gè) mget 請求的長尾延遲會越來越高,進(jìn)而導(dǎo)致服務(wù)的請求超時(shí)率持續(xù)上升。等集群達(dá)到一定規(guī)模之后,長尾延遲造成的可用性降低就超出業(yè)務(wù)的承受能力了。所以在水平擴(kuò)展之外,我們還需要解決好節(jié)點(diǎn)垂直擴(kuò)展上的挑戰(zhàn),來支持這種批量操作的業(yè)務(wù)場景。

另一個(gè)是可用性。隨著集群規(guī)模變大,要保證可用性維持在與小規(guī)模集群同等的水平,其實(shí)是很困難的。但業(yè)務(wù)服務(wù)卻不會因?yàn)榧阂?guī)模變大而能接受可用性有所降低。所以,美團(tuán)的挑戰(zhàn)是如何保證集群可用性不會隨著規(guī)模的變大而有所降低。

3 內(nèi)存 KV Squirrel 挑戰(zhàn)和架構(gòu)實(shí)踐

上圖是美團(tuán)的 Squirrel 架構(gòu)。中間部分跟 Redis 社區(qū)集群是一致的。它有主從的結(jié)構(gòu),Redis 實(shí)例之間通過 Gossip 協(xié)議去通信。我們在右邊添加了一個(gè)集群調(diào)度平臺,包含調(diào)度服務(wù)、擴(kuò)縮容服務(wù)和高可用服務(wù)等,它會去管理整個(gè)集群,把管理結(jié)果作為元數(shù)據(jù)更新到 ZooKeeper。

我們的客戶端會訂閱 ZooKeeper 上的元數(shù)據(jù)變更,實(shí)時(shí)獲取到集群的拓?fù)錉顟B(tài),直接對 Redis 集群節(jié)點(diǎn)進(jìn)行讀寫操作。

3.1 Squirrel水平擴(kuò)展的挑戰(zhàn)

但是基于 Redis Cluster 架構(gòu)的水平擴(kuò)展,會有如下問題:

一個(gè)是 Gossip 的消息通信量是節(jié)點(diǎn)數(shù)的平方,隨著集群節(jié)點(diǎn)數(shù)的增加,Gossip 通信的消息量會急劇膨脹。比如,我們實(shí)測對于一個(gè) 900 節(jié)點(diǎn)的集群,Gossip 消息的 CPU 消耗會高達(dá)12%,遠(yuǎn)高于小集群的 Gossip 資源消耗,這樣會造成極大的資源浪費(fèi)。

除了資源的浪費(fèi)以外,Gossip 消息過多,也會更多搶占用戶請求處理線程的資源,進(jìn)而會導(dǎo)致用戶請求經(jīng)常被 Gossip 消息的處理所阻塞,再導(dǎo)致用戶請求產(chǎn)生更多的超時(shí),影響服務(wù)可用性。

3.2 Gossip優(yōu)化

為了解決上述的擴(kuò)展性問題,我們對社區(qū)的 Gossip 方案進(jìn)行了優(yōu)化。首先針對 Gossip 傳輸?shù)南?,我們通過 Merkle Tree 對其做了一個(gè)摘要,把集群 Gossip 通信的數(shù)據(jù)量減少了90%以上。

服務(wù)端節(jié)點(diǎn)僅需要對比 Hash 值即可判斷元數(shù)據(jù)是否有更新,對于存在更新的情況也能快速判斷出更新的部分,并僅對此部分元數(shù)據(jù)進(jìn)行獲取、更新,大幅降低了 Gossip 消息處理的資源消耗。同時(shí),我們還增加了一個(gè)周期性的元數(shù)據(jù)全量同步功能,來解決可能因 Hash 沖突導(dǎo)致元數(shù)據(jù)無法更新的問題。

針對上述提到的 Gossip 消息處理影響業(yè)務(wù)請求的問題,我們把 Gossip 消息處理功能剝離到一個(gè)單獨(dú)的心跳線程里,并且由心跳線程來更新集群拓?fù)涞脑獢?shù)據(jù)。對于處理用戶請求的工作線程,僅需要對元數(shù)據(jù)進(jìn)行讀操作,可以做到無鎖讀。這樣的話,Gossip 請求處理就對業(yè)務(wù)請求完全沒有影響了。

3.3 Squirrel 垂直擴(kuò)展的挑戰(zhàn)

對基于 Redis 研發(fā)的 Squirrel 來說,垂直擴(kuò)展會存在如下問題:

首先是數(shù)據(jù)容量的問題。對一個(gè)內(nèi)存存儲來說,節(jié)點(diǎn)容量過大的話,很容易影響服務(wù)的可用性。例如,在主從節(jié)點(diǎn)要做數(shù)據(jù)同步時(shí),Redis 節(jié)點(diǎn)需要通過 fork 產(chǎn)生子進(jìn)程來生成全量數(shù)據(jù)的 RDB 快照。當(dāng)一個(gè) 8GB 的節(jié)點(diǎn)做 fork 調(diào)用時(shí),會由于頁表項(xiàng)過多,造成進(jìn)程出現(xiàn) 500 毫秒的阻塞。對于平均耗時(shí)只有幾毫秒的 KV 請求來說,這 500 毫秒的阻塞會造成大量的超時(shí)。

還有就是處理量的擴(kuò)展問題。雖然我們可以通過加從庫去擴(kuò)展集群的讀能力上限,但主庫的寫處理能力卻還是無力擴(kuò)展的。而且,受限于主庫的處理能力和機(jī)器帶寬限制,加從庫來擴(kuò)展讀能力也是有上限的。

3.4 forkless RDB

針對上述節(jié)點(diǎn)過大,fork 生成 RDB 會導(dǎo)致可用性降低的問題。我們實(shí)現(xiàn)了 forkless RDB 方案,這是一個(gè)不基于 fork,且不會中斷服務(wù)的生成數(shù)據(jù)快照 RDB 的方案。

如上圖所示,forkless RDB 的生成期間,它首先會停止哈希表的 rehash 過程,避免數(shù)據(jù)在哈希表之間的搬遷影響快照的一致性。然后,它會從頭開始對整個(gè)哈希表的 key 做迭代,每迭代一個(gè) key 就會把它 dump 一份出來放到復(fù)制隊(duì)列里邊。在迭代 key 的同時(shí),它會對迭代的位置記錄一個(gè)游標(biāo)。

如果在迭代哈希表的過程中,里面的 KV 有變更的話,在這個(gè)游標(biāo)之前的  KV 變更,也會把它放到復(fù)制隊(duì)列里邊,確保已經(jīng)復(fù)制的 KV 能夠持續(xù)獲得后續(xù)的變更。

如圖所示,RDB 游標(biāo)在 key 3,它會把之前已經(jīng)迭代過的 key 1 更新、key 2 刪除操作也插入到復(fù)制隊(duì)列里邊。在游標(biāo)之后的 key,因?yàn)檫€沒有做數(shù)據(jù)復(fù)制,所以等后續(xù)迭代到這個(gè) key 時(shí),把其最新值 dump 到復(fù)制隊(duì)列就好。通過這樣的方式,就實(shí)現(xiàn)了一個(gè)不需要 fork 就能獲得一個(gè)一致性數(shù)據(jù)快照 RDB 的過程。

這個(gè)方案的優(yōu)點(diǎn)很明顯,生成 RDB 的過程不會阻塞服務(wù)請求處理,并且因?yàn)槭菍?shí)時(shí)的發(fā)送一個(gè)個(gè) KV 數(shù)據(jù),所以就不需要等 RDB 生成好就可以向從庫復(fù)制數(shù)據(jù)了,大幅提升了數(shù)據(jù)同步的速度。但因?yàn)槿繑?shù)據(jù)迭代、復(fù)制是在工作線程去做的,而不是在子進(jìn)程內(nèi)。

所以,該方案會占用一部分工作線程的資源。另外,因?yàn)槭且?KV 為粒度做復(fù)制的,所以,如果哈希表里面有大 KV 的話,可能會因?yàn)楣ぷ骶€程復(fù)制大 KV 耗時(shí)過長,造成用戶請求等待耗時(shí)的上升。

3.5 工作多線程

對于處理量的擴(kuò)展,社區(qū)有一個(gè) IO 多線程的解決方案。但這個(gè) IO 多線程只是把網(wǎng)絡(luò)收發(fā)部分做了多線程處理,所以,其擴(kuò)展能力是比較有限的。比如 4個(gè) IO 線程下,它只能把整體的吞吐提升一倍,就到極限了。而且因?yàn)榇藭r(shí)工作線程已經(jīng)到瓶頸了,再往上去加 IO 線程,不僅無法提升性能,反而會消耗更多的 CPU 資源。對此,我們的解決方案是工作多線程,也就是說把請求處理的過程也多線程化。

如上圖所示,在工作多線程方案下,每個(gè)線程都會去處理請求,并且每個(gè)線程會完成從收包到請求處理,然后到發(fā)包的整個(gè)過程,是一個(gè) Run-to-Completion 線程模型。相比 IO 多線程,它會減少很多線程切換,節(jié)省很多的 CPU 資源。同時(shí)對于請求處理的過程,我們也通過細(xì)致的梳理,盡量縮小了臨界區(qū)的范圍,以保證大部分的請求處理過程是在臨界區(qū)之外的,來提升處理并發(fā)度。

如果一個(gè)工作線程需要加鎖的話,它會先 try lock。如果加鎖成功就繼續(xù)執(zhí)行了,但如果加鎖失敗的話,這個(gè)工作線程也不會阻塞等鎖。它會先去注冊一個(gè)管道的通知消息,然后就繼續(xù)處理網(wǎng)絡(luò)的收發(fā)包,還有非臨界區(qū)的請求了。等到鎖被釋放的時(shí)候,這個(gè)工作線程會通過 epoll 獲得管道里面的鎖釋放通知,然后去拿到這把鎖。這個(gè)時(shí)候它就可以去處理臨界區(qū)的請求操作了。

這樣的話,在整個(gè)加鎖、解鎖的過程中,工作線程沒有任何阻塞,仍然可以繼續(xù)做網(wǎng)絡(luò)收發(fā)、非臨界區(qū)請求的處理,獲得最大限度的處理能力。另外,對于新建 socket、數(shù)據(jù)復(fù)制等工作,跟工作線程的耦合很低,我們將其放到了單獨(dú)的線程去執(zhí)行,以盡量降低工作線程的負(fù)載。

通過實(shí)測,工作多線程方案的吞吐比社區(qū) IO 多線程提升了 70%,相對于社區(qū)單線程提升 3 倍多。

3.6 Squirrel可用性的挑戰(zhàn)

基于 Redis Cluster 的大規(guī)模集群可用性挑戰(zhàn)主要是維持機(jī)房容災(zāi)部署很困難。如上圖所示,由于 Redis Cluster 是去中心化的架構(gòu),所以部署上要求至少是三機(jī)房分布,以此來保證任何一個(gè)機(jī)房掛掉的時(shí)候,剩余的兩個(gè)機(jī)房仍然能有過半的節(jié)點(diǎn)來選出新的主節(jié)點(diǎn)。比如一個(gè)上千節(jié)點(diǎn)的集群要擴(kuò)容的話,可能需要幾百個(gè)分布在三個(gè)機(jī)房的節(jié)點(diǎn),一時(shí)之間其實(shí)很難湊齊這么多機(jī)房的資源。而當(dāng)業(yè)務(wù)大促容量需求很急時(shí),我們有時(shí)候只能犧牲機(jī)房容災(zāi)能力來滿足業(yè)務(wù)的容量需求。

還有在成本方面,對于一些數(shù)據(jù)可靠性要求較低的業(yè)務(wù),只需要兩副本冗余就夠了,極端情況下丟一點(diǎn)數(shù)據(jù)也是可以接受的。但受限于容災(zāi)要求,這些業(yè)務(wù)也只能使用三機(jī)房三副本部署,從成本角度考量很不劃算。

3.7 兩機(jī)房容災(zāi)

受 Google Spanner 的見證者節(jié)點(diǎn)啟發(fā),我們在 Squirrel 集群也引入了見證者節(jié)點(diǎn)角色。同 Spanner 一樣,Squirrel 見證者節(jié)點(diǎn)也不會存儲數(shù)據(jù),所以,它無法作為正常的主從庫提供請求處理能力,也不能發(fā)起選主投票。但見證者節(jié)點(diǎn)可以在集群選主時(shí)參與投票,幫助存活的機(jī)房節(jié)點(diǎn)完成過半選主過程。

見證者節(jié)點(diǎn)還可以設(shè)置權(quán)重,這樣只需要一個(gè)或幾個(gè)高權(quán)重見證者節(jié)點(diǎn),就能滿足一個(gè)大規(guī)模集群的容災(zāi)部署需求了。由于見證者節(jié)點(diǎn)不存儲數(shù)據(jù),且節(jié)點(diǎn)數(shù)很少,雖然集群還是三機(jī)房部署,但實(shí)際幾乎只需要兩機(jī)房的資源就能滿足機(jī)房容災(zāi)部署需求了,這樣就大幅降低了集群維持容災(zāi)部署的難度,從而節(jié)省大量的機(jī)器成本。

3.8 跨地域容災(zāi)

Squirrel 跨地域容災(zāi)的架構(gòu)如上圖所示,它通過一個(gè)集群間同步服務(wù)在兩個(gè)不同地域的集群之間做數(shù)據(jù)同步。這個(gè)同步服務(wù)首先偽裝為上游集群節(jié)點(diǎn)的 slave 把它的 RDB 和增量 log 拉取過來,然后再把拉取到的數(shù)據(jù)轉(zhuǎn)化成寫請求發(fā)到下游的集群,從而實(shí)現(xiàn)了一個(gè)集群間的數(shù)據(jù)同步。

通過這樣的架構(gòu),我們解決了服務(wù)的跨地域容災(zāi)問題。并且,通過在集群間搭建正反兩個(gè)方向的兩個(gè)同步任務(wù),就能實(shí)現(xiàn)集群間的雙向同步。

這樣的話,用戶服務(wù)就可以只在本地域?qū)?,但同時(shí)能讀到兩個(gè)地域分別寫入的數(shù)據(jù),解決了單向同步需要跨地域?qū)懙膯栴}。

雙向同步有兩個(gè)經(jīng)典問題需要解決:

一個(gè)是循環(huán)復(fù)制問題。我們?yōu)槊總€(gè) Squirrel 集群標(biāo)記了不同的 cluster id,并且記錄了每個(gè) KV 的初始寫入 cluster id,同步服務(wù)會過濾掉與目標(biāo)集群 cluster id 相同的數(shù)據(jù),以避免發(fā)生循環(huán)復(fù)制。

還有一個(gè)是數(shù)據(jù)沖突問題。我們一開始是通過業(yè)務(wù)層面保證在每個(gè)地域?qū)懖煌?Key 來解決的。但是在雙向同步的運(yùn)行過程中,還是會有一些極端場景可能會出現(xiàn)兩個(gè)地域并發(fā)寫同一個(gè) Key。比如像機(jī)房網(wǎng)絡(luò)故障場景,業(yè)務(wù)會把故障機(jī)房的所有寫入都切到正常機(jī)房。

但由于我們的集群間復(fù)制是異步的,可能故障機(jī)房有一些最新的 Key 變更還沒有復(fù)制到正常機(jī)房的集群。而如果在業(yè)務(wù)將寫切換到正常機(jī)房后,又寫入了相同 Key 的不同變更,就會產(chǎn)生兩個(gè)同步集群的數(shù)據(jù)沖突。在機(jī)房網(wǎng)絡(luò)恢復(fù)之后,業(yè)務(wù)還是要把一部分流量切回到之前故障的集群上,恢復(fù)到跨地域容災(zāi)的架構(gòu)。但由于兩個(gè)集群可能已經(jīng)有數(shù)據(jù)沖突了,所以,在業(yè)務(wù)切回之前,就需要對數(shù)據(jù)做沖突校驗(yàn)和修復(fù)。但是對大數(shù)據(jù)量集群來說,數(shù)據(jù)校驗(yàn)和修復(fù)的耗時(shí)可能會長達(dá)數(shù)天。在這樣長的時(shí)間內(nèi),只有一個(gè)單地域集群來支撐業(yè)務(wù),無論是從容災(zāi)還是容量的角度來看,都是有較大風(fēng)險(xiǎn)的。

3.9 雙向同步?jīng)_突自動解決

為了解決上述的雙向同步數(shù)據(jù)沖突問題,我們實(shí)現(xiàn)了一個(gè)基于數(shù)據(jù)寫入本地時(shí)間的 last write win 沖突自動解決功能。

如上圖所示,在 T1 時(shí)刻 Key money 的值在 A、B 兩個(gè)集群都是 100。T2 時(shí)刻,money 的值在 A 集群更新成了 120。但是在 A 集群的新值還沒復(fù)制到 B 集群的時(shí)候,B 集群在 T3 時(shí)刻把 money 的值更新成了 130。這時(shí)候 A、B 集群會互相向?qū)Ψ綇?fù)制各自寫入的新值,A 集群收到 B 集群的值 130 后,會發(fā)現(xiàn) B 集群 money 的更新時(shí)間大于自己(T3 > T2),它就會更新自己的 money 值為 130;B 集群也會收到 A 集群復(fù)制過來的 money 值 120,但它會發(fā)現(xiàn)這個(gè)值的更新時(shí)間小于自己本地值的更新時(shí)間(T2 < T3),就會忽略這個(gè)復(fù)制請求。通過這樣一個(gè)基于更新時(shí)間的 last write win 策略,就可以達(dá)到最終一致性。

上述方案看起來簡單,但是在復(fù)雜、大規(guī)模的業(yè)務(wù)場景下,還有很多問題要處理,所以,我們還做了以下的工作:保存最近更新的時(shí)間戳:當(dāng)發(fā)生時(shí)鐘回退時(shí),我們會繼續(xù)使用自己保存的時(shí)間戳,避免使用本地回退的時(shí)間導(dǎo)致數(shù)據(jù)也跟著發(fā)生了回退。

(PS:對于時(shí)鐘回退問題,我們調(diào)研過最新的 NTP 時(shí)鐘同步不會像以前一樣造成本地時(shí)鐘的回退或跳變,現(xiàn)在它通過把時(shí)鐘 tick 調(diào)快或調(diào)慢來完成類似的調(diào)整,所以,前述關(guān)于時(shí)鐘回退的解決方案在最新的 NTP 同步機(jī)制下就不是必要的了。

不過,為了保證我們的服務(wù)在任何系統(tǒng)下都能正常運(yùn)行,我們最終還是實(shí)現(xiàn)了這個(gè)功能。)記錄寫入數(shù)據(jù)的集群 id:我們會為所有寫入的 Key 保存寫入的集群 id。當(dāng)兩個(gè)值的更新時(shí)間相同時(shí),我們會比較集群 id,如果也相同,我們就知道是同一個(gè)集群先后寫入但獲取到相同本地時(shí)間的數(shù)據(jù),會允許其寫入;如果不同,我們僅會讓集群 id 更大的值寫入,來保證數(shù)據(jù)最終一致性。由復(fù)制操作改為復(fù)制變更后的數(shù)據(jù):像 INCR 類接口,A 集群的 money T1 時(shí)刻通過 INCRBY money 20 變成了 120,然后 B 集群 T2 時(shí)刻通過 INCRBY money 30 變成了 130。

A 集群收到 B 集群的復(fù)制時(shí),因?yàn)闀r(shí)間戳比自己的本地值大,它會執(zhí)行 INCRBY money 30 變成 150;然后 B 集群收到 A 集群的復(fù)制時(shí),因?yàn)闀r(shí)間戳比自己的本地值小,它會把這個(gè)復(fù)制請求給忽略掉,就造成了數(shù)據(jù)沖突。

針對這個(gè)問題,我們將所有操作的數(shù)據(jù)復(fù)制都改成了復(fù)制操作后的數(shù)據(jù),而不是這個(gè)操作本身,來解決類似 INCRBY 這種接口的數(shù)據(jù)沖突問題。保存最近刪除的 Key:像刪除類接口,A 集群 T2 時(shí)刻寫入了 money:120,然后 B 集群在 T3 時(shí)刻刪除了 money 這個(gè) Key。

A 集群收到 B 集群的復(fù)制時(shí),由于其時(shí)間戳比本地值大,A 會把數(shù)據(jù)刪了;但 B 集群收到 A 集群的復(fù)制時(shí),由于本地已經(jīng)不存在 money 這個(gè) Key 了,它就會把 money 當(dāng)做一個(gè)新 Key 進(jìn)行寫入,就造成了數(shù)據(jù)最終不一致。針對這個(gè)問題,我們通過保存最近一段時(shí)間刪除掉的 Key 及刪除時(shí)間戳,以便在刪除集群收到對端復(fù)制過來的舊 Key 時(shí)進(jìn)行甄別。

4 持久化 KV Cellar 挑戰(zhàn)和架構(gòu)實(shí)踐

上圖是我們最新的 Cellar 架構(gòu)圖,它跟阿里開源的 Tair 主要有兩個(gè)層面的不同。

第一個(gè)是 OB,第二個(gè)是 ZooKeeper。我們的 OB 跟 ZooKeeper 的 Observer 是類似的作用,提供 Cellar 中心節(jié)點(diǎn)元數(shù)據(jù)的查詢服務(wù)。它實(shí)時(shí)的與中心節(jié)點(diǎn)的 Master 同步最新的路由表,客戶端的路由表都是從 OB 去拿。

這樣做的好處主要有兩點(diǎn):

第一,把大量的業(yè)務(wù)客戶端跟集群的大腦 Master 做了隔離,防止路由表請求影響集群的管理;

第二,因?yàn)?OB 只提供路由表查詢服務(wù),不參與集群的管理,所以它可以水平擴(kuò)展,極大地提升了路由表的查詢能力。

第二個(gè)是我們引入了 ZooKeeper 做分布式仲裁,解決了上述提到的 Master、Slave 在網(wǎng)絡(luò)分割情況下的“腦裂”問題。并且通過把集群的元數(shù)據(jù)存儲到 ZooKeeper,從而提升了元數(shù)據(jù)的可靠性。

4.1 Cellar垂直擴(kuò)展的挑戰(zhàn)

在 Cellar 架構(gòu)下,不存在水平擴(kuò)展的問題,但與 Squirrel 一樣,它也有垂直擴(kuò)展方面的挑戰(zhàn)。而由于 Cellar 是持久存儲,它也很少遇到單機(jī)數(shù)據(jù)容量的問題,而要解決的問題主要是處理容量的垂直擴(kuò)展。

而且,由于 Cellar 是持久化引擎、多線程模型,它要解決的處理容量擴(kuò)展問題也是不一樣的,具體如下:

  • 引擎讀寫能力的不均衡性:Cellar 是基于 LSM-Tree 引擎模型的持久化存儲,這種引擎的多 Level compaction 會導(dǎo)致寫放大問題,進(jìn)而會造成其寫處理能力比讀低很多。所以,在一些寫相對較多的場景,機(jī)器資源雖然還有空閑,但寫處理能力卻已經(jīng)到瓶頸了。
  • 線程間同步的開銷:想要提升處理容量,就需要增加線程數(shù)。而隨著線程數(shù)的增加,線程間同步的開銷在整個(gè)服務(wù)的 CPU 使用占比也會越來越高。

所以,如果解決不好線程間同步的問題,想單純地增加線程數(shù)來提升處理容量行不通。

4.2 Bulkload 數(shù)據(jù)導(dǎo)入

對于上述提到引擎寫壓力達(dá)到瓶頸的集群,我們調(diào)研后發(fā)現(xiàn)其在線的實(shí)時(shí)寫入一般都是比較少的,高寫入量主要是用戶從離線批量寫數(shù)據(jù)到線上 Cellar 集群帶來的。

基于此,我們開發(fā)了 Bulkload 數(shù)據(jù)導(dǎo)入能力來解決這個(gè)問題。

Bulkload 整體架構(gòu)如上圖所示,它在普通寫入流涉及的客戶端和存儲節(jié)點(diǎn)之外,還引入了 S3 對象存儲來做導(dǎo)入數(shù)據(jù)的中轉(zhuǎn)。下面我們看下 Bulkload 具體的寫入流程:Bulkload 首先會在客戶端進(jìn)程內(nèi)生成分片內(nèi)有序的數(shù)據(jù)文件并寫到本地硬盤上。等客戶端的數(shù)據(jù)文件寫好之后,它會上傳到對象存儲,利用對象存儲做數(shù)據(jù)文件的中轉(zhuǎn),解決了客戶端與服務(wù)端之間直傳大文件容易失敗的問題。

分片 1 的數(shù)據(jù)文件寫入到對象存儲之后,客戶端會將數(shù)據(jù)文件的存儲地址告訴分片 1 的主所在的存儲節(jié)點(diǎn) DS1。然后 DS1 就會從對象存儲下載分片 1 的數(shù)據(jù)文件,并把它直接插入到 LSM-Tree 引擎里面。因?yàn)檫@是一個(gè)完整的文件插入,所以,它可以消除引擎在普通寫入時(shí)的內(nèi)存排序和刷盤壓力。同時(shí),因?yàn)檫@個(gè)文件的數(shù)據(jù)是分片內(nèi)有序的,所以,它在參與 Level 間 Compaction 時(shí)會與其他的引擎文件交叉很少,可以大幅減少多 Level compaction 的壓力。

然后 DS1 會把分片 1 數(shù)據(jù)文件的對象存儲地址復(fù)制發(fā)送到分片 1 的從所在的存儲節(jié)點(diǎn) DS2 。因?yàn)榇鎯?jié)點(diǎn)的復(fù)制只是傳輸數(shù)據(jù)文件的地址,所以復(fù)制速度是特別快的,也節(jié)省了很多傳輸?shù)膸挕S2 收到了分片 1 的地址后同樣會從對象存儲下載數(shù)據(jù)文件,并插入到引擎里面。

通過 Bulkload 解決方案,我們整體把數(shù)據(jù)離線導(dǎo)入的性能提升到舊版的 5 倍。

比如我們的一個(gè)存儲廣告特征的客戶使用 KV 方式從離線導(dǎo)數(shù)據(jù)到在線需要 14 小時(shí),受限于在線高峰期無法導(dǎo)數(shù)據(jù),如果需要繼續(xù)增加特征數(shù)據(jù),就需要擴(kuò)容集群了。而擴(kuò)容集群一方面會因?yàn)椤澳就靶?yīng)”導(dǎo)致請求長尾延遲問題,另一方面 Cellar 成本的上升也會抵消一部分廣告收益。而在 Bulkload 功能加持下,該客戶導(dǎo)入相同規(guī)模數(shù)據(jù)僅需不到 3 小時(shí),它可以在不增加 Cellar 資源的情況下,將廣告特征規(guī)模增加數(shù)倍,大幅提升了廣告的效果。

4.3 線程調(diào)度模型優(yōu)化

我們最初的線程模型與開源版 Tair 一樣,網(wǎng)絡(luò)線程池做收發(fā)包,收到的包經(jīng)過一個(gè)隊(duì)列轉(zhuǎn)出到一個(gè)大的工作線程池做請求處理。

這樣的線程模型,很容易發(fā)生請求間的互相影響。比如用戶有離線數(shù)據(jù)導(dǎo)入到 Cellar 的時(shí)候,就很容易導(dǎo)致在線讀請求的超時(shí)。

又比如當(dāng)有大 Value 讀寫的時(shí)候,工作線程處理會比較慢、占用線程的時(shí)間會很長,導(dǎo)致正常 Value 讀寫的快請求只能在隊(duì)列等待,進(jìn)而導(dǎo)致大量超時(shí)。

所以,為了隔離在離線請求、快慢請求的處理,讓服務(wù)資源優(yōu)先保證核心流量的處理,我們后來把線程模型改造成如上圖所示的 4 個(gè)隊(duì)列 + 4 個(gè)線程池的結(jié)構(gòu),將請求分成 4 類(讀快、讀慢、寫快、寫慢)分別放到不同的隊(duì)列和線程池去處理,進(jìn)而來提升服務(wù)核心流量的可用性。

但是,工作線程池按照請求類型分離之后帶來一個(gè)問題,就是不同業(yè)務(wù)場景、甚至同一業(yè)務(wù)的不同時(shí)段,不同類型請求量的占比是不一樣的。所以,給每個(gè)線程池分配多少線程是一個(gè)很棘手的問題。針對這個(gè)問題,我們增加了一個(gè)線程動態(tài)調(diào)度的邏輯:每個(gè)線程池都有一部分線程被設(shè)定為可共享線程,如果線程池比較空閑,共享線程就會去輪詢其他的隊(duì)列,處理一些繁忙線程池的請求,這樣就達(dá)到了自適應(yīng)調(diào)整各線程池資源的效果。但是在這樣的架構(gòu)下,雖然解決好了請求隔離性和不同請求類型線程資源的動態(tài)分配問題,但我們發(fā)現(xiàn)隨著節(jié)點(diǎn)流量的上漲,共享線程對于其他隊(duì)列的輪詢會消耗越來越多的 CPU 資源,而且集群業(yè)務(wù)的負(fù)載分布與默認(rèn)的線程數(shù)設(shè)置差異越大,這個(gè)消耗的占比也會越高。

為了解決上述線程池資源自適應(yīng)調(diào)度帶來的 CPU 消耗問題,我們對分離后的線程、隊(duì)列模型做出了如上圖的改造。改進(jìn)后的線程模型最主要的特點(diǎn)是引入了一個(gè)調(diào)度線程和一個(gè)空閑線程池,這個(gè)調(diào)度線程會實(shí)時(shí)統(tǒng)計(jì)每個(gè)線程池的負(fù)載,來評估每個(gè)線程池是否需要增加或減少線程并做出調(diào)度動作,空閑線程池用來存放當(dāng)前空閑的可用于調(diào)配的線程資源。

當(dāng)調(diào)度線程評估后決定做線程資源調(diào)配時(shí),它就會發(fā)送調(diào)度指令到相應(yīng)隊(duì)列中,當(dāng)線程池里的線程獲取并執(zhí)行了這個(gè)指令后,就實(shí)現(xiàn)了線程資源的調(diào)配。比如,它想給讀快線程池增加線程,就會給空閑線程池的隊(duì)列發(fā)送一個(gè)調(diào)度指令,空閑線程池的線程取到這個(gè)指令后,就會將自己加入到讀快隊(duì)列的線程池里面,去處理讀快隊(duì)列的請求。

當(dāng)調(diào)度線程想對讀慢線程池調(diào)減線程時(shí),它會向讀慢隊(duì)列發(fā)送一個(gè)調(diào)度指令,讀慢隊(duì)列的線程獲取到這個(gè)指令后,就會離開讀慢線程池加入到空閑線程池。通過調(diào)度線程準(zhǔn)實(shí)時(shí)的毫秒級負(fù)載統(tǒng)計(jì)、調(diào)度,我們實(shí)現(xiàn)了線程池資源的快速動態(tài)分配。對于每一個(gè)線程池的共享線程,也不再需要去輪詢其他線程池的隊(duì)列了,只需要專心處理自己隊(duì)列的請求即可,大幅降低了線程池資源調(diào)度的 CPU 消耗。通過上述的線程隊(duì)列模型優(yōu)化,服務(wù)在高負(fù)載場景下可以提高 30% 以上的吞吐量。

4.4 線程RTC模型改造

上圖左側(cè)畫的是我們服務(wù)請求的 IO 處理路徑:一個(gè)請求的處理流程會經(jīng)過網(wǎng)絡(luò)線程、請求隊(duì)列、工作線程、內(nèi)存和硬盤引擎。這個(gè)設(shè)計(jì)的問題是,請求在不同線程之間流轉(zhuǎn)會造成大量的 CPU 切換以及 CPU 高速緩存的 Cache Miss,進(jìn)而造成大量的 CPU 資源消耗。在大流量場景下,這樣的 CPU 消耗也是很可觀的一筆資源。

針對這個(gè)問題,我們對線程隊(duì)列模型又做了如上圖右側(cè)所示的改造。

新的模型下,我們讓網(wǎng)絡(luò)線程直接去做讀請求的處理,對于能夠命中內(nèi)存引擎的讀請求,其處理模型就是一個(gè) RTC(Run-to-Completion)模型。

具體來講,當(dāng)網(wǎng)絡(luò)線程收到一個(gè)請求之后,會先判斷是否為一個(gè)讀請求,如果是,就會直接去讀內(nèi)存引擎。我們服務(wù)的內(nèi)存引擎會緩存硬盤引擎上的熱點(diǎn)數(shù)據(jù),如果內(nèi)存引擎命中的話,網(wǎng)絡(luò)線程就可以直接返回結(jié)果給客戶端。

這樣在網(wǎng)絡(luò)線程內(nèi)就實(shí)現(xiàn)了請求的閉環(huán)處理,相比原來的模型可以去除所有因請求流轉(zhuǎn)造成的 CPU 資源消耗。而對于寫和讀未命中內(nèi)存引擎的請求,仍然需要經(jīng)過原來的請求處理路徑,去硬盤引擎讀或者寫數(shù)據(jù)。

新的線程模型,經(jīng)實(shí)測在 80% 內(nèi)存引擎命中率場景下,服務(wù)讀吞吐可以提升 30%+。

雖然新的線程隊(duì)列模型只實(shí)現(xiàn)了讀緩存命中請求的 RTC,但其實(shí)在線流量大多都是讀多寫少且熱點(diǎn)數(shù)據(jù)明顯、內(nèi)存引擎命中率比較高的場景,所以,新模型上線后在大多數(shù)的業(yè)務(wù)集群都取得了明顯的性能提升。

4.5 內(nèi)存引擎無鎖化

當(dāng)單機(jī)請求量達(dá)到了一定規(guī)模之后,我們發(fā)現(xiàn)服務(wù)內(nèi)的鎖操作會占用很多的 CPU 資源。經(jīng)分析發(fā)現(xiàn),大多數(shù)的鎖操作都發(fā)生在上節(jié)內(nèi)容提到的內(nèi)存緩存引擎上。

如上節(jié)所述,所有請求都會經(jīng)過內(nèi)存引擎,且大部分請求都會在內(nèi)存引擎命中并返回結(jié)果給客戶端。

所以,大部分請求都是純內(nèi)存處理,這個(gè)過程中的鎖操作就很容易成為瓶頸。

針對這個(gè)問題,我們對內(nèi)存引擎做了無鎖化改造,其改造后的結(jié)構(gòu)如下圖所示:

整體改造主要跟上圖的 HashMap 和 SlabManager 兩個(gè)數(shù)據(jù)結(jié)構(gòu)有關(guān)(其他數(shù)據(jù)結(jié)構(gòu)在圖中已略掉)。HashMap 是存儲 KV 數(shù)據(jù)的核心結(jié)構(gòu),它把 Key 通過 Hash 算法散列到不同的 Slot 槽位上,并利用鏈表處理 Hash 沖突;SlabManager管理不同尺寸內(nèi)存頁的申請和釋放,它利用鏈表把相同尺寸的內(nèi)存頁放到一起管理。

對于 HashMap,我們做了單寫多讀的無鎖鏈表改造。同時(shí),通過引入 RCU 機(jī)制實(shí)現(xiàn)了異步的內(nèi)存回收,解決了讀請求與寫請求內(nèi)存釋放操作的沖突,實(shí)現(xiàn)了讀請求處理全程的無鎖化。

寫請求雖仍需要加鎖,但我們對寫做了鎖粒度的優(yōu)化,可以大幅提升并發(fā)度。比如我們把 SlabManager 的訪問由一把大鎖改成每個(gè)內(nèi)存尺寸的管理鏈表單獨(dú)一把鎖,這樣在分配和釋放不同尺寸內(nèi)存頁的時(shí)候就可以實(shí)現(xiàn)并發(fā)。同時(shí) RCU 機(jī)制下的內(nèi)存異步回收,也解決了寫線程回收內(nèi)存時(shí)可能被阻塞的問題,進(jìn)一步提升了寫性能。內(nèi)存引擎通過無鎖化加 RCU 技術(shù)的改造,讀處理能力提升了 30% 以上。

4.6 Cellar可用性的挑戰(zhàn)

同 Squirrel 一樣,Cellar 也通過建設(shè)集群間數(shù)據(jù)同步能力,實(shí)現(xiàn)了跨地域的容災(zāi)架構(gòu)。不同的是,Cellar 因?yàn)槭亲匝?,無需考慮與社區(qū)版本的兼容性,同時(shí)為了簡化部署結(jié)構(gòu)、降低運(yùn)維成本,它把集群間數(shù)據(jù)同步功能做到了存儲節(jié)點(diǎn)內(nèi)部。

如上圖示例的北京集群 A 節(jié)點(diǎn)、上海集群 H 節(jié)點(diǎn),在接收到寫入之后,除了要做集群內(nèi)的數(shù)據(jù)同步以外,還需要把寫入數(shù)據(jù)同步到跨地域的另一個(gè)集群上。

Cellar 也可以通過配置兩個(gè)方向的跨集群數(shù)據(jù)同步鏈路,實(shí)現(xiàn)完全的本地域讀寫。Cellar 由于采用了存儲節(jié)點(diǎn)內(nèi)建的方案,它的集群間復(fù)制通過使用定制的復(fù)制包來甄別客戶寫入和復(fù)制寫入,并只為客戶寫入生成復(fù)制 log 來避免循環(huán)復(fù)制,相對Squirrel 會簡單一點(diǎn)。但同樣的,這種架構(gòu)也會遇到極端情況下,雙向同步導(dǎo)致的數(shù)據(jù)沖突問題。

4.7 雙向同步?jīng)_突自動解決

如上圖所示,Cellar 也實(shí)現(xiàn)了類似 Squirrel 的基于數(shù)據(jù)寫入本地時(shí)間的 last write win 沖突自動解決方案。

但 Cellar 的方案有一點(diǎn)區(qū)別是,它沒有通過在每條數(shù)據(jù)記錄 cluster id 的方式解決時(shí)鐘回退、兩次變更寫入的本地時(shí)間相同的問題,而是引入了 HLC(Hybrid Logic Clock)時(shí)鐘來解決這個(gè)問題。因?yàn)?HLC 可以保證每個(gè)集群寫入數(shù)據(jù)的時(shí)鐘是單調(diào)遞增的。

所以,接收端是不用擔(dān)心對端復(fù)制過來的數(shù)據(jù)有時(shí)間戳相同的問題。

而對于兩個(gè)集群分別寫入,時(shí)間戳相同且 HLC 的邏輯時(shí)鐘剛好也相同的情況,可以通過比較集群配置的 cluster id(不會存儲到每條 KV 數(shù)據(jù)內(nèi))來決定最終哪個(gè)數(shù)據(jù)可以寫入。

5 發(fā)展規(guī)劃和業(yè)界趨勢

未來,根據(jù)技術(shù)棧自上而下來看,我們的規(guī)劃主要覆蓋服務(wù)、系統(tǒng)、硬件三個(gè)層次。

首先,在服務(wù)層主要包括三點(diǎn):

第一,Squirrel && Cellar 去 ZK 依賴。如前所述,Squirrel 集群變更到客戶端的通知是依賴 ZK 來實(shí)現(xiàn)的,Cellar 的中心節(jié)點(diǎn)選主和元數(shù)據(jù)存儲也是依賴 ZK 實(shí)現(xiàn)的。但 ZK 在大規(guī)模變更、通知場景下,它的處理能力是無法滿足我們的需求的,很容易引發(fā)故障。

所以,Squirrel 會去掉對 ZK 的依賴,改為使用公司內(nèi)的配置管理、通知組件來實(shí)現(xiàn)集群變更到客戶端的通知。Cellar 會通過在中心節(jié)點(diǎn)間使用 Raft 協(xié)議組成 Raft 組,來實(shí)現(xiàn)選主和元數(shù)據(jù)多副本強(qiáng)一致存儲(注:本文整理自 DatafunSummit 2023 演講,此工作當(dāng)前已完成開發(fā),處于灰度落地階段)。

第二,向量引擎。大模型訓(xùn)練、推理場景有很多向量數(shù)據(jù)存儲和檢索需求,業(yè)界很多 NoSQL、SQL 數(shù)據(jù)庫都支持了向量引擎能力。KV 存儲作為高性能的存儲服務(wù),如果支持了向量引擎,可大幅提升大模型訓(xùn)練、推理的效率。

第三,云原生。當(dāng)前美團(tuán)的 KV 服務(wù)規(guī)模很大,相應(yīng)的運(yùn)維成本也比較高。所以,我們計(jì)劃做一些服務(wù)云原生部署、調(diào)度方面的探索,向更高運(yùn)維自動化水平邁進(jìn)。

其次是系統(tǒng)層,計(jì)劃對 Kernel Bypass 技術(shù)做一些探索和研發(fā)落地,比如新版內(nèi)核支持的 io_uring、英特爾的 DPDK、SPDK 技術(shù)等。由于 KV 存儲是典型的高吞吐服務(wù),它的網(wǎng)絡(luò) IO、硬盤 IO 壓力都很大,Kernel Bypass 技術(shù)可以大幅提升服務(wù)的 IO 能力,降低訪問延遲和成本。

最后是硬件層,計(jì)劃對計(jì)算型硬件的應(yīng)用做一些探索,比如配備了壓縮卡的 SSD,可以將服務(wù)引擎層使用 CPU 做的數(shù)據(jù)壓縮工作卸載到壓縮卡上,釋放出 CPU 資源做更高價(jià)值的計(jì)算工作。KV 服務(wù)是典型的低延遲、高網(wǎng)絡(luò)負(fù)載的服務(wù)。

所以,我們也計(jì)劃對 RDMA 網(wǎng)絡(luò)做一些探索,以期進(jìn)一步降低服務(wù)訪問延遲、提升網(wǎng)絡(luò)處理能力。

6 本文作者

澤斌,來自美團(tuán)基礎(chǔ)研發(fā)平臺/基礎(chǔ)技術(shù)部。

本文由人人都是產(chǎn)品經(jīng)理作者【湯師爺】,微信公眾號:【架構(gòu)師湯師爺】,原創(chuàng)/授權(quán) 發(fā)布于人人都是產(chǎn)品經(jīng)理,未經(jīng)許可,禁止轉(zhuǎn)載。

題圖來自Unsplash,基于 CC0 協(xié)議。

更多精彩內(nèi)容,請關(guān)注人人都是產(chǎn)品經(jīng)理微信公眾號或下載App
評論
評論請登錄
  1. 目前還沒評論,等你發(fā)揮!