什么是位圖
位圖(Bitmap)是通過(guò)一個(gè) bit
來(lái)表示某個(gè)元素對(duì)應(yīng)的值或者狀態(tài)。它并不是什么新的數(shù)據(jù)結(jié)構(gòu)徐紧。它的內(nèi)容其實(shí)就是普通的字符串。我們可以通過(guò) get/set
獲取位圖的內(nèi)容,也可以使用 getbit/setbit
操作 bit
值(0 或者 1)暇矫。
Bit
即比特,是目前計(jì)算機(jī)中數(shù)據(jù)最小的單位择吊。8個(gè)Bit一個(gè)Byte(字節(jié))李根。Bit的值,要么為 0 几睛,要么為 1房轿。由于Bit是計(jì)算機(jī)中最小的單位,使用它進(jìn)行儲(chǔ)存將非常節(jié)省空間所森。特別適合一些數(shù)據(jù)量大的場(chǎng)景囱持。例如,統(tǒng)計(jì)每日活躍用戶(hù)焕济、統(tǒng)計(jì)每月打卡數(shù)等統(tǒng)計(jì)場(chǎng)景纷妆。
常用命令介紹
1)SETBIT
作用:對(duì)于某個(gè)KEY的某位設(shè)值
用法:SETBIT key offset value
返回值: 原來(lái)儲(chǔ)存的位
redis> SETBIT bit 10086 1
(integer) 0
2)GETBIT
作用:獲取某KEY某位的值
用法:GETBIT key offset
返回值:0 或 1。 當(dāng) offset 比字符串值的長(zhǎng)度大晴弃,或者 key 不存在時(shí)凭需,返回 0 。
redis> SETBIT bit 10086 1
(integer) 0
redis> GETBIT bit 10086
(integer) 1
3)BITOP
作用:對(duì)多個(gè)鍵進(jìn)行位操作肝匆。 OP
是 operation
的簡(jiǎn)寫(xiě)粒蜈。
用法:BITOP operation destkey key1 key2 [key ...]
參數(shù)說(shuō)明:
operation 表示位運(yùn)算符。一共有四種操作旗国,見(jiàn)下表枯怖。
destkey 表示運(yùn)算結(jié)果保存的值
key1、key2能曾、key3 表示進(jìn)行運(yùn)算的key
operation | 描述 |
---|---|
AND | 邏輯并 |
OR | 邏輯或 |
NOT | 邏輯非 |
XOR | 邏輯異或 |
返回值:保存到 destkey 的字符串的長(zhǎng)度度硝,和輸入 key 中最長(zhǎng)的字符串長(zhǎng)度相等。
redis> SETBIT bits-1 0 1 # bits-1 = 1001
(integer) 0
redis> SETBIT bits-1 3 1
(integer) 0
redis> SETBIT bits-2 0 1 # bits-2 = 1011
(integer) 0
redis> SETBIT bits-2 1 1
(integer) 0
redis> SETBIT bits-2 3 1
(integer) 0
redis> BITOP AND and-result bits-1 bits-2
(integer) 1
redis> GETBIT and-result 0 # and-result = 1001
(integer) 1
redis> GETBIT and-result 1
(integer) 0
redis> GETBIT and-result 2
(integer) 0
redis> GETBIT and-result 3
(integer) 1
4)BITCOUNT
作用:計(jì)算給定字符串上寿冕,位為1的個(gè)數(shù)
用法:BITCOUNT key [start] [end] 注意:此處的[start] [end] 為 字節(jié)開(kāi)始和結(jié)束的位置蕊程,非偏移量的位置
返回值:被設(shè)置為 1 的位的數(shù)量。不存在的key驼唱,或空字符串藻茂,值為0
redis> SETBIT tian 0 1
(integer) 0
redis> BITCOUNT tian
(integer) 1
redis> SETBIT tian 2 1
(integer) 0
redis> BITCOUNT tian
(integer) 2
5)BITPOS
用法:獲取某個(gè)鍵第一位被設(shè)置為 0 或 1 位的位置
作用:BITPOS key bit [start] [end]
返回值:返回第一個(gè)被設(shè)為 0 或 1 的位置
redis> SET test_str 'youthcity'
OK
# 查看值為 1 的最開(kāi)始的位數(shù)
redis> BITPOS test_str 1
(integer) 1
# 查看值為 0 的最開(kāi)始位數(shù)
redis> BITPOS test_str 0
(integer) 0
redis> BITPOS test_1 1 # 若沒(méi)有找到指定位,則返回 -1
(integer) -1
6)魔術(shù)指令 BITFIELD
作用:一次對(duì)多個(gè)位范圍進(jìn)行操作。bitfield 有三個(gè)子指令辨赐,分別是 get/set/incrby优俘。每個(gè)指令都可以對(duì)指定片段做操作。
用法:BITFIELD key [GET type offset] [SET type offset value] [INCRBY type offset increment] [OVERFLOW WRAP|SAT|FAIL]
返回值:返回一個(gè)數(shù)組作為回復(fù)掀序, 數(shù)組中的每個(gè)元素就是對(duì)應(yīng)操作的執(zhí)行結(jié)果帆焕。
# 從第1位開(kāi)始取4位,設(shè)值為5(有符號(hào)數(shù))
redis> BITFIELD key SET i4 0 5
1) (integer) 0
# 從第1位開(kāi)始取4位不恭,結(jié)果為有符號(hào)數(shù)
redis> BITFIELD key GET i4 0
1) (integer) 5
# 從第1位取4位叶雹,結(jié)果為有符號(hào)數(shù)
# 從第5位取4位,設(shè)值為6换吧,結(jié)果為無(wú)符號(hào)數(shù)
# 從第5位去4位浑娜,值增加1,結(jié)果為無(wú)符號(hào)數(shù)
redis> BITFIELD key GET i4 0 SET u4 4 6 INCRBY u4 4 1
1) (integer) 5
2) (integer) 0
3) (integer) 7
BITFIELD還提供了三種溢出策略:
-
WRAP
(wrap around式散,回繞)筋遭。一個(gè)i8的整數(shù),值為127暴拄,遞增1會(huì)導(dǎo)致值變?yōu)?128漓滔; -
SAT
(saturation arithmetic,飽和計(jì)算)乖篷。一個(gè)i8的整數(shù)响驴,值為120,遞增10結(jié)果變?yōu)?27(i8 類(lèi)型所能儲(chǔ)存的最大整數(shù)值)撕蔼; -
FAIL
豁鲤。 發(fā)生溢出時(shí),操作失敗鲸沮。并返回空值表示計(jì)算未被執(zhí)行琳骡。
redis> BITFIELD tian_key SET i8 0 127 OVERFLOW WRAP INCRBY i8 0 1
1) (integer) 0
2) (integer) -128
redis> BITFIELD tian_key_2 SET i8 0 120 OVERFLOW SAT INCRBY i8 0 10
1) (integer) 0
2) (integer) 127
redis> BITFIELD tian_key_3 SET i8 0 127 OVERFLOW FAIL INCRBY i8 0 1
1) (integer) 0
2) (nil)
應(yīng)用場(chǎng)景
1) 統(tǒng)計(jì)用戶(hù)上線(xiàn)次數(shù)
實(shí)現(xiàn)原理:
每當(dāng)用戶(hù)在某一天上線(xiàn)的時(shí)候,我們就使用 SETBIT
讼溺,以用戶(hù)名作為 key
楣号,將那天所代表的網(wǎng)站的上線(xiàn)日作為 offset
參數(shù),并將這個(gè) offset
上的位設(shè)置為 1
怒坯。
例如:
- 某應(yīng)用上線(xiàn)第100天炫狱,若用戶(hù)A在該天上線(xiàn)一次。
SETBIT A 100 1
- 某應(yīng)用上線(xiàn)第101天剔猿,用戶(hù)A上線(xiàn)视译。
SETBIT A 101 1
- 統(tǒng)計(jì)用戶(hù) A 總共上線(xiàn)次數(shù)。
BITCOUNT A
2)用戶(hù)簽到
與統(tǒng)計(jì)用戶(hù)上線(xiàn)次數(shù)原理類(lèi)似归敬。
原理:以用戶(hù)ID為KEY酷含,以當(dāng)前時(shí)間距離開(kāi)始時(shí)間的時(shí)間差為偏移量鄙早,若用戶(hù)簽到一次,則將位置為 1
第美。最后 bitcount
KEY蝶锋,獲取用戶(hù)一共簽到的次數(shù)陆爽。
const start_date = '20180801';
const end_date = '20180830';
const offset = moment(start_date).unix() - moment(end_date).unix();
redis.setBit('user_id_2018', offset, 1);
// 統(tǒng)計(jì)活躍天數(shù)
redis.bitCount('user_id_2018');
3)統(tǒng)計(jì)活躍用戶(hù)
需求:統(tǒng)計(jì)某天或連續(xù)幾天什往,活躍用戶(hù)數(shù)
方案:若某用戶(hù)上線(xiàn),則以日期為KEY慌闭,以用戶(hù)user_id為偏移量(若ID不為整數(shù)别威,則將ID hash化為唯一ID),設(shè)置位為 1
redis.setBit('')
const status = 1;
const user_id = 100;
redis.setBit('active_20180820', user_id, status);
redis.setBit('active_20180821', user_id, status);
// 將20180820號(hào)與20180821日進(jìn)行和運(yùn)算驴剔,得出兩天都上線(xiàn)的結(jié)果省古。并存入KEY—— dest_201808_20_21
redis.bitOp('AND', 'dest_201808_20_21', 'active_20180820', 'active_20180821');
redis.bitCount('dest_201808_20_21');
4)用戶(hù)在線(xiàn)狀態(tài)
需求:提供接口檢查用戶(hù)是否在線(xiàn)。
方案:使用bitmap
存儲(chǔ)用戶(hù)在線(xiàn)狀態(tài)丧失。使用一個(gè)KEY豺妓,若用戶(hù)在線(xiàn),則以用戶(hù)ID位偏移量布讹,將位設(shè)為 1
琳拭;若不在線(xiàn),則設(shè)置為 0
描验。
參考資料
- 使用redis實(shí)現(xiàn)點(diǎn)贊功能的幾種思路
- Redis中bitmap的妙用
- When would you use a long, string ID instead of a simple integer
- 按位操作符
- Bitmap的秘密
- redis使用bitmap實(shí)現(xiàn)網(wǎng)站活躍用戶(hù)的統(tǒng)計(jì)·
- 模式:使用 bitmap 實(shí)現(xiàn)用戶(hù)上線(xiàn)次數(shù)統(tǒng)計(jì)
- Wikipedia - 有符號(hào)數(shù)處理
- 一看就懂系列之 詳解redis的bitmap在億級(jí)項(xiàng)目中的應(yīng)用