Redis系列1:深刻理解高效能Redis的本質
Redis系列2:資料持久化提高可用性
Redis系列3:高可用之主從架構
Redis系列4:高可用之Sentinel(哨兵模式)
Redis系列5:深入分析Cluster 叢集模式
追求效能極致:Redis6.0的多執行緒模型
追求效能極致:使用者端快取帶來的革命
我們在第一篇 深刻理解高效能Redis的本質 的時候就介紹過Redis的幾種基本資料結構,它是基於不同業務場景而設計的:
除了這常見資料型別,還有一些不常用的資料型別,如 BitMap、Geo、HyperLogLog 等等,他們在各自的領域為巨量資料量的統計,後面我們一一來介紹,學習下他們的實現原理和應用場景。
BitMap (點陣圖)的底層資料結構使用的是String型別的的 SDS 資料結構來儲存。因為一個位元組8個bit位,為了有效的將位元組的8個bit都利用到位,使用陣列模式儲存。
並且每個bit都使用二值狀態表示,要麼0,要麼1。
所以,BitMap 是通過一個 bit 位來表示某個元素對應的值或者狀態, 它的結構如下,key 對應元素本身;offset即是偏移量,固定整型,一般存陣列下表或者唯一值;value儲存的是二值(要麼0要麼1),一般用來表示狀態,如性別、是否登入、是否打卡等。
從上面可以看出這邊使用一個位元組表示1行,每1行儲存8個bit,就是可以儲存8個狀態位,極大的提高了空間利用。這也是BitMap的優勢,我們可以使用很少的位元組,儲存大量的線上狀態、打卡標記等狀態資訊,非常有效果。
我們可以使用 setbit, getbit, bitcount 等幾個相關命令來管理BitMap。語法如下:
SETBIT key offset value
上面說過了,key是元素名稱, offset 必須是數值型別,value 只能是 0 或者 1,如果我們儲存一個使用者的線上狀態,使用者,程式碼如下:
//設定線上狀態
// $redis->setBit('online', $uid, 1);
$redis->setBit('online', 5, 1);
$redis->setBit('online', 9, 1);
則具體體現為:
byte | bit0 | bit1 | bit2 | bit3 | bit4 | bit5 | bit6 | bit7 |
---|---|---|---|---|---|---|---|---|
buf[0] | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
buf[1] | 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 |
可以看出使用者ID為5和9被打上1的標誌,代表線上狀態,其他未設定值預設為0,是離線狀態。
除了Set之外,還有getBit、bitCount等語法,如下:
// 獲取是否線上的狀態
$isOnline = $redis->getBit('online', $uid);
// 獲取線上人數 統計
$onlineNum = $redis->bitCount('online');
上面介紹了BitMap的原理和狀態儲存的優勢。那我們儲存了bit位,其實目的還是為了高效的計算,而不是簡單的狀態記錄。
而在實際的應用場景中,他主要解決如下幾個型別的需求:
上面其實我們已經演示過了,這種場景最常見,因為值只能是1或者0,所以所有的二值狀態的,所有存在是否對照關係的場景都可以使用。如線上(1) 離線(0),打卡(1) 未打卡(0),登入(1) 未登入(0),群聊訊息已閱(1) 未閱(0) 等等。
我們以使用者 離線/線上 為例子,看看如何使用 Bitmap 在海量的使用者資料之中判斷某個使用者是否是線上狀態。
假設我們使用一個 online_statu
來作為key,用來儲存 使用者登入後的狀態集合,而使用者的ID則為offset,online的狀態就用1表示,offline的狀態就用0表示。
SETBIT online_statu 1024 1
GETBIT online_statu 1024
SETBIT online_statu 1024 0
基於上面的討論,我們可以總結出一個預評估公式,根據實際的資料量獲取儲存空間:( offset / 8 / 1024 / 1024 ) M
固定週期可能是年/月/周,按照不同維度,可能有 365,31,7的bit位的統計週期。
假設這時候我們如果對於某個使用者(如1024)全年的簽到情況做統計,可以這麼設計:
設計key 為 {bus_type}{uid}{yyyy} ,及業務型別+使用者id+年份
比如 sign_1024_2022
簽到則執行對應程式碼
舉例,1024使用者在2022 年的第1天和最後一天如果有簽到,那就是:
# 22年第一天
SETBIT sign_1024_2022 0 1
# 22年最後一天
SETBIT sign_1024_2022 364 1
GETBIT sign_1024_2022 150
BITCOUNT sign_1024_2022
$index = BITPOS sign_1024_2022 30
offset也是從0開始的,所以返回的值最好加個1,不會讓使用者看的暈頭轉向。
如果一個平臺有千萬級別以上的大量使用者,而我們需要統計每個使用者連續簽到的資訊,那需要怎麼設計呢?
如果這時候我們要判斷使用者是否整週都有簽到或者整個月都有簽到就可以使用 【與】運算
只有指定週期內的所有值都是1(簽到)的時候,結果才是1,否則是我們整週或者整個月都拿起來【與】運算,得到的結果是不是1就能確是否滿勤。
# 與運算: 0&0=0;0&1=0;1&0=0;1&1=1
# 下面為虛擬碼,類似:
(20221022 1024) & ( 20221023 1024) & ...
Redis 提供了 BITOP operation destkey key [key ...]這個指令用於對一個或者多個 鍵 = key 的 Bitmap 進行 位元 操作。
operation 可以是 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種:
除了 NOT 操作之外,其他操作都可以接受一個或多個 key 作為輸入。
# 統計一週的值(7個BitMap,10.17 ~ 10.23 號)並將結果存入到新的BitMap (sign-result) 中
redis> BITOP AND sign-result 20221017 20221018 20221019 20221020 20221021 20221022 20221023
(integer) 1
# 新的BitMap 中,獲取 1024的簽到結果,如果為1,則本週全部簽到
redis> GETBIT sign-result 1024
(integer) 1
可以理解下這張圖的運算過程:
這邊需要注意:當 BITOP 處理不同長度的字串時,較短字串所缺部分會被當作 0 對待。同樣的,空 key 也被看作是 0 的字串序列看待。
同理,類似HeapDump效能社群的使用者簽到統計,也可以用點陣圖(BitMap)這種方式計算!
1個byte等於8個bit,每個bit位只使用0或者1來表示,這樣能夠有效的降低儲存空間,而Redis是儲存在快取記憶體中的,所以實際上是大大減少了記憶體佔用。
很多場景都可以使用點陣圖計算,比如我們上面說到的 是否登入、是否線上、是否簽到、使用者性別狀態、IP黑名單、是否VIP使用者統計 等等場景,但凡涉及到二值狀態識別、海量統計的資料都可以考慮使用。