如何统计百万用户在线状态-bitmap

背景

经常你会听到,说我们的网站日活是多少,每天在线人数多少等等,那他是怎么实现的?

bitmap介绍

BitMap 原本的含义是用一个比特位来映射某个元素的状态。由于一个比特位只能表示 0 和 1 两种状态,所以 BitMap 能映射的状态有限,但是使用比特位的优势是能大量的节省内存空间。

在 Redis 中,可以把 Bitmaps 想象成一个以比特位为单位的数组,数组的每个单元只能存储0和1,数组的下标在 Bitmaps 中叫做偏移量。

位图不是实际的数据类型,而是在 String 类型上定义的一组面向位的操作,将其视为位向量。由于字符串是二进制安全 blob,其最大长度为 512 MB,因此它们适合设置最多 2^32 个不同位。

基本使用

SETBIT

语法

SETBIT key offset value

设置或者清空 key 的 value 在 offset 处的 bit 值(只能是 0 或者 1)。

警告:当设置最后一个可能的位(偏移量等于2^32 -1)并且存储在key处的字符串值尚未保存字符串值,或者保存一个小的字符串值时,Redis需要分配所有可能阻塞的中间内存服务器一段时间了。在 2010 MacBook Pro 上,设置位号 2^32 -1(512MB 分配)需要约 300ms,设置位号 2^30 -1(128MB 分配)需要约 80ms,设置位号 2^28 -1(32MB 分配)需要约 30 毫秒,设置位数 2^26 -1(8MB 分配)需要约 8 毫秒。请注意,一旦完成第一次分配,后续对同一键的 SETBIT 调用将不会产生分配开销。

示例:

//设置
setbit mykey 7 1
//清空
setbit mykey 7 0

GETBIT

返回 key 处存储的字符串值中偏移处的位值

当偏移量超出字符串长度时,字符串被假定为具有 0 位的连续空间。当 key 不存在时,它被假定为空字符串,因此 offset 总是超出范围,并且 value 也被假定为 0 位的连续空间

语法

GETBIT key offset

示例

getbit mykey 7

BITCOUNT 位计数

计算字符串中设置位的数量(总体计数)

语法

BITCOUNT key [start end]

示例

#获取mykey内值为 1 的个数
BITCOUNT mykey
# 获取指定范围内值为 1 的个数,start 和 end 以字节为单位
BITCOUNT mykey 0 1

BITOP

在多个键(包含字符串值)之间执行按位运算并将结果存储在目标键中

语法:

#AND 与运算 &
#OR 或运算 |
# XOR 异或 ^
#NOT 取反 ~
BITOP <AND | OR | XOR | NOT> destkey key [key ...]

BITOP 命令支持四种按位运算:AND、OR、XOR 和 NOT,因此调用该命令的有效形式为:

  • BITOP AND destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP OR destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP XOR destkey srckey1 srckey2 srckey3 … srckeyN
  • BITOP NOT destkey srckey

操作的结果始终存储在 destkey

示例:

BITOP AND destmap bitmap:01 bitmap:02 bitmap:03

场景

在线用户统计

只需要一个 key,然后用户 id 为 offset,如果在线就设置为 1,不在线就设置为 0。

执行步骤

第一步,执行以下指令,表示用户已登录。

SETBIT login_status 10086 1

第二步,检查该用户是否登陆,返回值 1 表示已登录。

GETBIT login_status 10086

第三步,登出,将 offset 对应的 value 设置成 0。

SETBIT login_status 10086 0

内存占用

假设当前站点有5000W用户,那么一天的数据大约为50000000/8/1024/1024=6MB,可以看出非常节约内存空间

代码实现

public static void main(String[] args) {
        Jedis jedis = new Jedis("10.1.250.157",6379);
        jedis.auth("google00");
        //设置用户登录在线
        jedis.setbit("login_status",10086L,true);
        //查询用户登录状态
        boolean loginStatus = jedis.getbit("login_status",10086L);
        if (loginStatus){
            System.out.println("10086 用户在线");
        }else {
            System.out.println("10086 用户离线");
        }
        //用户退出登录,值设置为0
        jedis.setbit("login_status",10086L,false);
    }

用户签到

在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位。一个月最多只有 31 天,只需要 31 个 bit 位即可。

比如统计编号 10086 的用户在 2024年 1 月份的打卡情况要如何进行?

key 可以设计成 uid:sign:{userId}:{yyyyMM},月份的每一天的值 - 1 可以作为 offset(因为 offset 从 0 开始,所以 offset = 日期 - 1)。

第一步,执行下面指令表示记录用户在 2024 年 1 月 16 号打卡。

SETBIT uid:sign:10086:202401 15 1

第二步,判断编号 89757 用户在 2024 年 1月 16 号是否打卡。

GETBIT uid:sign:10086:202401 15

第三步,统计该用户在 1 月份的打卡次数,使用 BITCOUNT 指令。该指令用于统计给定的 bit 数组中,值 = 1 的 bit 位的数量。

BITCOUNT uid:sign:89757:202405

代码实现

public static void main(String[] args) {
        Jedis jedis = new Jedis("10.1.250.157",6379);
        jedis.auth("google00");
        //记录用户在 2024 年 1 月 16 号打卡
        jedis.setbit("uid:sign:10086:202401",10086L,true);
        //判断用户在 2024 年 1 月 16 是否打卡
        boolean loginStatus = jedis.getbit("uid:sign:10086:202401",10086L);
        if (loginStatus){
            System.out.println("10086用户 2024 年 1 月 16 打卡");
        }else {
            System.out.println("10086用户 2024 年 1 月 16 未打卡");
        }
        //统计1月份打卡次数
        long num = jedis.bitcount("uid:sign:10086:202401");
        System.out.println("用户10086在1月份打卡:"+num+"次");
    }

统计活跃用户(用户登陆情况)

使用日期作为 key,然后用户 id 为 offset,如果当日活跃过就设置为1。具体怎么样才算活跃这个标准大家可以自己指定。

假如用户id为,「1,2,3,4,5,6」

用户2024年1月15号用登录情况

用户1登录
setbit login:20240115 1 1
用户2登录
setbit login:20240115 2 1
用户3登录
setbit login:20240115 3 1
用户4登录
setbit login:20240115 4 1
用户5登录
setbit login:20240115 5 1

用户2024年1月16号用登录情况

用户1登录
setbit login:20240116 1 1
用户2登录
setbit login:20240116 2 1
用户3登录
setbit login:20240116 3 1
用户4登录
setbit login:20240116 4 1

用户2024年1月17号用登录情况

用户1登录
setbit login:20240117 1 1
用户2登录
setbit login:20240117 2 1

用户4登录
setbit login:20240117 4 1
用户6登录
setbit login:20240117 6 1

统计连续两天活跃的用户总数:

bitop and dest1 login:20240115 login:20240116 
# dest1 中值为1的offset,就是连续两天活跃用户的ID
bitcount dest1

统计2024年1月15日 ~ 2024年1月17日 活跃过的用户:

bitop or dest2 login:20240115 login:20240116 login:20240117

掘金签到送矿石

image.png
通过上面的示例,小伙伴可以自己想想怎么去实现

提示:在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位,一个用户一个key

写作不易,刚好你看到,刚好对你有帮助,动动小手,点点赞,欢迎转发,有疑问的欢迎留言或者私信讨论,有问必回。