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万个重复数据的独立总数.
    @Test
    public 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组数据合并,再统计合并后的重复数据的独立总数

    @Test
    public 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);
    }

统计一组数据的布尔值

    @Test
    public 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() {
            @Override
            public Object doInRedis(RedisConnection connection) throws DataAccessException {
                return connection.bitCount(redisKey.getBytes());
            }
        });

        System.out.println(obj);
    }

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

    @Test
    public 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() {
            @Override
            public 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";

    //单日UV
    public static String getUVKey(String date) {
        return PREFIX_UV + SPLIT + date;
    }

    //区间UV
    public 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 {

    @Autowired
    private RedisTemplate redisTemplate;

    //在统计的时候,需要使用到日期(格式化成年月日的形式),实例化一个 SimpleDateFormat
    private SimpleDateFormat df = new SimpleDateFormat("yyyyMMdd");

    // 将指定的IP计入UV
    public void recordUV(String ip) {
        //得到 key记录到 Redis 中
        String redisKey = RedisKeyUtil.getUVKey(df.format(new Date()));
        redisTemplate.opsForHyperLogLog().add(redisKey, ip);
    }

    // 统计指定日期范围内的UV
    public 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);
    }

    // 将指定用户计入DAU
    public void recordDAU(int userId) {
        String redisKey = RedisKeyUtil.getDAUKey(df.format(new Date()));
        redisTemplate.opsForValue().setBit(redisKey, userId, true);
    }

    // 统计指定日期范围内的DAU
    public long calculateDAU(Date start, Date end) {
        if (start == null || end == null) {
            throw new IllegalArgumentException("参数不能为空!");
        }

        // 整理该日期范围内的key
        List<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() {
            @Override
            public 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 {

    @Autowired
    private DataService dataService;

    @Autowired
    private HostHolder hostHolder;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 统计UV
        String ip = request.getRemoteHost();
        dataService.recordUV(ip);

        // 统计DAU
        User user = hostHolder.getUser();
        if (user != null) {
            dataService.recordDAU(user.getId());
        }

        return true;
    }
}

在 WebMvcConfig 类中设置拦截器:

    @Autowired
    private 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 {

    @Autowired
    private 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

​​​​​​​

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/272783.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PYTHON基础:最小二乘法

最小二乘法的拟合 最小二乘法是一种常用的统计学方法&#xff0c;用于通过在数据点中找到一条直线或曲线&#xff0c;使得这条直线或曲线与所有数据点的距离平方和最小化。在线性回归中&#xff0c;最小二乘法被广泛应用于拟合一条直线与数据点之间的关系。 对于线性回归&…

华为——使用ACL限制内网主机访问外网网站示例

组网图形 图1 使用ACL限制内网主机访问外网网站示例 ACL简介配置注意事项组网需求配置思路操作步骤配置文件 ACL简介 访问控制列表ACL&#xff08;Access Control List&#xff09;是由一条或多条规则组成的集合。所谓规则&#xff0c;是指描述报文匹配条件的判断语句&#…

RFID技术在汽车制造:提高生产效率、优化物流管理和增强安全性

RFID技术在汽车制造:提高生产效率、优化物流管理和增强安全性 随着科技的进步&#xff0c;物联网技术已经深入到各个领域&#xff0c;尤其在制造业中&#xff0c;RFID技术以其独特的优势&#xff0c;如高精度追踪、实时数据收集和自动化操作&#xff0c;正在改变传统的生产方式…

FonePaw iOS Transfer for Mac: 让您的IOS设备数据无忧传输

在数字世界里&#xff0c;随着我们的生活与科技越来越紧密&#xff0c;数据传输成为了我们日常生活中的重要部分。尤其对于广大的苹果用户来说&#xff0c;如何方便、快速地传输数据成为了他们关注的焦点。今天&#xff0c;我要为大家介绍一款专门为Mac用户设计的IOS数据传输工…

云原生Kubernetes:K8S集群实现容器运行时迁移(docker → containerd) 与 版本升级(v1.23.14 → v1.24.1)

目录 一、理论 1.K8S集群升级 2.环境 3.升级策略 4.master1节点迁移容器运行时(docker → containerd) 5.master2节点迁移容器运行时(docker → containerd) 6.node1节点容器运行时迁移(docker → containerd) 7.升级集群计划&#xff08;v1.23.14 → v1.24.1&#…

macos Apple开发证书 应用签名p12证书 获取生成方法 codesign 证书获取

在开发macos应用的时候必须要对自己开发的应用进行签名才能使用, 下面介绍个人如何获取Apple开发签名证书. 必备条件, 你需要先安装 xcode , 注册一个苹果开发者账号 免费的就可以, 以下为获取流程 You need to create a cert through xcode. Additionally, you need to have…

Jmeter之从CSV文件获取数据

新建csv文件 新建一个excel&#xff0c;填充业务数据&#xff0c;然后导出csv格式文件。 添加一个CSV数据文件 使用

苏州科技大学计算机817程序设计(java) 学习笔记

之前备考苏州科技大学计算机&#xff08;专业课&#xff1a;817程序设计&#xff08;java&#xff09;&#xff09;。 学习Java和算法相关内容&#xff0c;现将笔记及资料统一整理归纳移至这里。 部分内容不太完善&#xff0c;欢迎提议。 目录 考情分析 考卷题型 刷题攻略…

Linux中Mysql数据库备份操作

逻辑备份 备份的是建表、建库、插入等操作所执行SQL语句&#xff0c;适用于中小型数据库&#xff0c;效率相对较低。 本质&#xff1a;导出的是SQL语句文件 优点&#xff1a;不论是什么存储引擎&#xff0c;都可以用mysqldump备成SQL语句 缺点&#xff1a;速度较慢&#xff0c;…

人工智能_机器学习078_聚类算法_概念介绍_聚类升维_降维_各类聚类算法_有监督机器学习_无监督机器学习---人工智能工作笔记0118

首先看一下什么是聚类,我们可以进入sklearn的官网去看看 可以看到这里,首先classification 这个分类我们学完了,然后就是regression回归我们也学完了对吧,其实我们现实生活中的,大部分问题就是 这两种问题就可以解决了. 然后我们再来看一个: clustering,这个就是聚类对吧.聚类算…

万界星空科技生产管理MES系统中的工时管理

工时管理的重大意义 1.提高生产效率 通过工时管理&#xff0c;企业可以更加精确地掌握研发人员的工时情况&#xff0c;及时调整项目进度和人力安排&#xff0c;提高生产效率。 2.降低人力成本 通过工时管理&#xff0c;企业可以更加精确地核算研发人员的工时费用&#xff0c…

KEPServerEX 6 之【外篇-1】PTC-ThingWorx服务端软件安装 Tomcat10本地安装

本文目标: 安装 Java 和 Apache Tomcat ,为ThingWorx安装做基础。 ----------------------------------------------------------------------- 安装重点 --------------------------------------------------------------------- 1. 安装 Java 11 / JDK 11 添加系…

Seem环境安装

创建虚拟环境 conda create -n seem python3.8 conda activate seem 安装相关依赖&#xff1a;&#xff08;不按照的话会报错&#xff09; sudo apt-get install openmpi-bin libopenmpi-devconda install gcc_linux-64pip install mpi4py 导入环境 export PYTHONPATH$(pwd…

在k8s中使用cert-manager部署gitlab集群

写在前面的话&#xff1a;前面有详细的分享过在k8s集群中部署gitlab&#xff0c;不过当时使用gitlab的访问证书是阿里云上免费的ssl证书&#xff0c;今天特意专门介绍下另外一种基于cert-manager发布自签证书的方式实现部署gitlab到k8s集群中。 往期gitlab部署系列如&#xff1…

文献速递:生成对抗网络医学影像中的应用——3DGAUnet:一种带有基于3D U-Net的生成器的3D生成对抗网络

文献速递&#xff1a;生成对抗网络医学影像中的应用——3DGAUnet&#xff1a;一种带有基于3D U-Net的生成器的3D生成对抗网络 给大家分享文献的主题是生成对抗网络&#xff08;Generative adversarial networks, GANs&#xff09;在医学影像中的应用。文献的研究内容包括同模态…

Elasticsearch 查询命令执行时,如何通过词项索引、词项字典、倒排表定位文档逻辑介绍

这里不涉及到源码&#xff0c;只是根据网上的一些文章总结一下&#xff0c;目前不需要细究&#xff0c;只需要知道大概就好&#xff0c;除非你的工作是二次开发ES 一、​Term Index(词项索引)1、FSM&#xff08;Finite State Machine&#xff09;有限状态机2、FSA&#xff08;F…

家政上门服务系统|上门服务系统优化您的生活质量

家政上门服务软件的开发为用户与家政服务供应商之间的互动提供了一个便捷的平台。通过这个软件&#xff0c;用户可以随时随地选择自己所需的服务&#xff0c;比如家庭清洁、保姆、月嫂、老人陪护等。而家政服务供应商则能够接收并响应用户的需求&#xff0c;从而提供一对一的服…

鸿蒙操作系统:从手机到物联网,打造全场景智能体验

随着科技的不断发展&#xff0c;人们对于操作系统的需求也在不断升级。鸿蒙操作系统&#xff0c;作为华为推出的新一代智能终端操作系统&#xff0c;凭借其强大的分布式能力、流畅的用户体验以及丰富的应用生态&#xff0c;正逐渐成为人们关注的焦点。 一、鸿蒙操作系统概述 …

Zookeeper在分布式命名服务中的实践

Java学习面试指南&#xff1a;https://javaxiaobear.cn 命名服务是为系统中的资源提供标识能力。ZooKeeper的命名服务主要是利用ZooKeeper节点的树形分层结构和子节点的顺序维护能力&#xff0c;来为分布式系统中的资源命名。 哪些应用场景需要用到分布式命名服务呢&#xff1…

什么是工厂方法模式,工厂方法模式解决了什么问题?

工厂方法模式是一种创建型设计模式&#xff0c;它定义了一个用于创建对象的接口&#xff0c;但将实际的实例化过程延迟到子类中。这样&#xff0c;客户端代码在不同的子类中实例化具体对象&#xff0c;而不是直接实例化具体类。工厂方法模式允许一个类的实例化延迟到其子类&…
最新文章