当前位置: 首页 > news >正文

Redis 高级数据结构:Bitmap、HyperLogLog、GEO 深度解析

🔥 Redis 高级数据结构:Bitmap、HyperLogLog、GEO 深度解析

文章目录

  • 🔥 Redis 高级数据结构:Bitmap、HyperLogLog、GEO 深度解析
  • 🧠 一、高级数据结构全景图
    • 💡 Redis 高级数据结构价值
  • 🔢 二、Bitmap:位操作的艺术
    • 💡 内部原理与特性
    • 🚀 常用命令详解
    • 🎯 应用场景案例
  • 📊 三、HyperLogLog:基数估算的魔法
    • 💡 内部原理与特性
    • 🚀 常用命令详解
    • 🎯 应用场景案例
  • 🌐 四、GEO:地理位置服务
    • 💡 内部原理与特性
    • 🚀 常用命令详解
    • 🎯 应用场景案例
  • 💡 五、总结与选型指南
    • 📊 高级数据结构对比
    • 🎯 选型决策指南
    • 🔧 生产环境建议
    • 🚀 性能优化技巧

🧠 一、高级数据结构全景图

💡 Redis 高级数据结构价值

位级操作
基数统计
地理位置
业务问题
选择解决方案
Bitmap
HyperLogLog
GEO
用户签到
布隆过滤器
UV统计
去重计数
附近的人
范围查询

为什么需要高级数据结构​​:

  • 🚀 ​​极致性能​​:特殊优化,远超普通实现
  • 💾 ​​内存高效​​:相同数据占用更少内存
  • 🔧 ​​功能专精​​:为解决特定问题而生
  • ⚡ ​​生产验证​​:经过大规模应用验证

🔢 二、Bitmap:位操作的艺术

💡 内部原理与特性

​​Bitmap 本质上是 String 类型​​,但 Redis 提供了专门的位操作命令。每个 bit 位可以存储 0 或 1,极其节省内存。

用户ID
计算偏移量
位操作
结果统计

内存计算示例​​:

  • 1000万用户签到数据 ≈ 10000000 / 8 / 1024 / 1024 ≈ 1.19MB
  • 相同数据用 Set 存储 ≈ 至少 100MB

🚀 常用命令详解

# 设置指定偏移量的位值
SETBIT user:sign:202310 100 1    # 用户ID=100在2023/10/01签到# 获取位值
GETBIT user:sign:202310 100      # 检查用户是否签到# 统计位数为1的数量
BITCOUNT user:sign:202310        # 统计当天签到总数# 位运算操作
BITOP AND destkey key1 key2      # 位与运算
BITOP OR destkey key1 key2       # 位或运算
BITOP XOR destkey key1 key2      # 位异或运算
BITOP NOT destkey key           # 位非运算# 查找第一个设置或未设置的位
BITPOS user:sign:202310 1        # 第一个签到的用户
BITPOS user:sign:202310 0        # 第一个未签到的用户

🎯 应用场景案例

​​1. 用户签到系统​​:

public class SignService {// 用户签到public void sign(Long userId) {LocalDate today = LocalDate.now();String key = "user:sign:" + today.format(DateTimeFormatter.ofPattern("yyyyMM"));long offset = userId % 1000000; // 用户ID偏移量redis.setbit(key, offset, 1);// 设置过期时间(1个月)redis.expire(key, 30 * 24 * 60 * 60);}// 检查签到状态public boolean hasSigned(Long userId) {LocalDate today = LocalDate.now();String key = "user:sign:" + today.format(DateTimeFormatter.ofPattern("yyyyMM"));long offset = userId % 1000000;return redis.getbit(key, offset) == 1;}// 统计当月签到人数public long getMonthSignCount() {LocalDate today = LocalDate.now();String key = "user:sign:" + today.format(DateTimeFormatter.ofPattern("yyyyMM"));return redis.bitcount(key);}// 获取连续签到天数public int getContinuousSignDays(Long userId) {List<byte[]> bitFields = new ArrayList<>();LocalDate endDate = LocalDate.now();LocalDate startDate = endDate.minusDays(30);// 获取最近30天的签到数据while (!startDate.isAfter(endDate)) {String key = "user:sign:" + startDate.format(DateTimeFormatter.ofPattern("yyyyMMdd"));bitFields.add(redis.get(key.getBytes()));startDate = startDate.plusDays(1);}// 计算连续签到天数(实际实现需要更复杂的位运算)return calculateContinuousDays(bitFields, userId);}
}

​​2. 布隆过滤器辅助实现​​:

public class SimpleBloomFilter {private static final int SIZE = 2 << 24; // 布隆过滤器大小private static final int[] SEEDS = new int[]{3, 5, 7, 11, 13, 17, 19, 23}; // 哈希种子public boolean mightContain(String value) {for (int seed : SEEDS) {int hash = hash(value, seed) % SIZE;if (redis.getbit("bloom:filter", hash) == 0) {return false;}}return true;}public void add(String value) {for (int seed : SEEDS) {int hash = hash(value, seed) % SIZE;redis.setbit("bloom:filter", hash, 1);}}private int hash(String value, int seed) {int result = 0;for (int i = 0; i < value.length(); i++) {result = seed * result + value.charAt(i);}return (result & 0x7FFFFFFF);}
}

