写点什么

基于 HyperLogLog 统计直播访问人数 -Redis

作者:储诚益
  • 2025-02-02
    安徽
  • 本文字数:3637 字

    阅读完需:约 12 分钟

HyperLogLog 是 Redis 提供的一种用于基数统计(去重计数)的数据结构。它非常适合用于统计大规模数据集的独立元素数量,例如统计直播的独立访问人数。以下是一个基于 HyperLogLog 的直播访问人数统计案例。


场景描述假设

我们有一个直播平台,需要统计每个直播间的独立访问人数。由于访问人数可能非常大,使用传统的集合(Set)会占用大量内存,而 HyperLogLog 可以在占用固定内存(12 KB)的情况下,高效地估算独立访问人数。实现步骤初始化 HyperLogLog:为每个直播间创建一个 HyperLogLog 数据结构。记录访问用户:每当有用户访问直播间时,将用户的唯一标识(如用户 ID)添加到 HyperLogLog 中。统计独立访问人数:使用 HyperLogLog 的 PFCOUNT 命令估算独立访问人数。合并多个 HyperLogLog:如果需要统计多个直播间的总访问人数,可以将多个 HyperLogLog 合并。

代码实现

2.1 添加依赖

在 pom.xml 中添加以下依赖:

<dependencies>    <!-- Spring Boot Starter Web -->    <dependency>        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>
<!-- Spring Data Redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>
<!-- Lombok (可选) --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <scope>provided</scope> </dependency></dependencies>
复制代码

2.2 创建 Redis 服务类

创建一个服务类 HyperLogLogService,用于操作 Redis 的 HyperLogLog。

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.stereotype.Service;
@Servicepublic class HyperLogLogService {
@Autowired private RedisTemplate<String, String> redisTemplate;
/** * 记录访问直播间的用户 * * @param liveId 直播间 ID * @param userId 用户 ID */ public void recordVisitor(String liveId, String userId) { String key = "live:" + liveId + ":visitors"; redisTemplate.opsForHyperLogLog().add(key, userId); System.out.println("User " + userId + " visited live " + liveId); }
/** * 获取直播间的独立访问人数 * * @param liveId 直播间 ID * @return 独立访问人数 */ public long getUniqueVisitors(String liveId) { String key = "live:" + liveId + ":visitors"; Long count = redisTemplate.opsForHyperLogLog().size(key); System.out.println("Live " + liveId + " has " + count + " unique visitors"); return count != null ? count : 0; }
/** * 合并多个直播间的访问人数 * * @param liveIds 直播间 ID 列表 * @param targetKey 合并后的 HyperLogLog 键名 */ public void mergeLiveVisitors(Iterable<String> liveIds, String targetKey) { String[] sourceKeys = new String[((java.util.Collection<?>) liveIds).size()]; int i = 0; for (String liveId : liveIds) { sourceKeys[i++] = "live:" + liveId + ":visitors"; } redisTemplate.opsForHyperLogLog().union(targetKey, sourceKeys); System.out.println("Merged visitors from " + ((java.util.Collection<?>) liveIds).size() + " lives into " + targetKey); } /** * 获取合并后的独立访问人数 * * @param targetKey 合并后的 HyperLogLog 键名 * @return 独立访问人数 */ public long getMergedUniqueVisitors(String targetKey) { Long count = redisTemplate.opsForHyperLogLog().size(targetKey); System.out.println("Merged live visitors: " + count + " unique visitors"); return count != null ? count : 0; }}
复制代码


2.3 创建控制器类

