当前位置:网站首页>Redis HyperLogLog 是什么?这些场景使用让我枪出如龙一笑破苍穹
Redis HyperLogLog 是什么?这些场景使用让我枪出如龙一笑破苍穹
2022-06-21 18:55:00 【InfoQ】
- 统计一个
APP的日活、月活数;
- 统计一个页面的每天被多少个不同账户访问量(Unique Visitor,UV));
- 统计用户每天搜索不同词条的个数;
- 统计注册 IP 数。
HyperLogLogRedission
使用 Set 实现
SADD Redis为什么这么快:uv 肖菜鸡 谢霸哥 肖菜鸡
(integer) 1
SCARDSCARD Redis为什么这么快:uv
(integer) 2
使用 Hash 实现
valueHLENHSET Redis为什么这么快 肖菜鸡 1
// 统计 UV
HLEN Redis为什么这么快
使用 Bitmap 实现
GETBIT、SETBIT
SETBIT 和 BITCOUNToffset6SETBIT 巧用Redis数据类型实现亿级数据统计 6 1
BITCOUNTBITCOUNT 巧用Redis数据类型实现亿级数据统计
HyperLogLog 王者方案
HashMapHyperLogLogHyperLogLog0.81%Redis 实战
PFADD、PFCOUNT、PFMERGEPFADD
HyperLogLogPFADD Redis主从同步原理:uv userID1 userID 2 useID3
PFCOUNT
PFCOUNTPFCOUNT Redis主从同步原理:uv
PFMERGE 使用场景
HyperLogLog` 除了上面的 `PFADD` 和 `PFCOIUNT` 外,还提供了 `PFMERGE
语法
PFMERGE destkey sourcekey [sourcekey ...]
PFMERGEHyperLogLogPFADD Redis数据 user1 user2 user3
PFADD MySQL数据 user1 user2 user4
PFMERGE 数据库 Redis数据 MySQL数据
PFCOUNT 数据库 // 返回值 = 4
Redission 实战
pom 依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.16.7</version>
</dependency>
添加数据到 Log
// 添加单个元素
public <T> void add(String logName, T item) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);
hyperLogLog.add(item);
}
// 将集合数据添加到 HyperLogLog
public <T> void addAll(String logName, List<T> items) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);
hyperLogLog.addAll(items);
}
合并
/**
* 将 otherLogNames 的 log 合并到 logName
*
* @param logName 当前 log
* @param otherLogNames 需要合并到当前 log 的其他 logs
* @param <T>
*/
public <T> void merge(String logName, String... otherLogNames) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);
hyperLogLog.mergeWith(otherLogNames);
}
统计基数
public <T> long count(String logName) {
RHyperLogLog<T> hyperLogLog = redissonClient.getHyperLogLog(logName);
return hyperLogLog.count();
}
单元测试
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(classes = RedissionApplication.class)
public class HyperLogLogTest {
@Autowired
private HyperLogLogService hyperLogLogService;
@Test
public void testAdd() {
String logName = "码哥字节:Redis为什么这么快:uv";
String item = "肖菜鸡";
hyperLogLogService.add(logName, item);
log.info("添加元素[{}]到 log [{}] 中。", item, logName);
}
@Test
public void testCount() {
String logName = "码哥字节:Redis为什么这么快:uv";
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 = "码哥字节:Redis多线程模型原理与实战:uv";
hyperLogLogService.addAll(otherLogName, items);
log.info("添加 {} 个元素到 log [{}] 中。", items.size(), otherLogName);
String logName = "码哥字节:Redis为什么这么快:uv";
hyperLogLogService.merge(logName, otherLogName);
log.info("将 {} 合并到 {}.", otherLogName, logName);
long count = hyperLogLogService.count(logName);
log.info("合并后的 count = {}.", count);
}
}
基本原理
1/2kk1knk1, k2 ... kn k_max
- https://www.zhihu.com/question/53416615
- https://en.wikipedia.org/wiki/HyperLogLog
- 用户日活月活怎么统计 - Redis HyperLogLog 详解
HyperLogLogHyperLogLog163842^14maxbitsbitsmaxbits=632^14 * 6 / 8 = 12k总结
HashBitmapHyperLogLogHash:算法简单,统计精度高,少量数据下使用,对于海量数据会占据大量内存;
Bitmap:位图算法,适合用于「二值统计场景」,具体可参考我这篇文章,对于大量不同页面数据统计还是会占用较大内存。
Set:利用去重特性实现,一个 Set 就保存了千万个用户的 ID,页面多了消耗的内存也太大了。在 Redis 里面,每个HyperLogLog键只需要花费 12 KB 内存,就可以计算接近2^64个不同元素的基数。因为HyperLogLog只会根据输入元素来计算基数,而不会储存输入元素本身,所以HyperLogLog不能像集合那样,返回输入的各个元素。
HyperLogLog是一种算法,并非Redis独有
- 目的是做基数统计,故不是集合,不会保存元数据,只记录数量而不是数值
- 耗空间极小,支持输入非常体积的数据量
- 核心是基数估算算法,主要表现为计算时内存的使用和数据合并的处理。最终数值存在一定误差
Redis中每个Hyperloglogkey占用了12K的内存用于标记基数(官方文档)
pfadd命令并不会一次性分配12k内存,而是随着基数的增加而逐渐增加内存分配;而pfmerge操作则会将sourcekey合并后存储在12k大小的key中,由hyperloglog合并操作的原理(两个Hyperloglog合并时需要单独比较每个桶的值)可以很容易理解。
- 误差说明:基数估计的结果是一个带有
0.81%标准错误(standard error)的近似值。是可接受的范围
Redis对HyperLogLog的存储进行优化,在计数比较小时,存储空间采用稀疏矩阵存储,空间占用很小,仅仅在计数慢慢变大,稀疏矩阵占用空间渐渐超过了阈值时才会一次性转变成稠密矩阵,才会占用 12k 的空间
好文推荐
- Redis 实战篇:巧用数据类型实现亿级数据统计
- 硬核 | Redis 布隆(Bloom Filter)过滤器原理与实战
- Redis 实战篇:巧用 Bitmap 实现亿级海量数据统计
- Redis 实战篇:通过 Geo 类型实现附近的人邂逅女神
- Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结
边栏推荐
猜你喜欢

