【Redis】Redis的5种核心数据结构和实战场景对应(在项目中的用法)
【Redis】Redis的5种核心数据结构和实战场景对应(在项目中的用法)
- 一、String
- 1. 缓存对象
- 2. 分布式锁
- 3. 共享Session
- 4. 计数器:记录访问量/播放量
- 5. 计数器限流:简单的流量控制
- 二、Hash
- 1. 存储对象
- 2. 购物车
- 3. 简单的动态配置管理
- 4. 多维度统计:按分类计数
- 三、List
- 1. 简单消息队列
- 2. 实时消息流:朋友圈/动态展示
- 3. 栈结构:实现“最近访问”功能
- 四、Set
- 1. 抽奖系统
- 2. 白名单/黑名单
- 3. 点赞功能
- 4. 集合运算:共同好友/共同兴趣
- 五、ZSet
- 1. 排行榜
- 2. 滑动窗口限流
- 3. 浏览记录
- 4. 在线用户列表
- 补充:Stream
一、String
String 就是单个的 KV 结构,用法灵活,是 Redis 最基础也最常用的数据结构之一。
1. 缓存对象
把高频访问的对象(比如用户信息、商品详情)序列化成JSON字符串,以“业务标识+ID”为key存入Redis,比如user:info:1001
,value存用户信息的JSON串。
优势:减轻数据库压力,查询速度从毫秒级(数据库)降到微秒级(Redis);还能通过设置过期时间,自动淘汰冷门数据。
2. 分布式锁
利用String的SET NX EX
命令(不存在则设置,同时加过期时间)实现分布式锁:
- 线程抢锁时执行
SET lock:order true NX EX 10
,成功则拿到锁,失败则等待重试; - 执行完业务后用
DEL lock:order
释放锁,也能靠过期时间避免“死锁”(比如线程崩溃没释放,过期后自动解锁)。
注意:实际使用时需配合“锁续期”(比如Redisson的看门狗机制),避免业务没执行完锁就过期。
3. 共享Session
分布式系统中,用户登录时生成一个 Token,以 Token 为 key、用户信息为 value,设置过期时间存到 Redis;
用户一段时间没访问系统,Token 就过期失效,需要重新登录;
只要用户访问,就通过拦截器给 Token 续期。
优势:替代传统的“服务器存Session”,实现多服务间的登录状态共享。
4. 计数器:记录访问量/播放量
利用String的原子自增命令INCR
,实现无并发问题的计数器:
以网页路径或视频 ID 为 key,次数为 value,访问一次就自增一次,实现记录器效果。
- 统计网页访问量:以
page:view:/home
为key,每次访问执行INCR page:view:/home
; - 统计视频播放量:以
video:play:10086
为key,每次播放执行INCR video:play:10086
。
特点:原子操作,不用担心多线程并发导致的计数不准。
5. 计数器限流:简单的流量控制
针对IP或接口做限流,比如“每个IP每分钟最多访问120次”:
- 以 IP 或接口路径为 key,访问次数为 value,访问一次就自增一次,并设置过期时间60秒;
- 若自增后的值超过120,则拒绝请求。
注意:这种方式有“临界问题”——比如第60秒来了120次请求,第61秒又来120次,两分钟内实际访问了240次,需结合滑动窗口限流(下文ZSet会讲)优化。
二、Hash
Hash 结构可以理解为能存储多组 KV 的 String,适合对对象字段做精细操作。
1. 存储对象
比如存用户信息,不用把整个用户JSON串存在String里,而是用Hash拆分字段:
- key为
user:1001
,field为name
(值:张三)、age
(值:25)、phone
(值:138xxxx); - 若要修改用户年龄,直接执行
HSET user:1001 age 26
,不用重新序列化整个对象。
优势:减少数据传输量,修改单个字段更高效。
2. 购物车
以用户ID为Hash的key,商品ID为field,商品数据为value:
- 用户添加商品:
HSET cart:user:1001 goods:2001 "数量:2,规格:红色"
; - 用户修改数量:
HSET cart:user:1001 goods:2001 "数量:3,规格:红色"
; - 用户删除商品:
HDEL cart:user:1001 goods:2001
; - 查看购物车:
HGETALL cart:user:1001
。
特点:天然适配购物车的“用户-商品”对应关系,操作灵活。
3. 简单的动态配置管理
比如做功能开关或动态参数管理,以 “configure:xxx” 为 key,功能名称为 field,true/false 为 value,相当于把 Redis 当成简单的配置中心。
但 Redis 基于内存,可靠性不足,且配置发布变更没有权限管控,有一定风险,最好用 Nacos或 Apollo 这类专业配置中心;如果系统不想引入重依赖,只用 Redis 做简单配置也完全可行。
4. 多维度统计:按分类计数
比如记录不同地区的视频访问量,以视频 ID 为 key,地区为 field,访问次数为 value,访问一次就自增一次,实现不同地区视频访问量的记录。
优势:不用创建多个String键,一个Hash就能聚合同一维度的多个统计数据。
三、List
List 本质是双端队列,有序且可重复,既能当先进先出的队列,也能当后进先出的栈。
1. 简单消息队列
用List的LPUSH
(从尾部加消息)和BRPOP
(阻塞从头部取消息)实现消息队列:
-
生产者发消息:
LPUSH queue:order "订单ID:1001,状态:待支付"
; -
消费者取消息:
BRPOP queue:order 0
(0表示永久阻塞,直到有消息)。
消息幂等性:要保证消息不重复处理(幂等性),需给消息分配唯一 ID,消费者保存已处理的消息 ID,避免重复消费。
不过 List 做消息队列有局限:无法实现消息持久化、没有死信队列,也不能让多个消费者共享一条消息。如果系统较小,不想引入重量级 MQ,用 Redis List 做消息队列能满足基本需求;若 Redis 版本较高,可用 Stream——Stream 是 Redis 高版本专门的消息队列工具,功能更丰富,更接近完整的消息队列,和正式 MQ 相比还有缺陷,但整体已好很多,不想引入重 MQ 的话,用 Stream 或 List 都可以。
2. 实时消息流:朋友圈/动态展示
以用户ID作为List的key,消息 ID 为 value 存入 List:
- 用户发朋友圈:
LPUSH feed:user:1001 "动态ID:5001,内容:今天去爬山了"
; - 用户打开朋友圈:
LRANGE feed:user:1001 0 9
(获取最新10条动态)。
优势:按时间顺序存储,取最新数据时不用排序,直接用LRANGE
分页,性能很高。
3. 栈结构:实现“最近访问”功能
比如“用户最近浏览的商品”,用List的LPUSH
(加商品ID)和LTRIM
(保留最近N条):
- 用户浏览商品2001:
LPUSH history:user:1001 2001
; - 只保留最近10条:
LTRIM history:user:1001 0 9
; - 查看最近浏览:
LRANGE history:user:1001 0 9
。
原理:每次加新商品到头部,超过10条就截断尾部,保证只存最新数据。
四、Set
Set 是无序且无重复的集合,核心优势是自动去重和高效的集合运算(交集、并集、差集),查询元素是否存在的时间复杂度是O(1)。
1. 抽奖系统
把所有参与用户的ID存入Set,利用Set的随机操作实现抽奖:
- 初始化奖池:
SADD lottery:1001 user:1001 user:1002 user:1003
; - 不允许重复中奖:
SPOP lottery:1001
(随机删除并返回一个用户ID,抽中后从奖池移除); - 允许重复中奖:
SRANDMEMBER lottery:1001
(随机返回一个用户ID,不删除,可重复抽)。
优势:自动去重,不用手动判断用户是否已参与,随机操作高效。
2. 白名单/黑名单
用Set存储白名单用户ID,判断用户是否在白名单时用SISMEMBER
:
-
加入白名单:
SADD whitelist:admin user:2001 user:2002
; -
校验身份:
SISMEMBER whitelist:admin user:2001
(返回1表示在白名单,0表示不在)。
优势:O(1)查询速度,比查数据库或用List遍历快得多,适合高频校验场景。
3. 点赞功能
以帖子或朋友圈 ID 为 key,点赞用户 ID 为 value 存入 Set:
- 用户1001给动态5001点赞:
SADD like:feed:5001 user:1001
; - 用户取消点赞:
SREM like:feed:5001 user:1001
; - 判断用户是否点赞:
SISMEMBER like:feed:5001 user:1001
; - 统计点赞数:
SCARD like:feed:5001
。
优势:自动去重,不用担心用户重复点赞,统计和判断都高效。
4. 集合运算:共同好友/共同兴趣
比如存好友关系时,以用户 ID 为 key,好友 ID 为 value 存入 Set,求两个用户 Set 的交集就能得到共同好友;
存用户兴趣标签时,以用户 ID 为 key,兴趣标签为 value 存入 Set,求不同用户 Set 的交集,就能计算共同兴趣。
利用Set的交集(SINTER
)、并集(SUNION
)实现社交场景的匹配:
- 存储好友关系:用户1001的好友存
set:friend:1001
,用户1002的好友存set:friend:1002
; - 查共同好友:
SINTER set:friend:1001 set:friend:1002
(返回两个用户都有的好友ID); - 兴趣匹配:用户1001的兴趣存
set:tag:1001
(比如“篮球、电影”),用户1002的兴趣存set:tag:1002
,用SINTER
查共同兴趣。
五、ZSet
ZSet 有序且无重复,每个元素有一个 score 用于排序,适合需要排序的场景。
1. 排行榜
积分排行榜、步数排行榜,以积分为 score,用户 ID 为 value 存入 ZSet,按分数排序:
- 用户1001积分为800:
ZADD rank:score 800 user:1001
; - 用户积分增加:
ZINCRBY rank:score 100 user:1001
(积分加100,变成900); - 查Top10(从高到低):
ZREVRANGE rank:score 0 9 WITHSCORES
(WITHSCORES表示返回分数); - 查用户排名:
ZREVRANK rank:score user:1001
(返回排名,0表示第1名)。
优势:自动排序,不用手动维护排名,查询TopN性能极高。
2. 滑动窗口限流
比如“每个IP每分钟最多访问120次”,用ZSet的分数存“请求时间戳”,元素存“请求ID”(或随机值):
- 每次请求时,先删除“1分钟前的旧请求”:
ZREMRANGEBYSCORE limit:ip:192.168.1.1 0 (当前时间戳-60000)
; - 统计当前窗口内的请求数:
ZCARD limit:ip:192.168.1.1
; - 若请求数<120,就把当前请求加入ZSet:
ZADD limit:ip:192.168.1.1 当前时间戳 请求ID:xxx
; - 若请求数≥120,拒绝请求。
优势:精准控制“任意1分钟内”的请求量,解决计数器限流的“临界问题”。
3. 浏览记录
以用户 ID 为 key,时间戳为 score,帖子/商品 ID 为 member 存入 ZSet,可按时间范围快速做分页查询。
和List的“最近访问”类似,但ZSet能按时间戳(分数)更灵活地筛选:
- 用户1001浏览商品2001(时间戳1690000000):
ZADD history:user:1001 1690000000 goods:2001
; - 查看“今天内的浏览记录”:
ZREVRANGEBYSCORE history:user:1001 (当前时间戳) (当前时间戳-86400000) WITHSCORES
; - 只保留最近100条:
ZREMRANGEBYRANK history:user:1001 100 -1
(删除排名100以后的记录)。
优势:支持按时间范围筛选,比List的分页更灵活。
4. 在线用户列表
以用户活跃时间戳为 score,用户 ID 为 value 存入 ZSet,能实现按登录时间排序、查询在线用户、强制下线等功能。
用ZSet的分数存“用户最后活跃时间戳”,元素存用户ID:
- 用户1001登录/操作:
ZADD online:user 1690000000 user:1001
(更新活跃时间戳); - 筛选“5分钟内活跃的用户”:
ZREVRANGEBYSCORE online:user (当前时间戳) (当前时间戳-300000)
; - 清理“30分钟未活跃的用户”:
ZREMRANGEBYSCORE online:user 0 (当前时间戳-1800000)
。
优势:能快速筛选活跃用户,实现“按登录时间排序”“踢下线”等功能。
补充:Stream
如果用List做消息队列满足不了需求(比如需要持久化、多消费者),可以用Redis 5.0+新增的Stream结构:
- 支持“消息持久化”:消息存在磁盘,Redis宕机后不会丢失;
- 支持“消费者组”:多个消费者可以组成一个组,共同消费一个队列,避免重复消费;
- 支持“ACK确认”:消费者处理完消息后发送ACK,未ACK的消息会重新分配,保证消息不丢失。
场景:中小系统不想引入RabbitMQ、Kafka等重量级MQ,用Stream做异步通信(比如订单回调、日志收集)完全够用。