Redis 億級用戶信息存儲實踐:bitmap 位圖存儲 bitmap簡介 Redis bitmap 的命令 bitmap的應用場景 布隆過濾器 參考資料

bitmap簡介

8 個 bit 組成一個 Byte,所以 bitmap 極大的節省儲存空間

你可以把它理解爲一個特殊處理過的 字符串
key代表業務屬性、標籤。一個 bit 位來表示某個元素對應的值或者狀態。

bitmap 並不是一種數據結構,實際上它就是字符串,但是可以對字符串的位進行操作。

bitmap有自己的一套命令。可以把bitmap想象成一個以bit爲單位的數組,數組的每個單元存儲0和1,數組的下標叫做偏移量。

Redis 提供 setbit,getbit,bitcount等幾個 bitmap 相關命令。但其實 setbit 等命令只不過是在 set 上的擴展而已。

setbit 命令介紹

指令

SETBIT key offset value

複雜度 O(1)

設置或者清空 key 的 value(字符串)在 offset 處的 bit 值(只能只 0 或者 1)。

空間佔用、以及第一次分配空間需要的時間
在一臺 2010MacBook Pro 上

offset 爲 2^32-1(分配 512MB)需要~ 300ms
offset 爲 2^30-1(分配 128MB)需要~ 80ms
offset 爲 2^28-1(分配 32MB)需要~ 30ms
offset 爲 2^26-1(分配 8MB)需要 8ms。
– <來自官方文檔: https://redis.io/commands/setbit>
大概的空間佔用計算公式是:($offset/8/1024/1024)MB

Available since 2.2.0.

Time complexity: O(1)

Sets or clears the bit at offset in the string value stored at key.

The bit is either set or cleared depending on value, which can be either 0 or 1.

When key does not exist, a new string value is created. The string is grown to make sure it can hold a bit at offset. The offset argument is required to be greater than or equal to 0, and smaller than 232 (this limits bitmaps to 512MB). When the string at key is grown, added bits are set to 0.

Warning: When setting the last possible bit (offset equal to 232 -1) and the string value stored at key does not yet hold a string value, or holds a small string value, Redis needs to allocate all intermediate memory which can block the server for some time. On a 2010 MacBook Pro, setting bit number 232 -1 (512MB allocation) takes ~300ms, setting bit number 230 -1 (128MB allocation) takes ~80ms, setting bit number 228 -1 (32MB allocation) takes ~30ms and setting bit number 226 -1 (8MB allocation) takes ~8ms. Note that once this first allocation is done, subsequent calls to SETBIT for the same key will not have the allocation overhead.

Redis bitmap 的命令

bitmap的命令

常用命令 作用
1、getbit key offset 用於獲取Redis中指定key對應的值,中對應offset的bit
2、setbit key key offset value 用於修改指定key對應的值,中對應offset的bit
3、 bitcount key [start end] 用於統計字符串被設置爲1的bit數
4、bitop and/or/xor/not destkey key [key …] 用於對多個key求邏輯與/邏輯或/邏輯異或/邏輯非

設置 offset 位的 01 值

127.0.0.1:6379> setbit user10001 0 1
(integer) 0
127.0.0.1:6379> setbit user10001 3 1
(integer) 0
127.0.0.1:6379> getbit user10001 0
(integer) 1
127.0.0.1:6379> getbit user10001 1
(integer) 0
127.0.0.1:6379> getbit user10001 2
(integer) 0
127.0.0.1:6379> getbit user10001 3
(integer) 1
127.0.0.1:6379> getbit user10001 7
(integer) 1

批量設置 offset 位的 01 值

使用 u1 類型批量設置 offset 位的 01 值:

127.0.0.1:6379> bitfield user10001 set u1 2 1 set u1 9 1 set u1 10 1
1) (integer) 0
2) (integer) 0
3) (integer) 0
127.0.0.1:6379> getbit user10001 10
(integer) 1
127.0.0.1:6379> getbit user10001 9
(integer) 1
127.0.0.1:6379> getbit user10001 8
(integer) 0
127.0.0.1:6379> getbit user10001 7
(integer) 1

bitmap的應用場景

使用經驗:
type = string,BitMap 是 sting 類型,最大 512 MB。
注意 setbit 時的偏移量,可能有較大耗時
位圖不是絕對好。

通過 bitcount可以很快速的統計,比傳統的關係型數據庫效率高很多

