02.7 - 高级数据结构(BitMap / HyperLogLog / GEO)
定位: Redis 专为特定场景优化的高级数据结构——位运算、基数统计、地理位置查询
面试高频度: ⭐⭐⭐
考查方式: BitMap 的应用场景、HyperLogLog 原理(概率算法)、GeoHash 编码
一、这是什么?为什么需要它们?
是什么
Redis 提供了三种"高级"数据类型,它们底层基于 String 或 ZSet,但对外暴露了特定场景的语义:
| 类型 | 本质 | 能力 | 关键命令 |
|------|------|------|---------|
| BitMap | String 的位操作 | 位级操作(与/或/非/异或/计数) | SETBIT/GETBIT/BITCOUNT/BITOP |
| HyperLogLog | 概率基数统计 | 大基数去重计数(~0.81% 误差) | PFADD/PFCOUNT/PFMERGE |
| GEO | ZSet 编码 | 地理位置存储、距离计算、半径查询 | GEOADD/GEODIST/GEORADIUS |
二、原理拆解
2.1 BitMap——位图
2.2 HyperLogLog——基数统计
2.3 GEO——地理位置
三、图解全景
四、实战验证
4.1 BitMap 实战:月签到
redis-cli
# 用户 1001 的 1 月签到(假设 31 天)
127.0.0.1:6379> SETBIT sign:1001:202401 0 1 # 1 号签到
(integer) 0
127.0.0.1:6379> SETBIT sign:1001:202401 5 1 # 6 号签到
(integer) 0
127.0.0.1:6379> SETBIT sign:1001:202401 14 1 # 15 号签到
(integer) 0
# 检查某天是否签到
127.0.0.1:6379> GETBIT sign:1001:202401 0
(integer) 1 # 1 号签到了
# 月签到次数
127.0.0.1:6379> BITCOUNT sign:1001:202401
(integer) 3 # 签到了 3 天
# 持续签到统计(连击)
# 7 天内签到天数
127.0.0.1:6379> BITOP AND results sign:1001:202401 signed_days
(integer) ...4.2 HyperLogLog 实战:UV 统计
redis-cli
# 模拟用户访问
127.0.0.1:6379> PFADD page:uv user:1001 user:1002 user:1003
(integer) 1
127.0.0.1:6379> PFADD page:uv user:1001 user:1004 user:1005
(integer) 1
# 统计 UV
127.0.0.1:6379> PFCOUNT page:uv
(integer) 5 # 去重后 5 个独立用户
# 精确值与 HLL 对比测试
# 插入 10 万个元素
for i in $(seq 1 100000); do
redis-cli PFADD uv:test "user:$i" > /dev/null
done
redis-cli PFCOUNT uv:test
# 输出: 大约 99200-100800(误差 ~0.81%)
# 合并多个 HLL(统计多个页面总 UV)
127.0.0.1:6379> PFADD page1 user:1 user:2 user:3
127.0.0.1:6379> PFADD page2 user:3 user:4 user:5
127.0.0.1:6379> PFMERGE total:uv page1 page2
OK
127.0.0.1:6379> PFCOUNT total:uv
(integer) 5 # 去重总数4.3 GEO 实战:附近的人
redis-cli
# 1. 添加地点
127.0.0.1:6379> GEOADD restaurants 116.397 39.908 "全聚德" 116.326 39.993 "海底捞" 116.479 39.994 "大董"
(integer) 3
# 2. 计算距离
127.0.0.1:6379> GEODIST restaurants "全聚德" "海底捞" km
"9.8254" # 约 9.8 公里
# 3. 附近餐厅(以天安门为中心,半径 5km 内)
127.0.0.1:6379> GEORADIUS restaurants 116.397 39.913 5 km
1) "全聚德"
# 4. 返回距离和坐标
127.0.0.1:6379> GEORADIUS restaurants 116.397 39.913 10 km WITHDIST WITHCOORD COUNT 5
1) 1) "全聚德"
2) "0.6247"
3) 1) "116.396999657154"
2) "39.90799998626198"
# 5. 删除 GEO 元素(底层就是 ZREM)
127.0.0.1:6379> ZREM restaurants "海底捞"
(integer) 1五、面试视角
| 追问 | 答案要点 |
|------|---------|
| BitMap 的应用场景有哪些? | 签到、在线状态、权限标志、需要位运算的场景;每个 bit 独立有意义 |
| HyperLogLog 有误差为什么还要用它? | 12KB 固定内存统计任意大基数,UV 统计场景 0.81% 误差可接受,Set 要 GB 级内存 |
| HyperLogLog 的误差来源? | 概率算法本质:用 hash 的位数模式估计基数,小基数时误差可能更大(可通过 PFCOUNT 的纠正算法缓解)|
| GEO 底层是什么数据结构? | ZSet,GeoHash 编码为 score;GEORADIUS 通过 ZRANGEBYSCORE 实现 |
| GeoHash 编码有什么问题? | 边界问题(相邻区域边界上的点可能被滤掉),Redis 通过检查 8 个相邻网格来解决 |
| 可以用 ZSet 直接操作 GEO 吗? | 可以 ZREM/ZCARD/ZRANK,但 ZSCORE 返回的是 GeoHash 整数(用处不大);推荐用 GEO 命令 |
| PFMERGE 在什么场景用? | 合并多个页面的 UV 数据;按小时统计后归并为按天,精度几乎无损失 |
📚 相关链接
**限流与计数器** — BitMap 做计数器的场景
**ZSet与Skiplist** — GEO 的 ZSet 底层
**内存优化** — 高级数据结构的内存优势
← 返回 **核心数据结构索引**