首页 > 数据库 >redis设计统计用户访问量

redis设计统计用户访问量

时间:2024-10-03 20:35:35浏览次数:8  
标签:String redis 用户 访问量 id HyperLogLog

需求:实现某个接口每天调用了多少次,每个用户只记录一次。

(例如,统计刷题模块,练题模块,模拟面试模块每天访问量,利于后续针对功能访问量做出其他优化设计。贴子的浏览量)
先分析几种不同的方案:

方案一:使用Hash哈希结构

实现方法:当用户访问网站时,我们可以使用用户的ID作为标识(若用户未登录,则生成一个随机标识)。通过Redis的HSET命令,以URI和日期拼接作为key,用户ID或随机标识作为field,将value设置为1。统计访问量时,使用HLEN命令获取结果。

优点:

  • 实现简单,易于理解。

  • 查询方便,数据准确性高。

缺点:

  • 随着key的增多,内存占用过大,性能可能下降。

  • 对于访问量巨大的网站(如拼多多),此方案可能无法承受。

方案二:使用Bitset

实现方法:利用Bitset对用户ID进行压缩存储。通过SETBIT命令标记用户访问,使用GETBIT查询用户是否访问,最后通过BITCOUNT统计访问量。

优点:

  • 占用内存更小,适用于大规模用户数据。

  • 查询方便,可指定查询某个用户。

缺点:

  • 用户稀疏时,内存占用可能比方案一更大。

  • 对于未登录用户,可能需要额外的映射开销。

方案三:使用概率算法

实现方法:采用Redis中的HyperLogLog算法,这是一种基数评估算法。使用PFADD命令记录用户访问,通过PFCOUNT命令计算访问量。

优点:

  • 占用内存极小,每个key仅需要12KB。

  • 非常适合超大规模用户访问量的网站。

缺点:

  • 查询指定用户时可能存在误差。

  • 总数统计存在一定的误差(约0.81%)。

以上三种方案各有优劣,具体选择应根据实际业务需求和场景来决定:

  • 若对数据准确性要求较高,且访问量适中,可以选择方案一。

  • 若需要处理大规模用户数据,且对内存占用有要求,可以选择方案二。

  • 若对数据精度要求不高,但需要处理超大规模用户访问量,可以选择方案三。

以上是针对Redis提供的解决方案,在项目中对前后端日志埋点数据,通过流式计算以及大数据分析,也是常用的解决方案。

首先最简单的做法就是当用户访问某个功能时,在进行查询时自动传递一个携带用户id,日期与功能字段的数据传入mysql中,当第二次进入先查询,当日已存在便不再存储该数据,但是该方案存在问题便是,每个用户都在频繁的与数据库做交互,是否针对具体业务情况将其存入单独的库存储也需要权衡,而且我们系统已经引入的redis,不妨直接用redis来做。首次先查询redis,redis存在直接切断,不存在存入则存入redis,后续通过定时任务调度在流量稳定时迁入数据库。

redis的HyperLogLog
Redis 在 2.8.9 版本添加了 HyperLogLog 结构。Redis HyperLogLog 是用来做基数统计的算法,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大时,计算基数所需的空间总是固定 的、并且是很小的。在 Redis 里面,每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。这和计算基数时,元素越多耗费内存就越多的集合形成鲜明对比。
但是,因为 HyperLogLog 只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog 不能像集合那样,返回输入的各个元素。比如数据集 {1, 3, 5, 7, 5, 7, 8}, 那么这个数据集的基数集为 {1, 3, 5 ,7, 8}, 基数(不重复元素)为5。 基数估计就是在误差可接受的范围内,快速计算基数。
简单的说HyperLogLog通过概率学统计访问数量,统计的时候存在误差,但是误差很小。所以适合特殊场景,例如日访问量统计。
操作指令如下:
用户001访问。如果 HyperLogLog 的内部被修改了,那么返回 1,否则返回 0 .

PFADD visit_{data} “001”

用户002访问。

PFADD visit_{data} “002”

查看访问数量。

PFCOUNT visit_{data}

 

 

 

集成spring

 

    public boolean add(String key, String obj) {
        return redisTemplate.opsForHyperLogLog().add(key, obj) > 0;
    }
    public long count(String key) {
        // pfcount 非精准统计 key的计数
        return redisTemplate.opsForHyperLogLog().size(key);
    }

我们可以抽象出单独接口方法,需要统计日活量的直接调用接口即可

功能接口

 

public interface UniqueVisitor {
    Boolean add(String id, String loginid);
    Boolean addMonth(String id, String loginid);
    Long getCount(String id);
}

  

@Service
public class UniqueVisitorImpl implements UniqueVisitor {
    @Resource
    private RedisUtil redisUtil;
    private static final String Unique_Visitor = "user_Unique_Visitor:";

    @Override
    public Boolean add(String id, String loginid) {
        LocalDate today = LocalDate.now();
        long dayOfYear = today.getDayOfYear();
        return redisUtil.add(Unique_Visitor + id+dayOfYear, loginid);
    }
    @Override
    public Boolean addMonth(String id, String loginid) {
        LocalDate today = LocalDate.now();
        long Month = today.getMonthValue();
        return redisUtil.add(Unique_Visitor + id+Month, loginid);
    }
    @Override
    public Long getCount(String id) {
        LocalDate today = LocalDate.now();
        long dayOfYear = today.getDayOfYear();
        return redisUtil.count(Unique_Visitor + id+dayOfYear);
    }
}

