Redis的HyperLogLog实现uv统计

为什么集合写的好好的,突然要来写 Redis 呢 ?

一切的原因都要从今天的需求说起,今天接了一个需求,目标是做一个推广页的 UV (统计访客, 每天只记录同一个访客一次) 统计,因为前段时间刚刚看过老钱写的 《Redis深度历险》 HyperLogLog 篇依旧历历在目,一样是统计 UV 的场景,我就直接提出用 HyperLogLog 去处理这个问题,然后被领导问为什么要用 HyperLogLog 好在哪的时候卡壳了,emmm ... 本来想凭借我这广阔的技术海无形装逼,这特么不尴尬了。

HyperLogLog 与 基数统计

HyperLogLog 是最早由 Flajolet 及其同事在 2007 年提出的一种 估算基数的近似最优算法,具体定义可以参考维基百科:https://en.jinzhao.wiki/wiki/HyperLogLog

所谓基数统计可以理解为用来统计一个集合中不重复的元素,也就是我们今天要讲解 HyperLogLog 的原因

业务场景分析

领导的需求是:统计每个网页的访客数量, 每个用户只记录一次

我们来捋顺一下思路, 可能你会想出如下几种方案:

  • Plan A : 数据库里建张表,没有一个表解决不了的问题,如果有那么就建两张表 .

  • Plan B : 建立一个 Set 集合, 存储当天访问过此页面的所有用户 id .

  • Plan C : bitmap,类似用户签到的解决方案,依据年月做 key , 日期作为偏移量 .

我们来分析一下这几种方案,首先数据库解决是不可能了,甭说做每个月的 UV 统计分析了, 一天的访问量存起来空间就占了太多了; 那么为每个页面设置一个 Set 怎么样呢 ? 依旧是访问量的问题,如果访问量很高 Set 的集合会非常大,集合一大去重的时候就 ... 你懂的, 所以 Set 也不行; 那么 bitmap 呢 ? bitmap 的一个元素可以对应到 bit 数组中的一位,使用 bitmap 确实很节省内存,可是在大数据量的场景下,依旧需要很多内存,并不适用 。

三个计划全部拉闸,怎么办呢?哎,拉闸就对了,不拉闸怎么讲我们今天要说的 HyperLogLog 呢?

概率算法

HyperLogLog 采取概率算法,所谓概率算法就是通过一定的概率统计方法预估基数值,而概率算法并不直接存储数据集合本身,这种方法可以极大的节省内存,并保证误差在一定范围内,HyperLogLog 在标准误差(0.81%)的前提下能够统计 2^64 个数据!

 实例

private static String COUNT = "cpp_bank_list_total_size_today";
private static String TOTAL_COUNT = "cpp_bank_list_total_size";
private static String TOTAL_ID = "0";

数据存入:

//获取访问者的ip
String ipAdress = IPUtil.getIpAddress(httpServletRequest);
log.debug("访问列表页的ip地址为:[{}]", ipAdress);
//将ip存入redis
HyperLogLogOperations<String, String> hyperlog = redisTemplate.opsForHyperLogLog();
hyperlog.add(COUNT, ipAdress);

数据读取:

public void saveUserAccessLog() {
    HyperLogLogOperations<String, String> hyperlog = redisTemplate.opsForHyperLogLog();
    int count = hyperlog.size(COUNT).intValue();
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_MONTH, -1);
    CppBankAccessLog cppBankAccessLog = CppBankAccessLog.builder()
            .id(Sequence.getInstance().getSequenceNumber())
            .time(new SimpleDateFormat("yyyy-MM-dd").format(calendar.getTime()))
            .count(count)
            .build();
    cppBankAccessLogMapper.insertSelective(cppBankAccessLog);
    //合并每天的访问量到总计中
    hyperlog.union(TOTAL_COUNT, COUNT);
    int totalCount = hyperlog.size(TOTAL_COUNT).intValue();
    cppBankAccessLogMapper.updateCountById(TOTAL_ID, totalCount);
    log.info("日活量信息入库,昨日数据:[{}],总数据:[{}]", count, totalCount);
    //删除today中的数据
    hyperlog.delete(COUNT);
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值