如何统计百万用户在线状态-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
掘金签到送矿石
通过上面的示例,小伙伴可以自己想想怎么去实现
提示:在签到统计中,每个用户每天的签到用 1 个 bit 位表示,一年的签到只需要 365 个 bit 位,一个用户一个key
写作不易,刚好你看到,刚好对你有帮助,动动小手,点点赞,欢迎转发,有疑问的欢迎留言或者私信讨论,有问必回。