前面介绍了 Spring Data Redis 对 Redis 中 value、hash、set、zset 数据类型的操作。下面将介绍怎样利用 Spring Data Redis 实现对 HyperLogLog 数据类型的操作。
注意:Redis 在 2.8.9 版本添加了 HyperLogLog 结构。
如果我们需要去统计APP接口被客户端请求的次数。如果使用 Redis 去实现该业务,我们最容易想到的就是使用 key=value 保存,key 保存请求接口,value 保存次数。然后使用,Redis 的 incr、incrby 等指令进行递增操作。如果存在多个请求接口,则使用多个 key=value 进行保存就是。
如果我们需要统计接口被客户端请求的次数,一个客户端多次请求只算一次?在 Java 里面的数据结构必然会想到Set(因为 Set 不允许有重复元素)。幸运的是,Redis 里面也有set。但是,对于小项目来说,客户端数量不多,数据量也不会很庞大,这是可行的。但是对大项目来说呢?这个 set 只会无限扩张,最后变得很庞大,非常占用内存,变得很臃肿。
问题来了,那么如何既能够实现该功能,又可以减少内存的浪费呢,可以考虑使用 Redis 的 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。基数估计就是在误差可接受的范围内,快速计算基数。
不像其他 Operations 操作接口定义很多方法,该 Operations 只定义了下面四个方法:
Long add(K key, V... values) 将给定一个或多个值添加到键。
void delete(K key) 删除给定的键。
Long size(K... keys) 获取键中的当前元素数。
Long union(K destination, K... sourceKeys) 将 sourceKeys 键的所有值合并到 destination 键中。
向 Redis 键为 hyper_log_log 的 hyperloglog 中随机写入 1000 次介于 0~99 之间的数,然后获取 hyper_log_log 键中元素的个数。代码如下:
HyperLogLogOperations<String,String> ops = redisTemplate.opsForHyperLogLog(); for(int i = 0; i < 1000; i++) { String val = String.valueOf((int)(Math.random() * 100)); ops.add("hyper_log_log", val); } Long size = ops.size("hyper_log_log"); System.out.println("size=" + size);
运行示例,输出如下:
size=100
从输出可知,hyperloglog 自动帮我们进行了元素去重,效果和 set 类似,只是使用 hyperloglog 占用内存少,减少内存浪费。
分别向 hyper_log_log1 和 hyper_log_log2 两个键中随机写入 100 个介于 0~99 的元素,然后使用 union() 方法合并 hyper_log_log1 和 hyper_log_log2 两个键,返回合并后去重元素的个数。代码如下:
Long size; HyperLogLogOperations<String,String> ops = redisTemplate.opsForHyperLogLog(); // 初始化数据 for(int i = 0; i < 100; i++) { String val = String.valueOf((int)(Math.random() * 100)); ops.add("hyper_log_log1", val); } size = ops.size("hyper_log_log1"); System.out.println("hyper_log_log1 size=" + size); for(int i = 0; i < 100; i++) { String val = String.valueOf((int)(Math.random() * 100)); ops.add("hyper_log_log2", val); } size = ops.size("hyper_log_log2"); System.out.println("hyper_log_log2 size=" + size); // 合并 hyper_log_log1 和 hyper_log_log2 两个键的元素,获取去重后元素个数 size = ops.union("hyper_log_log1", "hyper_log_log2"); System.out.println("union size=" + size);
运行示例,输出如下:
hyper_log_log1 size=66 hyper_log_log2 size=64 union size=90
从输出结果可知,union() 方法是将两个 hyperloglog 进行了合并,并且去掉了重复的元素,然后返回不重复元素个数。