Redis 不只是缓存 :一份能上生产、能扛面试的Redis使用指南 Redis 不只是缓存一份能上生产、能扛面试的使用指南很多人对 Redis 的印象停留在缓存。但只要你写过分布式系统就会发现它几乎无孔不入缓存、锁、限流、排行榜、消息队列、延迟任务、实时广播、UV 统计……它像一个瑞士军刀型的中间件。这篇文章不堆命令。我想把 Redis 在真实项目里最常用的几个场景讲透——每个都配上具体用法、踩过的坑以及为什么要这么做的底层逻辑。最后一节是面试高频问答直接按问 → 答组织能拿来背、也能拿来组织自己的话术。全文配套两个可跑 demo限流、Pub/Sub都在同目录文末有索引。零、先把 Redis 想清楚它凭什么哪里都能插一脚Redis 能横跨这么多场景靠的不是某一项黑科技而是四个特质的叠加纯内存读写直接操作内存不用过磁盘 IO内存访问 ~100ns vs 磁盘 ~10ms差了 5 个数量级。单线程命令串行执行天然无锁、无上下文切换所以多条命令的原子性免费——这是后面分布式锁、限流能用 Lua 的根基。IO 多路复用epoll一个线程处理几万连接哪条连接有数据就处理哪条不阻塞。丰富的数据结构String / Hash / List / Set / Sorted Set / Stream / HyperLogLog / GEO / Bitmap / Bloom Filter——每种都对应一类业务模型不用你手搓。一句话概括Redis 把快和数据结构做到了极致所以同一个东西别人要写一堆代码、上一堆中间件Redis 几条命令就搞定了。小知识Redis 6.0 引入了多线程但只用于网络读写命令执行仍然是单线程。所以多线程不破坏原子性——这点面试常考。下面这张速查表先混个眼熟后面每个结构都会展开数据结构典型用途String缓存、分布式锁、计数器、全局唯一 IDHash会话、用户信息、对象缓存、可重入锁List简单消息队列、最新列表Set去重、交并差运算共同关注Sorted Set排行榜、延迟队列、滑动窗口限流Stream可靠消息队列消费者组HyperLogLogUV 统计12KB 算百万级去重误差 0.81%GEO附近的人 / 门店Bitmap签到打卡、在线状态Bloom Filter缓存穿透防护、黑名单一、缓存最基础但缓存三件套才是考点GET user:1001 → 未命中 → 查 DB → SET user:1001 (ex 3600) → 返回缓存本身的逻辑简单到不值一提。真正决定你会不会用缓存的是这三个高频故障——穿透、击穿、雪崩。它们名字像、病因不同、药方也不同面试必问故障是什么怎么治穿透查的数据根本不存在缓存和 DB 都没有请求每次都直穿到 DB常见于恶意攻击、爬虫布隆过滤器预拦截 缓存空值TTL 设短点如 60s击穿单个热点 key 过期的瞬间海量并发请求同时打穿到 DB互斥锁SETNX只放一个请求去重建其他等待逻辑过期不设 TTL代码判断过期后异步刷新雪崩大量 key 同一时刻集体过期DB 压力骤增TTL加随机偏移3600 random(0,300) 多级缓存 熔断降级记忆口诀穿透是查了个不存在的治在前置拦截击穿是一个热点 key 炸了治在加锁重建雪崩是一片 key 同时炸治在打散过期时间。真实项目里我踩过穿透的坑恶意请求发大量不存在的 query缓存没拦住、直接打到后端 LLM把成本打爆。上了布隆过滤器 空值缓存才解决。布隆过滤器的好处是它能拦在查 DB / 查缓存之前——这个区分度很高面试说出来就知道你是真用过。二、分布式锁Redis 最考功底的场景为什么需要分布式锁单机多线程synchronized/ReentrantLock就够了。但服务一旦部署多台JVM 级别的锁就失效了——两台机器各自有自己的锁根本互斥不了服务器A: 库存1 → 读 → 判断0 → 扣减 → 库存0 ✓ 服务器B: 库存1 → 读 → 判断0 → 扣减 → 库存-1 ✗ 超卖分布式锁的本质让所有服务器去抢同一把锁而 Redis 是这把锁天然的存放地。第一版SET NX EX能跑但有坑SET lock:order:1001uuid-xxxxNX EX30三个细节决定了它是个锁NXkey 不存在才设置Not eXists——这是互斥性的来源EX 3030 秒自动过期——防止持锁进程崩溃造成死锁value 用 uuid标识这把锁是谁的释放时只删自己的。释放锁必须用 Lua 脚本这是第一个反直觉点。你也许会想先GET判断是不是自己的再DEL不就行了不行t1 GET lock → 是我的 ✓准备 DEL t2 —— 此时锁刚好过期 —— B 加锁成功value 是 B 的 t3 DEL lock → 把 B 的锁删了B 和 C 同时持锁锁形同虚设GET和DEL是两条命令中间有缝隙。Lua 脚本在 Redis 里原子执行把缝隙焊死ifredis.call(GET,KEYS[1])ARGV[1]thenreturnredis.call(DEL,KEYS[1])endreturn0第二版的问题锁过期了业务还没干完0s A 加锁成功EX 10 5s A 业务执行慢…… 10s 锁自动过期B 加锁成功 12s A 终于执行完DEL → 误删 B 的锁 12s C 也加锁成功 → A、B、C 并发执行锁彻底失效手动续期能解但自己写定时任务很容易出 bug。这就是Redisson 看门狗Watchdog要解决的问题。生产推荐RedissonRLocklockredisson.getLock(lock:order:1001);try{if(lock.tryLock(5,30,TimeUnit.SECONDS)){// 等5s持锁30s// 执行业务}}finally{lock.unlock();}它比手写好在哪逐条对可重入底层用Hash而不是 String。HSET lock uuid:threadId 1同一线程再次加锁就HINCRBY1解锁 -1到 0 才DEL。所以同一线程能反复拿同一把锁不死锁。看门狗自动续期加锁成功后定时任务每lockWatchdogTimeout/3默认 10s检查一次锁还被自己持有就续到 30s。业务结束unlock()看门狗停业务崩溃没人续30s 后自动释放。彻底解决了锁过期但业务没干完。等待通知抢不到锁时不是while sleep轮询而是用Pub/Sub 订阅锁释放消息谁释放了就通知等待者重试——高效得多。维度手写 SET NXRedisson互斥性有有防死锁EX 过期看门狗 过期兜底自动续期无有默认每 10s可重入不支持支持Hash释放安全需手写 Lua内置等待机制轮询Pub/Sub 通知一个诚实的边界主从切换会丢锁1. 客户端 A 在 master 加锁成功 2. master 还没来得及同步给 slave 就宕机 3. slave 升级为新 master但锁没了 4. 客户端 B 在新 master 也加锁成功 → 两把锁并存RedLockRedis 官方向 N 个独立Redis 实例加锁过半数成功才算获取到锁。争议很大Martin Kleppmann 和 antirez 隔空对线过那场。现实里绝大多数业务用单节点 Redisson 就够了主从切换概率极低。真·金融级绝对不能两把锁并存用ZooKeeper / etcd它们靠一致性算法ZAB / Raft保证。面试加分句“Redis 分布式锁是 AP可用 最终一致ZooKeeper 是 CP强一致。要钱要命的场景上 ZK要性能上 Redis。”三、限流先回答到底要不要 Redis这是我最想讲清楚的一节因为很多人一上来就用 Redis 做限流其实没必要。单机限流根本不用 Redis滑动窗口、令牌桶、漏桶这些算法单机用内存就能跑。现成轮子一堆GuavaRateLimiter、Bucket4j、Sentinel 单机模式。请求来了本地变量count判断一下纳秒级不走网络。只有多实例部署才轮到 Redis——和分布式锁同理服务部署 A/B/C 三台限每用户每秒 10 次 A 内存记 3 次、B 记 3 次、C 记 4 次 每台都觉得自己没超 10 → 实际用户已请求 10 次限流形同虚设必须把当前请求数 / 令牌数放到所有实例共享的同一个地方。判断标准就一条服务跑几个实例单实例用内存多实例才用 Redis。为什么不用 MySQL 做分布式限流因为限流在请求热路径上、每个请求都要走一次Redis 亚毫秒、INCR/Lua 原子MySQL 要SELECT … FOR UPDATE加锁慢且自己变成瓶颈。实际架构里限流常挡在更前面Nginxlimit_req、网关RequestRateLimiter底层正是 Redis Lua。为什么限流必须用 Lua限流是读当前值 → 判断 → 写回的读改写操作天然不原子t1 请求A GET count → 4 10准备 1 t2 请求B GET count → 4 10准备 1 ← 读到旧值 t3 请求A SET count 5 t4 请求B SET count 5 ← 本该是 6A 的 1 被覆盖 结果两个请求都放行计数却只 1Lua 脚本在 Redis 里原子执行读改写一气呵成。限流场景Lua 不是优化项是必需品。算法一滑动窗口精确无边界问题以当前时刻为终点往回看一个固定窗口统计请求数。用Sorted Setscore时间戳、member请求唯一 id三步包进 Luaredis.call(ZREMRANGEBYSCORE,key,0,now-window)-- 1. 清掉滑出窗口的旧请求localcountredis.call(ZCARD,key)-- 2. 数当前窗口内请求数ifcountlimitthenredis.call(ZADD,key,now,member)-- 3. 记一笔redis.call(EXPIRE,key,math.ceil(window)1)return1endreturn0⚠️坑member 必须唯一。别图省事拿时间戳当 member——同毫秒的两个请求会互相覆盖、计数就错了。用时间戳:uuid用 score 排序、用 member 区分。对比一下它为什么比固定窗口强。固定窗口INCR rate:user:1001:2026052814有个致命伤——临界点突发固定窗口每秒最多 5 个按整点切桶 │ 第1秒桶 │ 第2秒桶 │ │ 59ms 内打 5 个 ✓│ 01ms 内打 5 个 ✓│ ← 真实1秒内通过了10个滑动窗口以当前时刻为终点回看没有这个边界漏洞。算法二令牌桶允许突发桶以固定速率rate补令牌最多capacity个请求消耗令牌桶空则拒。状态只有两个数当前令牌数 上次补充时间存 Hash懒补充请求来时才算该补多少不开后台线程locallast_timetonumber(redis.call(HGET,key,last_time))ornowlocaltokenstonumber(redis.call(HGET,key,tokens))orcapacitylocaldeltanow-last_timeifdelta0thentokensmath.min(capacity,tokensdelta*rate)-- 懒补充封顶 capacityendlocalallowed0iftokenscostthentokenstokens-cost allowed1endredis.call(HSET,key,tokens,tokens,last_time,now)returnallowed⚠️坑tokens0别被默认值覆盖。HGET取不到返回 nil写成tonumber(...) or capacity。这里有个反直觉的点——Lua 里0是 truthy只有 nil/false 是 falsy所以0 or capacity结果是0恰好保住桶空状态换 Python/Java 思维写就会把桶空当不存在、误填满。滑动窗口 vs 令牌桶维度滑动窗口令牌桶限制对象窗口内请求数硬上限平均速率 突发额度突发流量严格拒绝超额允许瞬时突发到 capacity内存占用随请求量线性增长每请求一个 ZSET 成员恒定只存两个数典型场景精确配额每用户每秒 N 次API 网关、出口带宽控速完整可跑 demo 见同目录 限流demo_redis_python.py跑起来能直观看到窗口满→拒绝→滑过→放行和桶空→拒绝→补满→放行。四、排行榜Sorted Set 的封神之战ZINCRBY rank:daily10user:1001# 加分实时ZREVRANGE rank:daily099WITHSCORES# Top100ZREVRANK rank:dailyuser:1001# 某人第几名游戏积分榜、直播打赏榜、销量排行……这类需求 Redis 几乎是标配。为什么因为Sorted Set 天生为排序而生。它底层是Hash 跳表Skip ListZADD rank 100 张三 ZADD rank 95 李四 ZADD rank 88 王五 Hash: { 张三:100, 李四:95, 王五:88 } ← O(1) 查分数 跳表: 王五(88) → 李四(95) → 张三(100) ← O(log N) 插入/排序Hash 负责按 member 查分数跳表负责按分数排序。这组合拳直接秒杀 MySQL操作Sorted SetMySQL更新分数O(log N) 自动调整位置O(1) 写但要维护索引取 Top NO(log N M) 截取O(N log N)ORDER BY全表排序查某人排名O(1)ZREVRANKO(N)COUNT(*) WHERE score ?全表扫查某人分数O(1)ZSCOREO(1) 有索引多榜合并ZUNIONSTORE复杂 JOIN实际玩法很丰富日榜合并周榜ZUNIONSTORE加权、翻页ZREVRANGE切片、区间查ZRANGEBYSCORE 580 600找某个分数段的所有人。场景积分榜、销量榜、热搜榜、打赏榜——凡是实时更新 取 Top N 查排名的闭眼选 ZSET。五、消息队列三种姿势选错就翻车Redis 做消息队列有三种实现能力天差地别。选错了轻则丢消息重则线上事故。5.1 List —— 最简单的队列LPUSH queue:email{to:ab.com,body:验证码1234}# 生产BRPOP queue:email30# 消费阻塞没消息就挂着LPUSH 从左进、BRPOP 从右出天然 FIFOBRPOP 阻塞消费不烧 CPU一条消息只能被一个消费者取走竞争消费。致命局限消费即删除、不支持重投没有 ACK消费者崩了正在处理的消息就丢了不支持多消费者组。适合发个短信、发个邮件这种丢了也就丢了的简单异步任务。5.2 Pub/Sub —— 实时广播强调实时也就意味着不可靠发布者 ──PUBLISH 频道 消息──▶ Redis ──同时推给──▶ 所有订阅者每人一份 广播模型是发布者往频道喊一嗓子所有正在订阅的客户端各收到一份。核心命令SUBSCRIBE chat:room1# 订阅之后这条连接专门收消息UNSUBSCRIBE chat:room1# 退订PUBLISH chat:room1有人加入了# 发布返回值 收到它的订阅者数PSUBSCRIBE order:*# 模式订阅通配符一次匹配多频道它的性格很鲜明也正因为鲜明所以不能当可靠队列发完即弃fire-and-forgetRedis 不缓存消息订阅者此刻不在线 / 断网这条消息就永久错过没有未读、没有重投没有堆积能力慢消费者直接被压垮Redis 重启订阅关系和消息全没。最经典实战多实例 WebSocket 广播。单机直接conn.send()但服务扩成多实例后用户 A 连在实例 1、用户 B 连在实例 2互相看不见。解法是把 Redis Pub/Sub 当跨实例消息总线所有实例启动时都 SUBSCRIBE chat:global A 发消息 → 实例1 PUBLISH chat:global → Redis 广播 → 实例1/2/3 全收到 → 各自遍历本地 ws 连接推送 → A/B/C 全收到把哪个用户连在哪台机器这个分布式难题化解成每台机器各自推本地连接。聊天室、实时通知、协同编辑、配置热更新底层都是这套。⚠️坑订阅连接是长期阻塞占用的必须每个订阅者单独建一条连接别和普通读写共用否则互相卡死。完整 demo 见同目录 PubSub广播demo_redis_python.py。5.3 Stream —— 真正的消息队列5.0对标轻量 KafkaXADD orders * userId1001amount99.9# 生产* 自动生成有序IDXGROUP CREATE orders order-group $# 建消费者组XREADGROUP GROUP order-group consumer1 COUNT1BLOCK5000STREAMS orders# 消费XACK orders order-group1703000000000-0# 确认消费XPENDING orders order-group# 查未确认消费者崩了没ACK的XCLAIM orders order-group consumer1600001703000000000-0# 转给别人重试XREAD COUNT10STREAMS orders1703000000000-0# 历史回溯Stream 一次性补齐了 List/Pub/Sub 的所有短板持久化AOF/RDB重启不丢、消费者组一条消息只被组内一个消费者处理类似 Kafka partition、ACK 确认没确认的能被XCLAIM转移重试、历史回溯按 ID 任意位置重读。生产者 XADD → Stream 存储 → 消费者 XREADGROUP → 处理业务 → XACK ↓ 崩了 XPENDING 发现未确认 → XCLAIM 重试三种怎么选能力ListPub/SubStream持久化AOF 可选无AOF/RDB消费确认无无XACK消费者组无无有广播不支持支持支持多 Group历史回溯无无有丢消息风险消费后即删离线即丢未 ACK 可重试适用场景简单异步任务实时通知可靠消息队列一句话选型丢了无所谓 → List要广播且允许偶发丢 → Pub/Sub绝不能丢、要回溯、要消费者组 → Stream事务和海量堆积要求极高 → 老老实实上 RocketMQ/Kafka。六、其它场景一把抓但都很实用延迟队列Sorted Set 定时轮询——订单超时取消、优惠券到期ZADD delay:queue1703001000{task:cancelOrder,orderId:1001}# score到期时间戳ZRANGEBYSCORE delay:queue0nowLIMIT0100# 每秒轮询到期任务计数器 / UV——阅读数INCRUV 用 HyperLogLog12KB 算百万级去重INCR article:1001:views PFADD uv:20260528user:1001# 去重计数误差仅 0.81%PFCOUNT uv:20260528全局唯一 ID——比 UUID 有序、比 DB 自增高单机 10万 QPSINCR id:order:20260528# → 1, 2, 3 …会话管理——用 Hash 而非 String小 Hash 走 ziplist 压缩省内存、能HSET/HGET单字段操作、能部分读取省网络。地理位置——附近的人、门店搜索、骑手定位GEOADD stores:bj116.39739.909store:001GEORADIUS stores:bj116.39739.9093km COUNT20ASC# 查3km内门店七、贯穿全篇的灵魂Lua 脚本你会发现上面分布式锁、限流都离不开 Lua。单独拎出来讲因为它太重要也太被误解。为什么 Redis 需要 Lua因为 Redis 单线程执行 Lua 脚本期间不处理任何其他命令所以脚本里的多条命令原子执行、中间不会被插入。任何读-判断-写的非原子操作限流的计数、锁的判断删除都得靠它。怎么写标准三件套localkeyKEYS[1]-- 操作的 keyRedis Cluster 下必须通过 KEYS 传便于路由localargARGV[1]-- 业务参数localvredis.call(GET,key)-- 在脚本里调 Redis 命令-- ... 业务逻辑 ...returnv-- 返回值给客户端客户端用EVAL提交脚本或EVALSHA先SCRIPT LOAD拿到 sha1之后只传哈希省带宽——redis-py的register_script就是这么干的。两条铁律脚本必须短——它独占 Redis 整个线程脚本越长、其他请求阻塞越久。别在 Lua 里做耗时计算更别在里面访问外部资源。脚本要确定性——同样的输入必须产出同样的结果否则主从复制、RDB 回放会对不上。所以脚本里禁止用time()、random()这类非确定性函数。八、面试高频问答按问 → 答组织下面挑的都是高频且能拉开差距的题每题给一个达标答案——能直接背也能改成自己的话。Q1Redis 为什么这么快四点①纯内存读写不过磁盘内存 ~100ns vs 磁盘 ~10ms②单线程无锁竞争、无上下文切换命令串行还顺带保证了原子性③IO 多路复用epoll一个线程管几万连接④高效的数据结构编码小数据用 ziplist 压缩。追问Redis 6.0 不是多线程了吗→ 多线程只用于网络读写命令执行依然是单线程所以不破坏原子性。Q2Redis 的数据类型有哪些分别什么场景五大基础 五大扩展。基础String缓存/锁/计数器/ID、Hash会话/对象、List简单队列、Set去重/交并差、Sorted Set排行榜/延迟队列/滑动窗口。扩展Stream可靠队列、HyperLogLogUV、GEO附近、Bitmap签到、Bloom Filter防穿透。Q3缓存穿透、击穿、雪崩的区别和解决方案是什么怎么治穿透数据根本不存在请求直穿 DB布隆过滤器 缓存空值击穿单个热点 key 过期瞬间打穿互斥锁重建 逻辑过期雪崩大量 key 同时过期TTL 加随机偏移 多级缓存口诀穿透治在前置拦截击穿治在加锁重建雪崩治在打散过期。Q4怎么用 Redis 实现分布式锁Redisson 比手写好在哪基础版SET key uuid NX EX 30NX 互斥、EX 防死锁、uuid 标识归属释放必须用 Lua 脚本保证判断删除原子否则会误删别人的锁。手写有锁过期但业务没干完的问题。生产用RedissonHash 结构支持可重入、看门狗定时续期每 10s 续到 30s业务崩了不续就自动释放、Pub/Sub 实现等待通知。边界主从切换会丢锁master 宕机、锁没同步到 slave。极端可靠场景用 RedLock多实例过半数或直接上 ZooKeeper/etcdCP 一致性。Redis 锁是 APZK 锁是 CP。Q5Redis 怎么做限流一定要用 Redis 吗先判断要不要 Redis单机限流用内存就行Guava RateLimiter 等纳秒级只有多实例才用 Redis——把计数/令牌放到所有实例共享的地方。限流是读改写操作必须用 Lua 保证原子否则并发下计数会错。算法上固定窗口有临界突发问题滑动窗口ZSETscore时间戳精确无边界令牌桶Hash 存令牌数上次时间懒补充允许突发内存恒定。Q6Redis 持久化 RDB 和 AOF 的区别生产怎么配RDB全量快照fork 子进程把内存 dump 成二进制文件。文件小、恢复快但可能丢两次快照之间的数据。AOF追加写命令日志。数据更完整最多丢 1s文件大、恢复慢。写入策略appendfsyncalways最安全最慢/everysec默认平衡/no交 OS。文件会无限增长靠**重写rewrite**瘦身。混合持久化4.0生产推荐AOF 重写时前半段写 RDB、后半段追 AOF兼得快和完整。追问RDB 和 AOF 同时开重启加载哪个→AOF数据更完整。即使开了混合持久化也是读 AOFRDB 只在 AOF 关闭时用。Q7key 过期了怎么删内存满了怎么办过期策略怎么删到期的 key惰性删除访问时才检查怕漏定期删除每 100ms 随机抽查一批补漏。淘汰策略内存满了怎么办8 种重点记——allkeys-lru全部 key 淘汰最久未用的缓存最常用、volatile-lru只在设了 TTL 的里淘汰、allkeys-random随机淘汰、noeviction不淘汰、写操作报错默认。Q8Redis 和 MySQL 的数据一致性怎么保证业界主流先更新 MySQL再删除 Redis 缓存“Cache Aside”。为什么是删不是更新因为更新有并发覆盖问题删除更简单下次读会触发缓存重建。仍有短暂不一致的窗口删缓存后、另一请求读到旧值再写回。加一道延迟双删更新后删一次缓存sleep 几百毫秒再删一次兜底掉并发读带来的脏缓存。终极方案是对一致性要求极高的场景走消息队列 binlog 订阅如 Canal异步刷新缓存。Q9Redis 是单线程还是多线程核心命令执行始终单线程保证原子、无锁。6.0 引入多线程仅用于网络读写命令执行不变。所以多线程不破坏原子性。此外还有BIO 后台线程做异步任务AOF 刷盘、关闭文件、惰性删除大 key不算在工作主线程里。Q10Pipeline 是什么和 Lua 脚本、事务MULTI什么区别Pipeline把多条命令打包一次性发送减少网络往返RTT。注意它不保证原子——只是省网络命令之间仍可能插入别人的命令。对比Lua 脚本是真正原子执行期间独占MULTI/EXEC 事务能保证一起执行、不中断但不支持回滚且条件判断不如 Lua 灵活。生产里需要原子性优先用 Lua。Q11Redis 高可用怎么做的主从 / 哨兵 / 集群三层递进主从复制一主多从写主读从、分担读压力。同步是全量RDB 快照 增量repl_backlog 缓冲区有延迟。哨兵Sentinel在主从基础上加自动故障检测和主从切换。哨兵节点数建议奇数3/5 个便于投票。集群Cluster数据分片16384 个槽按 CRC16 路由 每个 slot 主从解决单机容量和性能上限。Q12什么是大 Key / 热 Key怎么处理大 Key单个 key 的 value 特别大如一个 List/Hash 几十 MB。危害读写阻塞Redis 单线程一个慢操作拖垮全局、网络拥塞、删除时卡顿。处理拆分大 Hash 拆成多个小 Hash、异步删除UNLINK代替DEL、用HSCAN/SSCAN渐进式遍历别用HGETALL/KEYS。热 Key某个 key 访问量极大单节点成瓶颈。处理本地缓存多级缓存、拆分热 key多副本分散到不同节点、读写分离。九、一张图收尾选型速查要缓存 → String / Hash记得 TTL 加随机偏移防雪崩 要互斥多实例 → 分布式锁生产用 Redisson 要限流多实例 → Lua 滑动窗口 / 令牌桶 要排序 / Top N / 排名 → Sorted Set 要可靠队列 → Stream消费者组 ACK 要实时广播 → Pub/Sub别拿来当可靠队列 要简单异步任务 → ListLPUSH/BRPOP 要延时 → Sorted Setscore 到期时间戳 要去重计数 / UV → HyperLogLog省内存/ Set精确 要附近 → GEO 要签到 / 布尔统计 → Bitmap 要防穿透 → Bloom Filter 要全局 ID → INCR最后的判断题问自己一句就够了服务跑几个实例——单实例能用本地内存就别请 Redis多实例要共享状态Redis 才是那个最顺手的存放地。这一条比记住一百条命令都重要。配套可跑 demo限流demo_redis_python.py PubSub广播demo_redis_python.py完整场景速查工具书风格Redis实际项目应用场景.mdRAG 项目实战话术含踩坑故事RAG项目面试话术.md