调用计入日活量

 

    @GetMapping("/uniquevisitor")
    public Result<Boolean> uniqueVisitor(@RequestParam("id") String id) {
        try {
            return Result.ok(uniqueVisitor.getCount(id));
        } catch (Exception e) {
            log.error("SubjectCategoryController.add.error:{}", e.getMessage(), e);
            return Result.fail("查询浏览量失败");
        }
    }

  

   /**
         * 统计用户今日访问新增题目
         */
        uniqueVisitor.add("addSubject",LoginUtil.getLoginId());

  实现效果

 

 

 我们测试了两个账号,可以看到今日新增题目的日活量为2次。

 

标签:String,redis,用户,访问量,id,HyperLogLog
From: https://www.cnblogs.com/azwz/p/18445967

相关文章

  • 六、redis之set
    Redis集合是成员的无序集合。可以用来保存唯一的成员。注意:对于以下的命令,涉及删除成员的,如果集合中的所有元素都被移除,则集合会被删除。如果集合原先不存在,被当作空集合。SADDSADDkeymember[member...]sadd命令将一系列成员添加到set中。SMEMBERSSMEMBERSkeysmemb......
  • Redis 发布订阅模式
    概述Redis的发布/订阅是一种消息通信模式:发送者(Pub)向频道(Channel)发送消息,订阅者(Sub)接收频道上的消息。Redis客户端可以订阅任意数量的频道,发送者也可以向任意频道发送数据。在发送者向频道发送一条消息后,这条消息就会被发送到订阅该频道的客户端(Sub)Redis有两种发布/订阅模式......
  • Redis: Sentinel工作原理和故障迁移流程
    Sentinel哨兵几个核心概念1)定时任务Sentinel它是如何工作的,是如何感知到其他的Sentinel节点以及Master/Slave节点的就是通过它的一系列定时任务来做到的,它内部有三个定时任务第一个就是每一秒每个Sentinel对其他Sentinel和Redis节点执行PING操作(监......
  • 五、redis之hash
    redis的hash类型就是平时说的hash表,字典。类似于Java中的HashMap。可以用来存储对象等结构。现在看下操纵hash类型的命令。HGETHGETkeyfieldhget获取hash中的field字段的值。HSETHSETkeyfieldvalue[fieldvalue...]hset命令将多个fieldvalue键值对设置到key中。......
  • MySQL数据库用户权限控制的实现方法
            控制用户权限在任何数据库管理系统中都是一个重要的需求。合理的权限管理能够保障数据的安全性和完整性。下面我们将讨论如何使用数据库来控制用户的权限,尤其是对于MySQL数据库的具体实现。1.概述权限控制通常涉及到以下几个方面:用户角色:定义不同的用户角......
  • Linux 学习笔记(二):深入理解用户管理、运行级别与命令行操作
    Linux学习笔记(二):深入理解用户管理、运行级别与命令行操作前置学习内容:Linux学习(一)1.用户管理1.1用户密码管理创建用户密码使用passwd命令可以为指定用户设置密码:sudopasswd`userName`密码找回当忘记密码时,可以采取以下步骤找回:在登录界面按e键进入编......
  • 高级java每日一道面试题-2024年9月30日-服务器篇[Redis篇]-Redis持久化有几种方式?
    如果有遗漏,评论区告诉我进行补充面试官:Redis持久化有几种方式?我回答:Redis是一个高性能的键值存储系统,常用于缓存、消息队列和实时数据分析等场景。为了保证数据的持久性,Redis提供了两种主要的持久化方式:RDB(RedisDatabaseBackup)和AOF(AppendOnlyFile)。这两种方......
  • redis的数据结构,内存处理,缓存问题
    redisObjectredis任意数据的key和value都会被封装为一个RedisObject,也叫redis对象:这就redis的头信息,占有16个字节redis中有两个热门数据结构1.SkipList,跳表,首先是链表,和普通链表有以下差异:元素按照升序排列存储节点可能包含多个指针,指针跨度不同那么跳表的特点有以下:......
  • 用C/C++构建自己的Redis——第五章、Redis中的AVL树实现
    用C/C++构建自己的Redis——第五章、Redis中的AVL树实现文章目录用C/C++构建自己的Redis——第五章、Redis中的AVL树实现前言一、键值对集查询概念1.1键值对集合查询1.2数据结构排序的复习排序数组(SortedArrays)树形数据结构(TreeDataStructures)通过随机性平衡(Balan......
  • 从手机发布会的现场测试谈“用户使用场景”
    一红米Note14发布会的现场测试前几天红米Note14发布,现场测试手机防摔、防水、防油触摸以及长时间续航。先不管测试结果怎么样,能不能让大多目标用户满意。单说这种介绍形式,就足够吸引人。这种形式站在了目标用户的使用场景之中,提炼出用户使用中的痛点,针对痛点提出自己的解决方......