redis设计统计用户访问量

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

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

方案一:使用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次。

 

posted @ 2024-10-03 20:30  橘子味芬达水  阅读(79)  评论(0编辑  收藏  举报