Anfulai embedded weekly report (issue 270): June 13, 2022 to June 19, 2022

IAR重大升级,支持VS Code,ST发布第一个带有处理单元的传感器

Delete the penultimate node - linked list topic

The highest monthly salary is 17k. As long as there is a field of hope in your heart, hard work will usher in a green land~

亿腾医药在港招股书第三次“失效”:上市实质延后,红杉等为股东

Servlet使用

瀚高数据库自定义操作符'!~~'

QX2308高效 PFM 同步升压 DC/DC 变换器

How MySQL sums columns
![[wechat applet failed to change appid] wechat applet failed to modify appid all the time and reported an error. Tourist appid solution](/img/b7/6ce97e345a4f8fce7f3aeb2c472e13.png)
[wechat applet failed to change appid] wechat applet failed to modify appid all the time and reported an error. Tourist appid solution
随机推荐
Inno setup installation path box learning
IAR major upgrade, support vs code, St release the first sensor with processing unit
JMeter thread duration
SD6.20集训总结
The difference between break and continue
集成公告|Anima协议上线Moonbeam
Servlet usage
The 17th National University RT thread innovation special award
Comment MySQL additionne les colonnes
Integration announcement | animation protocol launch moonbeam
JVM memory structure
高等代数_第9章:线性映射
Snake game project full version
互联网协议入门详解--五层模型
Laravel 使用 PhpOffice 导入导出 Excel
1156 Sexy Primes
How to query all tables in MySQL
Inno setup window drag learning
[wechat applet failed to change appid] wechat applet failed to modify appid all the time and reported an error. Tourist appid solution
break和continue的区别