当前位置: 首页 > news >正文

16.Redis 高级数据类型 + 网站数据统计

目录

1.Redis 高级数据类型

2.网站数据统计

2.1 业务层

2.2 表现层

2.2.1 记录数据

2.2.2 查看数据


1.Redis 高级数据类型

HyperLogLog:采用一种基数算法,用于完成独立总数的统计;占据空间小,无论统计多少个数据,只占12K的内存空间;不精确的统计算法,标准误差为 0.81%

Bitmap:不是一种独立的数据结构,实际上就是字符串;支持按位存取数据,可以将其看成是 byte 数组;适合存储索大量的连续的数据的布尔值

统计 20万个重复数据的独立总数

    // 统计20万个重复数据的独立总数.@Testpublic void testHyperLogLog() {String redisKey = "test:hll:01";for (int i = 1; i <= 100000; i++) {redisTemplate.opsForHyperLogLog().add(redisKey, i);}//再次循环 10万次for (int i = 1; i <= 100000; i++) {int r = (int) (Math.random() * 100000 + 1);redisTemplate.opsForHyperLogLog().add(redisKey, r);}long size = redisTemplate.opsForHyperLogLog().size(redisKey);//统计去重数据的数量System.out.println(size);}

将3组数据合并,再统计合并后的重复数据的独立总数

    @Testpublic void testHyperLogLogUnion() {String redisKey2 = "test:hll:02";for (int i = 1; i <= 10000; i++) {redisTemplate.opsForHyperLogLog().add(redisKey2, i);}String redisKey3 = "test:hll:03";for (int i = 5001; i <= 15000; i++) {redisTemplate.opsForHyperLogLog().add(redisKey3, i);}String redisKey4 = "test:hll:04";for (int i = 10001; i <= 20000; i++) {redisTemplate.opsForHyperLogLog().add(redisKey4, i);}String unionKey = "test:hll:union";redisTemplate.opsForHyperLogLog().union(unionKey, redisKey2, redisKey3, redisKey4);long size = redisTemplate.opsForHyperLogLog().size(unionKey);System.out.println(size);}

统计一组数据的布尔值

    @Testpublic void testBitMap() {String redisKey = "test:bm:01";// 记录redisTemplate.opsForValue().setBit(redisKey, 1, true);redisTemplate.opsForValue().setBit(redisKey, 4, true);redisTemplate.opsForValue().setBit(redisKey, 7, true);// 查询System.out.println(redisTemplate.opsForValue().getBit(redisKey, 0));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 1));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 2));// 统计Object obj = redisTemplate.execute(new RedisCallback() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {return connection.bitCount(redisKey.getBytes());}});System.out.println(obj);}

统计3组数据的布尔值, 并对这3组数据做OR运算

    @Testpublic void testBitMapOperation() {String redisKey2 = "test:bm:02";redisTemplate.opsForValue().setBit(redisKey2, 0, true);redisTemplate.opsForValue().setBit(redisKey2, 1, true);redisTemplate.opsForValue().setBit(redisKey2, 2, true);String redisKey3 = "test:bm:03";redisTemplate.opsForValue().setBit(redisKey3, 2, true);redisTemplate.opsForValue().setBit(redisKey3, 3, true);redisTemplate.opsForValue().setBit(redisKey3, 4, true);String redisKey4 = "test:bm:04";redisTemplate.opsForValue().setBit(redisKey4, 4, true);redisTemplate.opsForValue().setBit(redisKey4, 5, true);redisTemplate.opsForValue().setBit(redisKey4, 6, true);String redisKey = "test:bm:or";Object obj = redisTemplate.execute(new RedisCallback() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {connection.bitOp(RedisStringCommands.BitOperation.OR,redisKey.getBytes(), redisKey2.getBytes(), redisKey3.getBytes(), redisKey4.getBytes());return connection.bitCount(redisKey.getBytes());}});System.out.println(obj);System.out.println(redisTemplate.opsForValue().getBit(redisKey, 0));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 1));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 2));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 3));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 4));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 5));System.out.println(redisTemplate.opsForValue().getBit(redisKey, 6));}