📊 三、HyperLogLog:基数估算的魔法

💡 内部原理与特性

HyperLogLog 使用概率算法来估算基数,​​标准误差为 0.81%​​,但内存占用极低。

输入元素
哈希函数
计算前导零
概率估算
基数结果

内存优势​​:

  • 统计1亿个不重复元素 ≈ 12KB内存
  • 传统Set存储1亿元素 ≈ 至少500MB

🚀 常用命令详解

# 添加元素
PFADD daily:uv:20231001 "user1" "user2" "user3"# 统计基数
PFCOUNT daily:uv:20231001        # 统计当天UV# 合并多个HyperLogLog
PFMERGE weekly:uv daily:uv:20231001 daily:uv:20231002
PFCOUNT weekly:uv                # 统计周UV

🎯 应用场景案例

​​1. 网站UV统计​​:

public class UVStatisticsService {// 记录每日UVpublic void recordUV(String userId) {String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));String key = "uv:daily:" + today;redis.pfadd(key, userId);// 设置过期时间(2天)redis.expire(key, 2 * 24 * 60 * 60);}// 获取当日UVpublic long getTodayUV() {String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));String key = "uv:daily:" + today;return redis.pfcount(key);}// 获取多日合并UVpublic long getRangeUV(LocalDate start, LocalDate end) {List<String> keys = new ArrayList<>();LocalDate current = start;while (!current.isAfter(end)) {keys.add("uv:daily:" + current.format(DateTimeFormatter.ofPattern("yyyyMMdd")));current = current.plusDays(1);}String tempKey = "uv:range:temp:" + System.currentTimeMillis();redis.pfmerge(tempKey, keys.toArray(new String[0]));long count = redis.pfcount(tempKey);redis.del(tempKey);return count;}
}

​​2. 实时数据去重统计​​:

public class RealTimeStatistics {// 实时统计独立用户数public void trackUserAction(String action, String userId) {String key = "action:" + action + ":" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHH"));redis.pfadd(key, userId);redis.expire(key, 2 * 60 * 60); // 过期时间2小时}// 获取小时级统计public long getHourlyActionCount(String action, LocalDateTime time) {String key = "action:" + action + ":" + time.format(DateTimeFormatter.ofPattern("yyyyMMddHH"));return redis.pfcount(key);}
}

🌐 四、GEO:地理位置服务

💡 内部原理与特性

GEO 基于 ZSet 实现,使用 ​​Geohash​​ 算法将二维坐标编码为一维字符串。

经纬度坐标
Geohash编码
ZSet存储
范围查询
距离计算

精度与特性​​:

  • 有效精度:约±0.5米(取决于Geohash精度)
  • 支持半径查询、距离计算
  • 底层使用ZSet,支持所有ZSet命令

🚀 常用命令详解

# 添加地理位置
GEOADD cities:location 116.405285 39.904989 "北京"
GEOADD cities:location 121.472644 31.231706 "上海"# 获取地理位置
GEOPOS cities:location "北京"# 计算距离
GEODIST cities:location "北京" "上海" km# 半径查询
GEORADIUS cities:location 116.405285 39.904989 100 km WITHDIST# 获取Geohash值
GEOHASH cities:location "北京"

🎯 应用场景案例

​​1. 附近的人功能​​:

public class NearbyService {// 更新用户位置public void updateUserLocation(Long userId, double longitude, double latitude) {String key = "user:location";redis.geoadd(key, longitude, latitude, "user:" + userId);}// 查找附近的人public List<UserDistance> findNearbyUsers(Long userId, double radius) {// 先获取当前用户位置List<GeoCoordinate> position = redis.geopos("user:location", "user:" + userId);if (position == null || position.isEmpty()) {return Collections.emptyList();}GeoCoordinate coord = position.get(0);// 查询附近用户List<GeoRadiusResponse> responses = redis.georadius("user:location", coord.getLongitude(), coord.getLatitude(), radius, GeoUnit.KM,GeoRadiusParam.geoRadiusParam().withDist());// 转换为用户列表return responses.stream().map(response -> new UserDistance(response.getMemberByString(),response.getDistance())).collect(Collectors.toList());}// 计算两个用户距离public Double getDistance(Long user1, Long user2) {return redis.geodist("user:location", "user:" + user1, "user:" + user2, GeoUnit.KM);}
}

​​2. 地理位置搜索​​:

public class LocationSearchService {// 添加地点public void addPlace(Place place) {redis.geoadd("places:location",place.getLongitude(),place.getLatitude(),place.getId());// 同时存储地点详细信息redis.hset("place:info:" + place.getId(), toMap(place));}// 半径搜索public List<Place> searchNearby(double lng, double lat, double radius) {List<GeoRadiusResponse> responses = redis.georadius("places:location",lng,lat,radius,GeoUnit.KM,GeoRadiusParam.geoRadiusParam().withDist());// 批量获取地点详情List<Place> places = new ArrayList<>();for (GeoRadiusResponse response : responses) {String placeId = response.getMemberByString();Map<String, String> info = redis.hgetAll("place:info:" + placeId);Place place = toPlace(info);place.setDistance(response.getDistance());places.add(place);}return places;}
}

