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;
@Service
public 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;
@SpringBootApplication
public 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"
# 输出: 2
curl "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
4.2 使用其他基数估计算法
Linear Counting:适用于小规模数据集。内存占用与数据集大小成正比。
LogLog-Beta:改进版的 HyperLogLog,可以在相同内存占用下提供更低的误差率。
HyperLogLog++:Google 对 HyperLogLog 的改进版本,优化了内存占用和误差率。
4.3 使用精确计数
评论