(文章目录)
前言
从官网文档可以看出 HyperLogLog 是一种估计集合基数的数据结构。作为一种概率数据结构,HyperLogLog 以完美的准确性换取高效的空间利用。 HyperLogLog的应用场景是基数统计。 基数统计顾名思义就是统计一个集合中不重复元素的个数。 Redis HyperLogLog 实现最多使用 12 KB,并提供 0.81% 的标准错误。 限制: HyperLogLog 可以估计最多包含 18,446,744,073,709,551,616 (2^64) 个成员的集合的基数。
一、命令
hyperloglog 的命令很简单只有三条 + 俩条测试命令 (框中的俩条命令为测试命令)
PFADD 是将元素添加到hyperloglog 键 如果不存在就创建
PFADD test 1
PFADD test a b c d e f
PFCOUNT 是返回集合的唯一基数
PFCOUNT test
PFMERGE 将一个或多个HyperLogLog 合并(merge)为一个 HyperLogLog
PFMERGE test2 test test1
二、项目实战
1.pom 依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.20.1</version>
</dependency>
2.添加数据:
/** 添加数据 */
public <T> void add(String testName, T item) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(testName);
hyperLogLog.add(item);
}
/** 将集合数据添加到 HyperLogLog */
public <T> void addAll(String testName, List<T> items) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(testName);
hyperLogLog.addAll(items);
}
3.合并
/**
* 将 otherTestNames 的 log 合并到 testName
*
* @param testName 当前 log
* @param otherLogNames 需要合并到当前 log 的其他 logs
* @param <T>
*/
public <T> void merge(String testName, String... otherTestNames) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(testName);
hyperLogLog.mergeWith(otherTestNames);
}
4.基数统计
public <T> long count(String testName) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(testName);
return hyperLogLog.count();
}
5. 测试代码
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedissionApplication.class)
public class HyperLogLogTest {
@Autowired
private HyperLogLogService hyperLogLogService;
@Test
public void testAdd() {
String logName = "测试";
String item = "张三";
hyperLogLogService.add(logName, item);
log.info("添加元素[{}]到 log [{}] 中。", item, logName);
}
@Test
public void testCount() {
String logName = "测试";
long count = hyperLogLogService.count(logName);
log.info("logName = {} count = {}.", logName, count);
}
@Test
public void testMerge() {
ArrayList<String> items = new ArrayList<>();
items.add("张三");
items.add("李四");
items.add("王五");
String otherLogName = "测试2";
hyperLogLogService.addAll(otherLogName, items);
log.info("添加{}个元素到 log [{}] 中。", items.size(), otherLogName);
String logName = "测试";
hyperLogLogService.merge(logName, otherLogName);
log.info("将{}合并到{}.", otherLogName, logName);
long count = hyperLogLogService.count(logName);
log.info("合并后的count = {}.", count);
}
}
总结
从这里可以看出通过举例抛硬币引出redis的 hyperloglog 原理 HyperLogLog 所做的:它散列您观察到的每个新元素。散列的一部分用于索引寄存器(在我们之前的示例中,硬币+纸对。基本上我们将原始集合分成 m 个子集)。哈希的另一部分用于计算哈希中最长的前导零(我们的正面)。N+1 零游程的概率是长度 N 游程概率的一半,因此观察不同寄存器的值,它们被设置为迄今为止对给定子集观察到的最大零游程,HyperLogLog 能够提供一个非常好的近似基数。
HyperLogLog 的标准错误是 1.04/sqrt(m),其中“m”是使用的寄存器数。 Redis 使用了 16384 个寄存器,所以标准误差是 0.81%。 由于 Redis 实现中使用的散列函数具有 64 位输出,并且我们使用散列输出的 14 位来寻址我们的 16k 寄存器,我们还剩下 50 位,因此我们可以遇到的最长的零串将适合一个 6 位寄存器。这就是为什么 Redis HyperLogLog 值仅使用 12k 字节用于 16k 寄存器的原因。 由于使用了 64 位输出函数,这是 Google 在 [2] 中提出的算法的修改之一,因此对我们可以计数的集合的基数没有实际限制。此外值得注意的是,非常小的基数的误差往往非常小。下图显示了算法针对两个不同的大型集合的运行。集合的基数显示在 x 轴中,而相对误差(百分比)显示在 y 轴中。 红线和绿线是两个完全不相关的集合的两个不同运行。它显示了随着基数的增加,错误是如何保持一致的。然而,对于更小的基数,您可以享受更小的错误:
API 由三个新命令组成:
PFADD var element element … element PFCOUNT var PFMERGE dst src src src … src
为了纪念 Philippe Flajolet [4],命令前缀是“PF”。
更多了解链接: redis hyperloglog原理
如果喜欢的话,欢迎 🤞关注 👍点赞 💬评论 🤝收藏 🙌一起讨论 你的支持就是我✍️创作的动力! 💞💞💞