天天看點

redis系列之——資料類型bitmaps:今天你簽到了嗎?Redis系列目錄

Redis系列目錄

redis系列之——分布式鎖

redis系列之——緩存穿透、緩存擊穿、緩存雪崩

redis系列之——Redis為什麼這麼快?

redis系列之——資料持久化(RDB和AOF)

redis系列之——一緻性hash算法

redis系列之——高可用(主從、哨兵、叢集)

redis系列之——事物及樂觀鎖

redis系列之——資料類型geospatial:你隔壁有沒有老王?

redis系列之——資料類型bitmaps:今天你簽到了嗎?

布隆過濾器是個啥!

平台日活躍100,000,000!!!今天有80,000,000人簽到!!!

redis系列之——資料類型bitmaps:今天你簽到了嗎?Redis系列目錄

這個是怎麼統計出來的?如果讓你設計,你會如何設計?是不是使用mysql存儲使用者活躍的資訊,同時使用select count(*)統計總數?資料量大,是不是準備做離線資料處理?實時性如何保證?有沒有更簡單的方式?

redis系列之——資料類型bitmaps:今天你簽到了嗎?Redis系列目錄

你也許已經知道Redis并不是簡單的key-value存儲,還有Hash、List、Set、Zset。

實際上Redis是一個資料結構伺服器,支援不同類型的值。

今天給大家介紹redis中一種90%的程式員都不知道但是非常有用的資料類型,Bitmaps。

順便回顧一下之前的兩篇文章《redis系列之——緩存穿透、緩存擊穿、緩存雪崩》、《布隆過濾器是個啥!》提到的bloom filter。

什麼是 Bitmaps

來看看官方對Bitmaps的說明:

  • Bitmaps 并不是實際的資料類型,而是定義在String類型上的一個面向位元組操作的集合。因為字元串是二進制安全的塊,他們的最大長度是512M,最适合設定成2^32個不同位元組。
  • bitmaps的位操作分成兩類:1.固定時間的單個位操作,比如把String的某個位設定為1或者0,或者擷取某個位上的值 2.對于一組位的操作,對給定的bit範圍内,統計設定值為1的數目(比如人口統計)。
  • bitmaps最大的優勢是在存儲資料時可以極大的節省空間,比如在一個項目中采用自增長的id來辨別使用者,就可以僅用512M的記憶體來記錄40億使用者的資訊(比如使用者是否希望收到新的通知,用1和0辨別)

簡單來說bitmaps就是一個長度可變的bit數組。每個位隻能存儲0或1。我們先來看看bitmap的具體表示,當我們使用指令 setbit key (0,2,4,6) 1後,這個bit數組的具體表示為:

bit0 bit1 bit2 bit3 bit4 bit5 bit6 bit7
1 1 1 1

一天的1億人的登入情況(登入、未登入)就可以使用一個長度為1億的bit數組存儲,數組的索引就是使用者的userId(假設userId是自增的)。

如何使用

這裡介紹幾個常用的bitmaps的操作指令。

SETBIT key offset value

該指令起始版本:2.2.0;時間複雜度:O(1);傳回值:在offset處原來的bit值;該指令的作用:設定或者清空key的value(字元串)在offset處的bit值。

那個位置的bit要麼被設定,要麼被清空,這個由value(隻能是0或者1)來決定。當key不存在的時候,就建立一個新的字元串value。要確定這個字元串大到在offset處有bit值。參數offset需要大于等于0,并且小于2^32(限制bitmap大小為512MB)。當key對應的字元串增大的時候,新增的部分bit值都是設定為0。

警告:對于一個不存在的key,首次使用setbit指令在offset=2^32-1的位置(最後一個bit位)設定value時,或者對于一個存在的key,并非首次使用setbit指令在 offset=2^32-1 的位置設定value但之前設定offset的位置比較小時, Redis需要立即配置設定所有記憶體,這有可能會導緻服務阻塞一會。在一台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。注意,一旦第一次記憶體配置設定完,後面對同一個key調用SETBIT就不會預先得到記憶體配置設定。

比如,某個平台需要統計每天使用者的活躍情況(一個使用者當天登了就算是一個當天活躍使用者),每天都需要新生成一個bitmaps存儲當天使用者的登入情況。

這裡的設計是: setbit active:{date} {userId} value ;比如存儲2020-07-01該平台的使用者活躍情況,key=active:2020-07-01;

