Redis 数据结构详解
Redis 支持多种数据结构,每种结构都有其独特的特性和适用场景。以下是 Redis 中所有核心数据结构的详细解析,包括底层实现、操作命令、使用场景及注意事项:
一、String(字符串)
底层实现:
- 动态字符串(SDS, Simple Dynamic String),支持二进制安全(可存储图片、序列化对象等)。
- 根据内容自动选择编码方式:
int
(整数)、embstr
(短字符串)、raw
(长字符串)。
常用命令:
SET key value # 设置键值对
GET key # 获取值
INCR key # 值自增1(原子操作)
DECR key # 值自减1
APPEND key value # 追加内容
GETRANGE key start end # 截取子字符串
SETEX key seconds value # 设置值并指定过期时间
使用场景:
- 缓存:存储热点数据(如 HTML 片段、用户信息)。
- 计数器:统计页面访问量、点赞数(利用
INCR
的原子性)。 - 分布式锁:通过
SET key value NX EX
实现互斥锁。 - 二进制存储:存储序列化的对象或图片。
注意事项:
- 大字符串(如超过 10KB)可能影响性能,需谨慎使用。
二、List(列表)
底层实现:
- 3.2 版本前使用
ziplist
(压缩列表)或linkedlist
(双向链表)。 - 3.2 版本后统一为
quicklist
(由多个 ziplist 组成的双向链表,平衡内存和性能)。
常用命令:
LPUSH key value1 [value2] # 左侧插入元素
RPUSH key value1 [value2] # 右侧插入元素
LPOP key # 左侧弹出元素
RPOP key # 右侧弹出元素
LRANGE key start end # 获取指定范围内的元素
BLPOP key1 [key2] timeout # 阻塞式左侧弹出(用于消息队列)
使用场景:
- 消息队列:使用
LPUSH
+BRPOP
实现生产者-消费者模型。 - 最新消息列表:存储用户最近的 100 条动态。
- 栈或队列:通过
LPUSH
/RPOP
或RPUSH
/LPOP
组合实现。
注意事项:
- 避免单个 List 存储过多元素(如百万级),可能阻塞服务。
三、Hash(哈希表)
底层实现:
- 小规模数据使用
ziplist
(字段和值交替存储),大规模数据使用hashtable
(类似字典结构)。
常用命令:
HSET key field value # 设置字段值
HGET key field # 获取字段值
HGETALL key # 获取所有字段和值
HINCRBY key field increment # 字段值自增
HDEL key field # 删除字段
使用场景:
- 存储对象:如用户信息(
user:1
包含name
、age
等字段)。 - 聚合统计:记录商品的浏览次数、收藏量等。
注意事项:
HGETALL
可能返回大量数据,建议用HSCAN
分批次获取。- 字段过多时,内存占用可能较高。
四、Set(集合)
底层实现:
- 小规模数据使用
intset
(整数数组),大规模数据使用hashtable
(仅存储键,值为NULL
)。
常用命令:
SADD key member1 [member2] # 添加成员
SMEMBERS key # 获取所有成员(慎用,可能阻塞)
SISMEMBER key member # 判断成员是否存在
SINTER key1 key2 # 求交集(共同好友)
SUNION key1 key2 # 求并集
SREM key member # 删除成员
使用场景:
- 标签系统:记录用户的兴趣标签。
- 去重统计:记录文章的唯一阅读用户。
- 共同好友/兴趣:通过
SINTER
计算交集。
注意事项:
SMEMBERS
可能阻塞服务,优先使用SSCAN
。
五、Sorted Set(有序集合)
底层实现:
- 组合结构:
跳跃表(skiplist)
+哈希表
,支持按分数排序和快速查找成员。 - 小规模数据使用
ziplist
(按分数排序存储)。
常用命令:
ZADD key score1 member1 [score2 member2] # 添加成员及分数
ZRANGE key start end [WITHSCORES] # 按分数升序获取成员
ZREVRANGE key start end # 按分数降序获取成员
ZRANK key member # 获取成员排名(升序)
ZSCORE key member # 获取成员分数
ZRANGEBYSCORE key min max # 按分数范围查询
使用场景:
- 排行榜:实时更新游戏玩家分数排名。
- 带权重的队列:按优先级处理任务。
- 时间轴:按时间戳排序的消息列表。
注意事项:
- 分数(
score
)为浮点数,可重复,但成员(member
)唯一。
六、Bitmaps(位图)
底层实现:
- 基于 String 类型,按位操作,最大支持 2^32 位。
常用命令:
SETBIT key offset 1/0 # 设置某位的值(0或1)
GETBIT key offset # 获取某位的值
BITCOUNT key [start end] # 统计值为1的位数
BITOP AND/OR/XOR destkey key1 key2 # 位运算(与/或/异或)
使用场景:
- 用户在线状态:每天用一个 Bitmap 记录用户的登录状态。
- 活跃用户统计:统计一周内连续打卡的用户。
- 布隆过滤器:结合多个哈希函数实现低成本存在性检测。
注意事项:
- 偏移量(
offset
)从0开始,最大值为 2^32-1。
七、HyperLogLog(基数统计)
底层实现:
- 概率算法,固定使用 12KB 内存,误差率约 0.81%。
常用命令:
PFADD key element1 [element2] # 添加元素
PFCOUNT key1 [key2] # 估算基数(独立元素数量)
PFMERGE destkey key1 key2 # 合并多个 HLL
使用场景:
- 独立访客统计(UV):统计一天内访问网站的不同用户数。
- 大规模数据去重:如统计搜索关键词的唯一数量。
注意事项:
- 结果为近似值,不能获取具体成员。
八、GEO(地理位置)
底层实现:
- 基于 Sorted Set,使用 Geohash 编码将经纬度转换为分数(
score
)。
常用命令:
GEOADD key longitude latitude member # 添加地理位置
GEODIST key member1 member2 [unit] # 计算两地距离(米/千米等)
GEORADIUS key longitude latitude radius unit [WITHCOORD] # 查询范围内的成员
使用场景:
- 附近的人:根据用户位置查找附近的服务或用户。
- 地理位置围栏:监控车辆是否进入特定区域。
注意事项:
- 底层为 Sorted Set,可使用
ZREM
删除成员。
九、Stream(消息流)
底层实现:
- 类似日志结构,每个条目包含唯一 ID 和多个键值对,支持消费者组。
常用命令:
XADD key * field1 value1 [field2 value2] # 添加消息(*自动生成ID)
XREAD [COUNT n] [BLOCK ms] STREAMS key ID # 读取消息
XGROUP CREATE key groupname ID # 创建消费者组
XACK key groupname ID # 确认消息处理完成
使用场景:
- 消息队列:支持多消费者组、消息回溯和持久化。
- 事件溯源:记录用户操作日志,支持按时间查询。
注意事项:
- 消息 ID 格式为
<时间戳>-<序列号>
(如1630000000000-0
)。
十、其他扩展类型
-
Bitfield
- 将 String 视为位数组,支持对任意位进行原子操作(如无符号整数读写)。
- 命令:
BITFIELD key SET type offset value
。
-
JSON 模块(需额外加载)
- 支持 JSON 数据的存储和查询(如
JSON.SET
,JSON.GET
)。
- 支持 JSON 数据的存储和查询(如
总结:数据结构选择指南
场景 | 推荐数据结构 |
---|---|
简单键值存储 | String |
对象存储(多字段) | Hash |
队列/栈 | List |
无序去重集合 | Set |
有序排行榜 | Sorted Set |
二值状态统计 | Bitmaps |
近似去重统计 | HyperLogLog |
地理位置 | GEO |
消息队列(高级需求) | Stream |
通过合理选择数据结构,可以显著提升 Redis 的性能和内存利用率。如果需要更深入某个结构的实现原理(如跳跃表、压缩列表),或实际应用案例,可以进一步探讨!