Redis中BitMap技術簡介及應用

Redis中BitMap技術簡介及應用

BitMap簡介

BitMap是一串連續(xù)的二進制數(shù)字(0和1),類似于位數(shù)組,每一位所在的位置為偏移量(offset),類似于數(shù)組索引忧吟,BitMap就是通過最小的單位bit來進行0|1的設置,時間復雜度位O(1)斩披,表示某個元素的值或者狀態(tài)溜族。由于bit是計算機中最小的單位,使用它進行儲存將非常節(jié)省空間垦沉。特別適合一些數(shù)據(jù)量大的場景煌抒。例如,統(tǒng)計每日活躍用戶厕倍、統(tǒng)計每月打卡數(shù)等統(tǒng)計場景寡壮。1天記錄1000W用戶的活躍統(tǒng)計數(shù)據(jù),只需要10000000/8/1024/1024 ≈1.2M讹弯。

Redis中的BitMap

Redis從2.2.0版本開始新增了setbit况既,getbit,bitcount组民,bitop等幾個BitMap相關命令棒仍,雖然是新命令,但是并沒有增加新的數(shù)據(jù)類型臭胜,它還是屬于String類型莫其。Redis中的BitMap最大占用內存大小限制在512M之內,即2^32庇楞。

相關命令操作

setbit

設置某個key的指定偏移量的value值為0或者1榜配,key不存在時自動生成一個新的字符串值,字符串會進行伸展吕晌,該偏移量前面的位值默認為0蛋褥,偏移量offset參數(shù)必須大于等于0,小于2^32睛驳。

時間復雜度:O(1)

返回值:指定偏移量存儲的值

示例:

127.0.0.1:6379[3]> setbit login 2 1
(integer) 0
127.0.0.1:6379[3]> setbit login 2 1
(integer) 1
127.0.0.1:6379[3]> getbit login 2
(integer) 1
127.0.0.1:6379[3]> getbit login 1
(integer) 0

getbit

獲取key指定偏移量上的值烙心,當key不存在時,返回0乏沸。

時間復雜度:O(1)

返回值:指定偏移量上存儲的值

示例:

127.0.0.1:6379[3]> exists order
(integer) 0
127.0.0.1:6379[3]> getbit order 10
(integer) 0
127.0.0.1:6379[3]> setbit order 10 1
(integer) 0
127.0.0.1:6379[3]> getbit order 10
(integer) 1
127.0.0.1:6379[3]>

bitcount

統(tǒng)計給定key中淫茵,被設置為1的比特位的數(shù)量,可以通過start和end參數(shù)設置范圍蹬跃。

注意匙瘪!,setbit和getbit是對bit位進行操作,bitcount的參數(shù)start和end是對字節(jié)byte計數(shù)丹喻,1 byte = 8bit薄货。

時間復雜度:O(n)

返回值:key中被設置為1的數(shù)量

示例:

127.0.0.1:6379[3]> bitcount month     // 空的key,位為1的數(shù)量為0
(integer) 0
127.0.0.1:6379[3]> setbit month 4 1  
(integer) 0
127.0.0.1:6379[3]> bitcount month     // 默認統(tǒng)計整個key位為1的數(shù)量
(integer) 1
127.0.0.1:6379[3]> bitcount month 0 0   // 查詢month中第一個字節(jié)位為1的數(shù)量碍论,即0 1 2 3 4 5 6 7位谅猾。
(integer) 1
127.0.0.1:6379[3]> bitcount month 1 1   // 查詢第二個字節(jié)
(integer) 0
127.0.0.1:6379[3]> setbit month 8 1
(integer) 0
127.0.0.1:6379[3]> bitcount month 0 1    // [start, end]是一個閉區(qū)間,所以這里查詢的是第1鳍悠、2個字節(jié)
(integer) 2

bitop

對一個或多個key進行位操作税娜,并將結果保存到destkey上。操作方式可以是AND藏研、OR敬矩、NOT、XOR這四種遥倦,除了NOT操作之外谤绳,其他操作可接收多個key。

處理不同長度的字符串時袒哥,較短的那個字符串所缺少的部分會被看作0缩筛,空的key也被看做全是0的字符串序列。

時間復雜度:O(n)

返回值:保存到destkey的字符串的長度

示例:

127.0.0.1:6379[3]> setbit month1 3 1   // month1:00010000
(integer) 0
127.0.0.1:6379[3]> setbit month2 4 1   // month2:00001000
(integer) 0
127.0.0.1:6379[3]> bitop OR month month1 month2    // 對month1和month2做或運算堡称,結果:00011000
(integer) 1
127.0.0.1:6379[3]> bitcount month         // month中位為1的數(shù)量就為2
(integer) 2

