四两拨千斤 —— HyperLogLog

在开始这一节之前,我们先思考一个常见的业务问题:如果你负责开发维护一个大型的网站,有一天老板找产品经理要网站每个网页每天的 UV 数据,然后让你来开发这个统计模块,你会如何实现?

如果统计 PV 那非常好办,给每个网页一个独立的 Redis 计数器就可以了,这个计数器的 key 后缀加上当天的日期。这样来一个请求,incrby 一次,最终就可以统计出所有的 PV 数据。

但是 UV 不一样,它要去重,同一个用户一天之内的多次访问请求只能计数一次。这就要求每一个网页请求都需要带上用户的 ID,无论是登陆用户还是未登陆用户都需要一个唯一ID 来标识。

你也许已经想到了一个简单的方案,那就是为每一个页面一个独立的 set 集合来存储所有当天访问过此页面的用户 ID。但是,如果你的页面访问量非常大,比如一个爆款页面几千万的 UV,你需要一个很大的 set 集合来统计,这就非常浪费空间。如果这样的页面很多,那所需要的存储空间是惊人的。为这样一个去重功能就耗费这样多的存储空间,值得么?其实老板需要的数据又不需要太精确,105w 和 106w 这两个数字对于老板们来说并没有多大区别,So,有没有更好的解决方案呢?

这就是本节要引入的一个解决方案,Redis 提供了 HyperLogLog 数据结构就是用来解决这种统计问题的。HyperLogLog 提供不精确的去重计数方案,虽然不精确但是也不是非常不精确,标准误差是 0.81%,这样的精确度已经可以满足上面的 UV 统计需求了。

/**
 * @Auther: majx2
 * @Date: 2019-3-22 10:08
 * @Description:
 */
public class HyperLogLogTest {

    private static final String CODEHOLE = "codehole";
    private static final int EXPECT_VALUE = 1000;
    Jedis jedis = RedisDS.create().getJedis();

    @Test
    public void testHyperLogLog(){
        for (int i = 0; i < EXPECT_VALUE; i++) {
            jedis.pfadd(CODEHOLE+1, "user" + i);
        }
        System.out.printf("真实值:%d ,期待值:%d\n", jedis.pfcount(CODEHOLE+1), EXPECT_VALUE);
        for (int i = 0; i < EXPECT_VALUE; i++) {
            jedis.pfadd(CODEHOLE+2, "user" + i);
        }
        jedis.pfmerge(CODEHOLE,CODEHOLE+1,CODEHOLE+2);
        System.out.printf("真实值:%d ,期待值:%d\n", jedis.pfcount(CODEHOLE), EXPECT_VALUE);
        jedis.del(CODEHOLE,CODEHOLE+1,CODEHOLE+2);
    }
}

返回值:

真实值:1011 ,期待值:1000
真实值:1011 ,期待值:1000

这个方法使用还是要谨慎。经测试虽然已经实现了除重,但是还会出现真实值比期待值大的情况。当然如果是计算UV,结果大一点也是可以的。

本文基于《Redis深度历险:核心原理和应用实践》一文的JAVA实践。更多文章请参考:高性能缓存中间件Redis应用实战(JAVA)

转载于:https://my.oschina.net/u/1404949/blog/3039468

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值