【面试特集】Redis 面试题与应用场景
Redis 面试题与应用场景
一、数据结构与基础
Q1: Redis 为什么这么快? ⭐⭐⭐
答案:
- 纯内存操作:数据存储在内存中,读写无磁盘 I/O
- 单线程模型:避免上下文切换和锁竞争开销
- I/O 多路复用:epoll/kqueue 高效处理大量并发连接
- 高效数据结构:SDS、跳表、listpack 等专门优化的结构
- 6.0+ 多线程 I/O:网络读写并行化,命令执行仍单线程
追问:单线程为什么还能支撑 10万+ QPS?
- 瓶颈在网络 I/O 而非 CPU
- 内存操作本身极快(纳秒级)
- 避免了多线程的锁开销
Q2: Redis 的 String 底层是怎么实现的?
答案:
String 有三种内部编码:
- int:值为整数且 ≤ long 范围时,直接存储整数值
- embstr:字符串 ≤ 44 字节,SDS 和 redisObject 一次分配连续内存
- raw:字符串 > 44 字节,SDS 和 redisObject 分开分配
SDS(Simple Dynamic String)相比 C 字符串的优势:
- O(1) 获取长度(len 字段)
- 二进制安全(不以 \0 判断结束)
- 预分配空间减少内存重分配
- 兼容部分 C 字符串函数
// SDS 结构(简化)
struct sdshdr {
int len; // 已用长度,O(1) 获取
int free; // 剩余空间(预分配)
char buf[]; // 实际数据,以 \0 结尾(兼容 C)
};
// 三种编码切换
SET key 123 → int 编码(直接存整数)
SET key "hello" → embstr(≤44字节,一次内存分配)
SET key "very long…" → raw(>44字节,两次内存分配)
Q3: Sorted Set 为什么用跳表而不是红黑树? ⭐⭐
答案:
- 范围查询高效:跳表天然有序,范围查询 O(logN+M),红黑树需要中序遍历
- 实现简单:跳表代码比红黑树简单得多,易于维护和调试
- 内存友好:可以通过调整层数灵活平衡时间和空间
- 并发友好:跳表局部修改即可,红黑树可能需要旋转影响多个节点
实际上 Sorted Set 用的是 skiplist + hashtable 双结构:
- skiplist:支持有序遍历和范围查询
- hashtable:O(1) 按 member 查 score
跳表结构(level=3 示例):
ZADD leaderboard 100 alice 200 bob 150 carol
ZREVRANGE leaderboard 0 2 WITHSCORES # Top3 排行
ZRANGEBYSCORE leaderboard 100 200 # 分数区间查询 O(logN+M)
Q4: Redis 各数据类型的底层编码和使用场景?
答案:
| 类型 | 底层编码 | 切换条件 | 典型场景 |
|---|---|---|---|
| String | int / embstr / raw | 长度 > 44 字节用 raw | 缓存、计数器、分布式锁 |
| List | listpack / quicklist | 元素数 > 128 或单元素 > 64 字节 | 消息队列、最新列表 |
| Hash | listpack / hashtable | 字段数 > 128 或值 > 64 字节 | 对象存储、购物车 |
| Set | listpack / hashtable | 元素数 > 128 或非整数 | 标签、好友关系、去重 |
| ZSet | listpack / skiplist+hashtable | 元素数 > 128 或值 > 64 字节 | 排行榜、延迟队列 |
# String:计数器 / 分布式锁
INCR page:view:1001
SET lock:order:123 1 NX EX 30 # NX=不存在才设置
# Hash:存对象(比 JSON 字符串更节省内存,支持单字段更新)
HSET user:1 name Alice age 30
HINCRBY user:1 age 1
# List:最新消息(保留最近100条)
LPUSH news:feed "content"
LTRIM news:feed 0 99
# Set:共同好友 / 去重
SINTERSTORE common:1:2 friends:1 friends:2
# ZSet:实时排行榜
ZADD rank:daily 100 user:1
ZINCRBY rank:daily 50 user:1
ZREVRANGE rank:daily 0 9 WITHSCORES # Top10
Q5: Redis 的过期键删除策略? ⭐⭐⭐
答案:
Redis 使用两种策略结合:
惰性删除: 访问 key 时检查是否过期,过期则删除。
- 优点:CPU 友好(只在访问时检查)
- 缺点:过期 key 不访问则永不删除,内存泄漏
定期删除: 每隔一段时间(默认 100ms)随机抽取一批 key 检查,删除过期的。
- 每次抽取 20 个,若过期比例 > 25% 则继续抽取
- 限制每次执行时间,避免阻塞
追问:内存不足时怎么办?
触发内存淘汰策略(maxmemory-policy):
noeviction:不淘汰,写入报错(默认)allkeys-lru:淘汰最近最少使用的 key(推荐)volatile-lru:只淘汰设置了过期时间的 key 中 LRU 的allkeys-random:随机淘汰
# 查看/设置内存淘汰策略
CONFIG GET maxmemory-policy
CONFIG SET maxmemory-policy allkeys-lru
# 查看 key 的 TTL
TTL key # 剩余秒数,-1=永不过期,-2=不存在
PTTL key # 毫秒精度
PERSIST key # 移除过期时间
# 定期删除伪代码
every 100ms:
for db in databases:
sample 20 keys with TTL
delete expired ones
if expired_ratio > 25%: repeat (max 25% of CPU time)
二、持久化
Q6: RDB 和 AOF 的区别? ⭐⭐⭐
答案:
| 维度 | RDB | AOF |
|---|---|---|
| 原理 | 定时快照(fork 子进程) | 记录每条写命令 |
| 文件大小 | 小(二进制压缩) | 大(文本命令) |
| 恢复速度 | 快 | 慢(重放命令) |
| 数据安全 | 低(两次快照间数据丢失) | 高(最多丢失 1 秒) |
| 性能影响 | fork 时有短暂 STW | 每秒 fsync 影响小 |
| 适用场景 | 备份、快速恢复 | 数据安全要求高 |
混合持久化(推荐): AOF 文件前半段是 RDB 快照,后半段是增量 AOF 命令,兼顾恢复速度和数据安全。
# redis.conf 关键配置
save 900 1 # 900s 内有 1 次写则触发 RDB
appendonly yes
appendfsync everysec # always/everysec(推荐)/no
aof-use-rdb-preamble yes # 混合持久化(4.0+)
# 手动触发
BGSAVE # 后台生成 RDB(fork 子进程,不阻塞)
BGREWRITEAOF # 后台重写 AOF
Q7: AOF 重写的原理?
答案:
AOF 文件会随时间增大(同一 key 多次修改产生多条命令),重写将其压缩为最小命令集。
过程:
- fork 子进程,子进程读取当前内存数据,生成新 AOF 文件
- 主进程继续处理请求,新命令写入 AOF 重写缓冲区
- 子进程完成后,主进程将缓冲区命令追加到新 AOF 文件
- 原子替换旧 AOF 文件
触发条件:
auto-aof-rewrite-percentage 100:AOF 文件比上次重写后增长 100%auto-aof-rewrite-min-size 64mb:AOF 文件至少 64MB
三、高可用
Q8: Redis Sentinel 的工作原理? ⭐⭐
答案:
Sentinel 实现主从自动故障转移:
监控: 每个 Sentinel 每秒向 Master/Slave/其他 Sentinel 发送 PING。
主观下线(SDOWN): 单个 Sentinel 认为 Master 不可达(超过 down-after-milliseconds)。
客观下线(ODOWN): 超过 quorum 个 Sentinel 都认为 Master 主观下线,则判定客观下线。
故障转移:
- Sentinel 之间选举 Leader(Raft)
- Leader 从 Slave 中选出新 Master(优先级 > 复制偏移量 > runid)
- 向新 Master 发送
SLAVEOF NO ONE - 通知其他 Slave 复制新 Master
- 通知客户端新 Master 地址
# sentinel.conf
sentinel monitor mymaster 127.0.0.1 6379 2 # quorum=2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
# 故障转移流程(伪代码)
SDOWN(单个 Sentinel 超时)
→ ODOWN(quorum 个 Sentinel 同意)
→ Raft 选举 Leader Sentinel
→ 选新 Master:slave-priority > repl_offset > runid
→ SLAVEOF NO ONE → 通知其他 Slave → Pub/Sub 通知客户端
# Java 客户端(Jedis Sentinel)
JedisSentinelPool pool = new JedisSentinelPool(
"mymaster",
Set.of("s1:26379","s2:26379","s3:26379")
);
Q9: Redis Cluster 的数据分片原理? ⭐⭐⭐
答案:
Redis Cluster 使用 哈希槽(Hash Slot) 分片,共 16384 个槽:
slot = CRC16(key) % 16384
每个 Master 节点负责一部分槽(如 3 节点:0-5460 / 5461-10922 / 10923-16383)。
MOVED 重定向: 客户端请求到错误节点时,节点返回 MOVED slot ip:port,客户端重定向。
ASK 重定向: 槽迁移过程中,数据在源节点和目标节点都可能存在,用 ASK 临时重定向。
追问:为什么是 16384 个槽而不是更多?
- 心跳包中携带槽位图,16384 个槽只需 2KB(16384/8 字节)
- 节点数通常不超过 1000,16384 个槽已足够分配
Q10: Sentinel 和 Cluster 怎么选?
答案:
| 维度 | Sentinel | Cluster |
|---|---|---|
| 数据量 | 单机可承载(< 几十 GB) | 需要水平扩展(> 100 GB) |
| 写吞吐 | 单 Master 限制 | 多 Master 并行写 |
| 运维复杂度 | 低 | 高(槽迁移、多节点管理) |
| 多 key 操作 | 支持 | 受限(key 必须在同一槽) |
| 客户端 | 简单 | 需要 Cluster 感知客户端 |
选型: 数据量 < 50GB 且写压力不大 → Sentinel;需要水平扩展 → Cluster。
# Cluster 创建(3主3从)
redis-cli --cluster create \
127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 \
127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006 \
--cluster-replicas 1
# hash tag:强制多 key 落同一槽
MSET {user:1}.name Alice {user:1}.age 30
四、缓存模式与经典问题
Q11: 缓存穿透、击穿、雪崩的区别和解决方案? ⭐⭐⭐
答案:
缓存穿透: 查询不存在的数据,缓存和数据库都没有,每次都打到数据库。
- 解决:布隆过滤器(拦截不存在的 key);缓存空值(短 TTL)
缓存击穿: 热点 key 过期瞬间,大量请求同时打到数据库。
- 解决:互斥锁(只有一个请求重建缓存);逻辑过期(不设 TTL,异步更新)
缓存雪崩: 大量 key 同时过期,或 Redis 宕机,请求全部打到数据库。
- 解决:过期时间加随机抖动;Redis 高可用(Sentinel/Cluster);限流降级
// 缓存穿透:布隆过滤器 + 缓存空值
if (!bloomFilter.mightContain(id)) return null;
String val = redis.get(key);
if (val == null) {
val = db.query(id);
redis.setex(key, val != null ? 300 : 60, val != null ? val : "NULL");
}
// 缓存击穿:互斥锁重建
String val = redis.get(key);
if (val == null) {
if (redis.setnx("lock:" + key, "1", 10)) {
val = db.query(id);
redis.setex(key, 300, val);
redis.del("lock:" + key);
} else {
Thread.sleep(50); return get(key); // 重试
}
}
// 缓存雪崩:过期时间加随机抖动
int ttl = 300 + ThreadLocalRandom.current().nextInt(60);
redis.setex(key, ttl, value);
Q12: 如何保证缓存和数据库的一致性? ⭐⭐⭐
答案:
常见方案对比:
| 方案 | 原理 | 问题 |
|---|---|---|
| 先更新 DB,再删缓存(推荐) | 更新 DB 后删除缓存,下次读时重建 | 删缓存失败导致不一致 |
| 先删缓存,再更新 DB | 删缓存后更新 DB | 并发读在 DB 更新前重建旧缓存 |
| 延迟双删 | 更新 DB 后删缓存,延迟再删一次 | 延迟时间难以确定 |
| Canal 订阅 binlog | 监听 DB 变更,异步删缓存 | 架构复杂,有短暂不一致 |
推荐: 先更新 DB,再删缓存 + 删缓存失败重试(消息队列保证最终一致)。
// 推荐:先更新 DB,再删缓存
void updateUser(User user) {
db.update(user);
redis.del("user:" + user.getId());
// 删除失败则发 MQ 重试,保证最终一致
}
// Canal 方案:订阅 binlog 异步删缓存(业务代码无感知)
// MySQL binlog → Canal → MQ → Consumer.del(key)
Q13: 布隆过滤器的原理和误判率?
答案:
布隆过滤器用多个哈希函数将元素映射到 bit 数组的多个位置,全部置 1 表示存在。
查询: 所有对应位都为 1 → 可能存在;任意一位为 0 → 一定不存在。
特点:
- 不存在的元素一定返回"不存在"(无漏报)
- 存在的元素可能返回"不存在"(有误判,但概率可控)
- 不支持删除(删除会影响其他元素)
误判率控制: 增大 bit 数组或增加哈希函数数量可降低误判率,但增加内存和计算开销。
布隆过滤器(3个哈希函数,bit数组长度=10):
插入 "apple":h1→1, h2→4, h3→7 bit[1,4,7]=1
插入 "banana":h1→2, h2→4, h3→9 bit[2,4,9]=1
查询 "apple":bit[1,4,7] 全1 → 可能存在 ✓
查询 "grape":bit[3]=0 → 一定不存在 ✓
查询 "mango":bit[1,4,9] 全1 → 误判(假阳性)✗
# RedisBloom 模块
BF.RESERVE myfilter 0.01 1000000 # 误判率1%,预期100万元素
BF.ADD myfilter "user:123"
BF.EXISTS myfilter "user:123" # 1=可能存在,0=一定不存在
五、分布式锁
Q14: 用 Redis 实现分布式锁的正确姿势? ⭐⭐⭐
答案:
SET lock_key unique_value NX PX 30000
NX:不存在才设置(原子性)PX 30000:30 秒自动过期(防止死锁)unique_value:UUID,释放时验证是自己的锁
释放锁(Lua 脚本保证原子性):
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
追问:为什么不能直接 DEL?
- 可能删除别人的锁:锁过期后被其他线程获取,自己再 DEL 就删了别人的锁
Q15: Redlock 算法是什么?有什么争议? ⭐⭐
答案:
Redlock 在 N 个独立 Redis 节点上获取锁,超过 N/2+1 个成功才算获取成功。
步骤:
- 获取当前时间 t1
- 依次向 N 个节点请求加锁(超时时间远小于锁有效期)
- 计算获取锁耗时 t2-t1,锁实际有效期 = 设定有效期 - 耗时
- 成功节点数 > N/2+1 且有效期 > 0,则获取成功
- 失败则向所有节点释放锁
争议(Martin Kleppmann):
- 时钟漂移可能导致锁提前过期
- GC STW 期间锁可能过期,但进程认为仍持有锁
- 对于强一致性场景,建议用 ZooKeeper 或 etcd
实践: 大多数场景单节点 Redis 锁足够,Redlock 适合对锁安全性要求极高的场景。
// 单节点分布式锁(推荐)
String lockVal = UUID.randomUUID().toString();
// NX=不存在才设置,EX=原子设置过期,防死锁
boolean locked = "OK".equals(redis.set(lockKey, lockVal, "NX", "EX", 30));
// 释放锁:Lua 脚本保证原子性(判断+删除)
String lua =
"if redis.call('get',KEYS[1])==ARGV[1] then "
+ "return redis.call('del',KEYS[1]) else return 0 end";
redis.eval(lua, List.of(lockKey), List.of(lockVal));
// Redlock:向 N 个独立 Redis 节点加锁,成功数 > N/2+1 且有效期>0
六、性能优化
Q16: 什么是 BigKey?如何处理? ⭐⭐
答案:
BigKey:单个 key 的 value 过大(String > 10KB,集合元素数 > 5000)。
危害:
- 网络传输慢,阻塞其他命令(Redis 单线程)
- 内存分配/释放耗时,导致延迟抖动
- 集群模式下数据倾斜
发现:
redis-cli --bigkeys # 扫描各类型最大 key
redis-cli --memkeys # 按内存大小排序
处理:
- String BigKey:拆分为多个 key(分片存储)
- Hash/Set BigKey:按字段范围拆分为多个 key
- 删除 BigKey:用
UNLINK(异步删除)代替DEL(同步删除)
Q17: Pipeline 的原理和使用场景?
答案:
Pipeline 将多个命令批量发送,减少网络往返次数(RTT):
普通:命令1 → 等待响应 → 命令2 → 等待响应 → ...(N次RTT)
Pipeline:命令1+2+...+N → 一次发送 → 批量响应(1次RTT)
注意:
- Pipeline 不是原子的(中间可能有其他客户端命令插入)
- 需要原子性用 Lua 脚本或 MULTI/EXEC 事务
- 每批建议 100-1000 条命令,过多会占用大量内存
Q18: Redis 的慢查询如何排查?
答案:
# 配置慢查询阈值(微秒)
CONFIG SET slowlog-log-slower-than 10000 # 10ms
# 查看慢查询日志
SLOWLOG GET 10 # 最近 10 条
SLOWLOG LEN # 慢查询总数
SLOWLOG RESET # 清空
常见慢命令:
KEYS *:全量扫描,O(N),生产禁用,用SCAN替代HGETALL:返回所有字段,Hash 很大时慢SMEMBERS:返回所有成员,Set 很大时慢SORT:排序操作,复杂度高
七、Redis 新特性
Q19: Redis 7.x 的重要新特性?
答案:
- Redis Functions:替代 Lua 脚本,支持持久化和复制(脚本随数据一起复制)
- Multi-Part AOF:AOF 拆分为多个文件,解决重写时的性能问题
- ACL v2:更细粒度的权限控制(按 key 前缀、命令类别)
- Listpack 全面替代 ziplist:更紧凑的内存编码
# ACL v2:细粒度权限控制
ACL SETUSER appuser on >password ~cache:* +get +set +del
# ~cache:* 只能操作 cache: 前缀的 key
# Multi-Part AOF 文件结构
ls /var/lib/redis/appendonlydir/
# appendonly.aof.1.base.rdb ← RDB 基础快照
# appendonly.aof.1.incr.aof ← 增量命令
# Redis Functions(替代 Lua 脚本,支持持久化)
FUNCTION LOAD "#!lua name=mylib\nredis.register_function('hello', function(keys,args) return 'hi' end)"
FCALL hello 0
Q20: Redis Stream 是什么?和 List 做消息队列的区别?
答案:
Stream 是 Redis 5.0 引入的持久化消息流,支持消费者组:
| 维度 | List | Stream |
|---|---|---|
| 消费模式 | 单消费者(LPOP) | 消费者组(多消费者并行) |
| 消息确认 | 无(LPOP 即删除) | XACK 确认,未确认可重新消费 |
| 历史消息 | 消费后丢失 | 保留(可按 ID 范围查询) |
| 消费者组 | 不支持 | 支持(多组独立消费) |
| 持久化 | 支持 | 支持 |
Stream 适合: 需要消费确认、多消费者组、消息回溯的场景(轻量级 MQ)。
八、场景题
场景1: 电商秒杀系统如何用 Redis 防止超卖? ⭐⭐⭐
场景描述: 商品库存 100,瞬间 10 万并发请求,如何保证不超卖?
方案:
- 预热库存到 Redis:系统启动时
SET stock:1001 100 - 原子扣减(Lua 脚本):
local stock = tonumber(redis.call("get", KEYS[1]))
if stock <= 0 then return -1 end
redis.call("decrby", KEYS[1], ARGV[1])
return stock - tonumber(ARGV[1])
- 异步落库:扣减成功后发 MQ,消费者异步更新数据库
- 兜底校验:数据库层加乐观锁(
WHERE stock >= #{count})
关键点:
- Lua 脚本保证"查询+扣减"原子性,避免 TOCTOU 竞态
- Redis 单线程天然串行,不需要额外加锁
- 库存预热时可用
SETNX防止重复初始化
场景2: 如何用 Redis 实现接口限流(每用户每分钟 100 次)? ⭐⭐⭐
场景描述: API 网关需要对每个用户 ID 限制调用频率。
方案一:固定窗口(简单)
key = "rate:uid:1001:202605111430" # 分钟级 key
INCR key
EXPIRE key 60
缺点:窗口边界突刺(前一分钟末 + 后一分钟初可能 200 次)
方案二:滑动窗口(ZSet)
key = "rate:uid:1001"
now = current_timestamp_ms
ZREMRANGEBYSCORE key 0 (now - 60000) # 删除 1 分钟前的记录
count = ZCARD key
if count >= 100: reject
ZADD key now now
EXPIRE key 60
优点:精确滑动,无边界突刺
缺点:每次请求写入一条记录,内存占用较高
方案三:令牌桶(Redis + Lua)
- 用 Hash 存储
{tokens, last_refill_time} - 每次请求用 Lua 脚本计算补充令牌数并消费
选型建议: 普通限流用固定窗口;精确限流用滑动窗口 ZSet;高并发令牌桶场景用 Lua 脚本。
场景3: 用户登录 Session 如何用 Redis 管理?支持单点登录踢出旧设备? ⭐⭐
场景描述: 用户登录后生成 Token,要求同一账号只能在一台设备登录,新登录踢出旧 Token。
设计:
# 登录时
token = UUID()
SET session:{token} {user_info_json} EX 7200 # Token → 用户信息
SET user_token:{uid} {token} EX 7200 # uid → 当前有效 Token
# 验证时
current_token = GET user_token:{uid}
if current_token != request_token: reject (已被踢出)
user_info = GET session:{current_token}
# 新设备登录时
old_token = GET user_token:{uid}
DEL session:{old_token} # 删除旧 Session
SET session:{new_token} ... / SET user_token:{uid} {new_token}
追问:如何支持多设备登录(最多 3 台)?
- 用
user_tokens:{uid}存 ZSet,score 为登录时间 - 登录时 ZADD,若 ZCARD > 3 则 ZRANGE 取最旧的踢出
场景4: 排行榜系统(实时积分榜)如何设计? ⭐⭐
场景描述: 游戏积分实时排行榜,支持查询 Top 100、查询某用户排名。
设计:
# 更新积分
ZADD leaderboard {score} {uid}
# 查 Top 100(降序)
ZREVRANGE leaderboard 0 99 WITHSCORES
# 查某用户排名(0-based,+1 得名次)
ZREVRANK leaderboard {uid}
# 查某用户积分
ZSCORE leaderboard {uid}
# 积分增量更新
ZINCRBY leaderboard {delta} {uid}
分页查询:
ZREVRANGE leaderboard {(page-1)*size} {page*size-1} WITHSCORES
追问:榜单数据量很大(百万用户)怎么优化?
- 只维护 Top N(如 Top 10000)的 ZSet,超出范围的用数据库查
- 定时任务清理低分用户:
ZREMRANGEBYRANK leaderboard 0 -10001
场景5: 如何用 Redis 实现延迟队列(订单超时自动取消)? ⭐⭐⭐
场景描述: 用户下单后 30 分钟未支付自动取消,订单量大,如何高效实现?
设计(ZSet 延迟队列):
# 下单时,score = 当前时间 + 30分钟(Unix 时间戳)
ZADD delay_queue {now + 1800} {order_id}
# 消费者轮询(每秒执行)
now = current_timestamp
orders = ZRANGEBYSCORE delay_queue 0 {now} LIMIT 0 10
for order_id in orders:
# 用 Lua 保证原子性:取出并删除
ZREM delay_queue {order_id}
# 处理超时逻辑
Lua 原子取出:
local items = redis.call("ZRANGEBYSCORE", KEYS[1], 0, ARGV[1], "LIMIT", 0, 10)
for _, v in ipairs(items) do
redis.call("ZREM", KEYS[1], v)
end
return items
追问:和 RocketMQ 延迟消息相比?
- Redis 延迟队列:实现简单,精度高(秒级),但不支持消费确认和重试
- RocketMQ:支持消费确认、重试、死信队列,适合可靠性要求高的场景
场景6: 缓存预热方案设计(系统启动时如何避免冷启动打垮数据库)? ⭐⭐
场景描述: 服务重启后 Redis 缓存为空,大量请求直接打到数据库,如何处理?
方案:
- 启动时主动预热:服务启动时异步加载热点数据到 Redis(从数据库或离线统计的热点 key 列表)
- 分批预热:避免一次性加载导致数据库压力过大,分批次、限速加载
- 互斥锁防击穿:预热期间缓存未就绪时,用
SETNX互斥锁保证只有一个请求重建缓存 - 降级兜底:预热完成前,对非核心接口返回默认值或限流
// 互斥锁重建缓存
String value = redis.get(key);
if (value == null) {
if (redis.setnx("lock:" + key, "1", 10)) { // 获取锁
value = db.query(key);
redis.set(key, value, ttl);
redis.del("lock:" + key);
} else {
Thread.sleep(50);
return get(key); // 重试
}
}
应用场景总结
| 场景 | 数据结构 | 关键命令/方案 |
|---|---|---|
| 分布式缓存 | String | SET NX PX,缓存穿透用布隆过滤器 |
| 排行榜 | ZSet | ZADD/ZRANGE/ZREVRANK |
| 计数器/限流 | String | INCR,滑动窗口用 ZSet |
| 分布式锁 | String | SET NX PX + Lua 释放 |
| 消息队列 | Stream / List | Stream 支持消费确认 |
| 好友关系 | Set | SINTER(共同好友)/SUNION |
| 购物车 | Hash | HSET/HGETALL/HINCRBY |
| 延迟队列 | ZSet | score 为执行时间,定时 ZRANGEBYSCORE |
| 布隆过滤器 | BitMap / RedisBloom | BF.ADD/BF.EXISTS |
| 地理位置 | GEO | GEOADD/GEODIST/GEORADIUS |
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)