redis 使用 get 命令讀取 bitmap 型別的資料
阿新 • • 發佈:2018-12-22
在簽到統計場景中,可以使用 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天
註釋較多,業務程式碼不多,多多理解~