Redis数据结构大全(从基础到实战)

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

相关推荐
散一世繁华,颠半世琉璃4 小时前
高并发下的 Redis 优化:如何利用HeavyKeeper快速定位热 key
数据库·redis·缓存
共享家95275 小时前
从 Redis 到分布式架构
redis·分布式·架构
一叶飘零_sweeeet14 小时前
从单机到集群:Redis部署全攻略
数据库·redis·缓存
java1234_小锋18 小时前
Redis到底支不支持事务啊?
java·数据库·redis
Li_76953220 小时前
Redis —— 基本数据类型 Set Zset (三)
redis
kkoral21 小时前
单机docker部署的redis sentinel,使用python调用redis,报错
redis·python·docker·sentinel
java1234_小锋1 天前
Redis6为什么引入了多线程?
java·redis
DemonAvenger1 天前
Redis与MySQL双剑合璧:缓存更新策略与数据一致性保障
数据库·redis·性能优化