WEB常見應用

用戶行為統(tǒng)計

  • 是否點擊過某個按鈕
  • 是否領取過優(yōu)惠券
  • 點贊瞎抛、喜歡等
import * as Redis from "ioredis";
const redis = new Redis({});
// 記錄用戶行為,是否領取過優(yōu)惠券
const key = "got_coupon";
const uid = 100;
redis.setbit(key, uid, 1)
// 查詢用戶是否領取過
const is_got = redis.getbit(key, uid)
// 統(tǒng)計優(yōu)惠券已發(fā)放數(shù)量
const sended_count = redis.bigcount(key)

活躍用戶統(tǒng)計

import * as Redis from "ioredis";
const redis = new Redis({});

// 用戶(uid:100)登錄計數(shù)
const uid = 100;
const key = "userLogin:2019-08-01";
redis.setbit(key, uid, 1);

// 計算今天活躍用戶數(shù)
const active_nums = redis.bitcount(key);

// 昨天今天均活躍的用戶
const key2 = "userLogin:2019-08-02";
redis.bitop("AND", "both_active", key, key2);
const both_nums = redis.bitcount("both_active");

// 統(tǒng)計最近三天用戶活躍數(shù)
const key3 = "userLogin:2019-08-03";
redis.bitop("OR", "three_day_active", key, key2, key3);
const threedays_nums = redis.bitcount("three_day_active");

用戶簽到

簽到需求:

  1. 用戶使用簽到功能却紧,用戶的簽到狀態(tài)
  2. 用戶的周桐臊、月簽到記錄、次數(shù)
  3. 當天有多少用戶簽到
import redis
from datetime import date, timedelta
import calendar

# redis 連接
r = redis.Redis(
    host="192.168.0.200",
    port=6379,
    db=3
)

# 檢查參數(shù)裝飾器
def check_input(func):
    def wrapper(*args, **kwargs):
        if not isinstance(args[1], int):
            raise ValueError(f"User_id must be int, and your input is {type(args[1])}")
        return func(*args, **kwargs)

    return wrapper

class RedisCheckIn:
    _private_key = "_check_in_"

    def __init__(self):
        pass

    @check_input
    def sign(self, user_id: int) -> int:
        # 用戶簽到
        return r.setbit(self._get_key(date.today()), user_id, 1)

    @check_input
    def sign_status(self, user_id: int) -> int:
        # 用戶今日簽到狀態(tài)
        return r.getbit(self._get_key(date.today()), user_id)

    @check_input
    def week_sign_status(self, user_id: int) -> list:
        # 求出這個周的簽到狀況
        now = date.today()  # 2020-06-05
        # 周一是1 周日是7
        weekday = now.isoweekday()  # 5
        # 使用管道批量化操作
        with r.pipeline(transaction=False) as p:
            for d in range(weekday):
                check_day = now - timedelta(days=d)
                p.getbit(self._get_key(check_day), user_id)
            # 倒序晓殊,之前是倒著查詢的
            data = p.execute()[::-1]
        # 比如周三的時候我們只查3次getbit断凶,然后剩下補0
        data.extend([0] * (7 - len(data)))
        return data

    @check_input
    def month_sing_status(self, user_id: int) -> list:
        # 求出這個月的某個用戶簽到狀況
        now = date.today()
        day = now.day
        with r.pipeline(transaction=False) as p:
            for d in range(day):
                check_day = now - timedelta(days=d)
                p.getbit(self._get_key(check_day), user_id)
            data = p.execute()[::-1]
        # 獲取當月天數(shù),還沒到的天數(shù)補0
        month_range = calendar.monthrange(now.year, now.month)
        data.extend([0] * (month_range[1] - len(data)))
        return data

    @check_input
    def week_sign_num(self, user_id: int) -> int:
        # 求出這個周的簽到次數(shù)
        return sum(self.week_sign_status(user_id))

    @check_input
    def month_sign_num(self, user_id: int) -> int:
        # 求出這個月的簽到次數(shù)
        return sum(self.month_sing_status(user_id))

    @check_input
    def today_sign_all_num(self) -> int:
        # 求出當天有多少用戶簽到
        return r.bitcount(self._get_key(date.today()))

    @staticmethod
    def _get_key(check_date):
        return f"check_in_{check_date}"

