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只需获取命令返回接口即可。

猜你喜欢

转载自blog.csdn.net/qq_32020035/article/details/81197284