Redis中HyperLogLog的一些小知识

  1. HyperLogLog
    1. 是Redis的高级数据结构,可以实现统计用户访问情况中的UV情况,也就是独立访问次数,重复用户在每天只算一次。
    2. HyperLogLog提供了一种不精确的去重计数方案,标准误差为0.81%。
    3. 实现原理:
      1. 通过记录低位最长的连续零位数据,估计出总的数量。经过试验发现随机数总数的对数和最长零位长度存在显著线性关系。
      2. 通过设置多个桶,计算平均估计,达到较准确的结果
  2. 使用方法的代码:以下代码模拟了多线程时10000个用户访问时的统计情况,最终结果为10064。
 public static void testHyperLogLog() throws InterruptedException {
        Jedis jedis1 = new Jedis("192.168.198.128");
        Jedis jedis2 = new Jedis("192.168.198.128");
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    jedis1.pfadd("codehole" ,"user"+ i);
                }
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    jedis2.pfadd("codehole" , "user"+i);
                }
            }
        };
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(jedis1.pfcount("codehole"));
    }
  1. 原理分析的代码模拟
    1. BitKeeper类:每个BitKeeper相当于是一个桶。每次出现一个新数据时,随机选择一个桶进行统计,最后将所有桶的结果取调和平均。然后计算平均每个桶中的数量*桶的总个数,就得到了最后的结果。
package com.xliu.chapter1;

import org.junit.Test;
import redis.clients.jedis.Jedis;

import java.util.concurrent.ThreadLocalRandom;

/**
 * @author liuxin
 * @version 1.0
 * @date 2020/4/22 9:50
 */
public class HyperLogLogTest {

    private int n;
    private BitKeeper[] keepers;

    public HyperLogLogTest(int n) {
        this.n = n;
        this.keepers = new BitKeeper[1024];
        for(int i=0;i<1024;i++){
            keepers[i] = new BitKeeper();
        }
    }

    public static void testPF(){
        for(int i=100000;i<1000000;i+=10000){
            HyperLogLogTest hyperLogLogTest = new HyperLogLogTest(i);
            hyperLogLogTest.work();
            //hyperLogLogTest.debug();
            double estimate = hyperLogLogTest.estimate();
            System.out.printf("%d %.2f %.2f\n",i,estimate,Math.abs(estimate - i)/i);
        }


    }

    private double estimate() {
        double subitsInverse = 0.0;
        for(BitKeeper keeper:keepers){
            subitsInverse += 1/(float)keeper.maxbits;
        }
        double avgBits = (float)keepers.length / subitsInverse;
        return Math.pow(2,avgBits) * 1024;
    }

//    private void debug() {
//        System.out.printf("%d %.2f %d\n",n,Math.log(n)/Math.log(2),keeper.maxbits);
//    }

    private void work() {
        for(int i=0;i<n;i++){
            long m = ThreadLocalRandom.current().nextLong(1L<<32);
            BitKeeper keeper = keepers[(int) (((m & 0xfff0000) >> 16) % 1024)];
            keeper.random();
        }
    }

    public static void testHyperLogLog() throws InterruptedException {
        Jedis jedis1 = new Jedis("192.168.198.128");
        Jedis jedis2 = new Jedis("192.168.198.128");
        Thread thread1 = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    jedis1.pfadd("codehole" ,"user"+ i);
                }
            }
        };
        Thread thread2 = new Thread(){
            @Override
            public void run() {
                for(int i=0;i<10000;i++){
                    jedis2.pfadd("codehole" , "user"+i);
                }
            }
        };
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(jedis1.pfcount("codehole"));
    }


    public static void main(String[] args) throws InterruptedException {
        testPF();
        //testHyperLogLog();
    }

    private class BitKeeper {
        private int maxbits;

        public void random(){
            long value = ThreadLocalRandom.current().nextLong(2L << 32);
            int bits = lowZeros(value);
            maxbits = Math.max(bits,maxbits);
        }

        private int lowZeros(long value) {
            int i = 1;
            for(;i<32;i++){
                if(value>>i<<i != value){
                    break;
                }
            }
            return i-1;
        }
    }
}

  1. 试验结果:可以看到估算结果还是比较准确的,百分比误差率在个位数,当然实际的实现代码会更复杂更精确。
100000 93113.60 0.07
200000 192753.58 0.04
300000 309455.21 0.03
400000 399014.89 0.00
500000 488323.85 0.02
600000 614030.15 0.02
700000 726671.02 0.04
800000 819612.06 0.02
900000 893945.77 0.01
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值