
简介面向Redis学习者与实践者的系统课程资料包基于《Redis核心技术与实战》内容整理聚焦字符串、哈希等数据结构的选择AOF与RDB持久化、主从复制、哨兵机制、切片集群等核心原理也覆盖计数器统计、GEO类型、时间序列、消息队列、异步阻塞、CPU架构影响及响应延迟排查等高价值实战话题适合初中级开发者系统学习与查漏补缺也适合在面试和运维场景中快速查阅。压缩包为zip格式整体约856MB按课程模块组织目录包含开篇词、基础篇10讲、实践篇28讲等章节便于逐章检索。目前已有47人浏览学习。资料包含第19讲课后思考题答案与常见问题答疑以及大量可落地的案例思路能帮助读者理解Redis单线程模型为何够快、宕机后如何恢复数据以及面对亿级key统计、消息队列选型或突发响应延迟时如何定位与优化兼顾原理与实战。1. Redis核心技术与实战为什么说它是后端性能的最后一块短板很多后端系统在流量涨起来之前都把 Redis 当成一个“高级缓存”用存个验证码、存个 session、查不到就回源数据库。直到某天线上查询全部超时、缓存被穿透打垮数据库、主从切换后数据对不上你才会意识到 Redis 并不只是 GET/SET而是一套有数据模型、持久化策略、高可用架构和分布式协调能力的中间件。这里说的“核心技术与实战”指的是一套能把开发、部署、排障串起来的技术主线先搞懂数据结构和内存模型再搞定持久化与高可用最后解决缓存治理、分布式锁这些真实业务痛点。适合还在把 Redis 当纯缓存用的后端开发、刚接手 Redis 运维的 DBA以及准备 Redis 面试但只会背概念的求职者。2. 把 Redis 数据类型选对选错类型的代价比配置错误更大2.1 五种基础数据类型的选型逻辑Redis 的数据类型是它区别于普通 key-value 存储的核心。很多人一开始就踩了坑所有数据都用 string 塞 JSON结果一个对象字段更新就要读整个 string 再反序列化内存和带宽都被白白浪费。我在做业务建模时一般会先画一张表把业务映射到类型上类型底层编码典型业务场景核心命令stringint/embstr/raw验证码、计数器、分布式锁GET/SET/INCRhashziplist/hashtable对象属性缓存、实时更新字段HSET/HGET/HINCRBYlistquicklist消息队列、时间线、待办列表LPUSH/RPOP/BRPOPLPUSHsetintset/hashtable去重、标签、好友关系SADD/SINTER/SUNIONzsetskiplistziplist排行榜、延迟队列、限流ZADD/ZRANGE/ZSCOREstring 是最容易用错的类型。比如在线用户的点赞数直接用INCR like:post:1001就是原子操作根本不需要先读后写。反过来把用户对象塞进 string 虽然省事但每次只改一个字段都要整体序列化字段一多就变成大 key 隐患。对象型数据我建议优先用 hash# 对象缓存用 hash字段级更新不整体序列化 HSET user:1001 name zhang age 30 last_login 1735689600 HINCRBY user:1001 login_count 1 HGETALL user:1001这段命令的逻辑是HSET 允许按字段写入HINCRBY 能对数字字段做原子自增。相比 string 方式hash 的好处是更新一个字段只产生很小的网络开销也不会因为读改写造成并发覆盖。注意 hash 字段数量建议控制在 100 个以内字段再多底层从 ziplist 退化成 hashtable 后内存效率会明显下降。list 当队列是另一个高频场景但直接 LPUSH/RPOP 有个缺陷消费者拿走后如果处理失败消息就丢了。我一般会加上备份队列用BRPOPLPUSH原子地把消息从主队列搬到备份队列处理成功后再从备份队列删除# 生产者 LPUSH task:queue task:123 # 消费者原子取出并放入备份队列阻塞 30 秒 BRPOPLPUSH task:queue task:backup 30BRPOPLPUSH 的语义是“取出并放入另一条队列”这个动作是原子的不会出现消息既被取走又没进备份的情况。处理成功的任务调用LREM task:backup 1 task:123删除即可处理失败的任务可以从备份队列读回来重试。zset 不只是排行榜。利用 score 存时间戳zset 还可以做延迟队列定时任务把执行时间作为 score消费者用ZRANGEBYSCORE拉取到期的任务。这个模式在很多业务里比引入消息中间件更轻量但需要注意 zset 的 score 是双精度浮点毫秒级时间戳直接放进去会有精度损失。2.2 高级数据类型Bitmap、HyperLogLog、Geo 与 StreamBitmap 最适合做签到和在线状态这类布尔型统计。每个用户一天只占 1 bit一个月不到 4 字节# 用户 1001 在 2025 年 1 月 3 日签到 SETBIT sign:2025:01 user:1001 2 1 # 查询是否签到 GETBIT sign:2025:01 user:1001 2 # 统计一月签到人数 BITCOUNT sign:2025:01BITCOUNT 统计的是位值为 1 的数量。这里要注意 offset 从 0 开始第 3 天的下标是 2。签到场景用 bitmap 的最大好处是内存极省一亿用户一年只要 1.2GB 左右比存字符串省几十倍。HyperLogLog 用来做 UV 统计它牺牲了精度换内存。标准误差约 0.81%但每个 key 最多占用 12KB无论统计多少独立用户PFADD uv:2025:01:01 user:1001 user:1002 user:1003 PFCOUNT uv:2025:01:01注意 PFCOUNT 是近似值精确度要求高的场景比如对账不适合。如果同一批用户要按天去重再按月去重可以用 PFMERGE 合并多个 key。Stream 是 Redis 5.0 引入的消息队列类型支持消费者组和 ACK 机制。相比 list 队列Stream 能记录消费位点宕机后可以从上次未 ACK 的消息继续消费。但我的观点是如果业务已经引入了 Kafka/RabbitMQ不要把 Stream 当主力队列它更适合轻量级、单机或主从环境的内部异步任务一旦数据量上去Stream 的持久化和扩容成本都不低。2.3 序列化方案Java 场景下都在哪里翻车Spring Boot 项目里用 RedisTemplate最典型的翻车现场是 key 变成\xAC\xED\x00\x05t\x00一串乱码。这是因为默认的 JdkSerializationRedisSerializer 会把 key 也做 Java 序列化Redis Desktop Manager 里看到的全是乱码排查问题都无从下手。我的常规做法是key 一律用 StringRedisSerializervalue 用 JSON 序列化Configuration public class RedisConfig { Bean public RedisTemplateString, Object redisTemplate(RedisConnectionFactory factory) { RedisTemplateString, Object template new RedisTemplate(); template.setConnectionFactory(factory); // key 可读方便命令行和可视化工具排查 template.setKeySerializer(new StringRedisSerializer()); template.setHashKeySerializer(new StringRedisSerializer()); // value 用 JSON保留类型信息 template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); template.afterPropertiesSet(); return template; } }这里的逻辑是StringRedisSerializer 让所有 key 变成人眼可读的字符串GenericJackson2JsonRedisSerializer 在 value 里写入 class 类型标记反序列化时能还原成正确的 Java 对象。代价是类型标记会让 JSON 体积变大 10% 左右如果你对内存敏感可以换成不带类型标记的普通 Jackson但取值时要手动指定目标类型。另一个坑是把对象序列化成 JSON 字符串后塞进 string然后业务需要做自增操作——这时候只能先反序列化、加一、再序列化写回三个步骤不是原子的并发下必然丢更新。数字型字段要么拆出来用 string 存要么用 hash 的 HINCRBY不要整个对象塞一个 key。3. 从安装到持久化把 Redis 部署成一个可依赖的中间件3.1 三种环境下的最小安装方式macOS 上用 Homebrew 是最省心的方式装完直接注册成服务。Docker 方式适合任何环境也是我推荐的生产部署方式因为配置和数据目录的挂载非常明确# macOS brew install redis brew services start redis # Docker跨平台生产推荐 docker run -d --name redis \ -p 6379:6379 \ -v /etc/redis/redis.conf:/etc/redis/redis.conf \ -v /data/redis:/data \ redis:7-alpine \ redis-server /etc/redis/redis.confDocker 命令里两个 -v 是关键一个挂载配置文件一个挂载数据目录。Redis 官方镜像的默认数据目录是 /dataRDB 和 AOF 文件都会落在这里不挂载的话容器重建数据就没了。生产环境不要使用 redis:latest 标签而是锁定大版本比如 redis:7-alpine避免基础镜像更新带来的不确定性。Windows 上官方并不提供原生 Redis 安装包网上所谓的 Windows 版基本都是旧版本移植或者二次封装。我的建议是Windows 本机开发直接用 Docker Desktop 跑容器或者用 WSL2 里的 Linux 环境安装不要在 Windows 原生环境里跑老版本 Redis性能和特性都跟不上。Linux 源码编译安装适合需要定制编译参数的环境标准流程是下载源码、解压、make、make install。编译前确保系统有 gcc 和 make如果只是使用而不是定制还是直接用发行版的 redis-server 包更省心。3.2 redis.conf 必调的五个参数很多线上事故不是 Redis 坏了而是默认配置不符合生产场景。以下五个参数是我每次部署都会检查的参数默认值生产建议原因maxmemory0不限制物理内存的 60%-70%不限制会把系统内存耗尽触发 OOM Killermaxmemory-policynoevictionallkeys-lru 或 volatile-lrunoeviction 下内存满了所有写操作直接报错appendonlynoyes不开启 AOF重启可能丢几分钟数据requirepass空强密码空密码很容易被扫描工具发现并植入挖矿脚本rename-command无禁用 KEYS/FLUSHALL防止手滑和线上误操作maxmemory 的设定有个经验值Redis 内存占用达到物理内存的 70% 后fork 生成 RDB 的快照效率和系统稳定性会下降所以要留出余量。maxmemory-policy 的理解很简单allkeys-lru 对所有 key 做 LRU 淘汰volatile-lru 只淘汰设置了过期时间的 key。缓存场景用 allkeys-lru如果 Redis 里还存了不能丢的注册数据就要用 volatile-lru 并给数据设过期时间。requirepass 设置了密码后从节点也要配 masterauth否则主从复制断线后重连会被拒绝。这个坑出现频率很高后面第 4 章会再提。3.3 持久化机制详解RDB、AOF 与混合持久化RDB 是二进制快照原理是 fork 子进程利用 Copy-on-Write 机制把当前内存数据写成 dump.rdb。它的优点是恢复快、文件紧凑缺点是两次快照之间的数据会丢。默认配置下 60 秒内如果有超过 10000 次写操作才触发快照高并发下可能丢大量数据。AOF 是追加日志只要写入命令执行成功就会按策略追加到 appendonly.aof。appendfsync 参数决定刷盘时机appendfsync 值含义性能安全性always每个命令都 fsync最差最多丢一条命令everysec每秒 fsync折中最多丢 1 秒数据no交给操作系统刷盘最好可能丢好几秒数据生产环境默认 everysec这是性能和安全的平衡点。always 在 SSD 上写放大严重吞吐会掉到几千 QPS非极端金融场景不要用。AOF 文件会无限增长所以要有重写机制。BGREWRITEAOF会根据当前内存数据生成最小化的写命令集把历史冗余命令压缩掉。4.0 之后引入了混合持久化重写时把当前数据以 RDB 格式写入 AOF 文件头部后面的增量命令再用 AOF 格式追加。这样既解决了 AOF 文件体积大的问题又让重启加载速度快了很多。# 手动触发一次 RDB 快照 redis-cli -a yourpass BGSAVE # 手动触发 AOF 重写 redis-cli -a yourpass BGREWRITEAOFBGSAVE 和 BGREWRITEAOF 都会 fork 子进程fork 的瞬间会短暂阻塞主线程。如果内存很大几十 GBfork 耗时可能达到秒级这也是为什么我强调 maxmemory 不要顶满物理内存。我的经验是做持久化运维时提前在业务低峰期手动触发大 key 重写避免高峰期自动重写造成卡顿。4. 主从、哨兵与集群高可用架构的落地路径4.1 主从复制与读写分离的搭建生产环境至少应该是一主一从主节点负责写从节点负责读和备份。搭建主从的最小配置是在从节点 redis.conf 里写三行replicaof 192.168.1.10 6379 replica-read-only yes masterauth yourpassreplicaof 告诉从节点主节点地址和端口replica-read-only 强制从节点只读防止误写入造成主从不一致masterauth 配置的是主节点的密码很多人只配了主节点的 requirepass忘了配从节点的 masterauth主从断线重连时从节点会因为认证失败一直处于握手状态。验证主从状态用 INFO replicationredis-cli -p 6380 -a yourpass INFO replication输出里主要看 role 是 slave或 replicamaster_link_status 是 up。如果显示 down先去查从节点日志里的认证失败和时间偏移。主从延迟也是关注点INFO replication里的 master_repl_offset 和从节点的 slave_repl_offset 差值就是延迟的字节数持续增长说明从节点消费能力跟不上主节点的写入速度。主从只是高可用的基础不是高可用本身。主节点宕机后需要人工把某个从节点提升为主节点这个过程无法自动完成所以要引入哨兵。4.2 哨兵与 Cluster什么时候用哪个哨兵Sentinel解决的是主从架构的自动故障转移。生产环境至少要部署三个哨兵实例quorum 设为 2这样超过半数哨兵认定主节点客观下线时才会触发选举# sentinel.conf 关键配置 sentinel monitor mymaster 192.168.1.10 6379 2 sentinel down-after-milliseconds mymaster 5000 sentinel failover-timeout mymaster 15000down-after-milliseconds 是哨兵判定主节点主观下线的超时时间5000 意味着 5 秒内主节点没有响应才认为它挂了。时间设太短网络抖动就会频繁触发切换设太长故障恢复时间变长。5 到 10 秒是比较常见的取值。Redis Cluster 则是在数据量超过单机内存时引入的分片方案。数据分布在 16384 个哈希槽里每个主节点负责一段槽范围。搭建一个三主三从的最小集群redis-cli --cluster create \ 192.168.1.10:7000 192.168.1.11:7000 192.168.1.12:7000 \ 192.168.1.13:7000 192.168.1.14:7000 192.168.1.15:7000 \ --cluster-replicas 1--cluster-replicas 1 表示每个主节点配备一个从节点所以前三个是主后三个是从。创建过程中会打印每个节点的角色和槽范围确认后集群开始工作。集群模式下客户端连接任意一个节点如果 key 不在该节点会返回 MOVED 错误并告知正确的节点地址正常客户端 SDK 会自动处理重定向。选择哨兵还是 Cluster我的判断标准是单机内存能放下全部数据就用哨兵主从结构简单支持跨 key 事务数据超过单机内存、或者需要线性扩容才上 Cluster。Cluster 最大的限制是跨槽的 multi-key 操作MGET、事务、Lua 脚本不被支持业务代码如果大量依赖多 key 原子操作迁移成本会比较高。4.3 容器环境部署Docker 和 k8s 的注意点Docker 部署主从时建议用 docker-compose 统一管理否则每个容器手动 docker run 很容易漏配置。一个最小主从 compose 的关键片段services: redis-master: image: redis:7-alpine command: [redis-server, --requirepass, yourpass, --appendonly, yes] redis-slave: image: redis:7-alpine command: [redis-server, --replicaof, redis-master, 6379, --masterauth, yourpass] depends_on: - redis-master注意这里从节点通过容器名 redis-master 来解析主节点地址服务启动顺序由 depends_on 控制但 depends_on 只保证启动顺序不保证主节点 Redis 已经就绪所以从节点可能启动后连接失败重试几次这是正常的。k8s 里部署 Redis 要避免一个常见误区直接 Deployment 默认存储Pod 重建后数据落在新存储上老数据全丢。必须用 StatefulSet 配合 PVC给每个 Pod 稳定的网络标识和独立存储卷。如果是集群模式还要注意 Cluster 节点发现依赖固定的 IP 或域名StatefulSet 的 headless service 能提供这种稳定标识。另一个坑是容器 CPU 限制过低时fork 生成 RDB 快照可能触发 cgroup 的 CPU 限制导致 fork 时间异常长建议给 Redis 容器预留 1-2 核的余量。5. 缓存治理与排障穿透、击穿、雪崩和 Command timed out5.1 缓存穿透布隆过滤器与空值缓存缓存穿透指的是查询一个根本不存在的数据缓存和数据库都没有请求每次都会打到数据库。如果攻击者伪造大量不存在的 key数据库会被打挂。最典型的场景是用户查询一个已删除的商品 ID或者恶意遍历 ID。解决思路有两个。最简单的做法是空值缓存查询数据库发现没有就把空值也写入缓存设置 60 秒过期String value redis.get(product: id); if (value null) { Product p productDao.selectById(id); if (p null) { redis.set(product: id, , 60); // 空值也缓存 return null; } redis.set(product: id, JSON.toJSONString(p), 3600); return p; } if (value.isEmpty()) { return null; }这里有一个细节空值缓存要区分“缓存里没有”和“缓存里是空串”我习惯用 null 表示未命中空字符串表示命中了空值。另外空值的过期时间要比正常数据短比如 60 秒因为可能下次查询就有数据了。布隆过滤器是更彻底的方案把所有可能存在的 key 预先放入布隆过滤器查询前先判断 key 是否可能存在不存在直接返回。用 Redisson 的 RBloomFilter 实现RBloomFilterString filter redisson.getBloomFilter(product_keys); filter.tryInit(1000000L, 0.01); if (!filter.contains(product: id)) { return null; // 一定不存在直接拦截 }tryInit 的两个参数是 expectedInsertions预期数据量和 falseProbability误判率。误判率 0.01 表示有 1% 的概率会把不存在的 key 误判为存在误判的请求会继续打到数据库但这种量级已经可以接受。注意布隆过滤器不支持删除业务里如果有大量删除 key 的操作过滤器会逐渐失真需要定期重建。5.2 缓存击穿与雪崩热点 key 过期和批量过期击穿和穿透名字像场景完全不同。击穿是指某个热点 key 突然过期大量并发请求同时发现缓存未命中全部打到数据库。互斥锁方案是行业内最常见的做法缓存未命中时先尝试获取分布式锁只有拿到锁的线程去查数据库其他线程自旋等待后重新读缓存String value redis.get(key); if (value null) { String lockKey lock: key; boolean locked redis.set(lockKey, 1, NX, EX, 5); if (locked) { try { value db.query(key); redis.set(key, value, 300); } finally { redis.del(lockKey); } } else { Thread.sleep(50); value redis.get(key); // 自旋重试 } }这个场景里 SET 的 NX 参数保证只有一个线程能拿到锁EX 5 防止持有锁的线程崩溃导致死锁。sleep 50 毫秒是自旋间隔太短会放大 CPU 消耗太长增加响应延迟。另一个常用手段是“逻辑过期”缓存里额外存一个过期时间戳每次读取时发现逻辑过期就后台异步刷新让旧数据先返回这个方案不会阻塞请求但实现更复杂。雪崩是更大范围的击穿大量 key 在同一时刻过期或者 Redis 整个挂了。解决批量过期的方法是给过期时间加随机偏移SET product:1001 {json} EX 3600 SET product:1002 {json} EX 3673 SET product:1003 {json} EX 3548把过期时间从固定 3600 秒变成 3600 加减一个随机值错开过期高峰。Redis 挂了的兜底方案是多级缓存本地进程内缓存Caffeine挡一层Redis 挡一层数据库最后兜底。本地缓存的优点是每个应用实例自己持有不依赖网络但要注意本地缓存更新时多个实例间的数据一致性问题。5.3 RedisCommandTimedOutLettuce 客户端超时排查Spring Boot 默认用 Lettuce 作为 Redis 客户端遇到最多的报错是io.lettuce.core.RedisCommandTimeoutException: Command timed out after 5 second(s)这个异常出现时第一反应不要改客户端参数先确认 Redis 服务端是否有慢请求。查看慢日志SLOWLOG GET 10 SLOWLOG RESET慢日志会显示执行时间超过 slowlog-log-slower-than默认 10000 微秒的命令。常见元凶是 KEYS *、SMEMBERS 一个百万成员的 set、HGETALL 一个超大 hash。这类命令在单线程模型下会阻塞后续所有命令Lettuce 客户端等待超过 timeout 就抛异常。我的排查顺序是先跑redis-cli --bigkeys找出大 key再配合 SLOWLOG 确认是否有耗时命令最后看INFO CPU确认服务端 CPU 是否被打满。如果服务端正常再检查客户端连接池spring: redis: timeout: 3000ms lettuce: pool: max-active: 16 max-idle: 8 min-idle: 2 max-wait: 3000msLettuce 是异步框架底层用 Netty 管理连接单连接就能处理高并发。但 Spring Data Redis 的同步封装会在线程池里等待响应如果单个连接上的命令排队过长依然会超时。调大 max-active 可以让更多命令并行分发到不同连接上但要注意连接数过多会增加服务端文件描述符压力。还有一种场景是 Redis 在跑 BGSAVEfork 瞬间主线程阻塞所有命令延迟飙高。这个可以从 Redis 日志里看到forked child的时间戳或者用INFO persistence查看当前是否在持久化。应对方法是错峰备份以及给 Redis 主机预留足够内存减少 fork 时 copy-on-write 的页表复制开销。5.4 可视化工具连不上、ACL 配置错误和内存打满可视化工具方面RedisInsight 和 Another Redis Desktop Manager另一个 Redis 桌面管理器是现在用得比较多的客户端。连接失败的排查路径基本一致先redis-cli -h host -p port -a password ping在命令行验证连通性再检查工具里的配置。命令行能连上但工具连不上最可能是工具的 SSH 隧道配置或者 TLS 选项问题。需要注意如果 Redis 配置了bind 127.0.0.1外部机器无论用什么工具都连不上必须把 bind 改为内网 IP 或 0.0.0.0同时设置密码并开启 protected-mode。Redis 6.0 之后引入了 ACL访问控制列表很多人在配置 ACL 用户时踩坑。比如默认用户被FLUSHALL限制了但业务代码还在用默认用户执行写入就会报NOPERM错误。我一般会单独建一个业务用户只授权需要的命令ACL SETUSER bizuser on strongpass ~* all -FLUSHALL -KEYS这条命令的含义是启用 bizuser设置密码 strongpass允许访问所有 key所有命令组但禁止 FLUSHALL 和 KEYS。~*是 key 模式匹配生产环境如果业务只访问特定前缀的 key可以改成~cache:*收窄范围。内存打满也是高频事故。当 maxmemory 触发后如果客户端执行写命令会报OOM command not allowed when used memory maxmemory。这个报错说明 maxmemory-policy 失效或者配置成了 noeviction。处理方法不是简单调大内存而是先看INFO memory里的 used_memory 和redis-cli --bigkeys找出内存大头把无效 key 清掉再把 maxmemory-policy 改成 allkeys-lru。6. 进阶实战分布式锁、Redisson 与 Redis 面试冲刺6.1 分布式锁从 SET NX 到 Lua 脚本分布式锁是 Redis 在缓存之外用得最多的能力。经典的实现是单命令加锁# 加锁只有 key 不存在时才能设置成功NX EX 保证原子性 SET lock:order:1001 owner:thread-1 NX EX 30SET 命令的 NX 参数确保只有第一个请求能写入成功EX 30 设置 30 秒自动过期防止持有锁的进程崩溃后死锁。相比 SETNX 过期时间分两步设置的旧写法这种单命令方案避免了中间状态。加锁容易解锁才是重灾区。直接 DEL 解锁的问题是如果线程 A 的锁 30 秒过期了线程 B 抢到锁然后线程 A 执行完业务再 DEL就会把 B 的锁删掉分布式锁直接失效。正确做法是解锁前先校验持有者校验和删除必须是原子的用 Lua 脚本if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end脚本逻辑是只有当锁的值等于自己的标识时才删除否则返回 0。Redis 执行 Lua 脚本是原子的中间不会插入其他命令所以不会出现“判断之后、删除之前”被人抢锁的空档。调用时把锁 key 作为 KEYS[1]把线程唯一标识作为 ARGV[1] 传入。生产环境我强烈建议直接用 Redisson 封装好的 RLock而不是自己维护 Lua 脚本RLock lock redisson.getLock(lock:order:1001); lock.lock(30, TimeUnit.SECONDS); try { // 业务逻辑 } finally { lock.unlock(); }Redisson 的价值在于它内置了看门狗机制默认锁的租期是 30 秒只要业务没执行完看门狗会每 10 秒自动续期一次避免锁因为业务耗时过长而过期。调用 lock 时如果不传租期参数就会启用看门狗传了租期看门狗不会续期所以要么主动传一个足够大的值要么不传让它自动续期。6.2 锁续期、可重入与 RedLock 的争议分布式锁还有一个现实问题业务逻辑无法准确预估执行时间。我用过一个电商库存扣减场景正常 200 毫秒但遇到数据库慢查询时能跑 40 秒30 秒的锁早就过期了多个线程同时进入临界区超卖了。用 Redisson 之后看门狗自动续期这个情况被解决了。但看门狗不是万能的——如果你的业务里调了外部 HTTP 接口且没有配置超时锁永远不会释放其他请求全部阻塞。所以使用锁时一定要给每个外部调用设置明确的超时时间。Redisson 的 RLock 还支持可重入同一个线程可以多次加锁需要相应次数解锁。这个特性在处理嵌套锁的时候会省很多事。但也有个隐藏坑加锁次数和释放次数不匹配锁就永远不会释放最终依赖看门狗或者租期兜底。业界对 RedLock多节点投票式锁一直有争议。它的思路是向多个独立 Redis 节点加锁超过半数成功才算加锁成功降低单点故障对锁的影响。但在网络分区场景下RedLock 仍然可能失效而且维护成本很高。对于绝大多数业务单节点或主从架构下的分布式锁已经足够了。我的态度是先想清楚你的业务能不能接受锁偶尔失效能接受就上单锁必须极强一致比如金融扣款那就直接引入 etcd 或 ZooKeeper不要在 Redis 上死磕。6.3 面试高频为什么快、什么时候阻塞、怎么保证一致性Redis 相关的面试题本质上是在考察你有没有踩过性能坑。先回答“为什么快”第一数据在内存里内存随机读是纳秒级磁盘读是毫秒级差距在三个数量级以上第二Redis 的事件循环是单线程的用 IO 多路复用epoll同时监听大量连接不存在线程切换和锁竞争的开销。这里有个容易答错的点Redis 6.0 的网络处理引入了多线程但命令执行仍然是单线程的所以说“Redis 是单线程”要加前缀——执行核心命令的线程是单线程。接着面试官大概率会问“单线程是不是 Redis 的瓶颈”这时候你要能把第 5 章的慢日志排查串进去单线程意味着任何耗时操作都会阻塞所有请求。大 key 的读取、删除、过期扫描AOF 重写时 fork这些都会让其余命令排队。大 key 删除在 4.0 之后有了 UNLINK异步删除它只把 key 从主键空间摘除内存释放交给后台线程但大 key 的读取依然是个大坑。生产环境要严格控制单个 key 的大小超过 10KB 就要警惕。“缓存一致性”也是必考题。最常考的 Cache Aside 模式里是先更新数据库再删缓存还是先删缓存再更新数据库为什么。我的回答思路是先更新数据库再删缓存虽然极端情况下缓存删除失败会有一小段不一致窗口但通过消息队列异步重试删除就能兜底。而先删缓存再更新数据库并发读请求会把旧数据重新写进缓存不一致的时间窗口更长。所以结论是更新数据库 删除缓存 失败重试是工程上性价比最高的做法。最后分享一个我坚持了很久的习惯每个 Redis 实例都开启慢日志监控设置slowlog-log-slower-than 50005 毫秒每周扫描一次大 key。Redis 出问题从来不是某一刻突然发生的慢日志和大 key 就是最前置的预警信号提前处理掉线上才不会半夜把你叫醒。希望这篇笔记能帮你把 Redis 从“会 SET/GET”推进到“能独当一面”下次遇到性能问题不用再靠重启大法续命。本文还有配套的精品资源点击获取