<b>3.5 bitmaps</b>
<b>3.5.1 資料結構模型</b>
現代計算機用二進制(位)作為資訊的基礎機關,1個位元組等于8位,例如“big”字元串是由3個位元組組成,但實際在計算機存儲時将其用二進制表示,“big”分别對應的ascii碼分别是98、105、103,對應的二進制分别是01100010、01101001和01100111,如圖3-9所示。
圖3-9 字元串“big”用二進制表示
許多開發語言都提供了操作位的功能,合理地使用位能夠有效地提高記憶體使用率和開發效率。redis提供了bitmaps這個“資料結構”可以實作對位的操作。把資料結構加上引号主要因為:
bitmaps本身不是一種資料結構,實際上它就是字元串(如圖3-10所示),但是它可以對字元串的位進行操作。
bitmaps單獨提供了一套指令,是以在redis中使用bitmaps和使用字元串的方法不太相同。可以把bitmaps想象成一個以位為機關的數組,數組的每個單元隻能存儲0和1,數組的下标在bitmaps中叫做偏移量。
圖3-10 字元串"big"用二進制表示
<b>3.5.2 指令</b>
本節将每個獨立使用者是否通路過網站存放在bitmaps中,将通路的使用者記做1,沒有通路的使用者記做0,用偏移量作為使用者的id。
1.?設定值
setbit key offset value
設定鍵的第offset個位的值(從0算起),假設現在有20個使用者,userid=0,5,11,15,19的使用者對網站進行了通路,那麼目前bitmaps初始化結果如圖3-11所示。
圖3-11 setbit使用
具體操作過程如下,unique:users:2016-04-05代表2016-04-05這天的獨立通路使用者的bitmaps:
127.0.0.1:6379> setbit
unique:users:2016-04-05 0 1
(integer) 0
unique:users:2016-04-05 5 1
unique:users:2016-04-05 11 1
unique:users:2016-04-05 15 1
unique:users:2016-04-05 19 1
如果此時有一個userid=50的使用者通路了網站,那麼bitmaps的結構變成了圖3-12,第20位~49位都是0。
圖3-12 userid=50使用者通路
很多應用的使用者id以一個指定數字(例如10000)開頭,直接将使用者id和bitmaps的偏移量對應勢必會造成一定的浪費,通常的做法是每次做setbit操作時将使用者id減去這個指定數字。在第一次初始化bitmaps時,假如偏移量非常大,那麼整個初始化過程執行會比較慢,可能會造成redis的阻塞。
2.?擷取值
gitbit key offset
擷取鍵的第offset位的值(從0開始算),下面操作擷取id=8的使用者是否在2016-04-05這天通路過,傳回0說明沒有通路過:
127.0.0.1:6379> getbit
unique:users:2016-04-05 8
由于offset=1000000根本就不存在,是以傳回結果也是0:
unique:users:2016-04-05 1000000
3.?擷取bitmaps指定範圍值為1的個數
bitcount [start]?[end]
下面操作計算2016-04-05這天的獨立通路使用者數量:
127.0.0.1:6379> bitcount
unique:users:2016-04-05
(integer) 5
[start]和[end]代表起始和結束位元組數,下面操作計算使用者id在第1個位元組到第3個位元組之間的獨立通路使用者數,對應的使用者id是11,15,19。
unique:users:2016-04-05 1 3
(integer) 3
4.?bitmaps間的運算
bitop op destkey key?[key....]
bitop是一個複合操作,它可以做多個bitmaps的and(交集)、or(并集)、not(非)、xor(異或)操作并将結果儲存在destkey中。假設2016-04-04通路網站的userid=1,2,5,9,如圖3-13所示。
圖3-13 2016-04-04通路網站的使用者bitmaps
下面操作計算出2016-04-04和2016-04-03兩天都通路過網站的使用者數量,如圖3-14所示。
127.0.0.1:6379> bitop and
unique:users:and:2016-04-04_03 unique: users:2016-04-03
unique:users:2016-04-03
(integer) 2
unique:users:and:2016-04-04_03
如果想算出2016-04-04和2016-04-03任意一天都通路過網站的使用者數量(例如月活躍就是類似這種),可以使用or求并集,具體指令如下:
127.0.0.1:6379> bitop or
unique:users:or:2016-04-04_03 unique:
users:2016-04-03 unique:users:2016-04-03
unique:users:or:2016-04-04_03
(integer) 6
圖3-14 利用bitop and指令計算兩天都通路網站的使用者
5.?計算bitmaps中第一個值為targetbit的偏移量
bitpos key targetbit [start] [end]
下面操作計算2016-04-04目前通路網站的最小使用者id:
127.0.0.1:6379> bitpos
unique:users:2016-04-04 1
(integer) 1
除此之外,bitops有兩個選項[start]和[end],分别代表起始位元組和結束位元組,例如計算第0個位元組到第1個位元組之間,第一個值為0的偏移量,從圖3-13可以得知結果是id=0的使用者。
unique:users:2016-04-04 0 0 1
<b>3.5.3 bitmaps分析</b>
假設網站有1億使用者,每天獨立通路的使用者有5千萬,如果每天用集合類型和bitmaps分别存儲活躍使用者可以得到表3-3。
表3-3 set和bitmaps存儲一天活躍使用者的對比
資料類型 每個使用者id占用空間 需要存儲的使用者量 全部記憶體量
集合類型 64位 50?000?000 64位×50?000?000 = 400mb
bitmaps 1位 100?000?000 1位×100?000?000
= 12.5mb
很明顯,這種情況下使用bitmaps能節省很多的記憶體空間,尤其是随着時間推移節省的記憶體還是非常可觀的,見表3-4。
表3-4 set和bitmaps存儲獨立使用者空間對比
一天 一個月 一年
set 400m 12g 144g
bitmaps 12.5m 375m 4.5g
但bitmaps并不是萬金油,假如該網站每天的獨立通路使用者很少,例如隻有10萬(大量的僵屍使用者),那麼兩者的對比如表3-5所示,很顯然,這時候使用bitmaps就不太合适了,因為基本上大部分位都是0。
表3-5 set和bitmaps存儲一天活躍使用者的對比(獨立使用者比較少)
資料類型 每個userid占用空間 需要存儲的使用者量 全部記憶體量
集合類型 64位 100?000 64位 × 100?000 = 800kb
bitmaps 1位 100?000?000 1位 × 100?000?000 = 12.5mb