2.网站数据统计

  • UV(Unique Visitor):独立访问,需要通过用户 IP 排重统计数据;每次访问都要进行统计;HyperLogLog 性能好,且存储空间小
  • DAU(Daily Active User):日活跃用户,需要通过用户 ID 排重统计数据;访问过一次,则认为其活跃;Bitmap 性能好且可以统计精确的结果

使用 Redis,定义 RedisKey,打开 RedisKeyUtil 类添加

  • 添加两个前缀:uv、dau
  • 添加方法:获取单日uv、传入日期字符串,返回 前缀 + 分隔符 + 日期
  • 添加方法:获取区间uv(从哪天到哪天),传入开始日期,结束日期,返回 前缀 + 分隔符 + 开始日期 + 分隔符 + 结束日期
  • 添加方法:获取单日活跃用户,传入日期,返回 前缀 + 分隔符 + 日期
  • 添加方法:获取区间活跃用户,传入日期,返回 前缀 + 分隔符 + 开始日期 + 分隔符 + 结束日期
    //UV(Unique Visitor):独立访问private static final String PREFIX_UV = "uv";//DAU(Daily Active User):日活跃用户private static final String PREFIX_DAU = "dau";//单日UVpublic static String getUVKey(String date) {return PREFIX_UV + SPLIT + date;}//区间UVpublic static String getUVKey(String startDate, String endDate) {return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;}// 单日活跃用户public static String getDAUKey(String date) {return PREFIX_DAU + SPLIT + date;}// 区间活跃用户public static String getDAUKey(String startDate, String endDate) {return PREFIX_DAU + SPLIT + startDate + SPLIT + endDate;}

2.1 业务层

在 service 包下新建 DataService 类:

  • 注入 RedisTemplate
  • 在统计的时候,需要使用到日期(格式化成年月日的形式),实例化一个 SimpleDateFormat
  • 统计数据:首先记录数据,在每次请求当中截获请求,把相关数据记录到 Redis 中;其次,在查看的时候提供一个查询的方法
  • 处理 UV 的统计:构造方法,将指定的 IP 计入 UV(传入 IP)——得到 key记录到 Redis 中
  • 构造方法,统计指定的日期范围内的 UV:传入(开始日期、结束日期),把范围内每一天的 key 做一个合并得到某一组的 key,封装成集合;遍历日期,需要对日期做运算,实例化 Calender,包含开始日期做遍历。遍历完成之后合并数据并且返回统计的结果
  • 将指定用户计入 DAU:首先得到 key,传入当前时间,然后存入 Redis 中
  • 统计指定日期范围内的 DAU:同理上述(日期范围内每一天的 DAU 之间做 or运算:假设统计今天的活跃用户,只需要今天访问就代表活跃;假设以一周为单位,则这一周任意一次访问即活跃)
