redis从入门到熟练 (三) Geospecial,Hyperloglog,Bitmap类型详解

56 阅读5分钟

1.Geospecial

1.介绍,是什么

Redis的Geospatial(地理空间)功能是一组专门用于处理地理位置数据的命令,基于Redis的有序集合(Sorted Set)数据结构实现。 它允许你存储地理坐标(经度和纬度),并执行各种基于位置的计算和查询。

# GEOADD:添加一个或多个地理空间位置到指定的key中
GEOADD cities 13.361389 38.115556 "Palermo" 15.087269 37.502669 "Catania"
# GEOPOS:获取一个或多个位置的地理坐标
GEOPOS cities "Palermo" "Catania"
# GEODIST:计算两个位置之间的距离
GEODIST cities "Palermo" "Catania" km
# GEORADIUS:以给定的经纬度为中心,查找指定半径内的位置
GEORADIUS cities 15 37 100 km
# GEORADIUSBYMEMBER:以给定的成员为中心,查找指定半径内的位置
GEORADIUSBYMEMBER cities "Palermo" 200 km
# GEOHASH:获取一个或多个位置的Geohash值
GEOHASH cities "Palermo"

2.应用场景

  1. 附近的人/地点搜索:
  • 查找用户周围5公里内的餐厅
  • 显示附近的加油站或ATM机
  • 社交应用中查找附近的朋友
  1. 配送和物流
  • 查找用户周围5公里内的餐厅
  • 显示附近的加油站或ATM机

3.底层的数据结构

Geospecial的底层数据结构是Zset(有序集合)

所有 GEO 命令实际上都是操作一个特殊的 Sorted Set:
Key 对应 Sorted Set 的名称
Member 是地点名称(如"北京"、"上海")
Score 是经过 Geohash 编码后的 52 位整数值

Redis 使用 Geohash 将二维的经纬度转换为一维的分数(score):
经纬度标准化:
经度范围 [-180,180] → 映射到 [0,2^32]
纬度范围 [-90,90] → 映射到 [0,2^32]

交替编码:
将经度和纬度的二进制位交替排列
例如:经度比特1 → 纬度比特1 → 经度比特2 → 纬度比特2 → ...

生成52位整数:
Redis 使用 52 位整数表示(对应 double 类型的精度)
26位来自经度,26位来自纬度

例如存储坐标(经度13.361389,纬度38.115556):
1.标准化坐标
2.转换为二进制表示
3.交替组合经度和纬度比特
4.生成最终的52位整数作为Sorted Set的score

为什么选择这种设计?

高效的范围查询:
Geohash 具有"前缀相似性":附近的位置有相似的hash值
使用ZRANGEBYSCORE可以高效查询附近位置

内存效率: 相比单独存储经纬度,使用一个score更节省空间 复用已有的Sorted Set数据结构

计算优化: 距离计算时可以直接解码出原始坐标 使用Haversine公式计算实际距离

2.Hyperloglog

1.介绍,是什么

HyperLogLog 是 Redis 提供的一种概率性基数统计数据结构,用于高效地估计一个集合中不重复元素的数量(基数统计)。

核心特点:
极低的内存占用:统计上亿级别的基数只需约 12KB 内存
固定内存消耗:无论统计多少元素,内存占用基本恒定
概率性算法:有约 0.81% 的标准误差
支持合并:多个 HyperLogLog 可以合并计算总基数

2.应用场景

1.大规模的uv统计

  • 网站uv统计
  • 文章阅读量
  • 广告曝光 2.大数据去重
  • 搜索引擎:统计不同搜索查询的数量
  • 电商平台:统计不同搜索关键词的数量
  • 日志分析:统计不同错误类型的出现次数

3.底层的数据结构

有两种编码格式来优化不同场景下的存储

基本工作流程

1.哈希计算:对输入元素执行64位哈希(Redis使用MurmurHash64A)如果是稀疏矩阵,则使用32位哈希
2.寄存器选择:前14位作为寄存器索引(2^14=16384个寄存器)
3.前导零计数:计算剩余50位中第一个1出现的位置
4.寄存器更新:如果新值大于当前寄存器值,则更新

3.Bitmap

1.介绍,是什么

Redis的Bitmap实际上是基于String类型实现的,将字符串视为一个由二进制位组成的数组,每个位置可以存储0或1,用来处理二进制的数据

2.应用场景

  1. 横向统计 某一个用户存储多个状态值(不推荐)
    签到场景为例,用户的id为key,时间为offset偏移值,value为1代表签到,0代表未签到,但是这样维护起来很麻烦,每个用户都要维护一个bitmap
  2. 纵向统计 每个key值作为业务标识,offset为用户的uid (推荐)
    比如短视频平台,对每个视频的标识为key,然后uid作为偏移量,value为1代表该用户喜欢该视频,0代表不喜欢该视频
  3. 实现一个简单的布隆过滤器(利用位数组的特性)

3.底层的数据结构

底层数据结构是String

假设执行

SETBIT mybitmap 10 1

内存中的实际存储情况

字节偏移量:0        1
bit布局:  00000000 01000000
bit索引:  0-7      8-15
第10位被设为1

自动扩展机制:
当设置的offset超过当前字符串长度时,Redis会自动扩展字符串并用0填充间隙
扩展是以字节为单位的,即使只设置一个bit也可能导致多个字节的分配

内存预分配:
Redis采用预分配策略减少内存重分配次数
每次扩展时可能会分配比实际需要更多的空间

稀疏位图处理:
对于非常大的offset,Redis会分配中间所有必要的字节
这可能导致稀疏位图实际占用内存比预期大

与普通String的异同

特性Bitmap视角String视角
存储内容二进制位数组字节数组
访问方式按bit偏移量按字节偏移量
扩展单位bit(实际扩展字节)字节
典型操作位运算追加、截取等