Redis 各数据类型使用场景详解
1. 字符串(String)
场景 1:计数器(如文章阅读量)
-
问题:
-
高并发下对同一数值进行增减操作时,需保证原子性,避免竞态条件导致数据不一致。
-
频繁读写可能成为性能瓶颈。
-
-
解决方案:
-
INCR
/DECR
命令:Redis 的原子操作确保增减操作的线程安全,无需额外锁机制。 -
内存存储:数据直接存储在内存中,读写速度极快(微秒级响应)。
-
场景 2:分布式锁
-
问题:
-
多节点服务竞争同一资源时,需确保同一时间只有一个客户端持有锁。
-
锁需支持自动释放(如超时),避免死锁。
-
-
解决方案:
-
SETNX
+EXPIR
SET lock:order_123 "client1" NX EX 30 # 设置锁,30秒后自动过期
-
通过原子性命令确保锁的唯一性,过期时间防止客户端崩溃导致锁无法释放。
-
2. 列表(List)
场景 1:消息队列(生产者-消费者模型)
-
问题:
-
多生产者/消费者场景下,需保证消息的顺序性和可靠性。
-
消费者轮询队列会浪费资源(如空轮询)。
-
-
解决方案:
-
LPUSH
+BRPOP
:-
生产者使用
LPUSH
将消息推入队列头部。 -
消费者使用
BRPOP
阻塞式地从队列尾部获取消息,避免空轮询。
-
-
示例:
LPUSH task_queue "task_data" # 生产者推送任务 BRPOP task_queue 30 # 消费者阻塞30秒等待任务
-
场景 2:最新动态列表(如用户最近10条动态)
-
问题:
-
动态需按时间倒序展示,且需限制列表长度(如只保留最新100条)。
-
频繁插入可能导致内存占用过高。
-
-
解决方案:
-
LPUSH
+LTRIM
:LPUSH user:1:feeds "new_feed" # 插入新动态 LTRIM user:1:feeds 0 99 # 保留最新的100条
-
通过
LTRIM
自动裁剪旧数据,控制内存使用。
-
3. 哈希(Hash)
场景 1:存储用户信息(对象缓存)
-
问题:
-
对象字段较多时,若用多个
String
存储,会产生大量键,增加内存和管理成本。 -
部分更新需频繁序列化/反序列化整个对象。
-
-
解决方案:
-
HMSET
/HGETALL
:HMSET user:1001 name "Alice" age 30 email "alice@example.com" HGETALL user:1001 # 获取所有字段
-
哈希表将对象字段存储为单键下的多个字段,减少键数量,支持按字段读写。
-
场景 2:商品库存管理
-
问题:
-
高并发下单时,需保证库存扣减的原子性。
-
扣减后需实时更新库存值。
-
-
解决方案:
-
HINCRBY
命令:HINCRBY product:1001 stock -1 # 原子性扣减库存
-
哈希表结合
HINCRBY
实现原子性库存操作,避免超卖。
-
4. 集合(Set)
场景 1:用户标签系统
-
问题:
-
用户标签需唯一且无序,便于快速查询共同标签或去重。
-
多标签集合操作(如交集、并集)需高效执行。
-
-
解决方案:
-
SADD
+SINTER
:SADD user:1:tags "tech" "music" # 添加标签 SINTER user:1:tags user:2:tags # 获取共同标签
-
集合的
SINTER
直接计算交集,复杂度为 O(N*M),但内存中操作极快。
-
场景 2:唯一访问用户统计(去重)
-
问题:
-
同一用户多次访问同一资源时需去重统计。
-
直接记录用户ID可能导致重复存储。
-
-
解决方案:
-
SADD
+SCARD
:SADD article:1001:views "user:2023" # 用户访问时添加 SCARD article:1001:views # 获取唯一用户数
-
集合自动去重,
SCARD
直接返回唯一用户总数。
-
5. 有序集合(Sorted Set)
场景 1:游戏积分排行榜
-
问题:
-
玩家积分需实时排序,支持按名次快速查询。
-
积分更新频繁,需保证排序的高效性。
-
-
解决方案:
-
ZADD
+ZREVRANGE
:ZADD leaderboard 1000 "player1" # 添加/更新玩家积分 ZREVRANGE leaderboard 0 9 WITHSCORES # 获取前十名
-
有序集合内部通过跳表(Skip List)实现,插入和查询时间复杂度为 O(log N)。
-
场景 2:延迟队列(按时间戳调度任务)
-
问题:
-
任务需在指定时间执行,需按时间顺序处理。
-
轮询未到期的任务会浪费资源。
-
-
解决方案:
-
ZADD
+ZRANGEBYSCORE
:ZADD delay_queue 1630000000 "task_data" # 添加任务,分数为执行时间戳 ZRANGEBYSCORE delay_queue 0 <current_timestamp> # 获取所有到期任务
-
消费者定期查询分数小于当前时间戳的任务并处理。
-
6. 扩展数据类型
场景 1:用户签到统计(Bitmap)
-
问题:
-
每日签到状态只需记录是否签到(0/1),使用字符串浪费内存。
-
统计连续签到天数需高效计算。
-
-
解决方案:
-
SETBIT
+BITCOUNT
:SETBIT user:1001:sign:2023:10 5 1 # 10月第6天签到(偏移量从0开始) BITCOUNT user:1001:sign:2023:10 # 统计10月签到次数
-
位图每个位表示一天,每月31天仅需4字节,内存占用极低。
-
场景 2:独立访客统计(HyperLogLog)
-
问题:
-
海量用户访问记录需统计唯一用户数,精确统计内存占用过高。
-
允许一定误差(如0.81%)。
-
-
解决方案:
-
PFADD
+PFCOUNT
:PFADD uv:2023:10:01 "user1" "user2" # 记录UV PFCOUNT uv:2023:10:01 # 估算UV数
-
HyperLogLog 固定使用12KB内存,可统计上亿唯一值。
-
场景 3:附近商家查询(Geospatial)
-
问题:
-
需根据经纬度快速检索附近一定范围内的商家。
-
直接计算距离性能差(需遍历所有点)。
-
-
解决方案:
-
GEOADD
+GEORADIUS
:GEOADD shops 116.405285 39.904989 "shop1" # 添加商家坐标 GEORADIUS shops 116.40 39.90 10 km # 查询10公里内的商家
-
底层使用有序集合存储坐标(经度+纬度编码为分数),支持快速范围查询。
-
场景 4:订单事件日志(Stream)
-
问题:
-
订单状态变更需记录完整事件流,供后续审计或重放。
-
多消费者需独立消费同一消息流(如支付服务和库存服务)。
-
-
解决方案:
-
XADD
+ 消费者组(Consumer Group):XADD order:1001:events * status "paid" amount 200 # 添加事件 XGROUP CREATE order:1001:events group1 0 # 创建消费者组 XREADGROUP GROUP group1 consumer1 STREAMS order:1001:events > # 消费消息
-
Stream 支持多消费者组、消息确认和回溯,类似 Kafka 的日志机制。
-
总结
数据结构 | 核心问题 | Redis的解决方案 |
---|---|---|
String | 并发计数、分布式锁 | 原子操作(INCR )、SETNX + 过期时间 |
List | 消息顺序性、动态列表长度控制 | 阻塞弹出(BRPOP )、LPUSH + LTRIM |
Hash | 对象字段管理、部分更新 | 字段级读写(HSET /HGET ) |
Set | 去重、集合运算 | 唯一性存储、SINTER /SUNION |
Sorted Set | 按权重排序、范围查询 | 跳表排序、ZRANGEBYSCORE |
Bitmap | 二值状态统计(如签到) | 位操作(SETBIT /BITCOUNT ) |
HyperLogLog | 海量数据去重统计(允许误差) | 固定内存估算基数(PFADD /PFCOUNT ) |
Geospatial | 地理位置检索 | 有序集合编码坐标(GEORADIUS ) |
Stream | 消息多消费者组、事件溯源 | 消费者组、消息ID序列(XREADGROUP ) |
关键原则:
-
选择最匹配业务需求的数据结构,避免用
String
存储复杂对象。 -
优先利用 Redis 原子操作,减少客户端复杂逻辑。
-
关注内存效率(如小对象用
Hash
,二值数据用Bitmap
)。