Redis进阶——BitMap用户签到HyperLogLog实现UV统计

2024-04-20 10:44

本文主要是介绍Redis进阶——BitMap用户签到HyperLogLog实现UV统计,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 用户签到
    • 实现签到功能
  • 签到统计
  • HyperLogLog实现UV统计
    • UV和PV的概述
    • 测试百万数据的统计

用户签到

BitMap功能演示
我们针对签到功能完全可以通过MySQL来完成,例如下面这张表
20240420-070809-7a.png

用户签到一次,就是一条记录,假如有1000W用户,平均每人每年签到10次,那这张表一年的数据量就有1亿条

那有没有方法能简化一点呢?我们可以使用二进制位来记录每个月的签到情况,签到记录为1,未签到记录为0

把每一个bit位对应当月的每一天,形成映射关系,用0和1标识业务状态,这种思路就成为位图(BitMap)。这样我们就能用极小的空间,来实现大量数据的表示

Redis中是利用String类型数据结构实现BitMap,因此最大上限是512M,转换为bit则是2^32个bit位

BitMap的操作命令有:

SETBIT:向指定位置(offset)存入一个0或1
GETBIT:获取指定位置(offset)的bit值
BITCOUNT:统计BitMap中值为1的bit位的数量
BITFIELD:操作(查询、修改、自增)BitMap中bit数组中的指定位置(offset)的值
BITFIELD_RO:获取BitMap中bit数组,并以十进制形式返回
BITOP:将多个BitMap的结果做位运算(与、或、异或)
BITPOS:查找bit数组中指定范围内第一个0或1出现的位置

实现签到功能

需求:实现签到接口,将当前用户当天签到信息保存到Redis中

请求方式 Post
请求路径 /user/sign
请求参数 无
返回值 无

思路:我们可以把年和月作为BitMap的key,然后保存到一个BitMap中,每次签到就把对应位上的0变成1,只要是1就说明这一天已经签到了,反之则没有签到
由于BitMap底层是基于String数据结构,因此其操作也都封装在字符串相关操作中了
20240420-071557-oR.png

在UserController中编写对应的方法

@PostMapping("/sign")
public Result sign(){return userService.sign();
}

具体实现

@Override
public Result sign() {//1. 获取当前用户Long userId = UserHolder.getUser().getId();//2. 获取日期LocalDateTime now = LocalDateTime.now();//3. 拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;//4. 获取今天是当月第几天(1~31)int dayOfMonth = now.getDayOfMonth();//5. 写入Redis  BITSET key offset 1stringRedisTemplate.opsForValue().setBit(key, dayOfMonth - 1, true);return Result.ok();
}

通过直接使用登录用户信息,借助Redis的BitMap进行简单的签到记录。

签到统计

如何获取本月到今天为止的所有签到数据?

BITFIELD key GET u[dayOfMonth] 0

如何从后往前遍历每个bit位,获取连续签到天数
连续签到天数,就是从末尾往前数,看有多少个1
简单的位运算算法

int count = 0;
while(true) {if((num & 1) == 0)break;elsecount++;num >>>= 1;
}
return count;

需求:实现下面接口,统计当前用户截止当前时间在本月的连续签到天数

请求方式 GET
请求路径 /user/sign/count
请求参数 无
返回值 连续签到天数

代码实现:
在UserController中创建对应的方法

@GetMapping("/sign/count")
public Result signCount(){return userService.signCount();
}

在UserServiceImpl中实现方法

@Override
public Result signCount() {//1. 获取当前用户Long userId = UserHolder.getUser().getId();//2. 获取日期LocalDateTime now = LocalDateTime.now();//3. 拼接keyString keySuffix = now.format(DateTimeFormatter.ofPattern(":yyyyMM"));String key = USER_SIGN_KEY + userId + keySuffix;//4. 获取今天是当月第几天(1~31)int dayOfMonth = now.getDayOfMonth();//5. 获取截止至今日的签到记录  BITFIELD key GET uDay 0List<Long> result = stringRedisTemplate.opsForValue().bitField(key, BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.unsigned(dayOfMonth)).valueAt(0));if (result == null || result.isEmpty()) {return Result.ok(0);}//6. 循环遍历int count = 0;Long num = result.get(0);while (true) {if ((num & 1) == 0) {break;} elsecount++;//数字右移,抛弃最后一位num >>>= 1;}return Result.ok(count);
}

上述通过Redis的BitMap数据结构,加上简单的与运算,计算出来到今天为止连续签到的次数,计算的逻辑是使用计数器统计从末尾往前数,看有多少个1,判断1的依据是使用与运算。

HyperLogLog实现UV统计

UV和PV的概述

UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。
PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量。

通常来说PV会比UV大很多,所以衡量同一个网站的访问量,我们需要综合考虑很多因素。
UV统计在服务端做会很麻烦,因为要判断该用户是否已经统计过了,需要将统计过的信息保存,但是如果每个访问的用户都保存到Redis中,那么数据库会非常恐怖,那么该如何处理呢?

HyperLogLog(HLL)是从Loglog算法派生的概率算法,用户确定非常大的集合基数,而不需要存储其所有值,算法相关原理可以参考下面这篇文章:https://juejin.cn/post/6844903785744056333#heading-0

Redis中的HLL是基于string结构实现的,单个HLL的内存永远小于16kb,内存占用低的令人发指!作为代价,其测量结果是概率性的,有小于0.81%的误差。不过对于UV统计来说,这完全可以忽略。
常用的三个方法

PFADD key element [element...]
summary: Adds the specified elements to the specified HyperLogLogPFCOUNT key [key ...]
Return the approximated cardinality of the set(s) observed by the HyperLogLog at key(s).PFMERGE destkey sourcekey [sourcekey ...]
lnternal commands for debugging HyperLogLog values

测试百万数据的统计

使用单元测试,向HyperLogLog中添加100万条数据,看看内存占用是否真的那么低,以及统计误差如何

@Test
public void testHyperLogLog() {String[] users = new String[1000];int j = 0;for (int i = 0; i < 1000000; i++) {j = i % 1000;users[j] = "user_" + i;if (j == 999) {stringRedisTemplate.opsForHyperLogLog().add("HLL", users);}}Long count = stringRedisTemplate.opsForHyperLogLog().size("HLL");System.out.println("count = " + count);
}

执行结果:
20240420-072658-02.png

插入100W条数据,得到的count为997593,误差率为0.002407%
去Redis图形化界面中查看占用情况为:12.3K字节

这篇关于Redis进阶——BitMap用户签到HyperLogLog实现UV统计的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/920042

相关文章

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

shell脚本批量导出redis key-value方式

《shell脚本批量导出rediskey-value方式》为避免keys全量扫描导致Redis卡顿,可先通过dump.rdb备份文件在本地恢复,再使用scan命令渐进导出key-value,通过CN... 目录1 背景2 详细步骤2.1 本地docker启动Redis2.2 shell批量导出脚本3 附录总

批量导入txt数据到的redis过程

《批量导入txt数据到的redis过程》用户通过将Redis命令逐行写入txt文件,利用管道模式运行客户端,成功执行批量删除以Product*匹配的Key操作,提高了数据清理效率... 目录批量导入txt数据到Redisjs把redis命令按一条 一行写到txt中管道命令运行redis客户端成功了批量删除k

分布式锁在Spring Boot应用中的实现过程

《分布式锁在SpringBoot应用中的实现过程》文章介绍在SpringBoot中通过自定义Lock注解、LockAspect切面和RedisLockUtils工具类实现分布式锁,确保多实例并发操作... 目录Lock注解LockASPect切面RedisLockUtils工具类总结在现代微服务架构中,分布

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

Redis客户端连接机制的实现方案

《Redis客户端连接机制的实现方案》本文主要介绍了Redis客户端连接机制的实现方案,包括事件驱动模型、非阻塞I/O处理、连接池应用及配置优化,具有一定的参考价值,感兴趣的可以了解一下... 目录1. Redis连接模型概述2. 连接建立过程详解2.1 连php接初始化流程2.2 关键配置参数3. 最大连

Python实现网格交易策略的过程

《Python实现网格交易策略的过程》本文讲解Python网格交易策略,利用ccxt获取加密货币数据及backtrader回测,通过设定网格节点,低买高卖获利,适合震荡行情,下面跟我一起看看我们的第一... 网格交易是一种经典的量化交易策略,其核心思想是在价格上下预设多个“网格”,当价格触发特定网格时执行买

SQL Server跟踪自动统计信息更新实战指南

《SQLServer跟踪自动统计信息更新实战指南》本文详解SQLServer自动统计信息更新的跟踪方法,推荐使用扩展事件实时捕获更新操作及详细信息,同时结合系统视图快速检查统计信息状态,重点强调修... 目录SQL Server 如何跟踪自动统计信息更新:深入解析与实战指南 核心跟踪方法1️⃣ 利用系统目录

python设置环境变量路径实现过程

《python设置环境变量路径实现过程》本文介绍设置Python路径的多种方法:临时设置(Windows用`set`,Linux/macOS用`export`)、永久设置(系统属性或shell配置文件... 目录设置python路径的方法临时设置环境变量(适用于当前会话)永久设置环境变量(Windows系统