最近要做一個聖誕抽獎活動,需要記錄每天用戶簽到的記錄,以前一般都是用普通的字符串數據類型,每個用戶的簽到用一個 key
// 用戶10在活動第一天的簽到key為record:1:10
$key = "record:$day:$id";
if ($redis->get($key)) {
echo '已簽到';
} else {
$redis->set($key, 1)
}
那么一個用戶一天的簽到記錄就要占一個字節,用戶一多就產生非常多的 key,浪費寶貴的內存。
位圖
為了解決這個問題,redis 另一種數據類型位圖就非常適合。位圖並不是特殊的數據類型,內容其實就是字符串,每一位只存儲0或1,非常適合存儲這種布爾類型的數據
位圖使用 setbit/getbit 來存取數據
> SETBIT key offset value
> GETBIT key offset
比如一個用戶聖誕連續五天的簽到記錄可以只使用一個 key, 10010
代表用戶只有第二天和第五天簽過到
$key = "record:$id";
if ($redis->getbit($key, $day)) {
echo '已簽到';
} else {
$redis->setbit($key, $day, 1)
}
現在一個用戶五天的簽到記錄只會產生一個 key,占用內存僅為 5bit 不到一個字節
進一步,如果你的用戶系統中用戶 id 是連續的 int 類型,還能更節省。因為只記錄每個用戶5天的簽到記錄,在一串位圖中,每個用戶占5個坑,這樣所有的用戶的簽到數據只會使用一個 key
// 用戶1占前5個坑
$offset = ($id - 1) * 5 + $day -1;
if ($redis->getbit('record', $offset)) {
echo '已簽到';
} else {
$redis->setbit('record', $offset, 1)
}
現在只需要一個 key 就可以存下所有用戶的簽到記錄了。
需要注意的是位圖一個 key 最多存儲 512mb 的內容,如果你的用戶數大於 8*1024*1024*1024*512 / 5 ≈ 87 億
並不適用這個方法。
其他用法
bitcount 用來統計指定位置范圍內 1 的個數,bitpos 用來查找指定范圍內出現的第一個 0 或 1。
> setbit s 0 1
> setbit s 3 1 #s=1001
> bitcount s [start, end]
(integer) 2
> bitpos s 0 [start, end]
(integer) 1