💡 五、总结与选型指南

📊 高级数据结构对比

特性BitmapHyperLogLogGEO
底层实现String特殊结构ZSet
内存效率极高极高
精度精确近似(0.81%误差)精确
适用场景二值状态统计基数估算地理位置
典型应用签到、布隆过滤器UV统计、去重计数附近的人、LBS

🎯 选型决策指南

二值状态
基数估算
地理位置
业务需求
数据类型
Bitmap
HyperLogLog
GEO
用户签到
特征标记
UV统计
去重计数
附近的人
范围搜索

🔧 生产环境建议

  1. ​​Bitmap最佳实践​​:
    • 合理设计偏移量映射规则
    • 定期归档历史数据
    • 注意大Key问题(单个Bitmap不宜过大)
  2. HyperLogLog最佳实践​​: ​​
    • 理解并接受误差范围
    • 不适合需要精确计数的场景
    • 合并多个HLL时误差可能累积
  3. GEO最佳实践​​:
    • 合理设置Geohash精度
    • 结合传统数据库存储详细信息
    • 注意半径查询的性能影响

🚀 性能优化技巧

​​批量操作​​:使用管道(pipeline)提升批量操作性能
​​内存优化​​:定期清理过期数据,控制单个Key大小
​​架构设计​​:根据业务特点选择合适的数据结构
​​监控告警​​:设置内存使用监控和告警阈值


文章转载自:

http://uAsd0ff5.rszyf.cn
http://eHATa6oV.rszyf.cn
http://6ejrRtSp.rszyf.cn
http://Owi6OL5y.rszyf.cn
http://XPREVy2u.rszyf.cn
http://MTh24F2v.rszyf.cn
http://MPI23Q6a.rszyf.cn
http://J5FlMYyS.rszyf.cn
http://jfuqAVR6.rszyf.cn
http://3Itr4KUy.rszyf.cn
http://qTTnOKLy.rszyf.cn
http://47pVPa3Z.rszyf.cn
http://KEaop8Us.rszyf.cn
http://eIcwrloQ.rszyf.cn
http://6P5URqr6.rszyf.cn
http://hhqqcaSi.rszyf.cn
http://6UyqSDby.rszyf.cn
http://bguXURRg.rszyf.cn
http://JcXCSwne.rszyf.cn
http://Q7T8Talp.rszyf.cn
http://Fkku618p.rszyf.cn
http://Bx0encQs.rszyf.cn
http://eu0q7lKY.rszyf.cn
http://DW3Vd31m.rszyf.cn
http://5e8Seiaa.rszyf.cn
http://GR6pz9Tk.rszyf.cn
http://A4IpKTNe.rszyf.cn
http://TKIlC7V6.rszyf.cn
http://5g8cE3hw.rszyf.cn
http://8Pwix47D.rszyf.cn
http://www.dtcms.com/a/369086.html

相关文章:

  • 深度学习——迁移学习
  • 【uniapp】打包为h5在保留头部标题的同时配置网站标题不跟随页面路由更新
  • uni-app iOS 日志与崩溃分析全流程 多工具协作的实战指南
  • bat脚本- 将jar 包批量安装到 Maven 本地仓库
  • 力扣hot100:旋转图像(48)(详细图解以及核心思路剖析)
  • U盘文件系统转换指南:方法、原因与注意事项
  • AI智能优化SEO关键词策略实战
  • 共享线程池对@Scheduled定时任务的影响
  • 一张图看懂AI时代后端系统架构
  • 人工智能学习:什么是GRU模型
  • 高效管理网络段和端口集合的工具之ipset
  • 为什么要用VR全景?5个答案告诉你
  • 【Linux学习笔记】信号的深入理解之软件条件产生信号
  • 前端事件循环:代码世界的“排队”艺术!
  • JP4-7-MyLesson后台前端(一)
  • PPIO上线kimi-k2-0905,编码能力大幅提升
  • UniApp 页面通讯方案全解析:从 API 到状态管理的最佳实践
  • 嵌入式|Linux中打开视频流的两种方式V4l2和opencv
  • VBA 中的 Excel 工作表函数
  • Unix/Linux 平台通过 IP 地址获取接口名的 C++ 实现
  • EXCEL列数据前面补零
  • Big Data Analysis
  • 拿到一组数据在mars3d上渲染报错排查思路
  • 力扣hot100:搜索二维矩阵 II(常见误区与高效解法详解)(240)
  • 《从报错到运行:STM32G4 工程在 Keil 中的头文件配置与调试实战》
  • Meta AI眼镜Hypernova量产临近,微美全息构筑护城河引领人机交互变革浪潮
  • SQL表一共有几种写入方式
  • Vue3源码reactivity响应式篇之ReactiveEffect类
  • C++中的Reactor和Proactor模型进行系统性解析
  • 调试技巧:Chrome DevTools 与 Node.js Inspector