Skip to content

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 实战:月签到

bash

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 统计

bash

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 实战:附近的人

bash

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 底层

  • **内存优化** — 高级数据结构的内存优势

  • ← 返回 **核心数据结构索引**

Knowledge4J — Java 知识库