當前位置: 妍妍網 > 碼農

Redis大key多key拆分方案

2024-03-06碼農

來源:網路

  • 1:單個簡單的key儲存的value很大

  • 2:value中儲存過多的元素

  • 3:一個集群儲存了上億的key

  • 4:大Bitmap或布隆過濾器(Bloom)拆分

  • 背景

    業務場景中經常會有各種大key多key的情況, 比如:

    1:單個簡單的key儲存的value很大

    2:hash, set,zset,list 中儲存過多的元素(以萬為單位)

    3:一個集群儲存了上億的key,Key 本身過多也帶來了更多的空間占用

    (如無意外,文章中所提及的hash,set等數據結構均指redis中的數據結構 )

    由於redis是單執行緒執行的,如果一次操作的value很大會對整個redis的響應時間造成負面影響,所以,業務上能拆則拆,下面舉幾個典型的分拆方案。

    1:單個簡單的key儲存的value很大

    i:該物件需要每次都整存整取

    可以嘗試將物件分拆成幾個key-value, 使用multiGet獲取值,這樣分拆的意義在於分拆單次操作的壓力,將操作壓力平攤到多個redis例項中,降低對單個redis的IO影響;

    ii:該物件每次只需要存取部份數據

    可以像第一種做法一樣,分拆成幾個key-value, 也可以將這個儲存在一個hash中,每個field代表一個具體的內容,

    使用hget,hmget來獲取部份的value,使用hset,hmset來更新部份內容

    2:value中儲存過多的元素

    類似於場景一種的第一個做法,可以將這些元素分拆。

    以hash為例,原先的正常存取流程是 hget(hashKey, field) ; hset(hashKey, field, value)

    現在,固定一個桶的數量,比如 10000, 每次存取的時候,先在本地計算field的hash值,模除 10000, 確定了該field落在哪個key上。

    newHashKey = hashKey + ( set, zset, list 也可以類似上述做法

    但有些不適合的場景,比如,要保證 lpop 的數據的確是最早push到list中去的,這個就需要一些附加的內容,或者是在 key的拼接上做一些工作(比如list按照時間來分拆)。

    3:一個集群儲存了上億的key

    如果key的個數過多會帶來更多的記憶體空間占用,

    i:key本身的占用(每個key 都會有一個Category字首)

    ii:集群模式中,伺服端需要建立一些slot2key的對映關系,這其中的指標占用在key多的情況下也是浪費巨大空間

    這兩個方面在key個數上億的時候消耗記憶體十分明顯(Redis 3.2及以下版本均存在這個問題,4.0有最佳化);

    所以減少key的個數可以減少記憶體消耗,可以參考的方案是轉Hash結構儲存,即原先是直接使用Redis String 的結構儲存,現在將多個key儲存在一個Hash結構中,具體場景參考如下:

    一:key 本身就有很強的相關性,比如多個key 代表一個物件,每個key是物件的一個內容,這種可直接按照特定物件的特征來設定一個新Key——Hash結構, 原先的key則作為這個新Hash 的field。

    舉例說明:

    原先儲存的三個key

    user.zhangsan-id = 123;

    user.zhangsan-age = 18;

    user.zhangsan-country = china;

    這三個key本身就具有很強的相關特性,轉成Hash儲存就像這樣 key = user.zhangsan

    field:id = 123;

    field:age = 18;

    field:country = china;

    即redis中儲存的是一個key :user.zhangsan, 他有三個 field, 每個field + key 就對應原先的一個key。

    二:key 本身沒有相關性,預估一下總量,采取和上述第二種場景類似的方案,預分一個固定的桶數量

    比如現在預估key 的總數為 2億,按照一個hash儲存 100個field來算,需要 2億 / 100 = 200W 個桶 (200W 個key占用的空間很少,2億可能有將近 20G )

    原先比如有三個key :

    user.123456789

    user.987654321

    user.678912345

    現在按照200W 固定桶分就是先計算出桶的序號 hash(123456789) % 200W , 這裏最好保證這個 hash演算法的值是個正數,否則需要調整下模除的規則;

    這樣算出三個key 的桶分別是 1 , 2, 2。所以儲存的時候呼叫API hset(key, field, value),讀取的時候使用 hget (key, field)

    圖片

    註意兩個地方:1,hash 取模對負數的處理;2,預分桶的時候, 一個hash 中儲存的值最好不要超過 512 ,100 左右較為合適

    4:大Bitmap或布隆過濾器(Bloom)拆分

    使用bitmap或布隆過濾器的場景,往往是數據量極大的情況,在這種情況下,Bitmap和布隆過濾器使用空間也比較大,比如用於公司userid匹配的布隆過濾器,就需要512MB的大小,這對redis來說是絕對的大value了。

    這種場景下,我們就需要對其進行拆分,拆分為足夠小的Bitmap,比如將512MB的大Bitmap拆分為1024個512KB的Bitmap。不過拆分的時候需要註意,要將每個key落在一個Bitmap上。有些業務只是把Bitmap 拆開, 但還是當做一個整體的bitmap看, 所以一個 key 還是落在多個 Bitmap 上,這樣就有可能導致一個key請求需要查詢多個節點、多個Bitmap。如下圖,被請求的值被hash到多個Bitmap上,也就是redis的多個key上,這些key還有可能在不同節點上,這樣拆分顯然大大降低了查詢的效率。

    圖片

    因此我們所要做的是把所有拆分後的Bitmap當作獨立的bitmap,然後透過hash將不同的key分配給不同的bitmap上,而不是把所有的小Bitmap當作一個整體。這樣做後每次請求都只要取redis中一個key即可。

    圖片

    有同學可能會問,透過這樣拆分後,相當於Bitmap變小了,會不會增加布隆過濾器的誤判率?實際上是不會的,布隆過濾器的誤判率是哈希函式個數k,集合元素個數n,以及Bitmap大小m所決定的,其約等於 。因此如果我們在第一步,也就是在分配key給不同Bitmap時,能夠盡可能均勻的拆分,那麽n/m的值幾乎是一樣的,誤判率也就不會改變。具體的誤判率推導可以參考wiki:Bloom_filter

    同時,客戶端也提供便利的api (>=2.3.4版本), setBits/ getBits 用於一次操作同一個key的多個bit值 。

    建議 :k 取 13 個, 單個bloomfilter控制在 512KB 以下

    以上方案僅供參考,歡迎大家提供其他的優秀方案。


    >>

    END

    精品資料,超贊福利,免費領

    微信掃碼/長按辨識 添加【技術交流群

    群內每天分享精品學習資料

    最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎並行JVMMySQLRedisSpringSpringMVCSpringBootSpringCloud訊息佇列等多個型別),歡迎您的使用。

    👇👇

    👇點選"閱讀原文",獲取更多資料(持續更新中