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

(黑马点评)八、实现签到统计和uv统计

8.1 签到统计系列功能

8.1.1 认识BitMap结构

        BitMap是Redis基于String实现的一种高效的二进制数位的数据结构。因此一个BItMap的最大上线为512M,转为bit位可表示 2^32位

常见命令

SETBIT:向指定位置(offset)存入一个01

GETBIT :获取指定位置(offset)的bit

BITCOUNT :统计BitMap中值为1bit位的数量

BITFIELD :操作(查询、修改、自增)BitMapbit数组中的指定位置(offset)的值

BITFIELD_RO :获取BitMapbit数组,并以十进制形式返回

BITOP :将多个BitMap的结果做位运算(与 、或、异或)

BITPOS :查找bit数组中指定范围内第一个01出现的位置

 8.1.2 为什么要使用BitMap结构进行统计

传统的数据库表统计方式:十分耗费存储内存

采用二进制位的BitMap统计方式:高效、内存占用少、

key用来指定统计者的信息:姓名、编号、年月

vlaue采用二进制的0 | 1 串标识签到状况 ,0表示为未签到 1 表示为已签到

8.1.3 使用BitMap实现用户签到统计

按月来统计用户签到信息,签到记录为1,未签到则记录为0.

签到功能实现流程

1. 获取当前用户信息

2. 获取当前日期信息

3. 拼接业务key

4. 使用setBit(key,offset,value)实现签到打卡

    /**
     * 签到统计
     * @return
     */
    @PostMapping("/sign")
    public Result sign(){
        return userService.sign();
    }


/**
     * 签到功能
     * @return
     */
    @Override
    public Result sign() {
        //1. 获取当前的登陆用户
        UserDTO user = UserHolder.getUser();
        //2. 获取当前日期
        LocalDateTime now = LocalDateTime.now();
        //3. 拼接业务的key
        String keySuffix = now.format(DateTimeFormatter.ofPattern("yyyy/MM"));
        String key =
                USER_SIGN_KEY +
                        user.getNickName() +
                        '_' + user.getId() +
                        '_' + keySuffix;
        //4. 获取签到日期
        int day = now.getDayOfMonth();
        //5. 写入Redis SETBIT key offset ture/false
        stringRedisTemplate.opsForValue().setBit(key, day-1, true);


        return Result.ok();
    }
直接发送签到请求

查看Redis写入的签到卡

 

8.1.4 使用BitMap实现用户连续签到天数统计

实现统计连续签到天数的流程

1. 获取当前登录用户信息

2. 获取日期信息

3. 拼接业务key

4. 使用 stringRedisTemplate.opsForValue()
                .bitField(key,
                        BitFieldSubCommands
                                .create()
                                .get(BitFieldSubCommands
                                        .BitFieldType
                                        .unsigned(dayOfMonth))
                                .valueAt(0)); 获取统计信息【10进制】

5. 判空

6. 循环右移 + 与1 运算 得出连续签到天数

7. 返回结果

/**
     * 连续签到天数统计
     * @return
     */
    @GetMapping("/sign/count")
    public Result countSign(){
        return userService.countSign();
    }


/**
     * 连续签到天数统计
     * @return
     */
    @Override
    public Result countSign() {
        //1. 获取当前登录用户
        UserDTO user = UserHolder.getUser();
        //2. 获取当前日期
        LocalDateTime now = LocalDateTime.now();
        int dayOfMonth = now.getDayOfMonth();
        //3.拼接业务的key
        String keySuffix = now.format(DateTimeFormatter.ofPattern("yyyy/MM"));
        String key = USER_SIGN_KEY +
                    user.getNickName() +
                    '_' + user.getId() +
                    '_' + keySuffix;
        //4. 获取当前用户签到数据(返回十进制数)
        List<Long> signNum = stringRedisTemplate.opsForValue()
                .bitField(key,
                        BitFieldSubCommands
                                .create()
                                .get(BitFieldSubCommands
                                        .BitFieldType
                                        .unsigned(dayOfMonth))
                                .valueAt(0));
        if(signNum == null || signNum.isEmpty()){
            return Result.ok(0);
        }
        Long num = signNum.get(0);
        if(num == null || num == 0){
            return Result.ok(0);
        }

        //5. 循环右移 + 1与运算 得出连续签到天数
        int dayCount = 0;
        while(true) {
            if(((num & 1) == 0)){
                break;
            }else{
                dayCount ++;
            }
            num = num >> 1;
        }
        return Result.ok(dayCount);
    }
发送连续签到统计请求

 

8.2 百万级UV统计功能实现

8.2.1 百万级UV统计的实现策略——HyperLogLog

什么是UV统计

UV:全称Unique Visitor,也叫独立访客量,是指通过互联网访问、浏览这个网页的自然人。1天内同一个用户多次访问该网站,只记录1次。

