redis 使用 get 命令讀取 bitmap 類型的數據

在簽到統計場景中,可以使用 bitmap 數據類型高效的存儲簽到數據,但 getbit 命令只能獲取某一位值,就無法最優的滿足部分業務場景了。

比如我們按年去存儲一個用戶的簽到情況,365 天,只需要 365 / 8 ≈ 46 Byte,1KW 用戶量一年也只需要 44 MB 就足夠了。

setbit sign:uid:year 0 1 #第1天
setbit sign:uid:year 1 1 #第2天
...
setbit sign:uid:year 364 1 #第365天

但如果我想獲取某個用戶一年的簽到統計,使用 bitget 命令的話...要循環讀取 365 次,這是沒辦法接受的。

如果能一次讀取到以字符串

"1000100010100100...001"

的形式表示的位狀態數據,就很好做後續的處理了。

bitmap 其實也是一種特殊的字符串數據,使用 get 命令是可以讀取出來的,但是以 16 進制的流數據返回的,這裏就涉及到網絡編程中數據傳輸的打包/解包的知識,redis 使用 get 命令讀取 bitmap 數據時,將二進制數據打包成了 16 進制返回給我們,所以我們要對此數據包以 16 進制解包,然後轉爲二進制字符串。給出轉換方法:

<?php

// 第1天的簽到
$redis->setBit('sign:uid:year', 0, 1);
// 第234天的簽到
$redis->setBit('sign:uid:year', 233, 1);
// 第365天的簽到
$redis->setBit('sign:uid:year', 364, 1);

// 使用 get 命令一次性讀取用戶的 bitmap 簽到數據
$bitmap_str = $redis->get("sign:uid:year");

// 對數據流使用網絡字節序(大端)解包拿到16進制數據的字符串形式
$hex_str = unpack("H*", $bitmap_str)[1];

// hex str 的長度
$hex_str_len = strlen($hex_str);
// 爲了防止 hex to dec 時發生溢出
// 我們需要切分 hex str,使得每一份 hex str to dec 時都能落在 int 類型的範圍內
// 因爲 2 位 16 進製表示一個字節,所以用系統 int 類型的字節長度去分組是絕對安全的
$chunk_size = PHP_INT_SIZE;

// 對 hex str 做分組對齊,否則 str 的最後幾位可能會被當作低位數據處理
// 比如 fffff 以 4 位拆分 'ffff', 'f' 後 最後一組 'f' 就被低位數據處理了
// 對齊後 fffff000 分組 'ffff', 'f000' 就能保證 'f' 的數據位了
$hex_str = str_pad($hex_str, $hex_str_len + ($chunk_size - ($hex_str_len % $chunk_size)), 0, STR_PAD_RIGHT);

// 防止 hexdec 時溢出 使用 PHP_INT_SIZE 個 16 進制字符一組做拆分
// 因 16 進制 2 位標識一個字節 所以 PHP_INT_SIZE 是絕對不會溢出的
$hex_str_arr = str_split($hex_str, $chunk_size);

// 位數據的二進制字符串
$bitmap_bin_str = '';
array_walk($hex_str_arr, function($hex_str_chunk) use (&$bitmap_bin_str, $chunk_size) {
    $bitmap_bin_str .= str_pad(decbin(hexdec($hex_str_chunk)), $chunk_size * 4, 0, STR_PAD_LEFT);
});

// 一次讀取redis即可拿到 bitmap O(n)次操作的數據
echo $bitmap_bin_str{0} . PHP_EOL; //第1天
echo $bitmap_bin_str{233} . PHP_EOL;//第234天
echo $bitmap_bin_str{364} . PHP_EOL;//第365天

註釋較多,業務代碼不多,多多理解~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章