Redis数据结构大全(从基础到实战)
一、Redis核心数据结构总览
| 数据结构 | 底层实现 | 特性 | 常用场景 |
|---|---|---|---|
| String | SDS(简单动态字符串) | 二进制安全,可修改 | 缓存、计数器、分布式锁 |
| List | 双向链表/压缩列表 | 有序,可重复 | 消息队列、最新列表 |
| Hash | 哈希表/压缩哈希表 | 键值对集合 | 对象存储、购物车 |
| Set | 哈希表/整数集合 | 无序,不重复 | 标签、共同好友 |
| ZSet | 跳表+哈希表 | 有序,不重复 | 排行榜、延迟队列 |
| Bitmap | String的位操作 | 位操作 | 用户签到、活跃统计 |
| HyperLogLog | 特定算法 | 基数估算 | UV统计 |
| GEO | ZSet封装 | 地理位置 | 附近的人、摇一摇 |
| Stream | 链表+哈希 | 消息流 | 消息队列2.0 |
二、String(字符串) - 最常用
底层:SDS(Simple Dynamic String)
// SDS结构(简化版)
struct sdshdr {
int len; // 已用长度
int free; // 空闲长度
char buf[]; // 字节数组
};
// 优势:O(1)获取长度,二进制安全,可修改
使用场景
1. 缓存用户信息
# 存储用户信息(JSON格式)
SET user:1001 '{"id":1001,"name":"张三","age":20}'
EXPIRE user:1001 3600 # 1小时过期
# 获取
GET user:1001
# 批量操作
MSET user:1001 '张三' user:1002 '李四'
MGET user:1001 user:1002
2. 计数器
# 文章阅读数
INCR article:100:views # 阅读数+1
GET article:100:views # 获取阅读数
# 每日UV统计
INCR uv:2023-10-01 # 今天UV+1
# 商品库存
SET product:1001:stock 100
DECR product:1001:stock # 扣减库存
INCRBY product:1001:stock 10 # 增加库存
3. 分布式锁
# 加锁:SET key value NX PX
SET lock:order:1001 "client1" NX PX 30000
# NX: 不存在才设置
# PX: 30秒过期
# 值设为客户端ID,防止误删
# 解锁:Lua脚本保证原子性
EVAL """
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
""" 1 lock:order:1001 client1
4. 会话存储
# 存储session
SETEX session:abc123 1800 '{"user_id":1001,"login_time":...}'
# SETEX = SET + EXPIRE
# 续期
EXPIRE session:abc123 1800
三、Hash(哈希表) - 对象存储
底层:ziplist(小)或 hashtable(大)
Hash存储选择:
元素数 < 512 且 值 < 64字节 → 压缩列表(内存省)
否则 → 哈希表(性能好)
使用场景
1. 存储用户对象
# 存储用户信息(比JSON节省空间)
HSET user:1001 name "张三" age 20 city "北京"
# 获取部分字段
HGET user:1001 name
HMGET user:1001 name age
# 获取所有
HGETALL user:1001
# 修改单个字段
HSET user:1001 age 21
# 原子增减
HINCRBY user:1001 age 1
2. 购物车
# 用户1001的购物车
HSET cart:1001 product:1001 2 # 商品1001,数量2
HSET cart:1001 product:1002 1
# 增加商品数量
HINCRBY cart:1001 product:1001 1
# 删除商品
HDEL cart:1001 product:1002
# 获取购物车总价(需配合商品价格计算)
HGETALL cart:1001
3. 配置信息
# 系统配置
HSET config:system site_name "我的网站"
HSET config:system site_url "https://example.com"
HSET config:system admin_email "admin@example.com"
# 批量获取配置
HGETALL config:system
四、List(列表) - 有序列表
底层:ziplist(小)或 linkedlist(大)
Redis 3.2+:quicklist(双向链表+压缩列表结合)
使用场景
1. 消息队列
# 生产者:左进
LPUSH queue:task "任务1"
LPUSH queue:task "任务2"
# 消费者:右出
result = RPOP queue:task
# 返回"任务1"
# 阻塞版本(避免轮询)
BRPOP queue:task 30 # 最多等待30秒
2. 最新文章列表
# 用户发布文章
LPUSH user:1001:articles "文章3"
LPUSH user:1001:articles "文章2"
LPUSH user:1001:articles "文章1"
# 获取最新10篇文章
LRANGE user:1001:articles 0 9
# ["文章1", "文章2", "文章3"]
# 限制列表长度(只保留最近100条)
LTRIM user:1001:articles 0 99
3. 历史记录
# 用户浏览记录
LPUSH history:1001 "商品1001"
LPUSH history:1001 "商品1002"
# 获取最近10条浏览记录
LRANGE history:1001 0 9
# 去重历史记录(需配合Set)
LTRIM history:1001 0 49 # 只保留50条
五、Set(集合) - 无序唯一
底层:intset(整数)或 hashtable
intset:所有元素都是整数,节省内存
hashtable:元素复杂时用
使用场景
1. 标签系统
# 给用户打标签
SADD user:1001:tags "程序员" "90后" "北京"
SADD user:1002:tags "设计师" "90后" "上海"
# 获取用户标签
SMEMBERS user:1001:tags
# 共同标签(交集)
SINTER user:1001:tags user:1002:tags
# 返回:["90后"]
# 可能感兴趣(差集)
SDIFF user:1002:tags user:1001:tags
# 返回:["设计师", "上海"]
# 随机推荐标签
SRANDMEMBER user:1001:tags 2
2. 共同好友/关注
# 用户关注的人
SADD following:1001 1002 1003 1004
SADD following:1002 1001 1003
# 共同关注
SINTER following:1001 following:1002
# 返回:[1003]
# 你可能认识的人(差集)
SDIFF following:1002 following:1001
# 返回:[1001]
# 是否关注
SISMEMBER following:1001 1002
3. 抽奖/随机选取
# 参与抽奖用户
SADD lottery:20231001 1001 1002 1003 1004 1005
# 随机抽3人(不删除)
SRANDMEMBER lottery:20231001 3
# 随机抽3人(删除,不可重复抽)
SPOP lottery:20231001 3
# 统计参与人数
SCARD lottery:20231001
六、ZSet(有序集合) - 最重要的数据结构
底层:ziplist(小)或 skiplist+hashtable
跳表:支持范围查询,O(logN)复杂度
哈希表:支持O(1)的单个元素查找
使用场景
1. 排行榜
# 游戏分数排行榜
ZADD leaderboard:game1 1500 "玩家A"
ZADD leaderboard:game1 2000 "玩家B"
ZADD leaderboard:game1 1800 "玩家C"
# 获取前三名
ZREVRANGE leaderboard:game1 0 2 WITHSCORES
# 返回:[("玩家B", 2000), ("玩家C", 1800), ("玩家A", 1500)]
# 获取玩家排名(从0开始)
ZREVRANK leaderboard:game1 "玩家A" # 返回2
# 获取分数区间
ZRANGEBYSCORE leaderboard:game1 1700 2100
# 返回:["玩家C", "玩家B"]
# 增加分数
ZINCRBY leaderboard:game1 100 "玩家A"
2. 延迟队列
import time
import redis
# 添加延迟任务
def add_delay_task(task_id, execute_time, task_data):
# execute_time是Unix时间戳
redis.zadd("delay_queue", {task_id: execute_time})
redis.set(f"task:{task_id}", task_data)
# 获取到期的任务
def get_due_tasks():
now = int(time.time())
# 获取所有分数<=now的任务
task_ids = redis.zrangebyscore("delay_queue", 0, now)
for task_id in task_ids:
# 原子移除并获取
if redis.zrem("delay_queue", task_id):
task_data = redis.get(f"task:{task_id}")
process_task(task_id, task_data)
3. 时间轴/Feed流
# 用户发微博
def post_weibo(user_id, weibo_id, content):
timestamp = int(time.time())
# 写入自己的时间线
redis.zadd(f"timeline:{user_id}", {weibo_id: timestamp})
# 推送给粉丝
followers = redis.smembers(f"followers:{user_id}")
for follower_id in followers:
redis.zadd(f"feed:{follower_id}", {weibo_id: timestamp})
# 限制每个feed流只存1000条
redis.zremrangebyrank(f"feed:{follower_id}", 0, -1001)
# 读取时间线
def get_timeline(user_id, page=1, size=20):
start = (page-1) * size
end = start + size - 1
weibo_ids = redis.zrevrange(f"feed:{user_id}", start, end)
return weibo_ids
七、BitMap(位图) - 节省内存的王者
底层:String的位操作
用位来标记状态,极度节省内存
使用场景
1. 用户签到
# 用户1001签到记录
# 2023年10月签到(每月一个key)
key = "sign:1001:202310"
# 10月1日签到(位从0开始)
SETBIT key 0 1 # 第0位设为1
# 10月3日签到
SETBIT key 2 1
# 检查10月1日是否签到
GETBIT key 0 # 返回1
# 统计10月签到天数
BITCOUNT key # 返回2
# 获取10月前7天签到情况
GET key 0 0 # 获取第一个字节
2. 用户活跃统计
# 每日活跃用户统计
# 假设用户ID范围1-1000000
# 用户1001今天活跃
SETBIT active:2023-10-01 1001 1
# 用户1005今天活跃
SETBIT active:2023-10-01 1005 1
# 统计今天活跃用户数
BITCOUNT active:2023-10-01 # 返回2
# 计算连续7天活跃用户
# 用BITOP做位与运算
BITOP AND 7day_active active:20231001 active:20231002 ... active:20231007
BITCOUNT 7day_active
3. 布隆过滤器实现
# 简单的布隆过滤器
import mmh3 # 需要安装mmh3
class SimpleBloomFilter:
def __init__(self, size, hash_count):
self.size = size
self.hash_count = hash_count
self.key = "bloom:filter"
def add(self, item):
for seed in range(self.hash_count):
bit_index = mmh3.hash(item, seed) % self.size
redis.setbit(self.key, bit_index, 1)
def exists(self, item):
for seed in range(self.hash_count):
bit_index = mmh3.hash(item, seed) % self.size
if redis.getbit(self.key, bit_index) == 0:
return False
return True
八、HyperLogLog - 海量数据去重统计
特点:估算基数,误差0.81%,内存固定12KB
使用场景
1. 网站UV统计
# 统计每日UV
PFADD uv:2023-10-01 "192.168.1.1" "192.168.1.2" "192.168.1.1"
# 自动去重
# 获取UV
PFCOUNT uv:2023-10-01 # 返回2(去重后)
# 合并多日UV
PFMERGE uv:2023-10-week1 uv:20231001 uv:20231002 uv:20231003
PFCOUNT uv:2023-10-week1
2. 搜索词去重统计
# 统计每天不重复搜索词
PFADD search:20231001 "redis" "mysql" "redis" "python"
PFCOUNT search:20231001 # 返回3
九、GEO(地理位置) - 基于ZSet封装
使用场景
1. 附近的人
# 添加用户位置
GEOADD locations 116.397128 39.916527 "用户A" # 北京
GEOADD locations 121.473701 31.230416 "用户B" # 上海
# 查找北京附近500km的人
GEORADIUS locations 116.397128 39.916527 500 km WITHDIST
# 返回:[["用户A", "0.0000"], ["用户B", "1067.7652"]]
# 查找北京附近500km的人,按距离排序
GEORADIUS locations 116.397128 39.916527 500 km WITHDIST ASC
2. 摇一摇
# 用户摇一摇时,上报位置
GEOADD shake:20231001 116.397128 39.916527 "user:1001"
# 查找100米内的人
GEORADIUS shake:20231001 116.397128 39.916527 0.1 km WITHDIST
十、Stream - 消息队列增强版
使用场景
1. 消息队列
# 生产者:发送消息
XADD order:messages * user_id 1001 action "create" order_id 2001
# *表示自动生成ID,返回:1635678430000-0
# 消费者组
XGROUP CREATE order:messages order_group $ MKSTREAM
# 消费者
while True:
# 读取消息,>表示未读消息
messages = XREADGROUP GROUP order_group consumer1 COUNT 1 STREAMS order:messages >
for msg in messages:
process_message(msg)
# 确认消费
XACK order:messages order_group msg[0]
十一、实际项目组合应用
电商系统示例
# 1. 商品信息 - Hash
HSET product:1001 title "iPhone 15" price 6999 stock 100
# 2. 商品分类 - Set
SADD category:phones 1001 1002 1003
# 3. 购物车 - Hash
HSET cart:1001 product:1001 2
# 4. 用户最近浏览 - List
LPUSH history:1001 product:1001
LTRIM history:1001 0 49
# 5. 商品销量排行 - ZSet
ZINCRBY product:sales:202310 1 1001
# 6. 用户签到 - BitMap
SETBIT sign:1001:202310 0 1
# 7. 独立访客 - HyperLogLog
PFADD uv:20231001 1001
社交系统示例
# 1. 用户资料 - Hash
HSET user:1001 name "张三" age 20 city "北京"
# 2. 关注关系 - Set
SADD following:1001 1002 1003
SADD followers:1002 1001
# 3. 共同关注 - Set运算
SINTER following:1001 following:1002
# 4. 时间线 - ZSet
ZADD feed:1001 1635678430 "post:2001" 1635678440 "post:2002"
# 5. 私信 - List
LPUSH chat:1001:1002 "你好" 1635678450
RPOP chat:1001:1002
十二、选型指南
根据场景选择数据结构
需要缓存单个值 → String
存储对象 → Hash
有序列表/队列 → List
标签/去重 → Set
排行榜/延迟队列 → ZSet
签到/布隆过滤器 → BitMap
UV统计 → HyperLogLog
地理位置 → GEO
消息队列 → Stream
内存优化建议
1. 小对象用Hash,大对象用String
2. 列表元素<512用List,否则考虑拆分
3. 纯数字用Set的intset
4. 大量布尔标记用BitMap
5. 海量去重用HyperLogLog
十三、一句话记住
Redis数据结构 = 瑞士军刀,不同场景用不同工具
-
String:万金油,缓存、锁、计数
-
Hash:对象存储,购物车
-
List:队列、时间线
-
Set:标签、共同好友
-
ZSet:排行榜、延迟队列
-
BitMap:签到、布隆过滤器
-
HyperLogLog:UV统计
-
GEO:附近的人
-
Stream:消息队列2.0