if __name__ == '__main__':
    redis_sign_in = RedisCheckIn()
    redis_sign_in.sign(100)  # 簽到
    print(redis_sign_in.sign_status(100))   # 1表示已簽到
    print(redis_sign_in.sign_status(101))   # 0表示未簽到
    print(redis_sign_in.week_sign_status(100))   # userId為100的用戶這周簽到情況:[0, 0, 0, 0, 1, 0, 0]
    print(redis_sign_in.week_sign_num(100))   # 這周總共簽到1次

獲取用戶ID

之前的應用都是統(tǒng)計總數(shù)巫俺,但如果業(yè)務需要认烁,有時也可能需要獲取用戶ID,來做下一步操作介汹。

// 獲取活躍用戶的id却嗡,可進行下一步操作,比如發(fā)送優(yōu)惠信息
import redis
import time

r = redis.Redis(host="192.168.0.200", port=6379, db=3)
# byte字節(jié)
tmp = r.get("login")
# bit位
total_bits = tmp * 8
start = time.time()
for i in range(len(total_bits)):
    # 所屬字節(jié)
    offset_arr = i // 8
    # 偏移量
    offset_bit = i % 8
    # 與128(10000000)進行與運算嘹承,bit存在窗价,則表示該位為1,此時i就是用戶id
    bit = (tmp[offset_arr] << offset_bit) & 0b10000000
    if bit:
        print(f'user {i} is set')
# 統(tǒng)計時間叹卷,1000W數(shù)據(jù)撼港,只需要4s坪它;
print(f'end: {time.time() - start}')
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市餐胀,隨后出現(xiàn)的幾起案子哟楷,更是在濱河造成了極大的恐慌,老刑警劉巖否灾,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸣奔,居然都是意外死亡墨技,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門挎狸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來扣汪,“玉大人,你說我怎么就攤上這事锨匆≌副穑” “怎么了?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵恐锣,是天一觀的道長茅主。 經常有香客問我,道長土榴,這世上最難降的妖魔是什么诀姚? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮玷禽,結果婚禮上赫段,老公的妹妹穿的比我還像新娘。我一直安慰自己矢赁,他們只是感情好糯笙,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撩银,像睡著了一般给涕。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蜒蕾,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天稠炬,我揣著相機與錄音,去河邊找鬼咪啡。 笑死首启,一個胖子當著我的面吹牛,可吹牛的內容都是我干的撤摸。 我是一名探鬼主播毅桃,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼褒纲,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了钥飞?” 一聲冷哼從身側響起莺掠,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎读宙,沒想到半個月后彻秆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡结闸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年唇兑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桦锄。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡扎附,死狀恐怖,靈堂內的尸體忽然破棺而出结耀,到底是詐尸還是另有隱情留夜,我是刑警寧澤,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布图甜,位于F島的核電站碍粥,受9級特大地震影響,放射性物質發(fā)生泄漏具则。R本人自食惡果不足惜即纲,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望博肋。 院中可真熱鬧低斋,春花似錦、人聲如沸匪凡。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽病游。三九已至唇跨,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間衬衬,已是汗流浹背买猖。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留滋尉,地道東北人玉控。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像狮惜,于是被迫代替她去往敵國和親高诺。 傳聞我的和親對象是個殘疾皇子碌识,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內容

  • 基本的Redis key的操作都已經熟悉了之后,便可以開始針對Redis提供的各種可操作的數(shù)據(jù)結構進行學習和了解虱而。...
    Yorking閱讀 850評論 0 0
  • 一億個用戶筏餐,有的用戶頻繁登錄,也有不經常登錄的牡拇。如何記錄用戶的登錄信息魁瞪?如何查詢活躍用戶?[如一周內 登錄三次的]...
    小胖學編程閱讀 8,413評論 0 12
  • Redis key 值是二進制安全的惠呼,這意味著可以用任何二進制序列作為key值佩番,從形如”foo”的簡單字符串到一個...
    壹點零閱讀 1,404評論 0 2
  • 什么是位圖 位圖(Bitmap)是通過一個 bit 來表示某個元素對應的值或者狀態(tài)。它并不是什么新的數(shù)據(jù)結構罢杉。它的...
    youthcity閱讀 2,018評論 1 51
  • 每個字都認識讀完了以為自己懂了講給別人聽的時候才發(fā)現(xiàn)說不清楚 參加共讀《快思慢想》已經33天,永澄老師領讀了18次...
    了凡工作室閱讀 330評論 3 1