《Redis深度历险》HyperLogLog

HyperLogLog

基本使用

如果需要统计UV(每日访问用户数),而不需要过于精确数据量又比较大的情况下可以采用这种结构,它的标准误差为0.81%,但是set结构要节省空间。
它是Redis的一种高级数据结构。
使用pfadd key element [element...] 来向key中增添emement(可增添多个)
使用pfcount key获取数量
可就能够很好的解决日活用户的问题
还有pfmerge destkey sourcekey [sourcekey....]可用于汇总多个pf计数,注意这是有误差的,所以如果destkey远大于sourcekey,那么就可能莫得效果,还是原值,相反被误差掉的就是原值了。

在这里插入图片描述
在这里插入图片描述
用脚本测一波准确率还是挺高的。

import redis.clients.jedis.Jedis;
/**
 * @创建人 YDL
 * @创建时间 2020/6/12 3:18
 * @描述
 */
public class PfTest {
    public static void main(String[] args) {
        try(Jedis jedis = new Jedis()) {
            jedis.del("set");
            for (int i = 0; i < 100000; i++) {
                jedis.pfadd("set", "user" + i);
            }
            System.out.println(100000+" "+jedis.pfcount("set"));
            for (int i = 0; i < 100000; i++) {
                jedis.pfadd("set", "user" + i);
            }
            System.out.println(100000+" "+jedis.pfcount("set"));
        }
    }
}

在这里插入图片描述

实现原理

哇?书中给个图然后给个实验代码就算讲完了?让我自己去悟嘛???
参考了这篇文章
我大概有点意会了,它是基于这样的假设,当某个随机数为某值时比较大概率是进行了n次操作(可类比为抛硬币,因为计算机中的二进制与硬币的正反是对应的,如果连续两次为正面那么抛的次数不会太多,如果连续99次为正面那个次数肯定会很多了)
当然如果只基于“一次”抛全部硬币的就作为判断肯定是误差极大的(划分不精细),那么比较常规的思路就是多次求和取平均,但是一些极端值的存在会严重影响到输出结果(因为极端值的出现的概率较小),所以就使用调和平均数,相对比较偏向大多数值。

书中代码

这相当于只抛一次n枚硬币的代码,如果某个值介于2k~2k+1直接那么它们就算同样的数,但是这样误差肯定会很大

public class PfTest {
   
    static class BitKeeper{
        private int maxbits;
        获取一个随机数然后求取该随机数的前导0,如果前导0大于了之前的前导0则更新
        public void random(){
            long value = ThreadLocalRandom.current().nextLong(2L<<32);
            int bits = lowZeros(value);
            if(bits>this.maxbits)
                this.maxbits =bits;
        }
        //获取低位的0的数量,对应前导0
        private int lowZeros(long value){
            int i =1;
            for(;i<32;i++){
                if(value>>i<<i!=value){
                    break;
                }
            }
            return i-1;
        }
    }
    static class Experiment{
        private int n;
        private BitKeeper keeper;
        public Experiment(int n){
            this.n = n;
            this.keeper = new BitKeeper();
        }

        public void work(){
        等价于一次抛n个硬币
            for(int i=0;i<n;i++){
                this.keeper.random();
            }
        }
        public void debug(){
            System.out.printf("%d %.2f %d\n",
                    this.n,Math.log(this.n)/Math.log(2),
                    this.keeper.maxbits);
        }
    }
    @Test
    public void test(){
        for(int i=1000;i<100000;i+=100){
            Experiment exp = new Experiment(i);
            exp.work();
            exp.debug();
        }
    }
}

实验结果发现,硬币的数量n与随机数最大前导0的2k 近似相等。
n介于2k~2k+1之间。
在这里插入图片描述
这个相当于分桶(多次计算)+求调和平均数(Hn=n/(1/a1+1/a2+…+1/an)),这个算法会比较偏向于比较集中的数字

public class PfTest {

   //BitKeeper代码基本不变
	
	//分1024个桶,就是算1024次
    static class Experiment{
        private int n,k;
        private BitKeeper[] keepers;
        public Experiment(int n){
            this(n,1024);
        }
        public Experiment(int n,int k){
            this.k = k;
            this.n = n;
            this.keepers = new BitKeeper[k];
            for(int i=0;i<k;i++){
                this.keepers[i] = new BitKeeper();
            }
        }

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