package com.example.demo.service;import com.example.demo.util.RedisKeyUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;/*** 网站数据统计:UV、DAU*/
@Service
public class DataService {@Autowiredprivate RedisTemplate redisTemplate;//在统计的时候,需要使用到日期(格式化成年月日的形式),实例化一个 SimpleDateFormatprivate SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");// 将指定的IP计入UVpublic void recordUV(String ip) {//得到 key记录到 Redis 中String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));redisTemplate.opsForHyperLogLog().add(redisKey, ip);}// 统计指定日期范围内的UVpublic long calculateUV(Date start, Date end) {if (start == null || end == null) {throw new IllegalArgumentException("参数不能为空!");}// 整理该日期范围内的key//把范围内每一天的 key 做一个合并得到某一组的 key,封装成集合;// 遍历日期,需要对日期做运算,实例化Calender,包含开始日期做遍历。遍历完成之后合并数据并且返回统计的结果List<String> keyList = new ArrayList<>();Calendar calendar = Calendar.getInstance();calendar.setTime(start);while (!calendar.getTime().after(end)) {String key = RedisKeyUtil.getUVKey(df.format(calendar.getTime()));keyList.add(key);calendar.add(Calendar.DATE, 1);}// 合并这些数据String redisKey = RedisKeyUtil.getUVKey(df.format(start), df.format(end));redisTemplate.opsForHyperLogLog().union(redisKey, keyList.toArray());// 返回统计的结果return redisTemplate.opsForHyperLogLog().size(redisKey);}// 将指定用户计入DAUpublic void recordDAU(int userId) {String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));redisTemplate.opsForValue().setBit(redisKey, userId, true);}// 统计指定日期范围内的DAUpublic long calculateDAU(Date start, Date end) {if (start == null || end == null) {throw new IllegalArgumentException("参数不能为空!");}// 整理该日期范围内的keyList<byte[]> keyList = new ArrayList<>();Calendar calendar = Calendar.getInstance();calendar.setTime(start);while (!calendar.getTime().after(end)) {String key = RedisKeyUtil.getDAUKey(df.format(calendar.getTime()));keyList.add(key.getBytes());calendar.add(Calendar.DATE, 1);}// 进行OR运算return (long) redisTemplate.execute(new RedisCallback() {@Overridepublic Object doInRedis(RedisConnection connection) throws DataAccessException {String redisKey = RedisKeyUtil.getDAUKey(df.format(start), df.format(end));connection.bitOp(RedisStringCommands.BitOperation.OR,redisKey.getBytes(), keyList.toArray(new byte[0][0]));return connection.bitCount(redisKey.getBytes());}});}}

2.2 表现层

什么时候记录数据(拦截器)、查看数据

2.2.1 记录数据

在 controller 包下的 interceptor 包下新建 DataInterceptor 类

  • 实现 HandlerInterceptor 接口
  • 记录 UV、DAU 需要注入 DataService
  • 活跃用户需要注入 HostHolder
  • 在请求初期机型统计,重写 perHandle
package com.example.demo.controller.interceptor;import com.example.demo.entity.User;
import com.example.demo.service.DataService;
import com.example.demo.util.HostHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;@Component
public class DataInterceptor implements HandlerInterceptor {@Autowiredprivate DataService dataService;@Autowiredprivate HostHolder hostHolder;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 统计UVString ip = request.getRemoteHost();dataService.recordUV(ip);// 统计DAUUser user = hostHolder.getUser();if (user != null) {dataService.recordDAU(user.getId());}return true;}
}

在 WebMvcConfig 类中设置拦截器:

    @Autowiredprivate MessageInterceptor messageInterceptor;registry.addInterceptor(dataInterceptor).excludePathPatterns("/**/*.css", "/**/*.js", "/**/*.png", "/**/*.jpg", "/**/*.jpeg");

2.2.2 查看数据

在 controller 类包下新建 DataController 类:

  • 添加三个方法:访问统计页面、统计网站 UV、统计活跃用户
  • 访问统计页面:添加访问路径,方法中需要返回模板路径
  • 统计网站 UV:添加访问路径(提交两个日期按钮相当于提交表单,是一个 POST 请求),传入开始、结束日期以及模板,使用注解@DateTimeFormat(pattern = "yyyy-MM-dd"),设置日期格式。统计结果返回给模板的时候,网站 UV保留开始和结束的年月日格式,最后返回到模板
  • 统计活跃用户:同理
package com.example.demo.controller;import com.example.demo.service.DataService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;import java.util.Date;/*** 网站数据统计:UV、DAU*/
@Controller
public class DataController {@Autowiredprivate DataService dataService;// 统计页面@RequestMapping(path = "/data", method = {RequestMethod.GET, RequestMethod.POST})public String getDataPage() {return "/site/admin/data";}// 统计网站UV@RequestMapping(path = "/data/uv", method = RequestMethod.POST)public String getUV(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,@DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) {long uv = dataService.calculateUV(start, end);model.addAttribute("uvResult", uv);model.addAttribute("uvStartDate", start);model.addAttribute("uvEndDate", end);return "forward:/data";}// 统计活跃用户@RequestMapping(path = "/data/dau", method = RequestMethod.POST)public String getDAU(@DateTimeFormat(pattern = "yyyy-MM-dd") Date start,@DateTimeFormat(pattern = "yyyy-MM-dd") Date end, Model model) {long dau = dataService.calculateDAU(start, end);model.addAttribute("dauResult", dau);model.addAttribute("dauStartDate", start);model.addAttribute("dauEndDate", end);return "forward:/data";}}

最后处理 data.html

​​​​​​​

相关文章:

