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

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

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

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 進制解包,而後轉爲二進制字符串。給出轉換方法:ui

<?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天

註釋較多,業務代碼很少,多多理解~code

相關文章
相關標籤/搜索