1、比如統計年活躍用戶數量

用戶的ID作爲offset,當用戶在一年內訪問過網站,就將對應offset的bit值設置爲“1”;

通過bitcount 來統計一年內訪問過網站的用戶數量

2、比如統計三天內活躍用戶數量

時間字符串作爲key,比如 “190108:active“ “190109:active”“190110:active” ;

用戶的ID就可以作爲offset,當用戶訪問過網站,就將對應offset的bit值設置爲“1”;

統計三天的活躍用戶,通過bitop or 獲取一週內訪問過的用戶數量

3、連續三天訪問的用戶數量 bitop and

4、三天內沒有訪問的用戶數量 bitop not

5、統計在線人數 設置在線key:“online:active”,當用戶登錄時,通過setbit設置

bitmap的優勢,以統計活躍用戶爲例

每個用戶id佔用空間爲1bit,消耗內存非常少,存儲1億用戶量只需要12.5M

使用場景: 統計活躍用戶

使用時間作爲 cacheKey,然後用戶 ID 爲 offset,如果當日活躍過就設置爲 1
那麼我該如果計算某幾天/月/年的活躍用戶呢(暫且約定,統計時間內只有有一天在線就稱爲活躍),有請下一個 redis 的命令
命令

BITOP operation destkey key [key ...]

說明:對一個或多個保存二進制位的字符串 key 進行位元操作,並將結果保存到 destkey 上。
說明:BITOP 命令支持 AND 、 OR 、 NOT 、 XOR 這四種操作中的任意一種參數

//日期對應的活躍用戶
$data = array(
    '2020-01-10' => array(1,2,3,4,5,6,7,8,9,10),
    '2020-01-11' => array(1,2,3,4,5,6,7,8),
    '2020-01-12' => array(1,2,3,4,5,6),
    '2020-01-13' => array(1,2,3,4),
    '2020-01-14' => array(1,2)
);
//批量設置活躍狀態
foreach($data as $date=>$uids) {
    $cacheKey = sprintf("stat_%s", $date);
    foreach($uids as $uid) {
        $redis->setBit($cacheKey, $uid, 1);
    }
}

$redis->bitOp('AND', 'stat', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-12');

//總活躍用戶:6
echo "總活躍用戶:" . $redis->bitCount('stat') . PHP_EOL;

$redis->bitOp('AND', 'stat1', 'stat_2020-01-10', 'stat_2020-01-11', 'stat_2020-01-14') . PHP_EOL;

//總活躍用戶:2
echo "總活躍用戶:" . $redis->bitCount('stat1') . PHP_EOL;

$redis->bitOp('AND', 'stat2', 'stat_2020-01-10', 'stat_2020-01-11') . PHP_EOL;

//總活躍用戶:8
echo "總活躍用戶:" . $redis->bitCount('stat2') . PHP_EOL;

假設當前站點有 5000W 用戶,那麼一天的數據大約爲 50000000/8/1024/1024=6MB

布隆過濾器

bitmap - Redis布隆過濾器 (應對緩存穿透問題)

舉例:比如爬蟲服務器在爬取電商網站的商品信息時,首先經過緩存,如果緩存查不到,再去數據庫獲取信息,因爲爬蟲的效率很高,且sku很有可能是不存在或者已下架的,就會造成緩存穿透,大量請求被髮送到數據庫,導致服務器受到影響。

此時,可以在緩存層之前,添加一個布隆過濾器,布隆過濾器看作是一個bitmap,sku作爲offset值,如果商品真實存在,bit值設爲1。首先將商品數據初始化,當有請求時,通過getbit判斷sku是否有效。如果布隆過濾器認爲商品不存在,就拒絕訪問,這樣就可以保護存儲層。

Data structures are nothing different. They are like the bookshelves of your application where you can organize your data. Different data structures will give you different facility and benefits. To properly use the power and accessibility of the data structures you need to know the trade-offs of using one.

大意是不同的數據結構有不同的適用場景和優缺點,你需要仔細權衡自己的需求之後妥善適用它們,布隆過濾器就是踐行這句話的代表。

參考資料

https://blog.csdn.net/weixin_42383575/article/details/86684283

https://zhuanlan.zhihu.com/p/136337229

https://www.imooc.com/article/298707

https://www.jianshu.com/p/4c8e119f35db

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