  • 关于“Python”的核心知识点整理大全34
  • 交通流预测 | Matlab基于KNN-BiLSTM的交通流预测(对比SVR、LSTM、GRU、KNN-LSTM)
  • 什么时候用多线程、为什么要设计多线程?
  • JVM高频面试题(2023最新版)
  • rocky linux9 安装go 即接下去
  • 单元测试实战
  • devops使用
  • 为实体服务器配置Ubuntu
  • 【IO】IO模型与零拷贝
  • html table+css实现可编辑表格
  • 理解SpringMVC的工作流程
  • 【宇宙猜想】AR文创入驻今日美术馆、北京天文馆等众多展馆,在AR互动中感受科技魅力!
  • 软件工程快速复习(期末急救)
  • 国内前十大连锁酒店集团之一『东呈集团』商城项目启动,企企通赋能酒店管理集团采购数字化
  • 软件工程期末复习
  • Docker 笔记(2):Dockerfile
  • java 多线程基础, 我觉得还是有必要看看的
  • Java 内存分配及垃圾回收机制初探
  • Java|序列化异常StreamCorruptedException的解决方法
  • LintCode 31. partitionArray 数组划分
  • Linux学习笔记6-使用fdisk进行磁盘管理
  • OpenStack安装流程(juno版)- 添加网络服务(neutron)- controller节点
  • OSS Web直传 (文件图片)
  • overflow: hidden IE7无效
  • PermissionScope Swift4 兼容问题
  • swift基础之_对象 实例方法 对象方法。
  • Webpack4 学习笔记 - 01:webpack的安装和简单配置
  • 阿里云应用高可用服务公测发布
  • 翻译 | 老司机带你秒懂内存管理 - 第一部(共三部)
  • 聊聊redis的数据结构的应用
  • 实战|智能家居行业移动应用性能分析
  • 双管齐下,VMware的容器新战略
  • 一起参Ember.js讨论、问答社区。
  • 异步
  • 译有关态射的一切
  • d²y/dx²; 偏导数问题 请问f1 f2是什么意思
  • 3月27日云栖精选夜读 | 从 “城市大脑”实践,瞭望未来城市源起 ...
  • 第二十章:异步和文件I/O.(二十三)
  • 好程序员web前端教程分享CSS不同元素margin的计算 ...
  • ​ssh-keyscan命令--Linux命令应用大词典729个命令解读
  • #include<初见C语言之指针(5)>
  • #includecmath
  • #LLM入门|Prompt#1.7_文本拓展_Expanding
  • #Lua:Lua调用C++生成的DLL库
  • (11)工业界推荐系统-小红书推荐场景及内部实践【粗排三塔模型】
  • (2)MFC+openGL单文档框架glFrame
  • (26)4.7 字符函数和字符串函数
  • (Java)【深基9.例1】选举学生会
  • (Matlab)基于蝙蝠算法实现电力系统经济调度
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (原創) X61用戶,小心你的上蓋!! (NB) (ThinkPad) (X61)
  • (转)Android学习系列(31)--App自动化之使用Ant编译项目多渠道打包
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • .bat批处理(四):路径相关%cd%和%~dp0的区别