從內(nèi)部需求出發(fā),我們基于TiKV設(shè)計了一款兼容Redis的KV存儲?;赥iKV的數(shù)據(jù)存儲機制,對于窗口數(shù)據(jù)的處理以及過期數(shù)據(jù)的GC問題卻成為一個難題。本文希望基于從KV存儲的設(shè)計開始講解,到GC設(shè)計的逐層優(yōu)化的過程,從問題的存在到不同層面的分析,可以給讀者在類似的優(yōu)化實踐中提供一種參考思路。
一、背景介紹
當前公司內(nèi)部沒有統(tǒng)一的KV存儲服務(wù),很多業(yè)務(wù)都將 Redis 集群當作KV存儲服務(wù)在使用,但是部分業(yè)務(wù)可能不需要 Redis 如此高的性能,卻承擔著巨大的機器資源成本(內(nèi)存價格相對磁盤來說更加昂貴)。為了降低存儲成本的需求,同時盡可能減少業(yè)務(wù)遷移的成本,我們基于 TiKV 研發(fā)了一套磁盤KV存儲服務(wù)。
1.1 架構(gòu)簡介
以下對這種KV存儲(下稱磁盤KV)的架構(gòu)進行簡單描述,為后續(xù)問題描述做鋪墊。
1.1.1 系統(tǒng)架構(gòu)
磁盤KV使用目前較流行的計算存儲分離架構(gòu),在TiKV集群上層封裝計算層(后稱Tula)模擬Redis集群(對外表現(xiàn)是不同的Tula負責某些slot范圍),直接接入業(yè)務(wù)Redis客戶端。
圖1:磁盤KV架構(gòu)圖示
業(yè)務(wù)寫入數(shù)據(jù)基于Tula轉(zhuǎn)換成TiKV中存儲的KV對,基于類似的方式完成業(yè)務(wù)數(shù)據(jù)的讀取。
注意:Tula中會選舉出一個leader,用于進行一些后臺任務(wù),后續(xù)詳細說。
1.1.2 數(shù)據(jù)編碼
TiKV對外提供的是一種KV的讀寫功能,但是Redis對外提供的是基于數(shù)據(jù)結(jié)構(gòu)提供讀寫能力(例如SET,LIST等),因此需要基于TiKV現(xiàn)有提供的能力,將Redis的數(shù)據(jù)結(jié)構(gòu)進行編碼,并且可以方便地在TiKV中進行讀寫。
TiKV提供的API比較簡單:基于key的讀寫接口,以及基于字典序的迭代器訪問。
因此,Tula層面基于字典序的機制,對Redis的數(shù)據(jù)結(jié)構(gòu)基于字典序進行編碼,便于訪問。
注意:TiKV的key是可以基于字典序進行遍歷(例如基于某個前綴進行遍歷等),后續(xù)的編碼,機制基本是基于字典序的特性進行設(shè)計。
為了可以更好地基于字典序排列的搜索特性下對數(shù)據(jù)進行讀寫,對于復(fù)雜的數(shù)據(jù)結(jié)構(gòu),我們會使用另外的空間去存放其中的數(shù)據(jù)(例如SET中的member,HASH中的field)。而對于簡單的數(shù)據(jù)結(jié)構(gòu)(類似STRING),則可以直接存放到key對應(yīng)的value中。
為此,我們在編碼設(shè)計上,會分為metaKey和dataKey。metaKey是基于用戶直接訪問的key進行編碼,是編碼設(shè)計中直接對外的一層;dataKey則是metaKey的存儲指向,用來存放復(fù)雜數(shù)據(jù)結(jié)構(gòu)中的具體內(nèi)部數(shù)據(jù)。
另外,為了保證訪問的數(shù)據(jù)一致性,我們基于TiKV的事務(wù)接口進行對key的訪問。
(1)編碼&字段
以下以編碼中的一些概念以及設(shè)定,對編碼進行簡述。
key的編碼規(guī)則如下:
圖2:key編碼設(shè)計圖示
以下對字段進行說明
namespace
為了方便在一個TiKV集群中可以存放不同的磁盤KV數(shù)據(jù),我們在編碼的時候添加了前綴namespace,方便集群基于這個namespace在同一個物理TiKV空間中基于邏輯空間進行分區(qū)。
dbid
因為原生Redis是支持select語句,因此在編碼上需要預(yù)留字段表示db的id,方便后續(xù)進行db切換(select語句操作)的時候切換,表示不同的db空間。
role
用于區(qū)分是哪種類型的key。
對于簡單的數(shù)據(jù)結(jié)構(gòu)(例如STRING),只需要直接在key下面存儲對應(yīng)的value即可。
但是對于一些復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(例如HASH,LIST等),如果在一個value下把所有的元素都存儲了,對與類似SADD等指令的并發(fā),為了保證數(shù)據(jù)一致性,必然可以預(yù)見其性能不足。
因此,磁盤KV在設(shè)計的時候把元素數(shù)據(jù)按照獨立的key做存儲,需要基于一定的規(guī)則對元素key進行訪問。這樣會導致在key的編碼上,會存在key的role字段,區(qū)分是用戶看到的key(metaKey),還是這種元素的key(dataKey)。
其中,如果是metaKey,role固定是M;如果是dataKey,則是D。
keyname
在metaKey和dataKey的基礎(chǔ)上,可以基于keyname字段可以較方便地訪問到對應(yīng)的key。
suffix
針對dataKey,基于不同的Redis數(shù)據(jù)結(jié)構(gòu),都需要不同的dataKey規(guī)則進行支持。因此此處需要預(yù)留suffix區(qū)間給dataKey在編碼的時候預(yù)留空間,實現(xiàn)不同的數(shù)據(jù)類型。
以下基于SET類型的SADD指令,對編碼進行簡單演示:
圖3: SADD指令的編碼設(shè)計指令圖示
基于userKey,通過metaKey的拼接方式,拼接metaKey并且訪問
訪問metaKey獲取value中的
基于value中的uuid,生成需要的dataKey
寫入生成的dataKey
(2)編碼實戰(zhàn)
編碼實戰(zhàn)中,會以SET類型的實現(xiàn)細節(jié)作為例子,描述磁盤KV在實戰(zhàn)中的編碼細節(jié)。
在這之前,需要對metaKey的部分實現(xiàn)細節(jié)進行了解
(3)metaKey存儲細節(jié)
所有的metaKey中都會存儲下列數(shù)據(jù)。
圖4:metaKey編碼設(shè)計圖示
uuid:每一個metaKey都會有一個對應(yīng)的uuid,表示這個key的唯一身份。
create_time:保存該元數(shù)據(jù)的創(chuàng)建時間
update_time:保存該元數(shù)據(jù)的最近更新時間
expire_time:保存過期時間
data_type:保存該元數(shù)據(jù)對應(yīng)的數(shù)據(jù)類型,例如SET,HASH,LIST,ZSET等等。
encode_type:保存該數(shù)據(jù)類型的編碼方式
(4)SET實現(xiàn)細節(jié)
基于metaKey的存儲內(nèi)容,以下基于SET類型的數(shù)據(jù)結(jié)構(gòu)進行講解。
SET類型的dataKey的編碼規(guī)則如下:
keyname:metaKey的uuid
suffix:SET對應(yīng)的member字段
因此,SET的dataKey編碼如下:
圖5:SET數(shù)據(jù)結(jié)構(gòu)dataKey編碼設(shè)計圖示
以下把用戶可以訪問到的key稱為user-key。集合中的元素使用member1,member2等標注。
這里,可以梳理出訪問邏輯如下:
圖6:SET數(shù)據(jù)結(jié)構(gòu)訪問流程圖示
簡述上圖的訪問邏輯:
基于user-key拼接出metaKey,讀取metaKey的value中的uuid。
基于uuid拼接出dataKey,基于TiKV的字典序遍歷機制獲取uuid下的所有member。
1.1.3 過期&GC設(shè)計
對標Redis,目前在user-key層面滿足過期的需求。
因為存在過期的數(shù)據(jù),Redis基于過期的hash進行保存。但是如果磁盤KV在一個namespace下使用一個value存放過期的數(shù)據(jù),顯然在EXPIRE等指令下存在性能問題。因此,這里會有獨立的編碼支持過期機制。
鑒于過期的數(shù)據(jù)可能無法及時刪除(例如SET中的元素),對于這類型的數(shù)據(jù)需要一種GC的機制,把數(shù)據(jù)完全清空。
(1)編碼設(shè)計
針對過期以及GC(后續(xù)會在機制中詳細說),需要額外的編碼機制,方便過期和GC機制的查找,處理。
過期編碼設(shè)計
為了可以方便地找到過期的key(下稱expireKey),基于字典序機制,優(yōu)先把過期時間的位置排到前面,方便可以更快地得到expireKey。
編碼格式如下:
圖7:expireKey編碼設(shè)計圖示
其中:
expire-key-prefix:標識該key為expireKey,使用固定的字符串標識
slot:4個字節(jié),標識slot值,對user-key進行hash之后對256取模得到,方便并發(fā)掃描的時候線程可以分區(qū)掃描,減少同key的事務(wù)沖突
expire-time:標識數(shù)據(jù)的過期時間
user-key:方便在遍歷過程中找到user-key,對expireKey做下一步操作
GC編碼設(shè)計
目前除了STRING類型,其他的類型因為如果在一次過期操作中把所有的元素都刪除,可能會存在問題:如果一個user-key下面的元素較多,過期進度較慢,這樣metaKey可能會長期存在,占用空間更大。
因此使用一個GC的key(下稱gcKey)空間,安排其他線程進行掃描和清空。
編碼格式如下:
圖8:gcKey編碼設(shè)計圖示
(2)機制描述
基于前面的編碼,可以對Tula內(nèi)部的過期和GC機制進行簡述。
因為過期和GC都是基于事務(wù)接口,為了減少沖突,Tula的leader會進行一些后臺的任務(wù)進行過期和GC。
過期機制
因為前期已經(jīng)對過期的user-key進行了slot分開,expireKey天然可以基于并發(fā)的線程進行處理,提高效率。
圖9:過期機制處理流程圖示
簡述上圖的過期機制:
拉起各個過期作業(yè)協(xié)程,不同的協(xié)程基于分配的slot,拼接協(xié)程下的expire-key-prefix,掃描expireKey
掃描expireKey,解析得到user-key
基于user-key拼接得到metaKey,訪問metaKey的value,得到uuid
根據(jù)uuid,添加gcKey
添加gcKey成功后,刪除metaKey
就目前來說,過期速度較快,而且key的量級也不至于讓磁盤KV存在容量等過大負擔,基于hash的過期機制目前表現(xiàn)良好。
GC機制
目前的GC機制比較落后:基于當前Tula的namespace的GC前綴(gc-key-prefix),基于uuid進行遍歷,并且刪除對應(yīng)的dataKey。
圖10:GC機制處理流程圖示
簡述上圖的GC機制:
拉起一個GC的協(xié)程,掃描gcKey空間
解析掃描到的gcKey,可以獲得需要GC的uuid
基于uuid,在dataKey的空間中基于字典序,刪除對應(yīng)uuid下的所有dataKey
因此,GC本來就是在expire之后,會存在一定的滯后性。
并且,當前的GC任務(wù)只能單線程操作,目前來說很容易造成GC的遲滯。
1.2 問題描述
1.2.1 問題現(xiàn)象
業(yè)務(wù)側(cè)多次反饋,表示窗口數(shù)據(jù)(定期刷入重復(fù)過期數(shù)據(jù))存在的時候,磁盤KV占用的空間特別大。
我們使用工具單獨掃描對應(yīng)的Tula配置namespace下的GC數(shù)據(jù)結(jié)合,發(fā)現(xiàn)確實存在較多的GC數(shù)據(jù),包括gcKey,以及對應(yīng)的dataKey也需要及時進行刪除。
1.2.2成因分析
現(xiàn)網(wǎng)的GC過程速度比不上過期的速度。往往expireKey都已經(jīng)沒了,但是gcKey很多,并且堆積。
這里的問題點在于:前期的設(shè)計中,gcKey的編碼并沒有像expireKey那樣提前進行了hash的操作,全部都是uuid。
如果有一個類似的slot字段可以讓GC可以使用多個協(xié)程進行并發(fā)訪問,可以更加高效地推進GC的進度,從而達到滿足優(yōu)化GC速度的目的,窗口數(shù)據(jù)的場景可以得到較好的處理。
下面結(jié)合兩個機制的優(yōu)劣,分析存在GC堆積的原因。
圖11:GC堆積成因圖示
簡單來說,上圖的流程中:
過期的掃描速度以及處理速度很快,expireKey很快及時的被清理并且添加到gcKey中
GC速度很慢,添加的gcKey無法及時處理和清空
從上圖分析可以知道:如果窗口數(shù)據(jù)的寫入完全超過的GC的速度的話,必然導致GC的數(shù)據(jù)不斷堆積,最后導致所有磁盤KV的存儲容量不斷上漲。
二、優(yōu)化
2.1 目標
分析了原始的GC機制之后,對于GC存在滯后的情況,必然需要進行優(yōu)化。
如何加速GC成為磁盤KV針對窗口數(shù)據(jù)場景下的強需求。
但是,畢竟TiKV集群的性能是有上限的,在進行GC的過程也應(yīng)該照顧好業(yè)務(wù)請求的表現(xiàn)。
這里就有了優(yōu)化的基本目標:在不影響業(yè)務(wù)的正常使用前提下,對盡量減少GC數(shù)據(jù)堆積,加速GC流程。
2.2.實踐
2.2.1 階段1
在第一階段,其實并沒有想到需要對GC這個流程進行較大的變動,看可不可以從當前的GC流程中進行一些簡單調(diào)整,提升GC的性能。
分析
GC的流程相對簡單:
圖12:GC流程圖示
可以看到,如果存在gcKey,會觸發(fā)一個批次的刪除gcKey和dataKey的流程。
最初設(shè)計存在sleep以及批次的原因在于減少GC對TiKV的影響,降低對現(xiàn)網(wǎng)的影響。
因此這里可以調(diào)整的范圍比較有限:按照批次進行控制,或者縮短批次刪除之間的時間間隔。
嘗試
縮短sleep時間(甚至縮短到0,去掉sleep過程),或者提高單個批次上限。
結(jié)果
但是這里原生sleep時間并不長,而且就算提高批次個數(shù),畢竟單線程,提高并沒有太大。
小結(jié)
原生GC流程可變動的范圍比較有限,必須打破這種局限才可以對GC的速度得以更好的優(yōu)化。
2.2.2 階段2
第一階段過后,發(fā)現(xiàn)原有機制確實局限比較大,如果需要真的把GC進行加速,最好的辦法是在原有的機制上看有沒有辦法類似expireKey一樣給出并發(fā)的思路,可以和過期一樣在質(zhì)上提速。
但是當前現(xiàn)網(wǎng)已經(jīng)不少集群在使用磁盤KV集群,并發(fā)提速必須和現(xiàn)網(wǎng)存量key設(shè)計一致前提下進行調(diào)整,解決現(xiàn)網(wǎng)存量的GC問題。
分析
如果有一種可能,更改GC的key編碼規(guī)則,類似模擬過期key的機制,添加slot位置,應(yīng)該可以原生滿足這種多協(xié)程并發(fā)進行GC的情況。
但是基于當前編碼方式,有沒有其他辦法可以較好地把GC key分散開來?
把上述問題作為階段2的分析切入點,再對當前的GC key進行分析:
圖13:gcKey編碼設(shè)計圖示
考慮其中的各個字段:
namespace:同一個磁盤KV下GC空間的必然一致
gc-key-prefix:不管哪個磁盤KV的字段必然一致
dbid:現(xiàn)網(wǎng)的磁盤KV都是基于集群模式,dbid都是0
uuid:映射到對應(yīng)的dataKey
分析下來,也只有uuid在整個gcKey的編碼中是變化的。
正因為uuid的分布應(yīng)該是足夠的離散,此處提出一種比較大膽的想法:基于uuid的前若干位當作hash slot,多個協(xié)程可以基于不同的前綴進行并發(fā)訪問。
因為uuid是一個128bit長度(8個byte)的內(nèi)容,如果拿出前面的8個bit(1個byte),可以映射到對應(yīng)的256個slot。
嘗試
基于上述分析,uuid的前一個byte作為hash slot的標記,這樣,GC流程變成:
圖14:基于uuid劃分GC機制圖示
簡單描述下階段2的GC流程:
GC任務(wù)使用協(xié)程,分成256個任務(wù)
每一個任務(wù)基于前綴掃描的時候,從之前掃描到dbid改成后續(xù)補充一個byte,每個協(xié)程被分配不同的前綴,進行各自的任務(wù)執(zhí)行
GC任務(wù)執(zhí)行邏輯和之前單線程邏輯保持不變,處理gcKey以及dataKey。
這樣,基于uuid的離散,GC的任務(wù)可以拆散成并發(fā)協(xié)程進行處理。
這樣的優(yōu)點不容置疑,可以較好地進行并發(fā)處理,提高GC的速度。
結(jié)果
基于并發(fā)的操作,GC的耗時可以縮短超過一半。后續(xù)會有同樣條件下的數(shù)據(jù)對比。
小結(jié)
階段2確實帶來一些突破:再保留原有g(shù)cKey設(shè)計的前提下,基于拆解uuid的方法使得GC的速度有質(zhì)的提高。
但是這樣會帶來問題:對于dataKey較多(可以理解為一個HASH,或者一個SET的元素較多)的時候,刪除操作可能對TiKV的性能帶來影響。這樣帶來的副作用是:如果并發(fā)強度很高地進行GC,因為TiKV集群寫入(無論寫入還是刪除)性能是一定的,這樣是不是可能導致業(yè)務(wù)的正常寫入可能帶來了影響?
如何可以做到兼顧磁盤KV日常的寫入和GC?這成了下一個要考慮的問題。
2.2.3 階段3
階段2之后,GC的速度是得到了較大的提升,但是在測試過程中發(fā)現(xiàn),如果在過程中進行寫入,寫入的性能會大幅度下降。如果因為GC的性能問題忽視了現(xiàn)網(wǎng)的業(yè)務(wù)正常寫入,顯然不符合線上業(yè)務(wù)的訴求。
磁盤KV的GC還需要一種能力,可以調(diào)節(jié)GC。
分析
如果基于階段2,有辦法可以在業(yè)務(wù)低峰期的時候進行更多的GC,高峰期的時候進行讓路,也許會是個比較好的方法。
基于上面的想法,我們需要在Tula層面可以比較直接地知道當前磁盤KV的性能表現(xiàn)到底到怎樣的層面,當前是負荷較低還是較高,應(yīng)該用怎樣的指標去衡量當前磁盤KV的性能?
嘗試
此處我們進行過以下的一些摸索:
基于TiKV的磁盤負載進行調(diào)整
基于Tula的時延表現(xiàn)進行調(diào)整
基于TiKV的接口性能表現(xiàn)進行調(diào)整
暫時發(fā)現(xiàn)TiKV的接口性能表現(xiàn)調(diào)整效果較好,因為基于磁盤負載不能顯式反饋到Tula的時延表現(xiàn),基于Tula的時延表現(xiàn)應(yīng)該需要搜集所有的Tula時延進行調(diào)整(對于同一個TiKV集群接入多個不同的Tula集群有潛在影響),基于TiKV的接口性能表現(xiàn)調(diào)整可以比較客觀地得到Tula的性能表現(xiàn)反饋。
在階段1中,有兩個影響GC性能的參數(shù):
sleep時延
單次處理批次個數(shù)
加上階段2并發(fā)的話,會有三個可控維度,控制GC的速度。
調(diào)整后的GC流程如下:
圖15:自適應(yīng)GC機制圖示
階段3對GC添加自適應(yīng)機制,簡述如下:
①開啟協(xié)程,搜集TiKV節(jié)點負載
發(fā)現(xiàn)TiKV負載較高,控制GC參數(shù),使得GC緩慢進行
發(fā)現(xiàn)TiKV負載較低,控制GC參數(shù),使得GC激進進行
②開啟協(xié)程,進行GC
發(fā)現(xiàn)不需要GC,控制GC參數(shù),使得GC緩慢進行
結(jié)果
基于監(jiān)控表現(xiàn),可以明顯看到,GC不會一直強制占據(jù)TiKV的所有性能。當Tula存在正常寫入的時候,GC的參數(shù)會響應(yīng)調(diào)整,保證現(xiàn)網(wǎng)寫入的時延。
小結(jié)
階段3之后,可以保證寫入和但是從TiKV的監(jiān)控上看,有時候GC并沒有完全把TiKV的性能打滿。
是否有更加高效的GC機制,可以繼續(xù)提高磁盤KV的GC性能?
2.2.4 階段4
基于階段3繼續(xù)嘗試找到GC性能更高的GC方式。
分析
基于階段3的優(yōu)化,目前基于單個節(jié)點的Tula應(yīng)該可以達到一個可以較高強度的GC,并且可以給現(xiàn)網(wǎng)讓路的一種情況。
但是,實際測試的時候發(fā)現(xiàn),基于單個節(jié)點的刪除,速度應(yīng)該還有提升空間(從TiKV的磁盤IO可以發(fā)現(xiàn),并沒有占滿)。
這里的影響因素很多,例如我們發(fā)現(xiàn)client-go側(cè)存在獲取tso慢的一些報錯。可能是使用客戶端不當?shù)仍蛟斐伞?/p>
但是之前都是基于單個Tula節(jié)點進行處理。既然每個Tula都是模擬了Redis的集群模式,被分配了slot區(qū)間去處理請求。這里是不是可以借鑒分片管理數(shù)據(jù)的模式,在GC的過程直接讓每個Tula管理對應(yīng)分片的GC數(shù)據(jù)?
這里先review一次優(yōu)化階段2的解決方式:基于uuid的第一個byte,劃分成256個區(qū)間。leader Tula進行處理的時候基于256個區(qū)間。
反觀一個Tula模擬的分片范圍是16384(0-16383),而一個byte可以表示256(0-255)的范圍。
如果使用2個byte,可以得到65536(0-65535)的范圍。
這樣,如果一個Tula可以基于自己的分片范圍,映射到GC的范圍,基于Tula的Redis集群模擬分片分布去做基于Tula節(jié)點的GC分片是可行的。
假如某個Tula的分片是從startSlot到endSlot(范圍:0-16383),只要經(jīng)過簡單的映射:
startHash = startSlot* 4
endHash = (endSlot + 1)* 4 - 1
基于這樣的映射,可以直接把Tula的GC進行分配,而且基本在優(yōu)化階段2中無縫銜接。
嘗試
基于分析得出的機制如下:
圖16:多Tula節(jié)點GC機制圖示
可以簡單地描述優(yōu)化之后的GC流程:
① 基于當前拓撲劃分當前Tula節(jié)點的startHash與endHash
② 基于步驟1的startHash與endHash,Tula分配協(xié)程進行GC,和階段2基本一致:
GC任務(wù)使用協(xié)程,分成多個任務(wù)。
每一個任務(wù)基于前綴掃描的時候,從之前掃描到dbid改成后續(xù)補充2個byte,每個協(xié)程被分配不同的前綴,進行各自的任務(wù)執(zhí)行。
GC任務(wù)執(zhí)行邏輯和之前單線程邏輯保持不變,處理gcKey以及dataKey。
基于節(jié)點分開之后,可以滿足在每個節(jié)點并發(fā)地前提下,各個節(jié)點不相干地進行GC。
結(jié)果
基于并發(fā)的操作,GC的耗時可以在階段2的基礎(chǔ)上繼續(xù)縮短。后續(xù)會有同樣條件下的數(shù)據(jù)對比。
小結(jié)
基于節(jié)點進行并發(fā),可以更加提高GC的效率。
但是我們在這個過程中也發(fā)現(xiàn),client-go的使用上可能存在不當?shù)那闆r,也許調(diào)整client-go的使用后可以獲得更高的GC性能。
三、優(yōu)化結(jié)果對比
我們基于一個寫入500W的SET作為寫入數(shù)據(jù)。其中每一個SET都有一個元素,元素大小是4K。
因為階段2和階段4的提升較大,性能基于這兩個進行對比:
表1:各階段GC耗時對照表
可以比較明顯地看出:
階段2之后的GC時延明顯縮減
階段4之后的GC時延可以隨著節(jié)點數(shù)的增長存在部分縮減
四、后續(xù)計劃
階段4之后,我們發(fā)現(xiàn)Tula的單節(jié)點性能應(yīng)該有提升空間。我們會從以下方面進行入手:
補充更多的監(jiān)控項目,讓Tula更加可視,觀察client-go的使用情況。
基于上述調(diào)整跟進client-go在不同場景下的使用情況,嘗試找出client-go在使用上的瓶頸。
嘗試調(diào)整client-go的使用方式,在Tula層面提高從指令執(zhí)行,到GC,過期的性能。
五、總結(jié)
回顧我們從原來的單線程GC,到基于編碼機制做到了多線程GC,到為了減少現(xiàn)網(wǎng)寫入性能影響,做到了自適應(yīng)GC,再到為了提升GC性能,進行多節(jié)點GC。
GC的性能提升階段依次經(jīng)歷了以下過程:
單進程單協(xié)程
單進程多協(xié)程
多進程多協(xié)程
突破點主要在于進入階段2(單進程多協(xié)程)階段,設(shè)計上的困難主要來源于:已經(jīng)存在存量數(shù)據(jù),我們需要兼顧存量數(shù)據(jù)的數(shù)據(jù)分布情況進行設(shè)計,這里我們必須在考慮存量的gcKey存在的前提下,原版gcKey的編碼設(shè)計與基于字典序的遍歷機制對改造造成的約束。
但是這里基于原有的設(shè)計,還是有空間進行一些二次設(shè)計,把原有的問題進行調(diào)優(yōu)。
這個過程中,我們認為有幾點比較關(guān)鍵:
在第一次設(shè)計的時候,應(yīng)該從多方面進行衡量,思考好某種設(shè)計會帶來的副作用。
在上線之前,對各種場景(例如不同的指令,數(shù)據(jù)大?。┻M行充分測試,提前發(fā)現(xiàn)出問題及時修正方案。
已經(jīng)是存量數(shù)據(jù)的前提下,更應(yīng)該對原有的設(shè)計進行重新梳理。也許原有的設(shè)計是有問題的,遵循當前設(shè)計的約束,找出問題關(guān)鍵點,基于現(xiàn)有的設(shè)計嘗試找到空間去調(diào)整,也許存在調(diào)優(yōu)的空間。
審核編輯:劉清
-
SET
+關(guān)注
關(guān)注
0文章
17瀏覽量
7949 -
Redis
+關(guān)注
關(guān)注
0文章
374瀏覽量
10871 -
迭代器
+關(guān)注
關(guān)注
0文章
43瀏覽量
4307
原文標題:一種KV存儲的GC優(yōu)化實踐
文章出處:【微信號:OSC開源社區(qū),微信公眾號:OSC開源社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論