java使用redis進行位圖法統計活躍用戶

位圖法

位圖是通過將數組下標與應用中的一些值關聯映射,數組中該下標所指定的位置上的元素可以用來標識應用中值的情況(是否存在或者數目 或者計數等),位圖數組中每個元素在內存中佔用1位,所以可以節省存儲空間。位圖是一種非常簡潔快速的數據結構,它能同時使存儲空間和速度最優化。如可用一個10位長的字符串來表示一個所有元素都小於10的簡單的非負整數集合,例如,可以用如下字符串表示集合{1,2,4,5,8} ,對應位置數字存在標記爲1,否則標記爲0。

JAVA

Bitmap(即Bitset)
Bitmap是一串連續的2進制數字(0或1),每一位所在的位置爲偏移(offset),在bitmap上可執行AND,OR,XOR以及其它位操作。
BitSet是java的一個類,詳細的使用可以參考https://www.cnblogs.com/xupengzhang/p/7966755.html。其常用的方法有get(),set(),cardinality(),and(),or(),xor()等等

位圖計數(Population Count)

位圖計數統計的是bitmap中值爲1的位的個數。位圖計數的效率很高,例如,一個bitmap包含10億個位,90%的位都置爲1,在一臺MacBook Pro上對其做位圖計數需要21.1ms。SSE4甚至有對整形(integer)做位圖計數的硬件指令。
這裏寫圖片描述

Redis

Redis允許使用二進制數據的Key(binary keys) 和二進制數據的Value(binary values)。在redis中,字符串是以二進制的形式存儲的,因此位圖在redis中並不是一種數據類型,而是一種字符串的表現形式。
在redis中如果使用位圖,其常見的命令:getbit,setbit,bitop,BITCOUNT等等。
關於getbit,setbit,bitop的命令可以參考https://blog.csdn.net/a692055612/article/details/79717146

日活躍用戶

爲了統計今日登錄的用戶數,我們建立了一個bitmap,每一位標識一個用戶ID。當某個用戶訪問我們的網頁或執行了某個操作,就在bitmap中把標識此用戶的位置爲1。在Redis中獲取此bitmap的key值是通過用戶執行操作的類型和時間戳獲得的。

這裏寫圖片描述

這個簡單的例子中,每次用戶登錄時會執行一次redis.setbit(daily_active_users, user_id, 1)。將bitmap中對應位置的位置爲1,時間複雜度是O(1)。統計bitmap結果顯示有今天有9個用戶登錄。Bitmap的key是daily_active_users,它的值是1011110100100101。

因爲日活躍用戶每天都變化,所以需要每天創建一個新的bitmap。我們簡單地把日期添加到key後面,實現了這個功能。例如,要統計某一天有多少個用戶至少聽了一個音樂app中的一首歌曲,可以把這個bitmap的redis key設計爲play:yyyy-mm-dd-hh。當用戶聽了一首歌曲,我們只是簡單地在bitmap中把標識這個用戶的位置爲1,時間複雜度是O(1)。

JAVA代碼示例

    public void setActiveUserCount(Integer id) {
        Jedis jedis = factory.getConnection().getNativeConnection();
        Date currentTime = new Date();
        String currentStr = new SimpleDateFormat("yyyy-MM-dd").format(currentTime);
        String key = activeName+currentStr;
        jedis.setbit(key, id, true);
    }

解析:

以id爲偏移量,1爲用戶今天已登錄,0爲用戶今天未登陸。

示例代碼是spring配置管理的factory是spring管理的JedisConnectionFactory。關於如何在spring中配置redis,並且如何使用Jedis可百度

統計活躍用戶

需求:

統計七天內有進行至少一次登錄操作的用戶人數

思考:

  • 採取按位存儲的方式,bit的下標爲用戶的id,如果用戶id的下標value爲1,那麼該用戶在當天有進行登錄
  • 將所用的用戶生成一個位圖(0/1 未登錄/登錄)
  • 統計七天內,有過一次登錄的用戶,那麼他就是活躍用戶,所以是獲取七天的字節數組,取他們的並集,用or。如果想獲取七天都有登錄的用戶,那麼需要的是取他們的交集,用and
@Autowired
JedisConnectionFactory factory;
private static final String activeName = "activeUser:";

public int getActiveUserCount() {
        Jedis jedis = factory.getConnection().getNativeConnection();
        Date currentTime = new Date();
        BitSet all = new BitSet();
        for(int i=0;i<7;i++){
            //獲得前一天的日期
            currentTime=DateUtils.addDays(currentTime, -1);
            String currentStr = new SimpleDateFormat("yyyy-MM-dd").format(currentTime);
            String key = activeName+currentStr;
            //獲得這一天日期的活躍用戶字節數組
            byte[] loginByte = jedis.get(key.getBytes());
            //有可能當天沒有一個用戶登錄
            if(loginByte!=null){
                BitSet user = BitSet.valueOf(loginByte); 
                //取其並集
                all.or(user);
            }
        } 
        //統計人數
        int count = all.cardinality();
        return count;
    }

解析:

如果需求是統計七天內都有登錄的用戶人數,那麼取其交集,用and方法

總結:

上面代碼示例並沒有將數據截圖展示,但個人私下已經進行了數據填充,且進行了測試,代碼通過。除了使用java的BitSet的方法進行統計,還能使用redis的命令進行統計,java只需獲取命令返回接口即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章