创建一个控制器类 LiveController,用于提供 HTTP 接口。

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.*;
import java.util.Arrays;import java.util.List;
@RestController@RequestMapping("/live")public class LiveController {
@Autowired private HyperLogLogService hyperLogLogService;
/** * 记录用户访问直播间 * * @param liveId 直播间 ID * @param userId 用户 ID */ @PostMapping("/visit") public String recordVisitor(@RequestParam String liveId, @RequestParam String userId) { hyperLogLogService.recordVisitor(liveId, userId); return "User " + userId + " visited live " + liveId; }
/** * 获取直播间的独立访问人数 * * @param liveId 直播间 ID * @return 独立访问人数 */ @GetMapping("/visitors") public long getUniqueVisitors(@RequestParam String liveId) { return hyperLogLogService.getUniqueVisitors(liveId); }
/** * 合并多个直播间的访问人数 * * @param liveIds 直播间 ID 列表 * @param targetKey 合并后的 HyperLogLog 键名 */ @PostMapping("/merge") public String mergeLiveVisitors(@RequestParam List<String> liveIds, @RequestParam String targetKey) { hyperLogLogService.mergeLiveVisitors(liveIds, targetKey); return "Merged visitors from " + liveIds.size() + " lives into " + targetKey; }
/** * 获取合并后的独立访问人数 * * @param targetKey 合并后的 HyperLogLog 键名 * @return 独立访问人数 */ @GetMapping("/merged-visitors") public long getMergedUniqueVisitors(@RequestParam String targetKey) { return hyperLogLogService.getMergedUniqueVisitors(targetKey); }}
复制代码

 

2.4 启动类

确保有一个 Spring Boot 启动类:

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplicationpublic class HyperLogLogApplication { public static void main(String[] args) { SpringApplication.run(HyperLogLogApplication.class, args); }}
复制代码


3. 测试接口

启动 Spring Boot 应用后,可以使用以下方式测试接口:

3.1 记录用户访问

curl -X POST "http://localhost:8080/live/visit?liveId=live_1&userId=user_1"curl -X POST "http://localhost:8080/live/visit?liveId=live_1&userId=user_2"curl -X POST "http://localhost:8080/live/visit?liveId=live_2&userId=user_2"curl -X POST "http://localhost:8080/live/visit?liveId=live_2&userId=user_3"
复制代码

3.2 获取独立访问人数

curl "http://localhost:8080/live/visitors?liveId=live_1"# 输出: 2curl "http://localhost:8080/live/visitors?liveId=live_2"# 输出: 2
复制代码

4. 10 亿个数据的标准误差控制在 0.01%以内

要将 HyperLogLog 在 10 亿个数据(基数)的标准误差控制在 0.01% 以内,需要理解 HyperLogLog 的工作原理,并采取一些优化措施。以下是详细的解决方案:

1. HyperLogLog 的标准误差



2. 计算所需的寄存器数量


3. Redis 的 HyperLogLog 限制

Redis 的 HyperLogLog 实现固定使用 16,384 个寄存器,无法直接调整寄存器数量。因此,无法通过 Redis 的 HyperLogLog 实现将标准误差降低到 0.01%。

如果需要将标准误差降低到 0.01%,可以考虑以下替代方案:

4.1 使用多个 HyperLogLog

  • 将数据集分成多个部分,每个部分使用一个独立的 HyperLogLog。

  • 最后将所有 HyperLogLog 的结果合并。

  • 这种方法可以间接增加寄存器的数量,但实现复杂,且内存占用会增加。

4.2 使用其他基数估计算法

  • Linear Counting:适用于小规模数据集。内存占用与数据集大小成正比。

  • LogLog-Beta:改进版的 HyperLogLog,可以在相同内存占用下提供更低的误差率。

  • HyperLogLog++:Google 对 HyperLogLog 的改进版本,优化了内存占用和误差率。

4.3 使用精确计数

  • 如果需要极高的精度(如 0.01% 误差),可以考虑使用精确计数方法,例如:集合(Set):使用 Redis 的集合存储所有独立元素。内存占用较高,但误差为 0。布隆过滤器(Bloom Filter):适用于判断元素是否存在,但不适合精确计数


发布于: 刚刚阅读数: 3
用户头像

储诚益

关注

还未添加个人签名 2017-12-19 加入

还未添加个人简介

评论

发布
暂无评论
基于 HyperLogLog 统计直播访问人数-Redis_Java 面试_储诚益_InfoQ写作社区