Redis高級數(shù)據(jù)結(jié)構(gòu)實戰(zhàn)(一)BitMap用戶連續(xù)簽到

功能概述

  • 用戶連續(xù)登錄天數(shù)
  • 用戶累計登錄天數(shù)

1. 為什么選用 bitmap(位圖)

占用內(nèi)存更小,性能更高。這里偏實戰(zhàn),原理的東西就不細講了。

2. 實戰(zhàn)

2.1 基礎(chǔ)指令

記錄一個用戶某天登錄,只需要指令

redis:0> setbit key 6 1
"0"

bitmap 是一個bit數(shù)組,數(shù)據(jù)結(jié)構(gòu)大概是長這樣子的:

key 0 0 0 0 0 0 1 0 0

數(shù)字6是這個數(shù)組的偏移量(index,下標從0開始),表示第7天簽到了

redis:0> getbit key 6
"1"

查看累計登錄天數(shù):

redis:0> bitcount key
"1"

因為 bitfield 指令無符號獲取的偏移量最大是63,所以一個key只存一個月份的數(shù)據(jù),這樣key的結(jié)構(gòu)可以是這樣:

user:sign:userId:date

bitfield 指令其實就是獲取這個key的數(shù)組下標的一個list

bitfield user:sign:5:202105 get u14 0

u 表示無符號 ,14 表示今天是14號,0 表示索引,即從第一天開始

2.2 偽代碼

里面每一步的注釋都寫的非常明白,關(guān)鍵點在最后一個方法的移位操作

    // 簽到
    public void doSign(Integer userId, String dateStr) {
        // 獲取日期
        Date date = getDate(dateStr);
        // 獲取日期對應(yīng)的天數(shù),即多少號
        int offset = DateUtil.dayOfMonth(date) - 1;
        // 構(gòu)建 key user:sign:id:yyyyMM
        String signKey = buildKey(userId, date);
        // 查看是否簽到
        Boolean isSign = redisTemplate.opsForValue().getBit(signKey, offset);
        AssertUtil.isTrue(isSign, "當(dāng)前日期已簽到");
        // 簽到
        redisTemplate.opsForValue().setBit(signKey, offset, true);

    }
    
     private String buildKey(Integer dinerId, Date date) {
        return String.format("user:sign:%d:%s", dinerId,
                DateUtil.format(date, "yyyyMM"));
    }
    /**
     * 統(tǒng)計連續(xù)簽到的次數(shù)
     *
     * @param dinerId
     * @param date
     * @return
     */
    private int getContinuousSignCount(Integer dinerId, Date date) {
        // 當(dāng)前日期是幾號
        int dayOfMonth = DateUtil.dayOfMonth(date);
        // 構(gòu)建 key
        String key = buildKey(dinerId, date);

        int signCount = getSignCountFromRedis(key, dayOfMonth);
        if (dayOfMonth == signCount) {
            Date lastMonth = DateUtil.offsetMonth(date, -1);
            signCount += getLastMonthSignCount(dinerId, lastMonth);
        }

        return signCount;
    }
    
        /**
     * 遞歸獲取連續(xù)簽到天數(shù)
     * @param dinerId
     * @param lastMonth
     * @return
     */
    private int getLastMonthSignCount(Integer dinerId, Date lastMonth) {

        // 獲取當(dāng)月最后一天
        Date lastDay = DateUtil.endOfMonth(lastMonth);
        int dayOfMonth = DateUtil.dayOfMonth(lastDay);
        // 構(gòu)建 key
        String key = buildKey(dinerId, lastMonth);

        int signCountFromRedis = getSignCountFromRedis(key, dayOfMonth);
        if (signCountFromRedis == dayOfMonth) {
            Date lastMonth1 = DateUtil.offsetMonth(lastMonth, -1);
            signCountFromRedis += getLastMonthSignCount(dinerId, lastMonth1);
        }


        return signCountFromRedis;
    }
    
        public int getSignCountFromRedis(String key, int dayOfMonth) {

        // bitfield user:sign:5:202105 u14 0 ,u 表示無符號 ,14 表示今天是14號,0 表示索引,即從第一天開始
        BitFieldSubCommands bitFieldSubCommands = BitFieldSubCommands
                .create()
                .get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth))
                .valueAt(0);

        List<Long> list = redisTemplate.opsForValue().bitField(key, bitFieldSubCommands);
        if (list == null || list.isEmpty()) {
            return 0;
        }
        int signCount = 0;
        long count = list.get(0) == null ? 0 : list.get(0);

        // 移位操作:先右移再左移,結(jié)果未變則表示未簽到,結(jié)果變了則表示簽到了
        for (int i = dayOfMonth; i > 0; i--) { // i 表示位移的次數(shù)
            if (count >> 1 << 1 == count) {
                // 如果低位是0 且低位所在不是當(dāng)天,說明連續(xù)簽到中斷
                if (i != dayOfMonth) break;
            } else {
                signCount++;
            }
            // 把最后一位丟棄
            count >>= 1;
        }
        return signCount;
    }
    public void setBit(String key, Integer offset) {
        redisTemplate.opsForValue().setBit(key, offset, true);
    }

    public Boolean getBit(String key, Integer offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    public Long bitCount(String key) {
        return (Long) redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }

    public List<Long> bitField(String key, BitFieldSubCommands bitFieldSubCommands) {
        return redisTemplate.opsForValue().bitField(key, bitFieldSubCommands);
    }

2.3 移位

待續(xù)...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容