什么是PV统计
PV:全称Page View,也叫页面访问量或点击量,用户每访问网站的一个页面,记录1次PV,用户多次打开页面,则记录多次PV。往往用来衡量网站的流量
 

传统方式实现UV统计面对的难题

UV统计在服务端做会比较麻烦,因为要判断该用户是否已经统计过了,需要将统计过的用户信息保存。但是如果每个访问的用户都保存到Redis中,数据量会非常恐怖。

高效的解决措施——使用HyperLogLog

        Hyperloglog(HLL)是从Loglog算法派生的概率算法,用于确定非常大的集合的基数,而不需要存储其所有值。

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

HyperLogLog的原理

哈希处理
        对每个要加入的元素进行哈希处理。这一步骤将元素映射为一个固定长度的二进制串。


划分与索引——分桶
        在 Redis 的实现中,使用 64bit 的 Hash 函数,其中 14bit 用于桶索引(将原始集合分成多个子集),剩下的 50bit 用于计算前导零的个数。这意味着 Redis 中的 HyperLogLog 使用16384个桶,每个桶可以表达的最大数字是:2^5+2^4+...+1 = 63 


估计基数——桶估计值公式
        通过使用补偿和线性计数的技术,将最大前导零位转换为基数估计值。具体的计算方法可以使用查表或其他数学模型来实现。在 Redis 的 HyperLogLog 实现中,有一个基于经验值的参数 alpha_m 用于调整估计值,以确保在大多数情况下都能提供较为准确的估计。


误差修正——偏差修正因子
由于 HyperLogLog 是一种概率性算法,其估计结果会存在一定的误差。Redis 通过应用修正公式来纠正这些估计误差,从而提供更加准确的基数估计。

合并与查询
        
HyperLogLog 支持多个数据集的并集操作,可以将多个 Key 的 UV 进行去重合并,且合并的复杂度是 O(1)。同时,获取一个或多个 HyperLogLog 结构的基数估计值也非常高效,查询的复杂度同样是 O(1)。
 

更多请看:HyperLogLog 算法的原理讲解以及 Redis 是如何应用它的聪明的你可能会马上想到,用 HashMap 这种数 - 掘金 (juejin.cn)

8.2.2 使用HyperLogLog实现百万级UV统计测试

我们向Redis插入100万条数据,用来模拟超大UV访问量的统计,并计算使用HyperLogLog存储需要消耗的内存大小以及统计误差,从而向大家展示Hyper在统计方面的优秀性能

/**
     * 向Hyperloglog插入100万条数据进行测试,查看内存占用情况
     */
    @Test
    void testHyperLogLog() {
        // 开始循环
        for(int i=0;i<1000;i++){
            String[] values = new String[1000];
            for(int j=0;j<1000;j++){
                values[j] = "user_" + i + "_" + j;
            }
            stringRedisTemplate.opsForHyperLogLog().add("hl2",values);
        }

        // 统计数量
        Long count = stringRedisTemplate.opsForHyperLogLog().size("hl2");
        System.out.println("count = " + count);

    }


http://www.kler.cn/news/313196.html

相关文章:

  • 使用 Rust 和 wasm-pack 开发 WebAssembly 应用
  • SHT30温湿度传感器详解(STM32)
  • 【Linux】线程池(第十八篇)
  • 云计算第四阶段------CLOUD Day4---Day6
  • SpringBoot实现OAuth客户端
  • SQL编程题复习(24/9/20)
  • FPGA基本结构和简单原理
  • Mac下nvm无法安装node问题
  • 设计模式-行为型模式-命令模式
  • 001.从0开始实现线性回归(pytorch)
  • 【Docker】安装及使用
  • EmguCV学习笔记 C# 12.3 OCR
  • Vue vs React vs Angular 的区别和选择
  • 数据结构-2.9.双链表
  • 周末愉快!——周复盘
  • 深度学习-03 Pytorch
  • Android 空气质量刻度
  • CleanClip For Mac 強大的剪貼簿助手Paste替代工具 v2.2.1
  • 学习笔记——EffcientNetV2
  • React——点击事件函数调用问题
  • Gradio离线部署到内网,资源加载失败问题(Gradio离线部署问题解决方法)
  • docker搭建个人网盘,支持多种格式,还能画图,一键部署
  • Matlab可视化│常用绘图全家桶
  • HTTP中的301、302实现重定向
  • ActivityManagerService 分发广播(6)
  • Vue3:reactive丢失响应式,数据有更新但表单没有更新
  • gin配置swagger文档
  • 树与图的深度优先遍历(dfs的图论中的应用)
  • 【CPP】类与继承
  • [原创]全新安装最新版Delphi 12.2之前, 如何正确卸载旧版Delphi 12.1?