1. 程式人生 > >redis 使用 get 命令讀取 bitmap 型別的資料

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天

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

來源:https://segmentfault.com/a/1190000017470443