        public double estimate(){
            double sumbitsInverse = 0.0;
            for(BitKeeper keeper:keepers){
                sumbitsInverse += 1.0/(float)keeper.maxbits;
            }
            double avgBits = (float)keepers.length/sumbitsInverse;
            return Math.pow(2,avgBits)*this.k;
        }
    }
    @Test
    public void test(){
        for(int i=100000;i<1000000;i+=100000){
            Experiment exp = new Experiment(i);
            exp.work();
            double est = exp.estimate();
            System.out.printf("%d %.2f %.2f\n",i,est,Math.abs(est-i)/i);
        }
    }


}

真实的HyperLogLog原理与之类似,但是比之复杂精确。有兴趣可以看看上面贴的参考内容的相关部分。
以下内容也摘录自上面的文章。
Redis中和HyperLogLog相关的命令有三个:

PFADD hll ele: 将ele添加进hll的基数计算中。流程:
先对ele求hash(使用的是一种叫做MurMurHash的算法)
将hash的低14位(因为总共有2的14次方个桶)作为桶的编号,选桶,记桶中当前的值为count
从的hash的第15位开始数0,假设从第15位开始有n个连续的0(即前导0)
如果n大于count,则把选中的桶的值置为n,否则不变
PFCOUNT hll: 计算hll的基数。就是使用上面给出的DV公式根据桶中的数值,计算基数
PFMERGE hll3 hll1 hll2: 将hll1和hll2合并成hll3。用的就是上面说的合并算法。
Redis的所有HyperLogLog结构都是固定的16384个桶(2的14次方),并且有两种存储格式:
稀疏格式: HyperLogLog算法在刚开始的时候,大多数桶其实都是0,稀疏格式通过存储连续的0的数目,而不是每个0存一遍,大大减小了HyperLogLog刚开始时需要占用的内存
紧凑格式: 用6个bit表示一个桶,需要占用12KB内存

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Redis是一款内存数据库,性能、支持多种数据结构、提供丰富的功能,得到了广泛的应用。但其维护操作却需要一定的技巧,开源社区中也有大量的文档、文章来讲解。其中,Redis深度历险一书介绍了Redis的基础知识、级特性、应用场景和实战案例。该书深入剖析了Redis内部的实现原理,让读者更好地理解其运行机制、调优方法和错误排查。主要内容包括Redis线程模型、内存优化、IO模型、事务、持久化、集群、性能调优、应用场景等。其中,集群方面包括Redis Cluster和Redis Sentinel两类架构的详细介绍和使用方法。性能调优方面,介绍了一些常见的性能问题和解决方案,以及使用Redis的最佳实践。对于有一定Redis使用经验的开发人员或系统工程师,这本书可以帮助他们更好地优化和管理Redis实例,也可以让他们更深入地掌握Redis相关知识。对于想学习Redis的初学者,建议还需要通过其他资料了解Redis基本概念和用法。总的来说,Redis深度历险是一本值得阅读的Redis专业书籍,它为读者提供了许多经验和实践经验,也为企业中使用Redis的团队提供了宝贵的参考资料。 ### 回答2: Redis是一款开源的性能NoSQL数据库,近年来在企业级应用中广受欢迎。《Redis深度历险》是一本深入介绍Redis的技术书籍,由黄健宏等人撰写。 这本书详细介绍了Redis的架构、原理、数据结构、使用场景、性能优化、集群部署等方面的知识,通过系统化的学习可以在Redis的使用和优化方面获得很大的收获。 《Redis深度历险》中包含大量的实际代码示例和生产环境中的案例分析,可以帮助读者深入理解Redis的实现细节和应用场景,并快速应用到自己的实战项目中。同时,这本书也适合那些想深入了解分布式系统的架构师、程序员、运维工程师等 IT 技术人员。 此外,书中还介绍了很多Redis的新特性和应用场景,如Redis的流式计算、Redis与gRPC的结合使用等,让人们对Redis的使用和应用场景有了更深刻的认识。 总之,《Redis深度历险》是一本非常实用的Redis技术指南,对于想深入学习Redis的技术人员来说是非常必备的一本读物。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值