127.0.0.1:6379> setbit active:2020-07-01 666 1 #userId=666的使用者登陸,這是今天登陸的第一個使用者。
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 100000000 1 #userId=100000000的使用者登陸,這是今天第二個登陸的使用者。
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 33 1
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 666 1 
(integer) 0
127.0.0.1:6379> setbit active:2020-07-01 100000 1
(integer) 0
           

第一個使用者登陸時,會建立一個String,這個String被配置設定的空間時667bit,這是offset為0到665的使用者未登入,值為0;

第二個使用者登陸時,這個String已經存在,這時會被配置設定666到100000000的位元組空間,這個時候就會出現短時間阻塞。

GETBIT key offset

該指令起始版本:2.2.0;時間複雜度:O(1);傳回值:在offset處的bit值;該指令的作用:傳回key對應的string在offset處的bit值。

當offset超出了字元串長度的時候,這個字元串就被假定為由0比特填充的連續空間。當key不存在的時候,它就認為是一個空字元串,是以offset總是超出範圍,然後value也被認為是由0比特填充的連續空間。

比如,查詢userId=666的使用者在2020-07-01是否登陸:

127.0.0.1:6379> setbit active:2020-07-01 666
(integer) 1
           

傳回為1,表示登陸了。

BITCOUNT key [start end]

該指令起始版本:2.6.0;時間複雜度:O(N);該指令的作用:統計字元串被設定為1的bit數;傳回值:被設定為 1 的位的數量。

一般情況下,給定的整個字元串都會被進行計數,通過指定額外的 start 或 end 參數,可以讓計數隻在特定的位上進行。start 和 end 參數的設定和 GETRANGE 指令類似,都可以使用負數值:比如 -1 表示最後一個位,而 -2 表示倒數第二個位,以此類推。不存在的 key 被當成是空字元串來處理,是以對一個不存在的 key 進行 BITCOUNT 操作,結果為 0 。

比如,統計2020-07-01該平台有多少使用者活躍(登陸):

127.0.0.1:6379> bitcount active:2020-07-01 0 -1
(integer) 342342326
           

有342342326個使用者登陸過。

處理上面介紹的三個指令,對bitmaps操作的指令還有很多,因為bitmaps的本質是String,相關的操作指令和string一樣,可以參考官網:http://www.redis.cn/commands.html#string

除了上面的指令,還可以通過相關的指令比較兩個string的差異,對兩個string做與、或、非運算;有很多實際的場景都可以通過這些簡單的指令實作。

使用場景

由于bit數組的每個位置隻能存儲0或者1這兩個狀态;是以對于實際生活中,處理兩個狀态的業務場景就可以考慮使用bitmaps。如使用者登陸/未登入,簽到/未簽到,關注/未關注,打卡/未打卡等。同時bitmap還通過了相關的統計方法進行快速統計。

記憶體占用比較

假如一個平台有8億使用者,平均日活躍使用者有1億,,分别使用List和Bitmap存儲平台某一天是否活躍(登陸)使用者時記憶體占用情況(1KB=1000bit):

資料類型 每個userId占用空間 需要存儲的使用者量 記憶體使用總量
List 4 * 8bit=32bit(假設userId用的是int存儲) 100,000,000 32bit * 100,000,000= 400MB
Bitmaps 1bit 800,000,000 1bit * 800,000,000=100MB

假如一個平台有8億使用者,平均日活躍使用者有100萬,,分别使用List和Bitmap存儲平台某一天是否活躍(登陸)使用者時記憶體占用情況(1KB=1000bit):

資料類型 每個userId占用空間 需要存儲的使用者量 記憶體使用總量
List 4 * 8bit=32bit(假設userId用的是int存儲) 1,000,000 32bit * 1,000,000= 4MB
Bitmaps 1bit 800,000,000 1bit * 800,000,000=100MB

是以并不是在所有的情況下,使用bitmap都是最好的選擇。平台雖然有8億使用者,但是活躍的使用者很少,這是使用Bitmaps,如果隻有一個使用者登入(加入是userId=800,000,000-1這個使用者登入),也需要配置設定100MB的空間。

注意

bitmaps類型(string)最大長度為512M。

setbit時的偏移量很大時,可能會有較大耗時。

bitmaps不是絕對的好,有時可能更浪費空間。

Bloot Filter該怎麼做,你是不是已經知道了?

完成,收工!!

redis系列之——資料類型bitmaps:今天你簽到了嗎?Redis系列目錄

【傳播知識,共享價值】,感謝小夥伴們的關注和支援,我是【諸葛小猿】,一個彷徨中奮鬥的網際網路民工。

redis系列之——資料類型bitmaps:今天你簽到了嗎?Redis系列目錄

繼續閱讀