<?xml version="1.0" encoding="utf-8"?>
<ul>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。这本手册用于日常开发、值班排查和上线前检查。建议打印或放到团队知识库中，并结合自己公司的平台规范补充。1. 连接与基础命令1.1 连接redis-cli -h 127.0.0.1 -p 6379redis-cli -h 127.0.0.1 -p 6379 -a 'password'redis-cli -h 127.0.0.1 -p 6379 --tls --cacert ca.crtredis-cli -h 127.0.0.1 -p 6379 -u redis://user:password@host:6379/01.2 服务器状态PINGECHO helloSELECT 0CLIENT INFOCLIENT LISTCLIENT KILL ID 123INFO serverINFO clientsINFO memoryINFO statsINFO replicationINFO persistenceCONFIG GET maxmemoryCONFIG GET maxmemory-policyTIMEDBSIZELATENCY HISTORY eventSLOWLOG GET 201.3 key 通用操作EXISTS keyTYPE keyDEL keyUNLINK keyEXPIRE key 60PEXPIRE key 60000EXPIREAT key 1787568000TTL keyPTTL keyPERSIST keyRENAME old newRANDOMKEYSCAN 0 MATCH user:* COUNT 100OBJECT ENCODING keyOBJECT FREQ keyMEMORY USAGE keyMEMORY USAGE key SAMPLES 0生产禁用：KEYS *FLUSHDBFLUSHALLSMEMBERS big_setHGETALL big_hashZRANGE big_zset 0 -1如确需全量扫描，使用 SCAN、SSCAN、HSCAN、ZSCAN 并分批处理。2. 数据类型命令速查2.1 StringSET key valueSET key value EX 60SET key value PX 60000SET key value NXSET key value XXGET keyGETSET key new-valueMSET k1 v1 k2 v2MGET k1 k2INCR counterINCRBY counter 10INCRBYFLOAT score 1.5DECR counterSTRLEN keyAPPEND key suffixSETRANGE key 0 helloGETRANGE key 0 4典型场景：缓存 JSON、计数器、分布式锁、限流计数。2.2 HashHSET user:1 name Tom age 20HGET user:1 nameHMGET user:1 name ageHGETALL user:1HDEL user:1 ageHEXISTS user:1 nameHLEN user:1HKEYS user:1HVALS user:1HINCRBY user:1 login_count 1HSCAN user:1 0 COUNT 100典型场景：对象字段、用户资料、购物车。2.3 ListLPUSH queue a bRPUSH queue cLPOP queueRPOP queueBLPOP queue 5BRPOP queue 5LRANGE queue 0 -1LLEN queueLINDEX queue 0LSET queue 0 new-valueLREM queue 1 valueLTRIM queue 0 99典型场景：最新列表、简单任务队列。可靠队列建议使用 Stream，List 没有消费确认和 pending 机制。2.4 SetSADD tags a bSREM tags aSMEMBERS tagsSISMEMBER tags aSCARD tagsSINTER set1 set2SUNION set1 set2SDIFF set1 set2SPOP tagsSRANDMEMBER tags 3SSCAN tags 0 COUNT 100典型场景：标签、共同关注、去重、抽奖。2.5 ZSetZADD rank 100 user:1 200 user:2ZSCORE rank user:1ZINCRBY rank 10 user:1ZRANK rank user:1ZREVRANK rank user:1ZRANGE rank 0 9 WITHSCORESZREVRANGE rank 0 9 WITHSCORESZRANGEBYSCORE rank 100 200ZREVRANGEBYSCORE rank 200 100 LIMIT 0 10ZCOUNT rank 100 200ZCARD rankZREM rank user:1ZPOPMIN rankZPOPMAX rankZSCAN rank 0 COUNT 100典型场景：排行榜、延迟队列、时间线、优先级队列。2.6 BitmapSETBIT sign:1001:202608 5 1GETBIT sign:1001:202608 5BITCOUNT sign:1001:202608BITPOS sign:1001:202608 1BITOP AND result source1 source2BITOP OR result source1 source2典型场景：签到、活跃标记、布尔状态。2.7 HyperLogLogPFADD uv:page:1001 user1 user2PFCOUNT uv:page:1001PFMERGE total uv:page:1 uv:page:2典型场景：近似 UV 统计。标准误差约 0.81%，不保存元素。2.8 GEOGEOADD stores 116.397 39.909 store:1GEOPOS stores store:1GEODIST stores store:1 store:2 kmGEOSEARCH stores FROMLONLAT 116.39 39.90 BYRADIUS 3 km ASC COUNT 20典型场景：附近门店、附近的人。2.9 StreamXADD events * type order-created orderId 1001XRANGE events - +XREVRANGE events + - COUNT 10XLEN eventsXREAD COUNT 10 STREAMS events 0XGROUP CREATE events order-group 0XREADGROUP GROUP order-group consumer-1 COUNT 10 STREAMS events &gt;XPENDING events order-groupXACK events order-group 1234567890-0XCLAIM events order-group consumer-2 60000 1234567890-0XTRIM events MAXLEN 100000典型场景：轻量事件流、任务队列、消费组处理。3. 数据结构选型表            需求      推荐结构      原因                  整体缓存 JSON      String      简单、网络友好              对象局部字段更新      Hash      避免整对象重写              最新 N 条数据      List / ZSet      ZSet 更容易按时间分页              去重      Set      O(1) 判断存在              排行榜      ZSet      按分数排序和范围查询              签到      Bitmap      内存小              UV 近似统计      HyperLogLog      固定小内存              附近位置      GEO      geohash 范围查询              可确认队列      Stream      消费组和 pending              分布式锁      String + Lua      NX PX 原子加锁              滑动窗口限流      ZSet      按时间范围清理              固定窗口限流      String / Hash      原子计数              多字段索引      RediSearch      需要模块支持      4. 关键配置速查4.1 内存与淘汰maxmemory 8gbmaxmemory-policy allkeys-lfumaxmemory-samples 10lfu-log-factor 10lfu-decay-time 1lazyfree-lazy-eviction yeslazyfree-lazy-expire yeslazyfree-lazy-server-del yesreplica-lazy-flush yesactivedefrag no常用策略：            策略      行为      适用                  noeviction      内存满后写入报错      混存不可丢数据              allkeys-lru      全量近似 LRU      纯缓存              allkeys-lfu      全量近似 LFU      长期热点明显              volatile-lru      只淘汰有 TTL key      混合实例谨慎使用              volatile-ttl      优先淘汰短 TTL      特定缓存分层              allkeys-random      随机淘汰      少用      4.2 持久化save 3600 1 300 100 60 10000stop-writes-on-bgsave-error yesrdbcompression yesrdb-save-incremental-fsync yesappendonly yesappendfilename "appendonly.aof"appenddirname "appendonlydir"appendfsync everysecno-appendfsync-on-rewrite noauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mbaof-use-rdb-preamble yes4.3 复制与哨兵replicaof 127.0.0.1 6379replica-read-only yesreplica-serving-stale-data yesrepl-diskless-sync yesrepl-diskless-sync-delay 5repl-backlog-size 256mbrepl-backlog-ttl 3600min-replicas-to-write 1min-replicas-max-lag 10replica-priority 100哨兵配置示例：port 26379sentinel monitor mymaster 127.0.0.1 6379 2sentinel down-after-milliseconds mymaster 5000sentinel failover-timeout mymaster 60000sentinel parallel-syncs mymaster 14.4 网络与客户端bind 0.0.0.0protected-mode yesport 6379tcp-backlog 511timeout 300tcp-keepalive 300maxclients 10000client-output-buffer-limit normal 0 0 0client-output-buffer-limit replica 256mb 64mb 60client-output-buffer-limit pubsub 32mb 8mb 60io-threads 4io-threads-do-reads no4.5 安全requirepass strong-passwordaclfile /etc/redis/users.aclrename-command KEYS ""rename-command FLUSHALL ""tls-port 6380tls-cert-file /etc/redis/tls/redis.crttls-key-file /etc/redis/tls/redis.keytls-ca-cert-file /etc/redis/tls/ca.crt生产上建议网络隔离 + ACL + TLS，而不是只依赖密码。rename-command 在部分版本和云环境中受限，优先使用 ACL。ACL 示例：ACL SETUSER app_user on &gt;strong-password ~app:* +GET +SET +DEL +EXPIRE +PINGACL LISTACL WHOAMIACL CAT5. 关键 INFO 指标5.1 server            指标      含义                  redis_version      当前版本              process_id      进程 ID              uptime_in_seconds      运行时长              config_file      配置文件      5.2 clients            指标      含义                  connected_clients      当前客户端连接数              blocked_clients      阻塞命令等待数              maxclients      最大连接数              cluster_connections      集群连接      告警关注：连接数持续上涨、blocked_clients 异常。5.3 memory            指标      含义                  used_memory      Redis 分配器使用内存              used_memory_rss      操作系统视角 RSS              used_memory_peak      历史峰值              maxmemory      最大内存限制              maxmemory_policy      淘汰策略              mem_fragmentation_ratio      RSS / used_memory              total_system_memory      系统总内存      常见判断：used_memory / maxmemory &gt; 80%：容量预警mem_fragmentation_ratio &gt; 1.5：观察碎片mem_fragmentation_ratio &lt; 1：可能使用 swap，严重风险5.4 stats            指标      含义                  total_commands_processed      累计命令数              instantaneous_ops_per_sec      当前 QPS              hit_ratio      键空间命中率              expired_keys      累计过期删除              evicted_keys      因内存淘汰的 key              keyspace_hits      命中次数              keyspace_misses      未命中次数              rejected_connections      拒绝连接数      缓存命中率：keyspace_hits / (keyspace_hits + keyspace_misses)5.5 persistence            指标      含义                  rdb_bgsave_in_progress      是否正在 bgsave              rdb_last_save_time      上次 RDB 时间              rdb_last_bgsave_status      上次 bgsave 状态              aof_enabled      是否开启 AOF              aof_rewrite_in_progress      是否正在重写              aof_last_write_status      上次 AOF 写状态              aof_current_size      当前 AOF 大小              loading      是否正在加载数据      5.6 replication            指标      含义                  role      master 或 replica              connected_slaves      已连接从节点数              master_replid      主节点复制 ID              master_repl_offset      主节点复制偏移              slave_repl_offset      从节点复制偏移              slave_read_repl_offset      从节点读偏移              master_link_status      从节点连接主节点状态              master_last_io_seconds_ago      距上次主从 IO 秒数      复制延迟观察：master_repl_offset - slave_repl_offset5.7 clusterCLUSTER INFOCLUSTER NODESCLUSTER SLOTSCLUSTER COUNTKEYSINSLOT 1234CLUSTER KEYSLOT keyCLUSTER CHECK 127.0.0.1:7001重点指标：            指标      含义                  cluster_state      ok 或 fail              cluster_slots_assigned      已分配槽              cluster_slots_ok      正常槽              cluster_slots_pfail      疑似故障槽              cluster_slots_fail      故障槽              cluster_known_nodes      已知节点              cluster_size      至少一个槽的 master 数      6. 故障排查命令6.1 延迟升高redis-cli slowlog get 50redis-cli latency latestredis-cli latency history eventredis-cli info commandstatsredis-cli info clientsredis-cli info memoryredis-cli --latency -h host -p portredis-cli --intrinsic-latency 30redis-cli --bigkeysredis-cli --memkeys排查顺序：慢命令 -&gt; 大 key -&gt; 客户端连接 -&gt; 内存和 swap -&gt; fork/AOF -&gt; 网络 -&gt; 主从/集群事件6.2 CPU 高redis-cli info commandstatsredis-cli --hotkeysredis-cli cluster nodesperf top -p $(pidof redis-server)常见原因：  单分片热点 key；  QPS 真实超过单节点容量；  大量序列化或大 value；  过期删除和淘汰集中；  AOF/RDB fork；  客户端频繁重连；  系统进程干扰。6.3 内存高redis-cli info memoryredis-cli dbsizeredis-cli --bigkeysredis-cli --memkeysredis-cli scan 0 match 'prefix:*' count 100处理：  拆分大 key；  缩短 TTL；  清理冷数据；  拆分实例；  扩容；  检查淘汰策略是否符合数据价值。6.4 连接异常redis-cli client listredis-cli info clientsredis-cli config get maxclientsss -antp | grep 6379关注：            字段      含义                  age      连接存活时间              idle      空闲时间              cmd      最近命令              qbuf      输入缓冲区              qbuf-free      输入缓冲区剩余              oll      输出列表长度              omem      输出缓冲区内存      6.5 主从复制异常redis-cli -p 6379 info replicationredis-cli -p 6380 info replicationredis-cli -p 6380 roleredis-cli -p 6379 client list | grep replica处理顺序：  网络连通性和带宽；  master_link_status；  复制偏移差；  backlog 是否过小；  主节点写压力和 RDB 生成状态；  是否频繁全量同步。6.6 数据丢失排查1. 确认写入是否到达 Redis：TTL、WAIT、客户端确认、命令日志2. 确认是否发生故障切换：sentinel/cluster 日志3. 检查复制确认程度：min-replicas-to-write4. 检查 AOF/RDB 状态和丢失窗口5. 检查是否内存淘汰：evicted_keys6. 检查是否过期：expired_keys7. 检查是否误删：DEL/UNLINK/SCAN 脚本8. 检查是否有其他客户端写入：CLIENT LIST、ACL 审计7. 性能黄金清单上线前逐项确认：  所有 key 有前缀规范和 TTL；  value 平均大小和 P99 大小已评估；  单 key 建议小于 10KB，超过需要评审；  集合类元素数量有上限；  禁用 KEYS 和全量大集合命令；  pipeline 批量大小控制在几百条；  Lua 脚本短小、幂等、确定耗时；  连接池最大连接数和超时已配置；  命令 P99 延迟有监控；  慢日志和 latency monitor 开启；  maxmemory 有预警；  淘汰策略符合数据价值；  RDB/AOF 配置满足 RPO；  备份恢复已演练；  哨兵或 Cluster 已演练故障切换；  应用能处理连接抖动和重试；  重试有退避和上限；  热点 key 有识别手段；  回源数据库有限流；  有明确降级预案。8. 安全黄金清单  Redis 不暴露公网；  使用 VPC、安全组或防火墙限制来源；  开启 protected-mode；  使用 ACL 替代共享密码；  每个应用独立用户；  只授予必要 key 前缀和命令；  禁止普通应用执行 CONFIG、DEBUG、FLUSH、SHUTDOWN；  敏感传输使用 TLS；  密码和证书使用密钥管理系统；  定期轮换凭据；  开启操作审计；  及时升级安全版本；  备份文件加密并限制访问；  生产命令入口审批；  定期演练权限误配置后的影响。9. 客户端配置建议9.1 Java 常用参数            参数      建议                  connect timeout      500ms 到 2s              command timeout      50ms 到 500ms，按业务 SLA              max idle      接近 max total              max total      根据实例 QPS 和连接上限计算              min idle      保持少量预热连接              test while idle      谨慎开启              retry      只重试幂等命令              circuit breaker      必须具备      9.2 重试原则可以重试：GET、MGET、EXISTS、PING明确的连接建立失败幂等写且业务有唯一键不要盲目重试：INCR/DECRSET NX 分布式锁LPUSH/RPUSH非幂等业务写超时原因不明的命令超时原因不明时，命令可能已经执行，盲目重试会造成重复写入。10. 常见故障与处理            现象      优先检查      常见原因                  突然超时      慢日志、latency、大 key      慢命令、fork、网络抖动              CPU 100%      commandstats、hotkeys      热点 key、QPS 超限              内存持续增长      TTL、bigkeys、业务增长      冷数据、无 TTL、淘汰策略错误              大量写入报错      maxmemory、noeviction      内存满、淘汰不可用              命中率下降      业务访问模式、key 过期      TTL 到期、key 被误删              数据库压力升高      miss 指标      穿透、击穿、雪崩              主从断连      网络、复制缓冲、带宽      网络故障、backlog 不足              频繁全量同步      offset、replid、backlog      断线过久、拓扑切换              Cluster 状态 fail      cluster info/nodes      槽未覆盖、节点故障              客户端连接耗尽      client list、应用池      连接泄漏、池配置过小      11. 日常巡检表每日：1. used_memory 和 maxmemory2. QPS 和命令 P993. 慢查询数量4. 连接数5. 主从复制状态6. 集群状态7. 备份状态8. evicted_keys 和 expired_keys每周：1. 大 key Top 1002. 热点 key Top 1003. key 前缀内存分布4. 命中率变化5. 慢命令分类6. 主从延迟峰值7. 安全组和 ACL 变更每月：1. 容量趋势预测2. 备份恢复演练3. 故障切换演练4. 版本安全公告5. key 规范治理6. 值班 Runbook 更新12. 常用脚本模板12.1 加锁SET lock:order:1001 unique-token NX PX 1000012.2 释放锁if redis.call('GET', KEYS[1]) == ARGV[1] then    return redis.call('DEL', KEYS[1])else    return 0end12.3 秒杀扣减local stock = tonumber(redis.call('GET', KEYS[1]) or '0')if stock &lt;= 0 then    return 0endredis.call('DECR', KEYS[1])return 112.4 滑动窗口限流local key = KEYS[1]local now = tonumber(ARGV[1])local window = tonumber(ARGV[2])local limit = tonumber(ARGV[3])local member = ARGV[4]redis.call('ZREMRANGEBYSCORE', key, 0, now - window)if redis.call('ZCARD', key) &gt;= limit then    return 0endredis.call('ZADD', key, now, member)redis.call('PEXPIRE', key, window)return 112.5 分批 SCAN 删除redis-cli --scan --pattern 'tmp:*' | head -n 1000 | xargs -r redis-cli unlink删除前必须确认前缀、预估 key 数量、评估对业务影响，并保留回滚或重建方案。13. 学习资源官方：Redis docs:    https://redis.io/docs/latest/Redis commands: https://redis.io/docs/latest/commands/Redis GitHub:  https://github.com/redis/redisRedis release: https://github.com/redis/redis/releases推荐顺序：  官方数据类型文档；  官方配置说明；  INFO 指标解释；  持久化、复制、哨兵、Cluster 文档；  官方性能和延迟文档；  发布说明；  源码注释；  社区 issue 与讨论。14. 最后检查每次上线 Redis 相关功能前，问十个问题：  这个数据能丢吗？  内存满了会发生什么？  key 的生命周期是什么？  value 最大多大？  Redis 故障时业务返回什么？  数据库能承受多少回源？  缓存不一致如何发现和修正？  主从切换时客户端如何恢复？  如何确认这次变更是安全的？  出问题后第一处理动作是什么？能清楚回答这十个问题，说明这个 Redis 方案已经具备进入生产的基本条件。</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。会使用 Redis、会排查故障、会设计缓存架构，已经能胜任大多数业务场景。但要成为真正的 Redis 专家，还需要能阅读源码、理解版本演进、跟踪社区讨论，并把 Redis 的设计思想迁移到其他存储系统中。本章给出一套可执行的源码阅读路线、调试方法、版本演进重点和 L1 到 L5 成长地图。29.1 为什么值得读 Redis 源码Redis 源码有四个特点：  代码量相对小，核心模块清晰；  C 语言实现直接，能看清数据结构和内存布局；  单线程事件模型简洁，适合学习网络编程；  存储组件中的经典问题都能看到工程答案。读完源码后，你会更容易回答这些问题：  SDS 为什么要这样设计；  对象编码什么时候转换；  过期采样如何权衡 CPU 与内存；  RDB fork 和写时复制如何影响延迟；  AOF 重写如何保证增量不丢；  复制 backlog 为什么能部分重同步；  Cluster 如何通过 Gossip 维护拓扑；  命令表如何统一实现命令分发。更重要的是，你会开始理解“简单模型 + 极致工程优化”的设计哲学。29.2 获取与准备源码29.2.1 克隆源码git clone https://github.com/redis/redis.gitcd redisgit checkout 7.4建议选择一个稳定分支或 tag，不要长期在 master 上阅读。源码和线上版本保持一致，遇到行为差异时更容易对照。29.2.2 编译make -j8make test如果需要调试符号：make distcleanmake OPTIMIZATION=0 MALLOC=libc -j8常用调试工具：            工具      用途                  gdb / lldb      断点、单步、查看结构体              clangd / ccls      代码跳转              valgrind      内存问题              perf      CPU 热点              strace / ltrace      系统调用              redis-cli monitor      观察命令      29.2.3 调试启动gdb ./src/redis-server(gdb) break serverCron(gdb) run --port 6380 --save "" --appendonly no另一个窗口：redis-cli -p 6380 pingredis-cli -p 6380 set hello world调试生产问题时，不要直接对线上进程附加 gdb；长时间停顿会阻塞所有请求。应在测试环境复现，或使用指标、日志和采样工具。29.3 源码目录地图Redis 根目录中最值得关注的是 src/：            目录或文件      内容                  server.c      服务器启动、命令分发、serverCron              ae.c      事件循环抽象              networking.c      客户端、协议解析、输出缓冲区              db.c      数据库、查找、过期辅助逻辑              object.c      RedisObject 创建、引用计数              t_string.c 等      各数据类型命令实现              expire.c      过期逻辑与删除策略              evict.c      内存淘汰              latency.c      延迟监控              rdb.c      RDB 生成与加载              aof.c      AOF 写入、重写、恢复              replication.c      主从复制              cluster.c      集群与 Gossip              module.c      模块系统              config.c      配置解析              blocked.c      阻塞命令              listpack.c      紧凑编码              zipmap.c / ziplist.c      旧紧凑结构              sds.c      动态字符串              dict.c      字典              adlist.c      双端链表              skiplist.c      跳表              t_stream.c      Stream      测试目录：            目录      内容                  tests/unit      单元测试              tests/integration      复制、AOF、集群集成测试              tests/cluster      集群测试              deps      第三方依赖      阅读顺序建议从命令执行链路开始，再进入数据结构，最后读持久化、复制和集群。29.4 源码阅读路线阶段一：命令是怎么执行的目标文件：server.cnetworking.cdb.cobject.ct_string.c阅读链路：main -&gt; initServer -&gt; aeMain接受连接 -&gt; 创建 client读取 socket -&gt; 解析 RESP查找命令表 -&gt; 校验参数和权限调用命令实现函数添加输出缓冲区 -&gt; 写回客户端重点问题：  命令表在哪里定义；  命令的 arity、flags 表示什么；  lookupKey 读写都有哪些副作用；  client 的输入输出缓冲区如何管理；  为什么命令执行天然串行。练习：给一个普通命令加上自定义日志，观察执行链路。不要修改生产版本源码，只在本地分支实验。阶段二：核心数据结构目标文件：sds.cdict.ct_hash.ct_list.ct_set.ct_zset.ct_string.clistpack.cskiplist.c阅读重点：            文件      关注点                  sds.c      长度、扩容、二进制安全、多种 sdshdr 类型              dict.c      渐进 rehash、哈希算法、迭代器              t_zset.c      ziplist/listpack 与 skiplist 转换              listpack.c      紧凑存储、级联更新差异              skiplist.c      插入、删除、范围查询      重点问题：  对象编码阈值由哪些配置控制；  渐进 rehash 期间读写如何处理；  ZSet 为什么同时保留 dict 和 skiplist；  listpack 如何解决 ziplist 的级联更新问题；  内存预估为什么要看编码而不是只看元素数量。阶段三：过期与淘汰目标文件：expire.cevict.cdb.clazyfree.cobject.c阅读重点：  TTL 如何写入过期字典；  惰性删除和定期删除的触发条件；  定期删除的采样数量和时间上限；  LRU/LFU 时钟字段如何更新；  同步删除和异步删除的选择；  maxmemory 达到后的写入路径。练习：CONFIG SET maxmemory 100mbCONFIG SET maxmemory-policy allkeys-lruDEBUG SLEEP 0INFO memoryINFO stats观察 evicted_keys、expired_keys 和内存变化，再对应源码阅读。阶段四：事件循环与网络目标文件：ae.cae_epoll.cae_kqueue.cnetworking.cio_threads.c阅读重点：  aeMain 如何分发时间事件和文件事件；  epoll/kqueue 如何抽象；  beforeSleep 做哪些事情；  IO 线程何时启用；  输出缓冲区限制如何断开客户端。重点认知：Redis 的单线程不是没有并发，而是把并发点放在网络 IO、后台任务和异步释放内存上，命令执行保持串行以简化一致性。阶段五：持久化目标文件：rdb.caof.cchildinfo.cbio.c阅读重点：  bgsave 如何 fork 子进程；  写时复制对内存的影响；  RDB 格式和加载流程；  AOF 写入缓冲和刷盘策略；  AOF 重写期间增量如何追加；  Redis 7 multipart AOF 的文件组织；  混合持久化如何组织 RDB 前缀和 AOF 增量。练习：构造写负载，观察 INFO persistence：redis-cli info persistenceredis-cli latency latestredis-cli config get saveredis-cli config get appendfsync阶段六：复制与哨兵目标文件：replication.cserver.csentinel.c阅读重点：  PSYNC 参数与返回值；  replid、replid2 和 offset；  replication backlog 的写入与截断；  全量同步 RDB 与增量缓冲如何交接；  主从心跳和偏移量上报；  哨兵主观下线、客观下线和 leader 选举；  故障转移步骤和复制拓扑调整。实验：redis-server --port 6380redis-cli -p 6381 replicaof 127.0.0.1 6380redis-cli -p 6380 info replicationredis-cli -p 6381 info replication模拟断开和恢复，观察是部分重同步还是全量同步。阶段七：Cluster目标文件：cluster.ccluster_legacy.ccluster_slot_stats.c不同版本文件拆分差异较大，阅读时以当前分支为准。重点问题：  节点 ID、槽位和 epoch 的作用；  Gossip 消息类型；  PFAIL 和 FAIL 的判定；  MOVED、ASK 如何产生；  reshard 时 migrate 的 key 流程；  replica 迁移和故障转移；  为什么多 key 命令要求同槽。实验：redis-cli --cluster create 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003redis-cli --cluster check 127.0.0.1:7001redis-cli -p 7001 cluster inforedis-cli -p 7001 cluster nodes阶段八：扩展模块目标文件：module.ct_stream.credismodule.h如果使用 Redis Stack，还可以阅读对应模块仓库：            模块      能力                  RedisJSON      JSON 文档和路径操作              RediSearch      索引与查询              RedisTimeSeries      时间序列              RedisBloom      概率结构      模块学习重点：  模块如何注册命令；  模块如何访问 key 和类型；  模块阻塞命令如何实现；  模块内存如何统计；  模块升级与兼容性。29.5 高效阅读源码的方法29.5.1 带问题读不要从第一行顺序读到最后一行。每轮只回答一个问题：第一轮：SET 命令如何执行？第二轮：Hash 什么时候从 listpack 转 hashtable？第三轮：expire key 后内存什么时候释放？第四轮：BGSAVE 期间新写入为什么不进 RDB？第五轮：从节点断线后如何判断能否部分重同步？29.5.2 命令对照在 redis-cli 执行命令，同时用 gdb 或日志观察内部函数。命令行为是外部契约，源码是内部实现，双向对照最不容易迷路。示例：HSET user:1 name Tom age 20OBJECT ENCODING user:1DEBUG OBJECT user:1MEMORY USAGE user:1先猜结果，再看源码验证。29.5.3 画时序图每读一个模块，输出一张图。例如 AOF 重写：sequenceDiagram    participant C as Client    participant M as 主进程    participant B as 子进程    participant FS as 文件系统    M-&gt;&gt;B: fork    B-&gt;&gt;FS: 写 base AOF    C-&gt;&gt;M: 新写命令    M-&gt;&gt;FS: 写原 AOF / incr    M-&gt;&gt;FS: 写重写缓冲    B--&gt;&gt;M: 重写完成    M-&gt;&gt;FS: 追加增量并原子改名能画出来，才算真正理解主干。29.5.4 做实验记录建议维护一个实验日志：            日期      版本      实验      结果      源码解释                  2026-08-25      7.4      小 Hash 编码      listpack      阈值未超过              2026-08-25      7.4      100 万元素 ZSet      skiplist      超过阈值转换      长期积累后，这些记录会变成你的个人知识库。29.6 版本演进重点Redis 3.x  Redis Cluster 正式可用；  Sentinel 逐渐成熟；  常用数据结构稳定。Redis 4.x  混合持久化；  Module 系统；  PSYNC2 改进故障后部分重同步；  异步删除相关能力增强。Redis 5.x  Stream 数据类型；  改进 Cluster 管理；  Redis Module 生态扩展。Redis 6.x  ACL 权限体系；  客户端缓存 Tracking；  IO 多线程；  RESP3；  SSL/TLS 支持；  过期淘汰算法和配置进一步优化。Redis 7.x  Functions；  Sharded Pub/Sub；  multipart AOF；  listpack 使用范围扩大；  Cluster 管理和数据结构持续优化；  ACL、命令参数和安全能力增强。Redis 8.x 与许可证变化Redis 8 将部分 Redis Stack 能力并入主版本，Search、JSON 等能力更易使用；同时 Redis 许可证策略在社区引发分支和厂商方案变化。生产选型时要确认：  具体小版本许可证；  云厂商支持方式；  是否需要商业模块；  是否考虑 Valkey 等兼容实现；  长期升级路径。不要只记“Redis 开源”或“Redis 不开源”这种粗粒度结论，版本和发行版才是关键。29.7 社区与资源官方资源：  Redis 官方文档：https://redis.io/docs/latest/  Redis GitHub：https://github.com/redis/redis  Redis 发布说明：https://github.com/redis/redis/releases  Redis 问题和讨论：https://github.com/redis/redis/issues建议持续关注：  release notes，尤其升级前的破坏性变化；  官方配置说明；  INFO 指标含义；  性能延迟案例；  安全公告；  Valkey 等兼容项目的发展。阅读资料建议：            类型      建议                  命令手册      每天精读几个命令，注意时间复杂度和边界              官方文档      优先级高于二手博客              源码注释      很多设计原因写在注释里              release notes      理解行为变化              故障复盘      学习真实生产边界      29.8 L1 到 L5 成长地图L1 入门使用者能力：  安装和连接 Redis；  掌握五大基础类型；  能使用 RedisTemplate 或客户端 SDK；  理解 TTL 和简单缓存。进阶建议：  每周整理 20 个命令；  写一个增删改查 Demo；  观察每个结构的 OBJECT ENCODING；  用 MEMORY USAGE 对比设计。L2 业务开发者能力：  能设计 key 和 TTL；  能实现缓存、计数器、排行榜、签到；  能使用 pipeline、事务和 Lua；  理解序列化和连接池。进阶建议：  给业务输出 key 清单；  做缓存命中率埋点；  学习大 key 治理；  为每个写操作设计幂等。L3 生产工程师能力：  能部署哨兵和 Cluster；  理解 RDB、AOF、复制和故障切换；  能处理慢查询、大 key、热点 key；  能做备份恢复和容量规划。进阶建议：  定期故障演练；  建立监控告警；  输出 Runbook；  压测并记录性能上界。L4 架构师能力：  能设计多级缓存和一致性方案；  能拆分业务边界和数据生命周期；  能组合 Redis、MySQL、Kafka、ES 和数仓；  能评审容量、成本、风险和降级预案。进阶建议：  写架构决策记录；  建设统一 Cache SDK；  制定平台规范和配额；  每季度复盘故障与成本。L5 专家能力：  能阅读和修改源码；  能定位底层性能问题；  能参与社区讨论或提交 issue；  能设计存储引擎级别优化；  能指导团队建立技术体系。进阶建议：  完成源码阅读路线；  复现官方 issue；  研究 Valkey、KeyDB 等实现差异；  输出源码解析和性能报告；  关注操作系统、网络和存储底层。29.9 30 天进阶计划第 1 周：命令与数据结构  Day 01-02  五大类型命令复习  Day 03-04  Bitmap/HLL/GEO/Stream  Day 05-06  编码与内存实验  Day 07     输出 key 设计规范第 2 周：原理与源码  Day 08-09  server.c/networking.c 命令链路  Day 10-11  sds/dict/skiplist/listpack  Day 12     expire.c/evict.c  Day 13-14  输出对象编码与内存实验报告第 3 周：持久化与高可用  Day 15-16  RDB/AOF 实验  Day 17-18  主从复制实验  Day 19-20  Sentinel 实验  Day 21     Cluster 建立与检查第 4 周：生产与架构  Day 22-23  性能排查与监控  Day 24-25  缓存架构设计  Day 26-27  秒杀或 Feed 项目复盘  Day 28-29  故障演练  Day 30     输出个人 Redis 手册执行建议：每天至少一个实验、一条笔记。学习 Redis 最怕只看文章不动手。29.10 大师级工作习惯  先量化，再优化：没有 P99 延迟和 QPS，不做结论；  先边界，再方案：数据可不可丢、一致性要求多高；  先兜底，再加速：锁和队列必须有幂等；  先监控，再上线：不能观测的缓存等于隐形风险；  先演练，再相信：备份、高可用和降级都要真实执行；  先读文档，再读源码：外部行为先明确；  先复现，再修改：不要根据猜测改配置；  先小流量，再全量：任何配置变更都可能改变延迟曲线。29.11 本章小结  源码阅读应从命令执行链路开始，再进入数据结构、过期淘汰、事件循环、持久化、复制和集群；  Redis 源码的关键价值是看清工程权衡，而不是背函数名；  版本演进要关注 ACL、IO 多线程、AOF、Cluster、模块和许可证变化；  成长路线从会命令、会业务、会生产，逐步到会架构和会源码；  大师能力的核心是量化、验证、兜底和持续复盘。29.12 思考题  如果只能读 10 个 Redis 源码文件，你会选择哪 10 个？为什么？  如何设计一个实验证明 listpack 到标准结构的转换阈值？  AOF 重写期间发生宕机，如何分析哪些数据会保留？  Redis 6 IO 多线程为什么没有让命令执行并行化？  你如何评估 Redis、Valkey 和云托管方案未来三年的演进风险？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章按“基础 -&gt; 数据结构 -&gt; 原理 -&gt; 高可用 -&gt; 分布式锁 -&gt; 缓存架构 -&gt; 性能运维 -&gt; 场景设计”组织 50 道高频题。建议先遮住答案自己回答，再对照补充。28.1 基础篇Q1. Redis 是什么？有哪些典型用途？Redis 是基于内存的键值数据库，支持 String、Hash、List、Set、ZSet、Stream 等结构，并提供持久化、复制、哨兵和 Cluster。典型用途包括缓存、会话、计数器、排行榜、分布式锁、限流、轻量队列和实时状态存储。Q2. Redis 为什么快？核心原因：数据在内存、高效数据结构、单线程命令执行避免锁竞争、IO 多路复用、简单协议 RESP、渐进式复杂度设计和 pipeline 批量传输。Redis 6 后网络 IO 可以多线程处理，但命令执行仍是主线程串行，保证单个节点上的命令原子性。Q3. Redis 是单线程吗？不准确。命令执行通常由主线程完成，但后台线程负责 RDB、AOF 刷盘、异步删除等任务；Redis 6 引入 IO 多线程负责读写网络协议。面试时可以强调“命令执行是单线程，进程不是单线程”。Q4. Redis 中 DB 是物理隔离吗？不是。不同 DB 只是同一个实例内的命名空间，共享内存、CPU、持久化、网络和淘汰策略。生产上核心与非核心业务应使用不同实例，而不是只依赖 SELECT。Q5. RESP 是什么？RESP 是 Redis 序列化协议，客户端与服务端通过简单文本格式交互。RESP2 以 +、-、:、$、* 标识简单字符串、错误、整数、批量字符串和数组；RESP3 增加 map、double、bool、push 等类型。Q6. 缓存和数据库的本质区别是什么？缓存追求低延迟和吞吐，数据通常可丢、可重建；数据库追求完整性、一致性、事务和审计。缓存可以是数据库前面的投影，但不应替代事实源。Q7. Redis 支持事务吗？和 MySQL 事务有什么区别？Redis MULTI/EXEC 能把命令排队并顺序执行，期间不会被其他客户端命令插入，但执行中某条命令失败后不会回滚已执行命令。它更接近批量原子执行，不提供 MySQL 那样的完整隔离级别和回滚语义。Q8. WATCH 的作用是什么？WATCH 实现 CAS。事务执行前如果被 watch 的 key 发生变化，EXEC 返回 nil，事务放弃。适合秒杀扣减这类基于版本检查的小并发逻辑，复杂场景通常用 Lua 或数据库条件更新。Q9. Lua 脚本原子吗？有什么风险？Lua 脚本在单个 Redis 节点执行期间不会被其他命令插入，因此脚本内多命令原子。风险是脚本慢会阻塞节点，网络分区和主从切换仍可能带来业务层重复执行，脚本必须幂等且执行时间可控。Q10. Redis 常见阻塞点有哪些？大 key 读写、KEYS、SMEMBERS 全量集合、大范围 ZRANGE、HGETALL、复杂 Lua、SINTERSTORE 类聚合命令、AOF fsync 策略、RDB fork、交换内存、网络重传、大量连接和磁盘 IO 抖动。28.2 数据结构篇Q11. 五大基础类型的适用场景是什么？String 存储缓存、计数器、分布式锁；Hash 存对象字段；List 存简单队列和最新列表；Set 存去重和共同关注；ZSet 存排行榜、延迟队列和时间线。Q12. String 存对象和 Hash 存对象怎么选？String JSON 适合整体读写、序列化后传输、字段更新少；Hash 适合局部字段更新、字段级 TTL 需求较少、需要读取部分字段。若 value 很大或字段很多，要拆分或压缩。Q13. SDS 相比 C 字符串有什么优势？SDS 保存长度，获取长度 O(1)；支持动态扩容和空间预分配；二进制安全；可通过空闲空间减少内存重分配；部分类型支持惰性空间释放。Q14. ZSet 为什么同时使用跳表和字典？字典支持 member 到 score 的 O(1) 查找；跳表支持按 score 排序和范围查询 O(log N)。两者结合让单点查询和排序查询都快，代价是额外内存。Q15. 为什么 ZSet 使用跳表而不是红黑树？跳表实现更简单，范围查询和节点遍历方便，并可通过多层链表实现近似平衡。红黑树也能做到 O(log N)，但实现和迭代复杂，Redis 选择了工程上更容易维护的结构。Q16. List 的底层编码是什么？旧版本主要使用 ziplist 和 linkedlist；Redis 7 引入 listpack 替代 ziplist 的一部分场景。小列表使用紧凑编码节省内存，变大后转换标准结构。Q17. Hash 的小对象编码是什么？为什么省内存？小 Hash 可使用 listpack/ziplist，字段连续存储并保留少量元数据，减少指针和单独分配开销。元素过多或过大后转为 hashtable，避免紧凑结构更新成本过高。Q18. Bitmap 适合什么场景？签到、活跃用户、权限位、布尔状态。存储 N 天状态约 N/8 字节。适合稠密整数 ID，如果 ID 稀疏会浪费内存，需要映射或换 Roaring Bitmap。Q19. HyperLogLog 的原理和误差是多少？HyperLogLog 通过哈希值前导零的最大值估算基数，标准误差约 0.81%。它内存极小，但不保存元素，也不能取出成员，适合 UV 这类近似去重计数。Q20. GEO 的底层原理是什么？Redis GEO 基于 ZSet，将经纬度编码为 52 bit 的 geohash 作为 score。支持范围查询和距离计算，适合附近的人、附近门店等中小规模场景；复杂地理分析可用专业 GIS 或搜索引擎。28.3 内存与过期删除篇Q21. Redis 如何删除过期 key？结合惰性删除和定期删除。访问 key 时检查过期则删除；后台周期性随机采样过期字典并删除。Redis 6 后还支持异步释放内存，避免删除大 key 阻塞主线程。Q22. 定期删除为什么要限制时间？过期 key 很多时，如果一直删除会占用 CPU 并阻塞正常命令。Redis 通过采样数量、删除比例和时间上限做自适应，把清理任务摊开执行。Q23. maxmemory 达到后会发生什么？写入命令根据 maxmemory-policy 决定是否拒绝或淘汰 key。默认行为可能是 noeviction，写命令报错；也可选 LRU、LFU、random、TTL 相关策略。Q24. LRU 和 LFU 的区别？LRU 淘汰最近最少使用，可能被偶发扫描污染；LFU 淘汰访问频率低的 key，更适合长期热点。Redis 使用抽样近似 LRU/LFU，而不是全局精确双向链表。Q25. allkeys-lru 和 volatile-lru 怎么选？纯缓存实例可用 allkeys-lru，所有 key 都可淘汰；实例中混有锁、队列、状态等不可丢数据时不能用 allkeys 策略，应拆实例或谨慎设置 volatile-* 与 noeviction。Q26. 什么是内存碎片？如何观察和处理？碎片来自分配器空洞和大量不同大小对象的创建删除。观察 INFO memory 的 mem_fragmentation_ratio。适度升高可接受，过高可分析大 key 和淘汰，重启或使用 activedefrag 需评估负载和版本。Q27. 大 key 的危害和处理方式？大 key 导致网络阻塞、序列化慢、内存集中、删除阻塞、迁移慢。处理方式是拆分结构、压缩、分页、缩短 TTL、使用 SCAN 定位、异步删除和治理业务模型。Q28. 热点 key 的危害和处理方式？热点 key 使 Cluster 单分片 CPU、带宽或 QPS 过高。处理方式是本地缓存、key 副本拆分、读写分离、限流、静态化、CDN 和业务错峰。28.4 持久化与高可用篇Q29. RDB 和 AOF 的区别？RDB 是某个时刻的二进制快照，文件小、恢复快，但可能丢失上次快照后的数据；AOF 记录写命令，丢失窗口取决于刷盘策略，文件更大、恢复更慢。Redis 7 使用 multipart AOF，base 和 incr 文件分离。Q30. AOF 三种刷盘策略是什么？always 每条命令刷盘，最可靠但性能最低；everysec 每秒刷盘，通常推荐；no 交给操作系统，性能最好但丢失窗口更大。Q31. AOF 重写为什么存在？AOF 会记录多次修改同一 key 的历史命令，文件越来越大。重写根据当前内存状态生成等价的最小命令集，降低文件体积和恢复时间。重写由子进程执行，期间新写入进入重写缓冲区。Q32. 混合持久化是什么？AOF 重写文件前半部分使用 RDB 格式，之后的增量使用 AOF 命令。恢复速度和数据完整性取得平衡，Redis 7 中通常通过 aof-use-rdb-preamble 控制。Q33. fork 子进程时为什么会影响主进程？fork 需要复制页表，实例内存很大时耗时增加。写时复制期间如果页被大量修改，会额外消耗内存，还可能造成 latency 和内存压力。Q34. 主从复制的流程是什么？从节点发送 PSYNC；无法增量同步时执行全量同步，主节点 bgsave 生成 RDB 并发送，同时积累写缓冲区；从节点加载 RDB 后继续应用增量命令。断线后可根据 replid 和 backlog 尝试部分重同步。Q35. replid 和 replication backlog 的作用？主节点有 replid 和复制偏移量，backlog 保存最近的写命令。从节点携带自己的 replid 和 offset，若仍匹配且 offset 在 backlog 范围内，可只补增量，否则全量同步。Q36. 哨兵如何判断主节点故障？单个哨兵 ping 超时认为主观下线；达到 quorum 个哨兵同意后升级为客观下线；哨兵之间选举 leader 执行故障转移。down-after-milliseconds 和 quorum 要根据网络状况设置。Q37. 哨兵故障转移选择从节点的规则是什么？会过滤不健康或长时间断连的节点，再根据优先级、复制偏移量和 runid 排序。优先级小者优先；偏移量大者优先；最后比较 runid。执行转移时会升级从节点并让其他从节点复制新主。Q38. Redis Cluster 如何分片？共有 16384 个 slot，CRC16(key) mod 16384 计算 key 所属槽。节点负责部分槽，客户端可按 slot 映射直连目标节点。hash tag 可以强制相关 key 落在同一槽。Q39. MOVED 和 ASK 的区别？MOVED 表示槽已经归属新节点，客户端应更新 slot 映射；ASK 表示槽正在迁移，当前请求可先向目标节点发送 ASKING 再执行，属于临时重定向。Q40. Cluster 模式有哪些限制？多 key 命令、事务和 Lua 通常要求 key 在同一槽；数据库只有 DB0；客户端需要感知集群拓扑；reshard 期间有迁移复杂度；节点太少无法满足高可用副本分布要求。28.5 分布式锁与缓存篇Q41. Redis 分布式锁怎么实现？使用 SET key uniqueValue NX PX ttl 加锁，业务结束后通过 Lua 比较 value 并删除，避免误删别人的锁。执行时间可能超过 TTL 时使用 Redisson watchdog 续期，同时业务必须有幂等兜底。Q42. 为什么要保存唯一 value？如果客户端 A 过期后释放锁，客户端 B 已获取锁，A 直接 DEL 会把 B 的锁删掉。Lua 脚本先比较唯一 value 再删除，可以降低误删概率。Q43. Redlock 适合什么场景？Redlock 在多个独立 Redis 节点上加锁，多数成功并在有效期内才算成功。它试图减少单实例故障影响，但不能解决时钟跳变、长时间 GC、网络分区下的所有安全性问题。多数业务用单主锁加业务幂等更务实。Q44. 缓存穿透是什么？如何防护？请求不存在的数据，缓存和数据库都无法命中。防护包括参数校验、空值缓存、布隆过滤器、风控限流和恶意请求拦截。空值 TTL 应较短。Q45. 缓存击穿和雪崩的区别？击穿是单个热点 key 过期瞬间大量请求回源；雪崩是大量 key 同时失效或 Redis 整体不可用导致数据库压力骤增。击穿用互斥加载、逻辑过期和热点不过期；雪崩用 TTL 抖动、多级缓存、限流降级和高可用架构。Q46. 先更新数据库再删缓存是否保证一致？不保证强一致，只是出错概率较低的通用方案。仍存在读请求未命中后加载旧值、删除失败、主从延迟等问题。需要 TTL 兜底、失败重试、binlog 失效或版本号校验。Q47. 延迟双删的适用边界？延迟双删用于降低“先删缓存后更新库”或读写并发造成旧值回填的概率。延迟时间难以精确选择，且不能保证强一致。适合秒级最终一致业务，不适合账务等强一致场景。Q48. 如何设计缓存降级？缓存故障时按业务重要性处理：本地缓存返回旧值、返回静态兜底数据、非核心字段隐藏、核心接口限流排队，数据库回源必须有限流。降级策略要预先配置并演练。28.6 性能运维与场景设计篇Q49. Redis 延迟突然升高，如何排查？按顺序看：慢日志、latency monitor、命令统计、大 key、热点 key、客户端连接和超时、网络重传、CPU、内存和 swap、RDB/AOF 活动、主从复制状态、集群迁移和故障切换记录。不要一上来就重启。Q50. 设计一个秒杀库存系统，你会怎么做？分层防护：前端静态化和按钮控制；网关限流和黑名单；应用校验活动状态与资格；Redis Lua 原子完成重复判断和库存扣减；Kafka 异步创建订单；数据库唯一键和条件库存更新兜底；订单超时回补库存；活动后对账 Redis、消息和数据库；全链路监控和降级预案。28.7 回答框架建议面试中回答 Redis 问题可以使用四步：结论：先给出明确答案原理：说明 Redis 内部机制或时序边界：指出不适用场景或失败窗口实践：给出生产上的防护和监控例如回答持久化时，不要只说“RDB 快、AOF 不丢”，而要补充：RDB 有快照间隔的丢失窗口，AOF everysec 也可能丢一秒；生产要根据 RPO 选择策略，并用备份和故障演练验证恢复。28.8 本章小结  基础问题要准确，例如“命令执行单线程”不等于进程单线程；  数据结构问题要落到编码、复杂度和业务场景；  持久化与高可用问题必须说明丢失窗口和故障切换影响；  分布式锁不能只讲命令，要讲误删、续期、分区和业务幂等；  缓存问题要区分穿透、击穿、雪崩，并说明一致性等级；  性能和场景题要展示排查顺序、边界条件和兜底方案。28.9 思考题  你所在业务中哪些 Redis 数据是可丢的？哪些是不可丢的？分别如何验证？  如果面试官追问“Redis 能不能保证分布式锁绝对安全”，你的最终答案是什么？  如何向架构师解释“缓存强一致很难，但可以做可审计的最终一致”？  生产实例 CPU 100% 且命令 QPS 不高，你会优先排查哪些原因？  设计一个 10 万 QPS 的用户资料缓存，你会输出哪些容量、命中率和降级指标？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。当 Redis 从“某个服务的缓存”变成平台级组件后，问题就不再只是命令和性能，而是边界：哪些系统可以共用一套 Redis，哪些数据必须隔离，哪些场景适合 Redis，哪些场景应该交给 MySQL、Kafka、Elasticsearch 或对象存储。本章讨论 Redis 在大型系统中的架构位置、隔离策略、冷热分层、数据生态和替代方案比较。27.1 先定义 Redis 的角色同一套 Redis，在不同系统里可能承担完全不同的角色：            角色      典型数据      可靠性要求      失败影响                  缓存      商品详情、用户资料      可丢、可重建      数据库压力升高              会话存储      登录态、Token      中等      用户重新登录              计数器      浏览量、点赞数      允许少量误差      数据略旧              排行榜      热榜、积分榜      允许重算      排名短时间不可用              分布式锁      任务互斥、防重      高      重复执行或业务阻塞              轻量队列      异步任务、事件流      与业务绑定      任务延迟或丢失              状态存储      规则、限流窗口      高      系统误放行或误拒绝              主数据存储      订单、账务      很高      通常不适合      架构评审时应明确每个实例或每个 DB 的角色。最常见的问题是：一个 Redis 实例里同时有低价值缓存、高价值锁和大 value 会话，某个慢查询或内存淘汰影响所有业务。推荐隔离粒度：按环境隔离：dev / test / staging / prod按重要性隔离：核心交易 / 一般业务 / 离线分析按延迟形态隔离：在线低延迟 / 批量任务 / 异步任务按数据所有权隔离：商品、交易、用户、社区27.2 多租户与资源隔离27.2.1 常见隔离方案            方案      实现      优点      缺点                  DB 编号隔离      每个业务使用不同 Redis DB      管理简单      CPU、内存、网络仍共享              key 前缀隔离      同一 DB 内按前缀划分      灵活      只能逻辑隔离，容易互相影响              实例隔离      每个业务独立实例      故障隔离最好      成本和运维对象变多              Cluster 分片隔离      key 前缀路由到不同分片      局部隔离      仍共享节点和网络              代理多租户      云厂商或 Proxy 限流配额      可计量      依赖代理能力      Redis 的 DB 不是物理隔离。SELECT 0 和 SELECT 1 共享同一个进程的 CPU、内存淘汰策略、持久化和网络带宽。因此 DB 隔离只适合开发、测试或弱相关业务，不适合核心与非核心混部。27.2.2 key 前缀与路由多租户 key 设计：tenant:{tenantId}:app:{appName}:object:{objectId}示例：tenant:1001:mall:product:base:20001tenant:1002:mall:product:base:20001如果使用 hash tag 控制 Cluster 槽位：tenant:{1001}:lock:order:30001tenant:{1001}:order:state:30001同一个 hash tag 会落在同一个节点。适合需要事务、Lua 或管道原子操作的相关 key，但会牺牲分片均衡，只应小范围使用。27.2.3 配额与限流平台化 Redis 应该提供租户级配额：            控制项      建议                  key 数量      按前缀统计，限制增长              内存      通过监控和代理限制              QPS      应用客户端或代理限流              value 大小      SDK 拦截大 key              命令类型      禁用 KEYS、FLUSHALL、全量 SMEMBERS              连接数      每应用设置连接池上限      一个统一的 Cache SDK 可以在入口做治理：public &lt;T&gt; T execute(String key, RedisCallback&lt;T&gt; action) {    tenantQuota.checkKeyPrefix(key);    if (key.getBytes(StandardCharsets.UTF_8).length &gt; 512) {        throw new CacheKeyTooLongException(key);    }    return action.execute();}大 value 校验建议在序列化后执行：byte[] payload = serializer.serialize(value);if (payload.length &gt; maxBytes) {    metrics.tooLarge(prefix);    throw new CacheValueTooLargeException(prefix, payload.length);}27.3 冷热分层与数据生命周期Redis 的价值在于低延迟访问热数据。把冷数据长期留在 Redis，既浪费内存，也会增加 RDB、AOF 重写、备份和迁移成本。27.3.1 数据分级            等级      特征      推荐存储                  极热      秒级到分钟级大量访问      应用本地缓存、Redis              热      常态高访问      Redis              温      偶尔访问      MySQL、PostgreSQL              冷      合规或审计访问      对象存储、归档库              历史分析      批处理扫描      数据仓库、湖仓      生命周期设计示例：商品详情：  本地缓存 3 秒  Redis 1 小时  MySQL 永久或按业务归档用户行为：  Redis 聚合 5 分钟  Kafka 保留 3 天  数仓分区保留 1 年订单数据：  Redis 只缓存详情 10 分钟  MySQL 在线保留 2 年  对象存储归档 7 年27.3.2 冷热迁移策略常见迁移方式：  TTL 自动清理；  定时任务扫描过期数据；  访问频率标记，低频数据降级；  binlog 同步温库；  分析结果回写 Redis 热层。访问频率可以用 ZSet 记录：public void touch(Long productId) {    String key = "mall:product:access:" + LocalDate.now();    redis.opsForZSet().incrementScore(key, String.valueOf(productId), 1);    redis.expire(key, Duration.ofDays(3));}每日计算 Top N 写入热集合：@Scheduled(cron = "0 20 1 * * ?")public void refreshHotProducts() {    Set&lt;String&gt; hotIds = redis.opsForZSet()            .reverseRange("mall:product:access:" + LocalDate.now().minusDays(1), 0, 4999);    hotCacheService.replaceAll(hotIds);}冷热分层的核心不是自动越复杂越好，而是让每个数据都有明确生命周期和退出机制。27.4 Redis 与数据库、消息和搜索的组合27.4.1 Redis + MySQL这是最常见的组合：flowchart LR    App[应用] --&gt; Redis[(Redis 缓存)]    Redis -- miss --&gt; App    App --&gt; MySQL[(MySQL 事实源)]    MySQL --&gt; CDC[Canal / Debezium]    CDC --&gt; MQ[Kafka]    MQ --&gt; Invalidator[缓存失效服务]    Invalidator --&gt; Redis职责划分：            关注点      Redis      MySQL                  数据完整性      不承诺完整      事实源              事务      有限事务，不回滚      ACID 能力强              查询模型      key 查找为主      SQL、索引、关联              持久化      可配置，有丢失窗口      稳健持久化              容量成本      内存高      磁盘低              响应延迟      亚毫秒到毫秒      毫秒到几十毫秒      Redis 不应替代 MySQL 的唯一键、外键、事务和审计能力。尤其账务、订单、支付状态，必须有数据库约束兜底。27.4.2 Redis + KafkaRedis 和 Kafka 都能传递异步消息，但定位完全不同。            维度      Redis Stream      Kafka                  定位      轻量事件流、任务队列      海量分布式日志              持久化      内存为主，受 maxmemory 限制      磁盘顺序写，可长期保留              吞吐      实例能力限制      分区水平扩展              回放      受保留策略和内存约束      可按 offset 长期回放              消费组      支持      成熟              生态      简单      Connect、Streams、Schema Registry      推荐边界：Redis Stream：任务队列、短事件、小规模解耦、延迟低且数据量可控Kafka：业务事件、CDC、日志、埋点、跨系统数据管道、大规模回放常见组合：1. Redis 做秒杀原子扣减，Kafka 承接订单创建事件2. Redis 做实时计数，Kafka 记录原始行为3. Redis 做应用侧缓冲，批量写入 Kafka4. Kafka 事件触发 Redis 缓存失效或预热27.4.3 Redis + Elasticsearch            关注点      Redis Search / Elasticsearch                  数据量      Redis 受内存约束，ES 可承载大规模索引              查询能力      Redis Search 适合轻量全文和二次筛选，ES 查询语义更丰富              延迟      Redis 低且稳定，ES 取决于索引和查询复杂度              运维      Redis 简单，ES 集群和存储治理更复杂              生态      Redis Stack 模块，ES 有成熟分析和查询生态      常见架构：MySQL 存储事实 -&gt; Elasticsearch 支持搜索 -&gt; Redis 缓存搜索结果或热点文档不要把 Redis 当通用搜索引擎。如果一个系统需要复杂布尔查询、聚合分析、分词、 synonyms、高亮和大规模索引，Elasticsearch 通常更合适。27.4.4 Redis + 对象存储对象存储适合图片、视频、压缩包、日志归档：Redis 保存元数据、签名 URL、热点计数对象存储保存文件内容示例：media:metadata:10001 -&gt; Hash：大小、类型、宽度、存储路径media:view:10001 -&gt; String：播放计数media:url:token -&gt; 短 TTL：临时授权映射不要把大文件 Base64 后放 Redis。Base64 会增加约三分之一体积，还会造成大 key 和网络阻塞。27.4.5 Redis + 数仓典型数据链路：业务数据库 -&gt; CDC -&gt; Kafka -&gt; 数仓Redis 实时计数 -&gt; 定时导出 -&gt; Kafka/数仓数仓计算结果 -&gt; 回写 Redis 热点集合职责划分：            层      职责                  Redis      在线低延迟读写、短期聚合              Kafka      可靠传输和回放              数仓      离线建模、报表、归因              回写服务      把计算好的结果写回 Redis      在线层不要做复杂分析。Redis 适合 O(log N) 或更低的点查、计数和排序，不适合扫描全量数据做多维分析。27.5 Redis 的能力边界27.5.1 缓存边界适合：  热点读；  可重建数据；  允许短暂不一致；  value 尺寸可控。不适合：  必须强一致；  访问完全随机且命中率低；  value 达到 MB 级；  数据增长没有上界。27.5.2 队列边界Redis Stream 可以做轻量队列，但要回答：  数据丢失是否可接受；  堆积是否可能超过内存；  是否需要长期回放；  消费组和 pending 恢复是否满足需求；  是否需要跨机房容灾。如果答案是“海量、可回放、长期保留、多消费者生态”，Kafka 更合适。27.5.3 锁边界Redis 分布式锁适合效率锁：防止重复执行任务。对于正确性关键场景，要考虑：  网络分区下锁是否可能失效；  业务执行时间是否超过锁 TTL；  是否有数据库唯一键兜底；  是否需要 fencing token；  Redlock 是否真的必要。如果业务不能接受重复执行，必须设计业务幂等，而不能只相信锁。27.5.4 主存储边界Redis 作为主数据存储需要满足：  数据模型能全部用 key 表达；  内存成本可承受；  持久化策略满足 RPO；  备份和恢复演练完成；  集群扩缩容方案明确；  有完整审计和对账机制。多数业务系统中，Redis 更适合作为热数据层，而不是唯一事实源。27.6 Redis Stack 与增强模块Redis 8 将部分 Redis Stack 能力并入主版本，不同版本的模块支持差异较大。使用前应确认所用发行版、许可证和运维能力。27.6.1 RedisJSON适合存储和修改 JSON 文档：JSON.SET product:10001 $ '{"id":10001,"title":"Phone","price":1999,"tags":["digital"]}'JSON.GET product:10001 $.title $.priceJSON.SET product:10001 $.price 1899与 String JSON 相比，RedisJSON 支持路径级修改，不必每次反序列化整个对象。适合字段较多且需要局部更新的文档。边界：  仍然要控制文档大小；  需要客户端支持；  复杂查询依赖 Search；  不等同于通用文档数据库的完整事务能力。27.6.2 RediSearch适合二级索引和轻量搜索：FT.CREATE idx:product ON HASH PREFIX 1 mall:product: SCHEMA title TEXT price NUMERIC stock NUMERICFT.SEARCH idx:product "phone" LIMIT 0 10FT.SEARCH idx:product "@price:[1000 3000]" SORTBY price ASC适合：  hash 或 JSON 的字段索引；  简单全文搜索；  范围过滤和排序；  小规模地理查询。不适合超大规模复杂搜索、复杂聚合、深度分词和完整分析型查询。27.6.3 RedisTimeSeries适合时间序列：TS.CREATE sensor:1001 RETENTION 86400000TS.ADD sensor:1001 * 26.5TS.RANGE sensor:1001 - + AGGREGATION avg 60000适合设备指标、短周期监控、实时采样。如果是大规模可观测性平台，通常使用 Prometheus、VictoriaMetrics、InfluxDB 或 TimescaleDB 更合适。27.6.4 RedisBloom 与 RedisAIRedisBloom 提供布隆过滤器、Cuckoo Filter、Count-Min Sketch、Top-K：BF.ADD valid:user 10001BF.EXISTS valid:user 10001适合防穿透、去重和近似计数。RedisAI 则偏向在线推理和特征服务，使用前要评估模型生命周期和部署复杂度。27.7 与其他系统比较27.7.1 Redis vs Memcached            维度      Redis      Memcached                  数据结构      丰富      纯 key/value              持久化      RDB/AOF      无              高可用      复制、哨兵、Cluster      客户端分片为主              大 value      支持但需治理      常见场景              功能      Lua、Stream、Search 等      简单              简单缓存      可用      非常稳定      如果只需要多线程纯缓存，Memcached 依然是合理选择；如果需要数据结构、原子操作、复制和丰富生态，Redis 更合适。27.7.2 Redis vs ValkeyValkey 是从 Redis 开源版本分叉出来的项目，命令协议和运维习惯高度接近。选型时要关注：  所需版本和许可证；  客户端兼容性；  云厂商支持；  社区活跃度；  模块和商业功能需求；  长期维护承诺。27.7.3 Redis vs etcd / Consuletcd 和 Consul 更适合：  强一致元数据；  服务发现；  leader 选举元数据；  配置变更的低容量高一致存储。Redis 更适合：  高 QPS 读；  短 TTL；  数据结构计算；  可丢或可重建数据。不要用 Redis 的最终一致视图去做分布式协调系统的唯一元数据源。27.7.4 自建 vs 云托管            维度      自建      云托管                  成本模型      机器和人力      实例规格和流量              可控性      高      依赖厂商              运维      自己负责备份、监控、扩容      平台托管              高可用      自建哨兵/Cluster      厂商提供              安全      自己配置 ACL/TLS/网络      平台能力              排障      可登录节点      依赖日志和控制台      中小团队适合云托管降低运维成本；规模较大、合规要求特殊或成本敏感的团队可以自建，但必须投入监控、备份、演练和值班能力。27.8 事件驱动与 CQRS 中的 Redis27.8.1 事件驱动架构典型链路：命令写入 MySQL -&gt; 发布领域事件到 Kafka -&gt; 消费者更新投影 -&gt; Redis 提供在线查询职责：            组件      职责                  MySQL      保存命令和事实              Kafka      可靠传输、回放、解耦              Projection      事件转查询模型              Redis      低延迟查询视图      例如订单事件更新 Redis 投影：@KafkaListener(topics = "order.events")public void onOrderEvent(OrderEvent event) {    if (event.type() == OrderEventType.CREATED) {        redis.opsForHash().putAll("order:view:" + event.orderId(), Map.of(                "status", "CREATED",                "version", String.valueOf(event.version())        ));    }}消费必须幂等，且要按 orderId 分区，保证同订单事件顺序。27.8.2 CQRSCQRS 将写模型和读模型分离：flowchart LR    Client[客户端] --&gt; Command[命令服务]    Command --&gt; MySQL[(写模型)]    MySQL --&gt; CDC[CDC]    CDC --&gt; Kafka[Kafka]    Kafka --&gt; Projector[读模型构建]    Projector --&gt; Redis[(读模型)]    Client --&gt; Query[查询服务]    Query --&gt; RedisRedis 读模型可以采用：            查询      Redis 结构                  按 ID 查详情      String / Hash              用户订单列表      ZSet              状态分组      Set              聚合计数      String / Hash              搜索      Search 索引      注意：CQRS 意味着读写模型存在同步延迟，接口设计要明确返回的是“最终一致读模型”。27.9 平台化 Redis 架构当一个组织内有几十个应用使用 Redis，可以抽象统一平台：flowchart TD    Apps[业务应用] --&gt; SDK[统一 Cache SDK]    SDK --&gt; Proxy[Redis 代理]    Proxy --&gt; Cluster[(Redis Cluster)]    Cluster --&gt; Monitor[监控告警]    Cluster --&gt; Backup[备份恢复]    Proxy --&gt; Quota[租户配额]    Apps --&gt; Console[自助控制台]平台能力：  统一命名规范和 SDK；  命令白名单；  大 key 拦截；  慢查询和热点 key 监控；  租户 QPS 和内存配额；  缓存命中率和回源指标；  备份恢复演练；  容量预警和扩容流程；  ACL 与 mTLS；  故障预案和值班 Runbook。这不是为了增加层级，而是为了把散落在各团队的经验沉淀成默认安全边界。27.10 架构检查清单上线前逐项确认：            类别      检查项                  数据      是否可丢、可重建、可审计              一致性      失效策略、补偿机制、版本冲突处理              容量      key 数量、value 大小、增长率和峰值              性能      P99 延迟、QPS、连接数、分片均衡              可用性      哨兵/Cluster、客户端重试、熔断降级              安全      ACL、TLS、网络隔离、审计              运维      监控、备份、扩缩容、迁移、回滚              成本      内存、带宽、云服务和人力              边界      是否更适合 MySQL/Kafka/ES/对象存储      27.11 本章小结  Redis 的角色必须明确，缓存、锁、队列和状态存储的失败影响完全不同；  多租户至少要逻辑隔离，核心业务应物理隔离，Redis DB 不是物理隔离；  冷热分层能让 Redis 保持低延迟价值，历史数据应落到数据库或对象存储；  Redis 与 MySQL、Kafka、Elasticsearch、数仓组合时，要保留各自擅长的职责；  Redis Stack 提供搜索、JSON、时间序列等能力，但要评估规模、版本和许可证；  事件驱动与 CQRS 中，Redis 适合作为最终一致读模型，而不是事实源。27.12 思考题  一个公司只有一个 Redis Cluster 给所有业务使用，你会提出哪些改造步骤？  Redis Stream 和 Kafka 的队列能力边界在哪里？什么时候必须迁移到 Kafka？  为什么账务和支付状态不能只依赖 Redis 事务？  Redis Search 替代 Elasticsearch 的前提条件是什么？  在 CQRS 中，如何检测并修复 Redis 读模型落后于 MySQL 写模型的问题？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章通过五个项目串起全书知识：商品详情缓存、秒杀库存、Feed 流、排行榜和订单超时关闭。每个项目都从需求出发，给出架构、key 设计、核心代码、风险点和上线清单。这些方案不是“能跑的 Demo”，而是可以拿去做架构评审的起点。落地时仍要结合自己的数据库能力、流量规模和一致性要求调整参数。26.1 项目一：商品详情缓存系统26.1.1 需求描述电商商品详情页包含基础信息、价格、库存、评论数、推荐列表和运营详情。假设流量特征如下：读 QPS：常态 5000，大促 50000写 QPS：常态 100，大促 1000一致性：价格和状态允许秒级延迟，但不能长期错可用性：Redis 故障时详情页要能降级这类场景的核心不是“把对象放进去”，而是拆分数据、控制失效和防止回源风暴。26.1.2 方案设计推荐把商品详情拆成多个缓存对象：            数据      存储结构      建议 TTL      更新方式                  基础信息      String JSON / Hash      30 到 120 分钟      更新数据库后删除              价格      String JSON      5 到 30 分钟      价格变更删除              库存      String + Lua      活动周期      原子扣减              评论数      String      5 分钟      异步聚合              详情描述      String 压缩      24 小时      运营保存后删除              推荐列表      ZSet / String      10 分钟      定时任务刷新      不要把所有字段塞进一个大 JSON。商品详情可能达到几十 KB，一次返回会放大网络与序列化成本，修改任意字段也要重写整个 value。26.1.3 key 设计mall:product:base:10001mall:product:price:10001mall:product:detail:10001mall:product:stat:10001mall:product:recommend:10001mall:product:empty:10001统一使用“业务域 + 对象 + 字段 + ID”的结构，便于按前缀监控、清理和治理。26.1.4 读路径实现@Servicepublic class ProductCacheService {    private final StringRedisTemplate redis;    private final ProductMapper productMapper;    private final ObjectMapper objectMapper;    private final Cache&lt;Long, ProductBase&gt; localCache;    private final RateLimiter dbLimiter = RateLimiter.create(2000);    public ProductCacheService(StringRedisTemplate redis,                               ProductMapper productMapper,                               ObjectMapper objectMapper) {        this.redis = redis;        this.productMapper = productMapper;        this.objectMapper = objectMapper;        this.localCache = Caffeine.newBuilder()                .maximumSize(50_000)                .expireAfterWrite(Duration.ofSeconds(3))                .recordStats()                .build();    }    public ProductDetail getDetail(Long id) {        if (id == null || id &lt;= 0) {            throw new BadRequestException("invalid product id");        }        ProductBase base = localCache.getIfPresent(id);        if (base == null) {            base = getJson("mall:product:base:" + id,                    ProductBase.class, () -&gt; productMapper.selectBase(id));            if (base != null) {                localCache.put(id, base);            }        }        if (base == null) {            return null;        }        ProductPrice price = getJson("mall:product:price:" + id,                ProductPrice.class, () -&gt; productMapper.selectPrice(id));        ProductStat stat = getJson("mall:product:stat:" + id,                ProductStat.class, () -&gt; productMapper.selectStat(id));        return ProductDetail.of(base, price, stat);    }    private &lt;T&gt; T getJson(String key, Class&lt;T&gt; type, Supplier&lt;T&gt; loader) {        String value = redis.opsForValue().get(key);        if (value != null) {            if (CacheValues.NULL.equals(value)) {                return null;            }            try {                return objectMapper.readValue(value, type);            } catch (JsonProcessingException e) {                redis.delete(key);            }        }        if (!dbLimiter.tryAcquire(100, TimeUnit.MILLISECONDS)) {            throw new ServiceUnavailableException("database busy");        }        T loaded = loader.get();        if (loaded == null) {            redis.opsForValue().set(key, CacheValues.NULL, Duration.ofSeconds(60));            return null;        }        redis.opsForValue().set(key, writeJson(loaded), randomTtl());        return loaded;    }    private Duration randomTtl() {        Duration base = Duration.ofMinutes(30);        long jitter = ThreadLocalRandom.current().nextLong(0, 180);        return base.plusSeconds(jitter);    }}这段实现包含五个关键保护：  ID 参数校验；  本地缓存承接极热点；  空值缓存防穿透；  TTL 随机抖动防雪崩；  数据库回源限流。26.1.5 写路径与一致性推荐流程：1. MySQL 事务更新商品2. 事务提交成功3. 删除相关缓存 key4. 删除失败写入补偿表5. 后台任务重试删除实现示例：@Transactionalpublic void updateBase(ProductBase base) {    productMapper.updateBase(base);    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {        @Override        public void afterCommit() {            invalidateProduct(base.getId());        }    });}private void invalidateProduct(Long id) {    List&lt;String&gt; keys = List.of(            "mall:product:base:" + id,            "mall:product:price:" + id,            "mall:product:detail:" + id    );    try {        redis.delete(keys);    } catch (Exception e) {        invalidationMapper.insert(new CacheInvalidation("mall:product", id, keys));    }}后台补偿：@Scheduled(fixedDelay = 1000)public void retryInvalidation() {    List&lt;CacheInvalidation&gt; tasks = invalidationMapper.selectPending(100);    for (CacheInvalidation task : tasks) {        try {            redis.delete(task.getKeys());            invalidationMapper.markDone(task.getId());        } catch (Exception e) {            invalidationMapper.increaseRetry(task.getId());        }    }}对于大型平台，可以把补偿表替换为 Canal 或 Debezium 监听 MySQL binlog，再通过 Kafka 串行消费变更事件，统一处理缓存失效。26.1.6 风险点与上线清单            风险      表现      处理                  空值缓存过长      新上架商品查不到      空值 TTL 控制在 60 秒内              详情 value 过大      Redis 网络带宽飙升      压缩、拆 key、CDN 静态化              热点商品      单分片 CPU 高      本地缓存 + key 副本              缓存删除失败      页面长期旧数据      补偿表或 binlog              Redis 故障      数据库被打崩      限流 + 降级      上线清单：  压测读路径，确认 miss 时数据库可承受；  记录商品 JSON 平均大小和 P99 大小；  明确 TTL、抖动范围和空值策略；  缓存命中率按前缀上报；  删除失败有重试和告警；  Redis 超时和数据库限流参数已配置；  大促前完成热点预热。26.2 项目二：秒杀库存系统26.2.1 需求描述秒杀的典型特征是开始前大量用户刷新页面，开始后瞬时流量放大百倍，库存快速减少，结束后请求应尽早拒绝。系统目标：  不超卖；  少卖可控；  保护数据库；  用户体验可接受；  事故可追踪。26.2.2 总体架构flowchart LR    Client[用户] --&gt; Gateway[网关限流]    Gateway --&gt; App[秒杀应用]    App --&gt; Local[本地活动状态]    App --&gt; Redis[(Redis Lua 扣减)]    Redis --&gt; MQ[Kafka]    MQ --&gt; Order[订单服务]    Order --&gt; MySQL[(MySQL)]分层拦截：            层      动作      拦截效果                  CDN/前端      按钮置灰、静态化      减少无效请求              网关      用户限流、黑名单、答题      控制总流量              应用      活动状态与资格校验      拒绝无资格请求              本地内存      活动结束标记、粗粒度库存标记      快速失败              Redis Lua      原子资格与库存扣减      最终竞争层              数据库      唯一约束 + 状态机      兜底防重      不要把所有请求直接打到 Redis。Redis 虽快，但也有容量和 CPU 上限。26.2.3 key 设计seckill:activity:1001                 # 活动 Hashseckill:stock:1001                    # 剩余库存seckill:user:1001                     # 已购买用户 Setseckill:order:1001:uid                # 用户订单资格seckill:result:1001                   # 结果 ZSetseckill:end:1001                      # 结束标记初始化示例：HSET seckill:activity:1001 skuId 200001 total 1000 start 1787568000000 end 1787571600000 status STARTEDSET seckill:stock:1001 1000EXPIRE seckill:user:1001 720026.2.4 Lua 原子扣减把“活动校验、用户重复校验、库存扣减、写入资格”放在一个 Lua 脚本中执行：local activityKey = KEYS[1]local stockKey = KEYS[2]local userKey = KEYS[3]local orderKey = KEYS[4]local userId = ARGV[1]local now = tonumber(ARGV[2])local holdSeconds = tonumber(ARGV[3])local status = redis.call('HGET', activityKey, 'status')if status ~= 'STARTED' then    return -1endlocal startAt = tonumber(redis.call('HGET', activityKey, 'start') or '0')local endAt = tonumber(redis.call('HGET', activityKey, 'end') or '0')if now &lt; startAt or now &gt; endAt then    return -2endif redis.call('SISMEMBER', userKey, userId) == 1 then    return -3endlocal stock = tonumber(redis.call('GET', stockKey) or '0')if stock &lt;= 0 then    return -4endredis.call('DECR', stockKey)redis.call('SADD', userKey, userId)redis.call('SET', orderKey, userId, 'EX', holdSeconds)redis.call('ZADD', activityKey .. ':result', now, userId)return 1Java 调用：@Servicepublic class SeckillService {    private static final RedisScript&lt;Long&gt; SECKILL_SCRIPT = new DefaultRedisScript&lt;&gt;("""            local status = redis.call('HGET', KEYS[1], 'status')            if status ~= 'STARTED' then return -1 end            if redis.call('SISMEMBER', KEYS[3], ARGV[1]) == 1 then return -3 end            local stock = tonumber(redis.call('GET', KEYS[2]) or '0')            if stock &lt;= 0 then return -4 end            redis.call('DECR', KEYS[2])            redis.call('SADD', KEYS[3], ARGV[1])            redis.call('SET', KEYS[4], ARGV[1], 'EX', tonumber(ARGV[3]))            return 1            """, Long.class);    public SeckillResult submit(Long activityId, Long userId) {        if (!activityStateHolder.isOpen(activityId)) {            return SeckillResult.notStarted();        }        Long result = redisTemplate.execute(                SECKILL_SCRIPT,                List.of(                        "seckill:activity:" + activityId,                        "seckill:stock:" + activityId,                        "seckill:user:" + activityId,                        "seckill:order:" + activityId + ":" + userId                ),                String.valueOf(userId),                String.valueOf(System.currentTimeMillis()),                "900"        );        if (result == null || result &lt;= 0) {            return SeckillResult.fail(result);        }        seckillOrderProducer.send(SeckillOrderEvent.of(activityId, userId));        return SeckillResult.success();    }}Lua 脚本解决的是 Redis 内部多个命令的原子性，不等于整个业务链路的事务。Redis 扣减成功后，还需要消息链路和数据库约束兜底。26.2.5 数据库兜底Redis 扣减成功只代表获得下单资格，订单仍要落数据库。表结构要点：CREATE TABLE seckill_order (    id BIGINT PRIMARY KEY AUTO_INCREMENT,    activity_id BIGINT NOT NULL,    user_id BIGINT NOT NULL,    order_no VARCHAR(64) NOT NULL,    status VARCHAR(20) NOT NULL,    create_time DATETIME(3) NOT NULL,    UNIQUE KEY uk_activity_user (activity_id, user_id),    UNIQUE KEY uk_order_no (order_no));库存预留使用条件更新：UPDATE seckill_activitySET reserved_stock = reserved_stock + 1WHERE activity_id = ?  AND reserved_stock &lt; total_stock;如果数据库更新失败，要释放 Redis 中的资格并回补库存：public void compensate(Long activityId, Long userId) {    redisTemplate.execute(releaseScript(),            List.of("seckill:stock:" + activityId, "seckill:user:" + activityId),            String.valueOf(userId));}回补脚本同样要保证“检查用户资格、删除用户、增加库存”原子执行。即使如此，仍可能因为消息丢失、重复补偿或应用宕机产生少量误差，活动结束后必须对账。26.2.6 少卖与超卖超卖的防线：  Lua 保证 Redis 内部判断和扣减原子；  数据库唯一键防止重复下单；  数据库库存条件更新防止超发；  订单状态机防止重复支付。少卖的原因：  Redis 扣减成功但消息丢失；  用户获得资格但未支付；  应用宕机导致资格未落库；  回补脚本重复执行。处理方式：  Kafka 发送使用确认和本地事件表；  消费端幂等；  15 分钟未支付自动取消并回补库存；  活动结束后对账 Redis、数据库和订单；  最终库存以数据库为准。26.2.7 上线清单  压测 Lua 脚本，确认 Redis 单分片最大 QPS；  活动数据和库存提前预热；  用户 Set 设置与活动等长的过期时间；  数据库唯一键和状态机已验证；  消息链路有重试、死信和幂等；  本地结束标记能快速切换；  网关限流按用户和 IP 分层；  全链路日志包含 activityId、userId 和 traceId；  演练 Redis 故障、应用重启、数据库慢查询场景。26.3 项目三：Feed 流与时间线26.3.1 需求描述实现用户主页时间线和关注流：  发布动态后，粉丝能尽快看到；  支持关注、取消关注；  支持分页读取；  大 V 粉丝量可能达到千万；  热门内容有推荐流量。常见模式：            模式      写入方式      读成本      适用                  推模式      发布时写入每个粉丝收件箱      低      粉丝少              拉模式      读时查关注者最新动态      高      大 V              推拉结合      普通用户推，大 V 拉      可控      大多数社交系统      26.3.2 key 设计feed:outbox:2001               # 用户发布箱 ZSetfeed:inbox:3001                # 用户收件箱 ZSetfeed:following:3001            # 关注集合feed:follower:2001             # 粉丝集合post:content:90001             # 动态内容ZSet 的 member 保存动态 ID，score 保存发布时间戳。动态正文不要放进 ZSet，否则同一个 ID 出现在多个收件箱时会被复制大量份。26.3.3 发布动态public void publishPost(Post post) {    postMapper.insert(post);    redis.delete("post:content:" + post.getId());    stringRedisTemplate.opsForZSet().add(            "feed:outbox:" + post.getUserId(),            String.valueOf(post.getId()),            post.getPublishedAt().toEpochMilli()    );    if (isBigV(post.getUserId())) {        return;    }    fanoutService.asyncFanout(post);}普通用户扩散不要使用 SMEMBERS 一次读取所有粉丝。应分批读取，或把扩散任务交给 Kafka：public void fanout(Post post) {    String cursor = ScanOptions.SCAN_CURSOR_START;    do {        ScanOptions options = ScanOptions.scanOptions().count(1000).build();        try (Cursor&lt;String&gt; scan = redis.opsForSet()                .scan("feed:follower:" + post.getUserId(), options)) {            while (scan.hasNext()) {                String followerId = scan.next();                stringRedisTemplate.opsForZSet().add(                        "feed:inbox:" + followerId,                        String.valueOf(post.getId()),                        post.getPublishedAt().toEpochMilli()                );            }            cursor = scan.getCursor();        }    } while (!"0".equals(cursor));}生产实现中，粉丝关系通常以数据库为主，Redis 为辅，并由异步任务按批次展开。收件箱要裁剪长度：public void trimInbox(Long userId) {    stringRedisTemplate.opsForZSet()            .removeRange("feed:inbox:" + userId, 0, -1001);}26.3.4 读取 Feed普通用户直接读收件箱：public List&lt;Post&gt; readInbox(Long userId, long maxScore, int limit) {    Set&lt;String&gt; ids = stringRedisTemplate.opsForZSet()            .reverseRangeByScore("feed:inbox:" + userId,                    Double.NEGATIVE_INFINITY, maxScore, 0, limit);    return loadPosts(ids);}大 V 内容采用拉模式合并：public List&lt;Post&gt; readTimeline(Long userId, long maxScore, int limit) {    Set&lt;String&gt; following = stringRedisTemplate.opsForSet()            .members("feed:following:" + userId);    if (following == null || following.isEmpty()) {        return List.of();    }    List&lt;Post&gt; merged = new ArrayList&lt;&gt;();    for (String authorId : following) {        Set&lt;String&gt; ids = stringRedisTemplate.opsForZSet()                .reverseRangeByScore("feed:outbox:" + authorId,                        Double.NEGATIVE_INFINITY, maxScore, 0, limit);        merged.addAll(loadPosts(ids));    }    return merged.stream()            .sorted(Comparator.comparingLong(Post::getPublishedAt).reversed())            .limit(limit)            .toList();}当关注数很大时，不要在请求路径全量合并。常见优化是：  只合并活跃关注者和近期发布者；  为用户生成独立缓存时间线；  推荐流与关注流分开；  深翻页改用搜索服务或数据库游标；  热门内容走单独候选池。26.3.5 取消关注取消关注不能立刻遍历删除收件箱中的所有该作者内容，代价太高。推荐：  收件箱只存 postId 和时间；  读取时批量加载动态详情；  动态详情中包含作者 ID；  过滤已取消关注的作者；  不足一页时继续读取补齐。private List&lt;Post&gt; filterBlockedAuthors(Long userId, List&lt;Post&gt; posts) {    Set&lt;String&gt; following = stringRedisTemplate.opsForSet()            .members("feed:following:" + userId);    return posts.stream()            .filter(post -&gt; following.contains(String.valueOf(post.getUserId())))            .toList();}26.3.6 风险点            风险      处理                  大 V 发布扩散风暴      大 V 只写 outbox，读取时拉取合并              收件箱无限增长      ZSet 裁剪 + TTL 策略              动态内容缓存失效      数据库为事实源，读时回源              排序分数重复      score 加序列号或使用时间戳 + ID              长期冷用户占内存      活跃用户才保留 inbox              删除动态      删除 post 缓存，读取时过滤不存在动态      26.4 项目四：实时排行榜26.4.1 需求描述实现商品销量榜、主播人气榜、游戏积分榜：  实时更新分数；  查询 Top N；  查询用户排名；  按日、周、月维度统计；  支持历史榜单落库。ZSet 是天然选择：ZINCRBY 更新分数，ZREVRANGE 查询排名，ZREVRANK 查询成员名次。26.4.2 key 设计rank:product:daily:20260825rank:product:weekly:2026-W35rank:product:monthly:202608rank:anchor:room:90001rank:history:product:20260825按日期拆 key 有三个好处：  天然隔离周期；  方便设置过期；  历史榜单可归档后清理。26.4.3 更新分数public void increaseSales(Long productId, int count) {    String daily = "rank:product:daily:" + LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE);    String weekly = "rank:product:weekly:" + weekKey();    String monthly = "rank:product:monthly:" + monthKey();    redis.executePipelined(new SessionCallback&lt;Void&gt;() {        @Override        public Void execute(RedisOperations operations) {            operations.opsForZSet().incrementScore(daily, String.valueOf(productId), count);            operations.opsForZSet().incrementScore(weekly, String.valueOf(productId), count);            operations.opsForZSet().incrementScore(monthly, String.valueOf(productId), count);            return null;        }    });}分数更新是高频写场景。如果每次销售都写数据库，压力较大，可以用 Redis 聚合，再定时落库。26.4.4 查询 Top N 和用户排名public List&lt;RankItem&gt; topN(String rankKey, int n) {    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; tuples =            stringRedisTemplate.opsForZSet().reverseRangeWithScores(rankKey, 0, n - 1);    if (tuples == null) {        return List.of();    }    List&lt;RankItem&gt; items = new ArrayList&lt;&gt;();    int rank = 1;    for (ZSetOperations.TypedTuple&lt;String&gt; tuple : tuples) {        items.add(new RankItem(rank++, Long.valueOf(tuple.getValue()), tuple.getScore()));    }    return items;}public RankItem getUserRank(String rankKey, Long userId) {    Long rank = stringRedisTemplate.opsForZSet()            .reverseRank(rankKey, String.valueOf(userId));    Double score = stringRedisTemplate.opsForZSet()            .score(rankKey, String.valueOf(userId));    if (rank == null || score == null) {        return null;    }    return new RankItem(rank + 1, userId, score);}排行榜展示可以再做一层短 TTL 缓存，避免 Top N 每次都打到 Redis。26.4.5 同分排序ZSet 排名在 score 相同时按成员字典序，不一定符合业务需求。例如业务要求“分数相同，更早达到者排前面”。解决方式：  ZSet 取出候选集；  应用层用 score + timestamp 精确排序；  必要时用 Redis Hash 保存成员的扩展排序字段。List&lt;RankItem&gt; items = loadCandidates(rankKey, 500);return items.stream()        .sorted(Comparator.comparingDouble(RankItem::score).reversed()                .thenComparingLong(RankItem::achievedAt))        .limit(100)        .toList();不建议把复杂业务编码进 double score，因为浮点精度和调试成本会让排障变得困难。26.4.6 历史榜单归档每日任务：@Scheduled(cron = "0 10 0 * * ?")public void archiveDailyRank() {    String key = "rank:product:daily:" + LocalDate.now().minusDays(1)            .format(DateTimeFormatter.BASIC_ISO_DATE);    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; tuples =            stringRedisTemplate.opsForZSet().reverseRangeWithScores(key, 0, 999);    if (tuples == null || tuples.isEmpty()) {        return;    }    rankHistoryMapper.batchInsert(buildRows(key, tuples));    stringRedisTemplate.expire(key, Duration.ofDays(7));}归档原则：  Redis 保留短期和实时数据；  数据库或对象存储保留历史数据；  归档任务幂等；  归档失败告警；  榜单支持重算。26.5 项目五：订单超时关闭26.5.1 需求描述订单创建后 15 分钟未支付要自动关闭，并释放库存、优惠券和积分。常见方案对比：            方案      精度      可靠性      复杂度      适用                  定时扫表      分钟级      高      低      订单量小              JDK DelayQueue      秒级      低      低      单机、可丢任务              Redis ZSet 轮询      秒级      中      中      中小规模              Redis Stream 消费      秒级      中高      中      需要消费确认              RocketMQ 延迟消息      取决于队列      高      中      大规模生产      这里使用 ZSet 提升时效，用数据库状态机和定时扫表兜底。26.5.2 key 设计order:timeout:zset          # score 为超时时间戳order:lock:close:10001      # 关闭互斥锁order:status:10001          # 状态缓存创建订单：@Transactionalpublic void createOrder(CreateOrderRequest request) {    Order order = orderDomainService.create(request);    orderMapper.insert(order);    stringRedisTemplate.opsForZSet().add(            "order:timeout:zset",            String.valueOf(order.getId()),            order.getCreateTime().plusMinutes(15).toEpochMilli()    );}26.5.3 扫描到期订单@Scheduled(fixedDelay = 1000)public void closeTimeoutOrders() {    long now = System.currentTimeMillis();    Set&lt;String&gt; orderIds = stringRedisTemplate.opsForZSet()            .rangeByScore("order:timeout:zset", 0, now, 0, 99);    if (orderIds == null || orderIds.isEmpty()) {        return;    }    orderIds.forEach(orderId -&gt; closeIfTimeout(Long.valueOf(orderId)));}关闭订单：public void closeIfTimeout(Long orderId) {    String lockKey = "order:lock:close:" + orderId;    String token = UUID.randomUUID().toString();    Boolean locked = stringRedisTemplate.opsForValue()            .setIfAbsent(lockKey, token, Duration.ofSeconds(10));    if (!Boolean.TRUE.equals(locked)) {        return;    }    try {        int updated = orderMapper.closeIfUnpaid(orderId,                OrderStatus.UNPAID, OrderStatus.CLOSED);        if (updated &gt; 0) {            inventoryService.release(orderId);            couponService.release(orderId);            eventPublisher.publish(OrderClosedEvent.of(orderId));        }        stringRedisTemplate.opsForZSet()                .remove("order:timeout:zset", String.valueOf(orderId));    } finally {        releaseLock(lockKey, token);    }}SQL 条件更新是关键：UPDATE `order`SET status = 'CLOSED', close_time = NOW(3), version = version + 1WHERE order_id = ?  AND status = 'UNPAID'  AND create_time + INTERVAL 15 MINUTE &lt;= NOW(3);只有更新成功才释放资源，避免重复释放。26.5.4 支付成功与超时关闭竞争存在两个并发动作：支付服务：UNPAID -&gt; PAID超时任务：UNPAID -&gt; CLOSED必须依赖数据库状态机条件更新：UPDATE `order`SET status = 'PAID', pay_time = NOW(3)WHERE order_id = ?  AND status = 'UNPAID';谁更新成功谁生效，失败方放弃。不要只依赖 Redis 锁判断订单状态。26.5.5 可靠性兜底Redis ZSet 可能丢任务或漏删，因此需要数据库扫描兜底：@Scheduled(cron = "0 */5 * * * ?")public void scanDatabaseTimeoutOrders() {    List&lt;Order&gt; orders = orderMapper.selectTimeoutUnpaid(500);    orders.forEach(order -&gt; closeIfTimeout(order.getId()));}上线前必须演练：  Redis 清空；  关闭任务重复部署；  支付回调和关闭任务同时到达；  释放库存接口超时；  消息重复消费。26.6 项目通用工程规范26.6.1 key 治理每个项目上线前提交 key 清单：            key 前缀      类型      预估数量      平均大小      TTL      负责人                  mall:product:base      String      100 万      2KB      1h      商品组              seckill:stock      String      1000      8B      活动期      交易组              feed:inbox      ZSet      500 万      20KB      7d      社区组              order:timeout      ZSet      100 万      20B      1h      订单组      没有 TTL、没有负责人、没有容量预估的 key 不允许上线。26.6.2 幂等设计所有写操作都要有幂等键：业务动作 + 实体 ID + 版本或请求 ID常见幂等手段：  数据库唯一键；  状态机条件更新；  Redis SET NX EX 保存请求 ID；  消费端按事件 ID 去重；  乐观锁版本号。Redis 幂等键要设置 TTL，并用数据库唯一约束兜底。26.6.3 降级预案每个项目必须回答：            问题      商品缓存      秒杀      Feed      排行榜      订单关闭                  Redis 不可用      本地缓存/查库限流      活动暂停或排队      降级数据库分页      返回缓存快照      数据库扫表              数据丢失      重新加载      数据库对账      重建 outbox      重新统计      数据库扫表              流量突增      限流降级      网关拦截      静态化      快照缓存      延迟执行              数据错误      删除重建      停售/回补      重建时间线      重算      状态机校正      26.6.4 观测指标通用指标：cache_hit_totalcache_miss_totalcache_load_duration_msredis_command_duration_msredis_error_totallua_script_result_totalidempotent_reject_totaldatabase_fallback_total业务指标：            项目      指标                  商品缓存      命中率、旧值率、回源 QPS              秒杀      扣减成功率、售罄时间、消息落库延迟              Feed      inbox 长度、扩散延迟、读取补页次数              排行榜      更新 QPS、TopN 计算耗时、归档成功率              订单关闭      关闭延迟、重复关闭拒绝数、资源释放成功率      26.7 本章小结  商品详情缓存要拆字段、控制 value 大小，并为删除失败设计补偿；  秒杀必须在网关、应用、Redis、数据库多层防护，Lua 只解决 Redis 内部原子性；  Feed 流根据大 V 与普通用户采用推拉结合，收件箱要裁剪，内容与 ID 分离；  排行榜用 ZSet 天然适合，但历史榜单要落库，同分排序要单独设计；  订单超时关闭用 Redis 提升时效，用数据库状态机和定时扫表保证可靠性；  所有项目都需要 key 清单、容量预估、幂等、降级和监控。26.8 思考题  商品缓存删除成功但 binlog 事件后又到达，如何避免旧事件把缓存重新删除或写入旧值？  秒杀 Lua 脚本执行成功但 Kafka 消息发送失败，系统应如何恢复？  千万粉丝大 V 发布动态时，为什么不能直接全量推送到收件箱？  排行榜 score 相同但业务要求先达到者排前面，如何设计存储结构？  Redis ZSet 订单超时任务与支付回调并发，如何保证最终状态正确？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。缓存是 Redis 最常见的用途，也是最容易在生产环境出事故的用途。很多团队接入 Redis 时只做了“查库前先查缓存”，上线后却发现命中率忽高忽低、数据库偶发被打穿、缓存和数据库数据不一致、热点 key 把单个节点打满。本章把缓存架构拆成四个核心问题：  数据该不该缓存、缓存多久；  读路径和写路径如何设计；  穿透、击穿、雪崩如何防护；  一致性、热点与容量如何治理。读完本章，你应该能独立完成一次缓存架构评审，而不是只会说“加一个 Redis”。25.1 先问清楚缓存目标设计缓存前，先回答五个问题：            问题      关键判断      常见错误                  解决什么问题      降低数据库读压力、减少响应时间、减少下游调用      为了用 Redis 而用 Redis              数据是否可丢      纯缓存可重建；状态数据必须考虑持久化与恢复      把订单状态当纯缓存              一致性要求多高      秒级延迟可接受还是必须强一致      盲目追求“永远一致”              读放大有多大      高频读、低频写最适合缓存      冷数据全量入缓存              容量是否可控      key 数量、value 大小、增长率      无 TTL 无限增长      一个典型判断公式是：适合缓存：读多写少 + 数据可重建 + 允许短暂不一致 + 热点集中谨慎缓存：写多读少 + 数据强一致 + 访问随机 + value 很大对于强一致数据，正确的做法通常是数据库作为唯一事实源，Redis 只做可丢弃的读加速，并通过版本号、时间戳或失效机制降低不一致窗口。25.2 key 与 TTL 设计缓存设计的第一步不是写代码，而是定义 key 规范。25.2.1 key 命名规范推荐格式：业务域:对象类型:业务标识[:字段]示例：mall:product:base:10001mall:product:stock:10001mall:user:profile:8888mall:rank:daily:20260825命名原则：            原则      说明                  可读      能从 key 看出业务归属，方便排查              可查      支持按前缀 SCAN、监控和治理              可过期      每个 key 都应有明确生命周期              可隔离      多环境、多租户加环境或租户前缀              不太长      在可读性和内存占用间取得平衡      如果多个服务共用 Redis Cluster，建议加上环境或租户前缀：mall:prod:product:base:10001user:prod:profile:1000125.2.2 TTL 策略TTL 不是随手写的数字，而是缓存生命周期设计。            数据类型      建议 TTL      说明                  商品详情      30 分钟到 24 小时      允许短暂不一致，可主动失效              用户资料      10 分钟到 1 小时      写后失效或短 TTL              配置      5 分钟到 1 小时      支持手动刷新              会话      与业务会话等长      通常 7 到 30 天              验证码      5 分钟      必须固定过期              热点排行榜      周期 + 少量滑差      避免统一过期              空值缓存      30 秒到 5 分钟      防穿透，时间要短      建议使用“基础 TTL + 随机抖动”：public Duration cacheTtl(Duration base) {    long jitter = ThreadLocalRandom.current()            .nextLong(0, Math.max(1, base.toSeconds() / 10));    return base.plusSeconds(jitter);}抖动不要太大，否则排障困难。通常取基础值的 5% 到 10% 即可。25.3 四种读写模式25.3.1 Cache AsideCache Aside 是最常用模式：业务代码同时维护数据库和缓存。读流程：1. 查 Redis2. 命中：直接返回3. 未命中：查数据库4. 写回 Redis 并设置 TTL5. 返回数据写流程通常有两种选择：方案 A：更新数据库 -&gt; 删除缓存方案 B：更新数据库 -&gt; 更新缓存一般推荐方案 A，即“先更新数据库，再删除缓存”。原因：  删除是幂等操作，重复执行没有副作用；  避免并发写时旧值覆盖新值；  懒加载避免冷数据常驻内存；  不需要计算完整缓存对象。示例：public Product getProduct(Long id) {    String key = "mall:product:base:" + id;    String cached = redis.opsForValue().get(key);    if (cached != null) {        if ("NULL".equals(cached)) {            return null;        }        return objectMapper.readValue(cached, Product.class);    }    Product product = productMapper.selectById(id);    if (product == null) {        redis.opsForValue().set(key, "NULL", Duration.ofSeconds(60));        return null;    }    redis.opsForValue().set(key, writeJson(product), cacheTtl(Duration.ofMinutes(30)));    return product;}@Transactionalpublic void updateProduct(Product product) {    productMapper.updateById(product);    redis.delete("mall:product:base:" + product.getId());}优点：简单、通用、缓存可丢。缺点：存在短暂不一致；缓存删除失败需要补偿；代码侵入较高。25.3.2 Read ThroughRead Through 把“未命中后加载”封装到缓存层，业务只调用缓存接口。sequenceDiagram    participant App as 业务应用    participant Cache as 缓存组件    participant Redis as Redis    participant DB as 数据库    App-&gt;&gt;Cache: get(id)    Cache-&gt;&gt;Redis: GET key    alt 命中        Redis--&gt;&gt;Cache: value    else 未命中        Cache-&gt;&gt;DB: select(id)        DB--&gt;&gt;Cache: value        Cache-&gt;&gt;Redis: SET key TTL    end    Cache--&gt;&gt;App: value优点：业务代码干净，加载、空值、序列化、监控统一治理。缺点：需要基础设施支持，缓存层组件容易变成复杂度中心。适合中大型团队做统一缓存 SDK。25.3.3 Write ThroughWrite Through 在写请求到来时同步写缓存和数据库。写请求 -&gt; 缓存层 -&gt; 更新/删除 Redis -&gt; 更新数据库 -&gt; 返回如果把 Redis 写成功但数据库写失败，会出现缓存领先于事实源的问题。因此工程上更常见的是：先数据库事务提交，再同步操作缓存，失败后补偿删除适合更新频率可控、要求读新数据的配置或元数据。25.3.4 Write BehindWrite Behind 先写缓存或内存缓冲，再异步批量写数据库。写请求 -&gt; Redis/内存队列 -&gt; 立即返回             |             +--&gt; 异步消费 -&gt; 批量写数据库适合计数、浏览量、行为日志等允许丢失或可对账的数据。示例：public void increaseView(Long productId) {    redis.opsForZSet().incrementScore(            "mall:product:view:buffer", String.valueOf(productId), 1    );}@Scheduled(fixedDelay = 5000)public void flushViewBuffer() {    String key = "mall:product:view:buffer";    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; batch =            redis.opsForZSet().rangeWithScores(key, 0, 499);    if (batch == null || batch.isEmpty()) {        return;    }    List&lt;ViewUpdate&gt; updates = batch.stream()            .map(tuple -&gt; new ViewUpdate(                    Long.valueOf(tuple.getValue()),                    tuple.getScore().longValue()))            .toList();    for (ZSetOperations.TypedTuple&lt;String&gt; tuple : batch) {        redis.opsForZSet().remove(key, tuple.getValue());    }    viewMapper.batchIncrease(updates);}注意：上面的“先删缓冲再落库”在宕机时可能丢数据。更稳妥的做法是使用 Stream 或消息队列，消费成功后再确认；强一致数据不要用 Write Behind。四种模式对比：            模式      写路径      一致性      复杂度      适用场景                  Cache Aside      更新库后删缓存      最终一致      低      绝大多数业务缓存              Read Through      缓存层负责加载      最终一致      中      统一缓存 SDK              Write Through      同步写库和缓存      接近强一致      中      配置、元数据              Write Behind      异步批量写库      弱一致      高      浏览量、行为日志      25.4 缓存穿透、击穿与雪崩这三个词经常被混着说，但它们对应的是三种不同故障。            问题      触发条件      典型现象      核心解法                  穿透      查询不存在的数据      每次都打到数据库      空值缓存、布隆过滤器、参数校验              击穿      热点 key 过期瞬间      单点热点打崩数据库      互斥加载、逻辑过期、热点不过期              雪崩      大量 key 同时失效或 Redis 不可用      数据库整体飙高      TTL 抖动、多级缓存、限流降级      25.4.1 缓存穿透常见攻击形态：恶意请求 id=-1恶意请求 id=999999999恶意请求随机 UUID因为数据库里也没有这些数据，Redis 永远无法命中。第一层防线是参数校验：if (id == null || id &lt;= 0) {    throw new BadRequestException("invalid product id");}第二层是空值缓存：if (product == null) {    redis.opsForValue().set(key, "", Duration.ofSeconds(60));    return Product.empty(id);}注意空值 TTL 不要太长，否则新建数据后短时间内查不到。第三层是布隆过滤器。它适合“能事先枚举全量合法 key”的场景：public boolean mayExist(Long id) {    return bloomFilter.mightContain("mall:product:" + id);}布隆过滤器特点：  判断不存在则一定不存在；  判断存在则可能存在；  标准布隆过滤器不能删除元素；  重建时通常使用版本号切换。适合商品、用户、订单号这类可枚举数据，不适合无限增长且没有边界的随机查询。25.4.2 缓存击穿一个百万级 QPS 的热点商品缓存过期，成千上万请求同时查库，数据库瞬间被打垮。方案一：互斥加载。public Product getProductWithLock(Long id) {    String key = "mall:product:base:" + id;    Product cached = readCache(key);    if (cached != null) {        return cached;    }    String lockKey = "lock:load:product:" + id;    String token = UUID.randomUUID().toString();    Boolean locked = redis.opsForValue()            .setIfAbsent(lockKey, token, Duration.ofSeconds(5));    if (Boolean.TRUE.equals(locked)) {        try {            cached = readCache(key);            if (cached != null) {                return cached;            }            Product product = productMapper.selectById(id);            writeCache(key, product, Duration.ofMinutes(30));            return product;        } finally {            release(lockKey, token);        }    }    sleepQuietly(30);    return getProduct(id);}注意：示例中的递归重试在生产中要限制次数或改为短暂自旋。热点极高时，还可以让部分请求直接降级返回旧数据。方案二：逻辑过期。value 中保存业务数据和逻辑过期时间，Redis key 本身不设置 TTL：{  "data": {"id": 10001, "price": 199.00},  "expireAt": 1787600000000}读取时：  未到逻辑过期时间，直接返回旧数据；  已过期，尝试拿锁异步重建；  拿不到锁也返回旧数据。优点是读路径不阻塞，缺点是重建期间返回旧值，适合“可用性优先”的热点数据。方案三：热点 key 不过期，由变更事件主动删除或更新。适合首页配置、活动页、热门榜单等可控数据。25.4.3 缓存雪崩雪崩有两类。第一类是大量 key 同时过期：00:00 活动开始，预热了 100 万个 key，TTL 全部 1 小时01:00 全部过期，数据库被打崩解决：  TTL 加随机抖动；  分批预热；  预热时间错开；  热点数据使用逻辑过期；  设置数据库侧限流。第二类是 Redis 本身不可用：Redis 主从切换 / 网络分区 / 大量慢命令 / 内存达到 maxmemory解决：  应用侧缓存兜底；  数据库查询加限流；  核心接口降级；  Redis 使用哨兵或 Cluster；  客户端设置合理超时和熔断；  告警先行，不要等到数据库崩溃才发现。一个可用的读路径骨架：public Product safeGetProduct(Long id) {    Product cached = localCache.getIfPresent(id);    if (cached != null) {        return cached;    }    try {        cached = redisGetProduct(id);        if (cached != null) {            localCache.put(id, cached);            return cached;        }    } catch (RedisConnectionException e) {        log.warn("Redis unavailable, product id={}", id, e);    }    if (!databaseLimiter.tryAcquire()) {        throw new ServiceUnavailableException("cache and db are busy");    }    return productMapper.selectById(id);}缓存故障时最重要的原则：宁可拒绝部分流量，也不要让数据库和 Redis 一起失败。25.5 缓存与数据库一致性25.5.1 为什么没有简单方案看一个并发时序：事务 A：更新数据库 price=100事务 B：更新数据库 price=200事务 B：写缓存 price=200事务 A：写缓存 price=100数据库最终是 200，缓存却是 100。这就是“更新缓存”在并发写下的覆盖问题。再看“先删缓存，再更新数据库”：写请求：删除缓存读请求：未命中，查数据库，得到旧值写请求：更新数据库读请求：把旧值写入缓存缓存会长期保留旧值，直到 TTL 过期或再次变更。因此主流选择通常是：先提交数据库事务 -&gt; 删除缓存 -&gt; 失败则重试或订阅 binlog 补偿25.5.2 延迟双删延迟双删用于缓解“读写并发导致旧值回填”的问题：1. 删除缓存2. 更新数据库3. 等待一小段时间4. 再删除一次缓存伪代码：@Transactionalpublic void update(Product product) {    String key = "mall:product:base:" + product.getId();    redis.delete(key);    productMapper.updateById(product);    delayQueue.send(new CacheInvalidation(key), Duration.ofMillis(500));}延迟时间没有万能值，通常取主从同步延迟、事务提交耗时、读请求耗时的上界的组合，常见 200ms 到 1s。延迟双删能降低概率，不能保证强一致。如果业务要求强一致，应让数据库成为唯一读取来源，或使用分布式事务与串行化控制。25.5.3 基于 binlog 的失效更可靠的做法是监听 MySQL binlog，由数据变更事件触发缓存删除。flowchart LR    App[业务应用] --&gt; DB[(MySQL)]    DB --&gt; Canal[Canal / Debezium]    Canal --&gt; MQ[Kafka]    MQ --&gt; Invalidator[缓存失效服务]    Invalidator --&gt; Redis[(Redis)]业务写路径只负责数据库提交，缓存失效由独立组件异步完成。优点：  不依赖业务代码双写；  能覆盖漏删、失败重试和存量修正；  与应用语言解耦；  可按表和行精确失效。挑战：  事件顺序要保证，通常同 key 串行；  消费要幂等；  需要处理删库、DDL、重放和归档；  链路变长，排查成本上升；  仍不能提供强一致，只能提供可审计的最终一致。消费示例：@KafkaListener(topics = "mysql.mall.product")public void onProductChange(ConsumerRecord&lt;String, String&gt; record) {    ProductChange event = objectMapper.readValue(record.value(), ProductChange.class);    if (event.after() != null) {        String key = "mall:product:base:" + event.after().getId();        invalidationGuard.deleteIfVersionMatch(key, event.after().getVersion());    }}如果数据库有版本号，可以用版本号避免旧事件覆盖新缓存。25.5.4 一致性等级选择            业务要求      推荐方案      说明                  可接受分钟级延迟      TTL + 定时刷新      实现最简单              秒级最终一致      更新库后删缓存 + 重试      大多数业务              高可用读旧值      逻辑过期 + 异步重建      热点详情页              较强一致      版本号 + binlog 串行失效      需要基础设施              强一致      不缓存或读写锁串行化      数据库直读      缓存一致性口诀：数据库是事实源，缓存是投影；投影可以旧，但不能没人负责修正。25.6 多级缓存架构当单层 Redis 仍然扛不住读压力，或 Redis 抖动会直接影响核心接口时，可以引入多级缓存。flowchart TD    Client[客户端] --&gt; Edge[CDN / 边缘缓存]    Edge --&gt; Nginx[Nginx shared dict / Lua]    Nginx --&gt; Local[应用本地缓存 Caffeine]    Local --&gt; Redis[(Redis Cluster)]    Redis --&gt; DB[(MySQL)]25.6.1 本地缓存层本地缓存常用 Caffeine：private final Cache&lt;Long, Product&gt; localCache = Caffeine.newBuilder()        .maximumSize(20_000)        .expireAfterWrite(Duration.ofSeconds(5))        .recordStats()        .build();本地缓存特点：            维度      本地缓存      Redis                  延迟      纳秒到微秒级      网络往返，通常亚毫秒到毫秒              容量      受 JVM 内存限制      可独立扩容              一致性      多实例更新困难      集中存储，较易失效              故障影响      Redis 故障仍可兜底      Redis 故障影响全局              适用      极热点、短 TTL、可旧值      大部分共享缓存      本地缓存更新方式：  短 TTL 自动过期；  Redis 变更后发布 Pub/Sub 通知；  通过 Kafka 广播失效事件；  定时刷新热点集合。Pub/Sub 通知示例：redis.convertAndSend("cache:invalidate", "mall:product:base:10001");订阅方：public void onMessage(String message) {    localCache.invalidate(extractId(message));}Pub/Sub 不保证可靠送达。重要失效事件建议使用 Stream 或 Kafka，Pub/Sub 只做加速通知。25.6.2 热点 key 治理热点 key 是 Cluster 中最常见的负载不均问题。识别方式：redis-cli --hotkeysredis-cli info commandstatsredis-cli monitor   # 只用于短时间诊断也可以在应用侧采样 key 并上报监控系统。治理方案：            方案      做法      适用                  本地缓存      热点放 Caffeine      读极多、允许秒级旧值              key 拆分      key#1 到 key#N 随机读      可拆分的只读热点              复制多分片      多副本读      特定代理或客户端方案              限流熔断      控制到达 Redis 的流量      保护底层              静态化      生成静态文件或 CDN      首页、活动页      热点 key 拆分示例：String key = "mall:product:base:" + id + ":" + ThreadLocalRandom.current().nextInt(16);Product product = readFromRedis(key);if (product == null) {    product = loadAndFillAllCopies(id);}写多副本时要保证所有副本同步更新，或者只对不可变数据使用拆分。25.7 缓存预热、降级与限流25.7.1 缓存预热预热适合可预测流量，例如大促、活动上线、新版本发布。预热清单：  热点商品、活动页、类目树；  用户维度短期热点；  配置、开关、字典表；  排行榜和库存；  防穿透空值集合。分批预热脚本：@Scheduled(cron = "0 30 23 * * ?")public void warmUpTomorrowTopProducts() {    List&lt;Long&gt; ids = productMapper.selectTomorrowTop(50_000);    for (int i = 0; i &lt; ids.size(); i += 500) {        List&lt;Long&gt; batch = ids.subList(i, Math.min(i + 500, ids.size()));        cacheLoader.asyncWarmUp(batch);        sleepQuietly(200);    }}预热不是越多越好。冷数据预热会浪费内存，还会挤压真正热点。25.7.2 降级策略            场景      降级动作                  Redis 连接失败      返回本地缓存或默认值              数据库压力过高      限流、排队、返回静态数据              非核心数据缺失      返回兜底空结构              商品详情失败      返回基础商品信息              推荐列表失败      返回热门榜单              评论数失败      隐藏字段或返回 0      降级要在开发期定义，不要在事故现场临时改代码。25.7.3 限流设计缓存失效后的数据库加载必须有限流：RateLimiter dbLimiter = RateLimiter.create(2000); // 每秒最多 2000 次查库public Product loadFromDatabase(Long id) {    if (!dbLimiter.tryAcquire(50, TimeUnit.MILLISECONDS)) {        throw new ServiceUnavailableException("database limiter rejected");    }    return productMapper.selectById(id);}限流阈值来自数据库压测，而不是拍脑袋。常见分层限流：接口限流 -&gt; 用户限流 -&gt; 缓存加载限流 -&gt; 数据库连接池限流25.8 命中率与监控缓存不是上线就结束，命中率决定它是否真的在工作。核心指标：            指标      定义      说明                  命中率      hits / (hits + misses)      按 key 前缀和接口分别统计              未加载数      未命中但也没有触发查库      空值、被拦截请求              平均延迟      Redis 命令耗时      关注 P99              加载耗时      miss 后到数据库返回      评估雪崩风险              缓存大小      每前缀 key 数和内存      容量治理              过期速率      单位时间过期 key 数      发现集中失效              旧值率      版本落后比例      一致性治理              错误率      超时、连接失败、反序列化失败      可用性      建议在 SDK 中埋点：public &lt;T&gt; T getWithMetrics(String key, Class&lt;T&gt; type, Supplier&lt;T&gt; loader) {    long start = System.nanoTime();    String value = redis.get(key);    long redisCost = System.nanoTime() - start;    if (value != null) {        metrics.recordHit(prefix(key), redisCost);        return deserialize(value, type);    }    metrics.recordMiss(prefix(key), redisCost);    long dbStart = System.nanoTime();    T loaded = loader.get();    metrics.recordLoad(prefix(key), System.nanoTime() - dbStart);    return loaded;}治理建议：  命中率低于 60% 时重新评估缓存价值；  空 key 大量出现时检查恶意请求；  miss 突增优先看是否 TTL 到期；  Redis 延迟升高的同时命中率下降，通常是故障前兆；  每天输出 key 前缀 Top N 内存和 QPS。25.9 常见错误设计            错误      后果      修正                  所有 key 相同 TTL      集中失效      TTL 抖动              缓存对象无限大      网络和内存压力      拆字段、压缩、分页              用 KEYS 扫描生产      阻塞 Redis      使用 SCAN              更新缓存而不是删除      并发覆盖      删除 + 重载              强一致业务走缓存      数据错乱      数据库直读或读写锁              只缓存不监控      不知道缓存是否有效      建立命中率指标              Redis 失败直接查库      数据库被打崩      限流降级              空值 TTL 很长      新数据不可见      缩短空值 TTL      25.10 一个生产级读路径模板综合本章内容，一个健壮的商品读路径如下：public Product getProduct(Long id) {    validateId(id);    Product product = localCache.getIfPresent(id);    if (product != null) {        return product;    }    try {        product = redisCache.getProduct(id);        if (product != null) {            localCache.put(id, product);            return product;        }    } catch (CacheUnavailableException e) {        metrics.redisFailure("product");    }    if (!loadLimiter.tryAcquire()) {        return fallbackProduct(id);    }    product = productMapper.selectById(id);    if (product == null) {        redisCache.putEmpty(id, Duration.ofSeconds(60));        return null;    }    redisCache.putProduct(id, product, randomTtl(Duration.ofMinutes(30)));    localCache.put(id, product);    return product;}这个模板体现五个原则：  参数先校验；  本地缓存保护极热点；  Redis 失败不影响进程存活；  查库必须限流；  空值和正常值都有 TTL。25.11 本章小结  Cache Aside 是默认选择，写路径推荐“先更新数据库，再删除缓存”；  穿透、击穿、雪崩分别对应不存在数据、单热点过期、批量失效或整体不可用；  空值缓存、TTL 抖动、互斥加载、逻辑过期、限流降级是核心防护手段；  延迟双删和 binlog 失效只能实现最终一致，强一致场景应放弃缓存或引入串行化；  本地缓存适合极热点，Redis 适合共享热点，数据库是最后的事实源；  缓存架构必须监控命中率、miss 加载、旧值率、内存和错误率。25.12 思考题  为什么“先删除缓存，再更新数据库”更容易造成长期旧值？  逻辑过期方案在什么业务下比互斥加载更合适？  如果缓存删除失败，你会设计怎样的重试表和补偿任务？  Redis Cluster 中某个分片 CPU 达到 100%，如何确认是否为热点 key，如何治理？  一个接口缓存命中率只有 40%，但业务方坚持要缓存，你会从哪些角度分析？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 上线只是开始。本章覆盖日常运维、容量治理、备份恢复、版本升级、扩缩容、迁移与多活，让 Redis 长期稳定运行。24.1 生命周期治理每个 key 都应该有生命周期：            数据      生命周期                  页面缓存      分钟级 TTL              会话      小时级 TTL              token/验证码      分钟级或更短              排行榜      活动周期              延迟任务      执行后删除              配置缓存      版本失效              历史数据      迁出到 DB/对象存储      没有 TTL 的 key 必须有登记、容量预估和清理策略。24.2 容量治理容量水位used_memory / maxmemory数据增长趋势淘汰数量碎片率复制缓冲客户端输出缓冲建议：70% 进入容量规划80% 告警90% 严重，必须扩容或清理key 治理  建立 key 前缀登记；  定期扫描大 key；  识别无 TTL 的常驻 key；  业务下线时同步清理 key；  破坏性结构变更走新版本 key。24.3 日常巡检PINGINFO memoryINFO statsINFO persistenceINFO replicationSLOWLOG GET 10CLIENT LISTMEMORY DOCTORCluster 额外：CLUSTER INFOCLUSTER NODES建议节奏：            频率      动作                  实时      核心指标告警              每日      检查容量趋势、慢查询              每周      大 key/hot key 扫描              每月      备份恢复演练、配置审计              每季度      故障切换演练、容量评审      24.4 备份与恢复备份策略对象: 从节点执行 BGSAVE，避免影响主节点频率: 按数据重要性，日备/小时级保留: 按恢复点目标 RPO存储: 异机/对象存储/异地验证: 定期恢复到临时实例示例：redis-cli BGSAVEredis-cli LASTSAVEcp /data/dump.rdb /backup/dump-$(date +%F-%H%M).rdb恢复流程1. 声明影响与目标 RTO2. 准备新实例/临时实例3. 放置 RDB/AOF4. 启动并校验 DBSIZE、抽样 key5. 修改客户端配置或切流量6. 观察指标7. 输出恢复报告恢复演练必须真实执行，不能只确认备份文件存在。24.5 版本升级升级路径1. 阅读 release notes 与升级指南2. 测试环境完整演练3. 确认客户端兼容性4. 备份当前数据5. 逐个从节点升级6. 主从切换7. 升级旧主节点8. 观察稳定注意  不建议跨太多大版本直接升级；  数据结构编码阈值可能变化；  持久化格式、AOF manifest 需兼容；  Cluster 所有节点版本需协同；  升级前导出配置快照；  预留回滚窗口。24.6 主从扩缩容扩容从节点：replicaof new-master-host 6379masterauth password注意：  避免多个从节点同时全量同步；  观察主节点复制缓冲；  大实例同步会占网络/磁盘；  新从节点稳定后再接入读流量。缩容从节点：确认 Sentinel 不依赖该节点摘除读流量执行 CLUSTER FORGET（Cluster 场景）关闭实例24.7 Cluster 扩缩容添加主节点：redis-cli --cluster add-node new:6379 old:6379添加从节点：redis-cli --cluster add-node new-replica:6379 old:6379 \  --cluster-slave --cluster-master-id &lt;master-id&gt;迁移槽：redis-cli --cluster reshard old:6379redis-cli --cluster check old:6379缩容：redis-cli --cluster reshard old:6379  # 先迁走槽redis-cli --cluster del-node old:6379 &lt;node-id&gt;要点：  分批 reshard；  低峰执行；  大 key 拆分后再迁移；  期间监控 ASK 重定向和延迟；  迁移前后 CLUSTER CHECK。24.8 数据迁移常见方案  redis-cli --rdb 导出/导入；  主从复制迁移：目标实例 replicaof 源实例；  SCAN + pipeline 双写/搬运；  云厂商迁移服务；  从上游数据库/事件流重建。迁移流程1. 建立目标集群并验证配置2. 全量迁移3. 增量追平4. 双写或短暂只读5. 校验数量与抽样值6. 客户端灰度切流7. 观察指标8. 保留回滚窗口校验DBSIZE 对比（需稳定窗口）key 前缀数量对比抽样 value/hashTTL 对比业务指标对比24.9 多活与容灾同城双活单元化路由：用户请求固定落在同城单元缓存：本地单元为主数据：DB 双向同步或单元封闭Redis 缓存通常不跨机房强同步，而由业务数据源重建。异地灾备主站点 Redis -&gt; 定期/实时迁移到备站点RPO = 数据同步窗口RTO = 切流与重建时间必须明确：允许丢多少缓存？能否从 DB 重建？切换时会不会击穿 DB？是否需要预热？灾备不是复制一份文件，还要有预热、限流和演练。24.10 运维自动化建议平台能力：  实例申请与配额；  key 前缀登记；  配置模板与审计；  大 key 扫描；  容量趋势预测；  备份恢复演练；  哨兵/Cluster 健康巡检；  一键切换/扩容 runbook；  费用与僵尸数据治理。治理比救火更能决定长期稳定性。本章小结  所有 key 都要有生命周期，无 TTL key 必须登记；  容量水位 80% 告警、90% 必须处理；  备份必须在从节点执行并定期演练恢复；  升级采用滚动方式，从从节点开始；  Cluster 扩容后必须 reshard，迁移要分批；  多活设计要先回答 RPO/RTO 与缓存重建策略。思考题  Redis 缓存异地多活时，为什么通常不双向同步所有 key？  Cluster 添加节点后不 reshard 会发生什么？  设计一个每小时备份一次、RPO 小于 1 小时的恢复方案。</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。默认 Redis 设计运行在可信内网，不代表生产可以裸奔。公网暴露、无密码、root 运行、任意命令执行，都是常见事故入口。本章覆盖认证、ACL、TLS、网络隔离、审计与命令治理。23.1 安全模型网络层: 谁能连到 Redis认证层: 连上来的是谁授权层: 这个用户能执行什么传输层: 数据是否加密数据层: value 是否需要业务加密/脱敏审计层: 谁做了什么Redis 不是权限完整的数据库，最佳策略是网络隔离优先，最小权限补足。23.2 网络隔离bind 10.0.0.5protected-mode yes要求：  Redis 不监听公网；  安全组只放行应用网段；  运维跳板机单独访问；  Sentinel/Cluster 总线端口同样受控；  不同业务环境不互通。云上常见错误：0.0.0.0:6379 暴露公网无密码root 启动这会直接导致数据被清空或勒索。23.3 requirepass兼容密码方式：requirepass strong-passwordmasterauth strong-password从节点连接主节点也需要 masterauth。连接：redis-cli -a strong-password更安全的方式是用环境变量 REDISCLI_AUTH 或 secret 注入，避免密码进入 shell history。23.4 ACL 用户体系Redis 6 引入 ACL：ACL WHOAMIACL LISTACL USERSACL CATACL GETUSER app创建业务用户：ACL SETUSER order-service on &gt;order-strong-pass ~order:* +ping +get +set +del +expire +hget +hset含义：            片段      含义                  on      启用用户              &gt;password      设置密码              ~order:*      只允许访问该 pattern 的 key              +get      允许 GET              -keys      禁止 KEYS              +@read      允许读命令类别              +@write      允许写命令类别      更完整示例：ACL SETUSER app_read on &gt;read-pass ~shop:* +ping +get +mget +hget +hmget +existsACL SETUSER app_write on &gt;write-pass ~shop:* +@read +@write -keys -flushall -flushdb -shutdown -config持久化：aclfile /etc/redis/users.aclACL SAVE23.5 命令治理高危命令：KEYSFLUSHALL / FLUSHDBSHUTDOWNDEBUGCONFIGSCRIPT FLUSHCLUSTER RESET通过 ACL 限制：ACL SETUSER app ... -keys -flushall -flushdb -shutdown -config -debug原则：  应用用户无运维命令；  运维命令走管理员账号和审批；  生产禁用交互式 redis-cli -a 常驻脚本；  变更记录审计日志；  使用 SCAN 替代 KEYS。23.6 TLS配置：port 0tls-port 6379tls-cert-file /etc/redis/tls/redis.crttls-key-file /etc/redis/tls/redis.keytls-ca-cert-file /etc/redis/tls/ca.crttls-auth-clients yestls-replication yestls-cluster yes说明：  port 0 关闭明文端口；  复制和 Cluster 总线也要启用 TLS；  客户端必须支持 TLS；  会增加 CPU 和延迟，需要压测；  证书轮换要有流程。客户端：RedisURI uri = RedisURI.builder()        .withHost("redis.example.com")        .withPort(6379)        .withAuthentication("default", "pass")        .withSsl(true)        .build();23.7 数据安全Redis 不理解业务字段，敏感数据必须在业务层处理：  手机号、身份证、token 可以加密后存储；  日志和监控不得输出完整敏感 value；  缓存 DTO 只放必要字段；  会话 token 设置短 TTL；  跨租户访问必须在应用层校验；  key 中避免直接放敏感明文。示例：不推荐: user:13800138000推荐:   user:1001value:  加密手机号或 hash 后的引用23.8 进程与文件权限useradd -r redischown -R redis:redis /data/redis /etc/redischmod 600 /etc/redis/redis.conf建议：  非 root 用户运行；  配置文件和数据目录最小权限；  RDB/AOF 目录不开放给普通用户；  禁止容器 privileged 运行。systemd 示例：[Service]User=redisGroup=redisExecStart=/usr/bin/redis-server /etc/redis/redis.confLimitNOFILE=65535NoNewPrivileges=trueProtectSystem=full23.9 审计与变更最低要求：  记录 ACL 变更；  记录 CONFIG SET；  记录发布/回滚；  记录高危命令执行审批；  监控异常登录来源；  定期导出配置快照对比。Redis 本身审计能力有限，可结合：  跳板机命令审计；  配置管理工具；  网络流日志；  应用侧操作日志；  代理层审计（如有）。23.10 安全检查清单网络:  [ ] 不暴露公网  [ ] bind 内网地址  [ ] 安全组按应用网段放行  [ ] Sentinel/Cluster 总线端口受控认证授权:  [ ] requirepass 或 ACL  [ ] 应用用户独立  [ ] key pattern 限制  [ ] 禁止高危命令  [ ] 密钥进 secret 系统传输:  [ ] 敏感场景启用 TLS  [ ] 复制和总线同步启用数据:  [ ] 敏感字段加密/脱敏  [ ] TTL 与权限匹配  [ ] 日志不打印敏感 value运维:  [ ] 非 root 运行  [ ] 文件权限最小化  [ ] 定期漏洞扫描与版本升级  [ ] 审计与回滚流程本章小结  安全先是网络隔离，再是认证授权；  ACL 可以按用户、key pattern、命令类别精细控制；  高危命令必须从应用用户剥离；  TLS 保护传输，业务加密保护敏感内容；  配置审计和最小权限是长期安全的底线。思考题  只设置 requirepass，为什么仍然不够安全？  多业务共用 Redis 时，如何用 ACL 做 key 隔离？  开启 TLS 后性能下降，如何评估是否值得？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章按症状组织：连接失败、延迟飙升、内存告警、数据丢失、主从异常、Cluster 报错、缓存雪崩，给出排查路径和处置动作。22.1 排查总原则1. 先定影响面：单实例？单业务？全局？2. 看时间线：变更、发布、活动、网络、磁盘；3. 分层确认：客户端 -&gt; 网络 -&gt; Redis -&gt; 系统 -&gt; 架构；4. 保留现场：INFO、SLOWLOG、CLIENT LIST、日志、指标；5. 先恢复再根因：限流、降级、扩容、切流量。万能命令：PINGINFO allSLOWLOG GET 20CLIENT LISTMEMORY DOCTORLATENCY DOCTORCONFIG GET *22.2 连接失败排查telnet host 6379redis-cli -h host -p 6379 pingredis-cli -h host -p 26379 SENTINEL get-master-addr-by-name mymaster确认：  Redis 进程是否存活；  bind/防火墙/安全组；  密码或 ACL；  客户端连接地址是否旧 Master；  maxclients 是否达到上限；  连接池是否耗尽。关键指标rejected_connectionsconnected_clientsmaxclients处置临时：清理空闲连接、提高 maxclients、扩客户端池长期：排查连接泄漏、规范化连接生命周期22.3 延迟飙升排查顺序1. SLOWLOG 是否有慢命令2. 是否大 key / HGETALL / SMEMBERS / KEYS3. Lua 是否执行过长4. QPS 与 CPU 是否突增5. 网络 RTT 与重传6. RDB fork / AOF fsync7. 客户端 GC/池等待/序列化快速定位SLOWLOG GET 20LATENCY DOCTORINFO statsINFO persistenceCLIENT LIST系统：top -H -p $(pidof redis-server)iostat -x 1sar -n DEV 1应急停止危险命令来源限流业务切读流量到从节点隔离热点业务必要时重启前先保留现场与评估数据22.4 内存告警INFO memoryMEMORY STATSMEMORY DOCTOR确认：used_memory 是否接近 maxmemoryevicted_keys 是否增长是否存在 OOM command not allowed碎片率是否异常是否 BGSAVE/COW 导致 RSS 上涨处理：            原因      动作                  缓存自然增长      扩容或调 TTL              大 key      拆分/UNLINK              僵尸 key      生命周期清理              集中过期/淘汰      随机 TTL              复制/客户端缓冲      治理慢消费者              COW      控制实例大小、加内存      22.5 数据丢失可能原因1. 未开启持久化且进程重启2. 使用缓存淘汰策略，key 被驱逐3. TTL 到期4. 主从切换丢失异步复制数据5. 误执行 FLUSHALL/DEL6. 应用 bug 写错实例/DB7. RDB/AOF 损坏或未恢复成功确认CONFIG GET appendonlyCONFIG GET saveCONFIG GET maxmemory-policyINFO keyspaceINFO statsMONITOR（短期小流量实例慎用）检查客户端写入日志和业务请求 ID，不要只看 Redis。恢复有备份：新实例恢复 RDB/AOF -&gt; 校验 -&gt; 切流无备份：从数据库/上游事件重建业务必须幂等，避免重放造成重复副作用22.6 主从复制异常INFO replication主节点：connected_slaves 减少slave offset 停滞从节点：master_link_status:downmaster_sync_in_progress:1master_repl_offset 停滞常见原因：  网络抖动；  backlog 太小导致全量同步；  从节点性能不足；  主节点输出缓冲超限断开；  auth 配置错误；  大实例全量同步占满磁盘/网络。处理：修网络；增大 repl-backlog-size；错峰同步；限制同时同步的 replica 数；必要时重建从节点。22.7 Sentinel 故障切换异常排查：SENTINEL mastersSENTINEL ckquorum mymasterSENTINEL replicas mymaster日志关注：sdown masterodown master+failover-state-begin+switch-master-failover-abort常见问题：            问题      原因                  不切主      quorum/majority 不满足              反复切主      网络分区、超时配置过小              切主后丢数据      异步复制落后              客户端仍连旧主      未接入 Sentinel 事件              新 Master 不可写      只读配置/认证异常      22.8 Cluster 报错CROSSSLOT原因：多 key 操作的 key 不在同一槽。处理：hash tag 或拆分请求。MOVED 频繁原因：槽归属变化、客户端拓扑未刷新。处理：使用智能客户端，开启拓扑刷新。CLUSTERDOWN原因：槽未全覆盖、多数节点不可用、cluster-require-full-coverage=yes。处理：redis-cli --cluster check node:6379redis-cli --cluster fix node:6379迁移卡住检查：CLUSTER NODESCLUSTER SLOTSCLUSTER GETKEYSINSLOT slot count处理：确认源/目标节点存活检查大 key分批迁移必要时 --cluster fix22.9 缓存雪崩、击穿、穿透详细设计见第 25 章。排查要点：            症状      常见原因                  DB QPS 突增，Redis miss 突增      大量 key 同时过期或 Redis 故障              单热点 key miss 导致 DB 压力      热点 key 失效              不存在 key 大量请求      攻击/爬虫/非法 ID      应急：接口限流空值缓存热点 key 逻辑过期本地缓存兜底DB 读写分离/扩容22.10 事故复盘模板影响：  开始/结束时间、请求量、失败量、业务影响时间线：  告警时间、响应时间、动作时间、恢复时间根因：  技术根因 + 流程根因证据：  指标截图、SLOWLOG、配置快照、变更记录修复：  立即修复 + 中期治理 + 长期防线行动项：  负责人 + 截止时间 + 验收标准本章小结  排查先定影响面和时间线，再分层确认；  延迟问题优先看慢命令、大 key、QPS、系统资源；  内存问题区分数据增长、淘汰、碎片、COW；  数据丢失要同时检查持久化、淘汰、TTL、误操作和复制；  Cluster 报错先看槽状态与客户端重定向；  事故后必须把修复固化为监控、扫描和流程防线。思考题  Redis PING 正常但业务大量超时，下一步查什么？  数据“丢失”但 Redis 未重启，有哪些可能性？  如何区分 Redis 慢、网络慢、客户端慢？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。没有监控的 Redis 就是一颗定时炸弹。本章建立覆盖可用性、内存、性能、持久化、复制、客户端与业务的指标体系。21.1 监控分层业务层：命中率、降级次数、锁失败、库存失败应用层：连接池、命令耗时、异常、序列化Redis层：QPS、内存、慢查询、淘汰、持久化高可用层：主从状态、哨兵、Cluster 拓扑系统层：CPU、内存、磁盘、网络、进程只监控 Redis 进程存活是不够的，缓存命中率下降本身就是重大业务风险。21.2 核心指标可用性            指标      获取方式      告警                  ping      PING      连续失败 P0              connected_clients      INFO clients      异常突增              blocked_clients      INFO clients      &gt;0 持续              rejected_connections      INFO stats      &gt;0      内存            指标      说明                  used_memory      Redis 数据内存              used_memory_rss      OS 驻留内存              maxmemory      上限              mem_fragmentation_ratio      碎片率              evicted_keys      淘汰 key 数              expired_keys      过期 key 数      建议告警：used_memory / maxmemory &gt; 80% 警告used_memory / maxmemory &gt; 90% 严重evicted_keys 突增碎片率 &gt; 1.5 持续性能            指标      说明                  instantaneous_ops_per_sec      当前 QPS              slowlog_len      慢查询条数              慢查询条目      命令与耗时              latency_percentiles_usec      命令延迟分位（新版本）              total_commands_processed      累计命令              latest_fork_usec      最近 fork 耗时      持久化            指标      说明                  rdb_bgsave_in_progress      RDB 进行中              rdb_last_save_time      上次成功保存              rdb_last_bgsave_status      上次结果              aof_enabled      是否开启 AOF              aof_last_write_status      AOF 写状态              aof_delayed_fsync      fsync 延迟次数              aof_current_size      AOF 大小      复制与高可用主节点：roleconnected_slavesmaster_repl_offsetslaveN offsetrepl_backlog_size从节点：master_link_statusmaster_last_io_seconds_agoslave_read_repl_offsetmaster_repl_offsetCluster：cluster_statecluster_slots_assigned / ok / fail / pfailcluster_known_nodescluster_size21.3 业务指标缓存：cache_request_totalcache_hit_totalcache_miss_totalcache_hit_ratiocache_fallback_db_totalcache_rebuild_durationcache_error_total锁：lock_acquire_totallock_acquire_failure_totallock_wait_durationlock_hold_durationlock_release_failure_total队列：queue_lengthenqueue_totaldequeue_totalpending_totaldead_letter_total命中率下降往往比 Redis CPU 更早暴露问题。21.4 采集方式redis-cli 定时采集while true; do  redis-cli INFO memory | grep used_memory  sleep 10done适合临时排查，不适合生产完整监控。Prometheus redis_exporterscrape_configs:  - job_name: redis    static_configs:      - targets:          - redis-exporter:9121redis-exporter 示例参数：redis_exporter \  --redis.addr=redis://redis:6379 \  --redis.password=xxxGrafana 面板推荐至少四块：  可用性与连接；  内存与淘汰；  QPS/延迟/慢查询；  复制/哨兵/Cluster。再加一块业务面板：命中率、回源 DB、降级、锁失败。21.5 日志采集Redis 服务端日志：慢查询RDB/AOF 错误复制断开OOM主从切换Cluster 槽迁移客户端日志：连接超时命令超时重试序列化失败池耗尽MOVED/ASK建议统一 trace_id + key 前缀（不要打完整敏感 value）。21.6 告警分级            级别      条件示例                  P0      PING 失败；Cluster state fail；主从全部不可用；写入持续失败              P1      内存 &gt;90%；慢查询持续增加；复制断开；AOF 写失败；命中率骤降              P2      内存 &gt;80%；碎片率高；单从节点延迟增长；连接数异常              提醒      淘汰增长、大 key 数量、配置变更、备份失败      告警必须带 runbook：现象影响确认命令应急动作升级路径后续修复21.7 巡检脚本示例#!/usr/bin/env bashREDIS="redis-cli -h redis -a $PASS"echo "== ping =="$REDIS pingecho "== memory =="$REDIS INFO memory | grep -E \  'used_memory_human|maxmemory_human|mem_fragmentation_ratio'echo "== stats =="$REDIS INFO stats | grep -E \  'instantaneous_ops_per_sec|evicted_keys|rejected_connections'echo "== slowlog =="$REDIS SLOWLOG GET 5echo "== replication =="$REDIS INFO replication | grep -E \  'role:|connected_slaves:|master_link_status:'本章小结  监控覆盖业务、应用、Redis、高可用、系统五层；  内存、慢查询、复制、AOF、命中率是核心生命线；  业务命中率与回源 DB 能提前发现缓存失效；  告警分级并配套 runbook；  Grafana 至少包含可用性、内存、性能、高可用、业务五块面板。思考题  Redis CPU 正常但接口变慢，应该看哪些客户端指标？  evicted_keys 增长一定是问题吗？如何结合数据类型判断？  如果只能保留 5 个 Redis 指标，你选什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 性能问题的常见真相是：不是 Redis 不快，而是用法破坏了它的假设。本章给出一套从压测、定位到落地的调优方法。20.1 先定义目标调优前必须量化：目标 QPS 是多少？P99 / P999 延迟要求是多少？读写比例是多少？value 大小分布是多少？允许丢失吗？是否允许强依赖降级？Redis 常见目标：缓存：P99 &lt; 5ms，命中率 &gt; 95%会话：P99 &lt; 10ms，可用性优先锁/库存：正确性优先，P99 &lt; 20ms大对象读取：必须拆分，不能直接进 Redis20.2 基准测试redis-benchmarkredis-benchmark \  -h 127.0.0.1 \  -p 6379 \  -a redis123 \  -n 1000000 \  -c 200 \  -d 1024 \  -t get,set \  -P 16 \  --threads 4参数：            参数      含义                  -n      总请求数              -c      并发连接              -d      value 大小              -t      测试命令              -P      pipeline 数              --threads      客户端线程      输出关注：throughputaverage latencypercentile latency压测建议  使用与生产相同的数据大小与命令；  分离压测客户端与 Redis；  分别测 GET/SET/HGETALL/ZADD/Lua；  改一个参数测一轮；  记录 CPU、内存、网卡、磁盘；  不要在业务高峰压生产实例。20.3 性能问题分层客户端  连接池不足 / GC / 序列化 / 超时配置网络  RTT / 带宽 / 重传 / 连接重建Redis  慢命令 / 大 key / 热点 / 内存淘汰 / fork / AOF系统  CPU limit / swap / NUMA / 磁盘 / 中断架构  单点热点 / 强依赖 / 缓存 miss / 数据倾斜不要看到延迟就改 io-threads。先看慢查询和大 key。20.4 慢命令治理SLOWLOG GET 50常见慢命令：            命令      原因      替代                  KEYS pattern      全量扫描      SCAN              HGETALL big      大响应      HMGET / HSCAN              SMEMBERS big      大响应      SSCAN / SRANDMEMBER n              LRANGE 0 -1      大列表      分页              ZRANGE 0 -1      大 ZSet      分页              DEL big      同步释放      UNLINK              SINTER/SDIFF big      集合计算      预计算/拆分              EXPIRE 大量 key 同刻      删除尖刺      TTL 随机化      20.5 大 key 治理发现：MEMORY USAGE keyMEMORY DOCTORSCAN cursor MATCH pattern COUNT 100推荐工具：  redis-cli --bigkeys：低峰周期扫描；  RDB 离线分析工具；  云厂商大 key 分析；  业务埋点上报 value 大小。治理：  Hash 按字段分片；  ZSet 按时间/范围分片；  List 使用 LTRIM 控制长度；  Stream 使用 XTRIM；  大文本放对象存储，Redis 存 URL；  读取必须分页；  删除使用 UNLINK。20.6 热点 key 治理现象：单 key QPS 极高，单节点 CPU/网卡打满，其他节点空闲。1. 本地缓存请求 -&gt; JVM 本地缓存 -&gt; Redis -&gt; DB适合不频繁变化的配置、商品基础信息。2. key 打散hot:key:{0..N}随机读一个副本，写时更新全部或异步失效3. 读写分离读请求分散到多个 replica；注意复制延迟。4. Cluster + 前置路由热点业务单独拆集群或节点，避免影响其他 key。5. 业务削峰前端限流、合并请求、异步刷新。20.7 Pipeline 与批量逐条请求：100 命令 * 1ms RTT = 100ms 网络Pipeline：1 次批量传输 + Redis 快速执行建议：  每批 100-1000 条；  控制总字节数；  大 key 不入同一批；  异常时仍可分批重试；  不要在高峰一次性导入百万数据。20.8 客户端调优连接池大小：按 QPS * Redis平均耗时估算命令超时：通常 100ms-1s连接超时：100ms-1s重试：读幂等操作 1 次，写操作业务幂等后再重试序列化：避免超大大对象连接名：便于 CLIENT LIST 定位客户端侧常见问题：  池太小导致等待；  池太大导致 Redis 连接数高；  每次请求新建连接；  反序列化大 JSON；  应用 GC 停顿被误判为 Redis 慢；  重试叠加造成雪崩。20.9 内存调优INFO memory关注：used_memorymaxmemorymem_fragmentation_ratioevicted_keyslazyfree_pending_objects动作：  控制实例内存规模（常见单实例 10-30GB 内）；  拆大 key；  缓存加 TTL；  调整 listpack 阈值；  清理僵尸 key；  碎片率高时评估 active defrag；  不把 Redis 当对象存储。20.10 系统层调优            项      建议                  CPU      独占或设置合理 limit，避免与重负载混部              内存      禁用 swap，预留 fork/COW              网卡      关注带宽与重传，大 value 场景重点              磁盘      AOF/RDB 独立盘或高性能盘              NUMA      绑定节点减少跨节点访问              文件句柄      ulimit -n 足够              overcommit      COW 场景合理配置      Linux 示例：sysctl vm.overcommit_memory=1sysctl vm.swappiness=1云容器特别注意：CPU limit 被打满时，延迟尖刺很像 Redis 慢；内存 limit 未预留 COW 时，BGSAVE 可能 OOM。20.11 AOF/RDB 调优appendfsync everysecauto-aof-rewrite-min-size 2gbno-appendfsync-on-rewrite no原则：  磁盘慢会传导为写延迟；  大实例 fork 时间变长；  多实例错峰 rewrite；  备份在从节点执行；  观察 latest_fork_usec 和 aof_delayed_fsync。20.12 IO 多线程调优适用：网络读写和协议解析成为瓶颈，CPU 核心充足。io-threads 4io-threads-do-reads yes不适用：  慢命令为主；  大 key 为主；  CPU 已经不足；  容器只有 1-2 核。验证方式：开启前: 记录 QPS/P99/CPU开启后: 相同压测对比如果吞吐无提升或 P99 变差，回退20.13 容量规划估算：数据内存 =  key数量 * 平均key大小  + value数量 * 平均对象头  + 集合索引/指针  + TTL元数据预留 =  业务增长  + 复制缓冲  + 客户端输出缓冲  + fork/COW  + 碎片经验：纯缓存：maxmemory 设置机器内存 70%-80%持久化/状态实例：预留 30%-40% 给 COW/缓冲Cluster：每主节点保持 10-30GB，过大不利迁移和故障恢复20.14 一个调优案例现象：接口 P99 从 8ms 涨到 300msRedis CPU 85%SLOWLOG 大量 HGETALL，耗时 20-80ms定位：某 Hash 有 12 万 field接口每次 HGETALL 后本地取 3 个字段治理：1. 接口改为 HMGET 3 个必要字段2. Hash 按用户维度拆分3. 大 Hash UNLINK 下线4. 上线大 key 扫描和发布检查结果：Redis CPU 降到 35%P99 回到 6ms这类“读全量再丢掉大部分”的模式，是 Redis 最常见的反模式。本章小结  先定 QPS/P99 目标，再做对比压测；  优先治理慢命令、大 key、热点 key 和客户端；  Pipeline 能显著降低 RTT，但要控制批次；  内存容量必须预留复制缓冲、fork/COW 和碎片；  io-threads 只解决网络 IO 瓶颈，不解决慢命令；  系统资源、持久化与容器 limit 同样关键。思考题  Redis P99 高但 CPU 低，可能的问题在哪里？  为什么大 key 会同时造成 Redis 延迟、客户端超时和主从复制压力？  如何为 20GB 数据量的 Redis 实例规划机器内存？列出所有预留项。</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。当单实例内存、写入或网卡到达瓶颈，Redis Cluster 通过分片横向扩展。本章讲槽、key 分布、Gossip、MOVED/ASK、故障转移、扩缩容与限制。19.1 Cluster 解决什么问题单实例限制：内存上限CPU 写入上限网卡吞吐持久化 fork 时间复制全量成本Cluster 把数据按 key 分片到多个主节点：Node A: slots 0-5460Node B: slots 5461-10922Node C: slots 10923-16383每个主节点可以有从节点，主故障后从节点提升。19.2 16384 个槽计算：CRC16(key) mod 16384示例：CLUSTER KEYSLOT user:1001CLUSTER COUNTKEYSINSLOT 1234槽是迁移和归属的最小单位。节点只负责自己的槽，不保存全量数据。hash tagorder:{1001}:detailorder:{1001}:stock只有 {} 中内容参与计算，两个 key 同槽，可执行多 key/Lua 操作。风险：{hot-id} 会导致单槽热点，需避免所有请求集中同一 tag。19.3 集群拓扑Master A + Replica A'Master B + Replica B'Master C + Replica C'节点间通过 cluster bus（默认 16379）Gossip 通信业务端口默认 6379最小生产建议：3 主 3 从。6 节点可部署在不同机器/可用区。配置：port 6379cluster-enabled yescluster-config-file nodes-6379.confcluster-node-timeout 15000cluster-require-full-coverage yescluster-replica-validity-factor 10cluster-migration-barrier 119.4 创建集群redis-cli --cluster create \  10.0.0.11:6379 \  10.0.0.12:6379 \  10.0.0.13:6379 \  10.0.0.14:6379 \  10.0.0.15:6379 \  10.0.0.16:6379 \  --cluster-replicas 1检查：CLUSTER INFOCLUSTER NODESCLUSTER SLOTSCLUSTER MYID健康状态：cluster_state:okcluster_slots_assigned:16384cluster_slots_ok:16384cluster_known_nodes:619.5 请求路由：MOVED客户端请求错误节点：Client -&gt; NodeB GET user:1001NodeB -&gt; MOVED 1234 NodeA:6379Client -&gt; NodeA GET user:1001智能客户端会在本地缓存槽映射，并根据 MOVED 更新，稳定后一次直达正确节点。非智能客户端每次都可能多一次 RTT，生产应使用支持 Cluster 的客户端。19.6 请求路由：ASK迁移槽时：槽 1234 正在从 A 迁移到 BClient 访问 AA 本地无该 key -&gt; ASK BClient 先向 B 发 ASKINGB 允许执行本次请求区别：            重定向      语义                  MOVED      槽已归属新节点，客户端更新映射              ASK      槽正在迁移，只对本次请求临时转向      迁移完成后客户端才会收到 MOVED。19.7 Gossip 协议节点通过 cluster bus 交换：  节点 ID、IP、端口；  负责槽位；  主从关系；  节点时间与状态；  PING/PONG/MEET/FAIL 消息。作用：  发现拓扑；  传播故障标记；  传播配置纪元；  支持槽迁移与故障切换。Gossip 是最终一致的，短时间内不同节点视图可能不同。19.8 故障检测与转移流程：1. 节点 A 定期 PING 其他节点2. 超过 cluster-node-timeout 未有效回复 -&gt; PFAIL 疑似下线3. 多数 Master 认为 A PFAIL -&gt; 标记 FAIL4. A 的 Replica 发起选举5. 获得多数 Master 投票后提升为新 Master6. 接管旧 Master 的槽相关配置：cluster-node-timeout 15000cluster-replica-validity-factor 10cluster-replica-no-failover nocluster-require-full-coverage yes 表示任一槽不可用时集群拒绝相关请求；设为 no 可提升局部可用性，但需要业务明确接受。19.9 多 key 操作限制Cluster 中多 key 命令、事务、Lua 要求所有 key 同槽：MSET user:1001 a user:2002 b# 如果槽不同 -&gt; CROSSSLOT解决：user:{1001}:profileuser:{1001}:score或业务侧拆分请求，分别路由。19.10 槽迁移与扩缩容添加节点：redis-cli --cluster add-node new-node:6379 old-node:6379reshard：redis-cli --cluster reshard any-node:6379迁移过程：1. 目标节点准备导入槽2. 源节点标记迁移3. 逐个 key 迁移4. 迁移期间 ASK 重定向5. 完成后槽归属更新并广播检查：redis-cli --cluster check node:6379redis-cli --cluster fix node:6379生产要点：  分批迁移，避免网络/磁盘打满；  关注迁移速率与延迟；  大 key 会显著拖慢单槽迁移；  迁移前后执行 CLUSTER CHECK；  尽量业务低峰执行。19.11 Cluster 的限制  多 key 操作必须同槽；  DB 只建议使用 DB0；  槽迁移期间性能抖动；  客户端必须支持 Cluster 协议；  热点 key 仍落在单节点；  事务/Lua 复杂度受限；  集群不是自动数据均衡器，新增节点需 reshard；  Pub/Sub 会广播到所有节点，带宽可能放大。Sharded Pub/Sub 改善广播问题，但需要客户端与版本支持。19.12 客户端配置Spring Boot Lettuce：spring:  data:    redis:      cluster:        nodes:          - node1:6379          - node2:6379          - node3:6379        max-redirects: 3      timeout: 500ms客户端要做：  拓扑刷新；  读写分离配置；  MOVED/ASK 处理；  节点故障重试；  请求级超时。本章小结  Cluster 用 16384 个槽分片，key 通过 CRC16 mod 16384 定位；  hash tag 可让相关 key 同槽，但会带来倾斜风险；  MOVED 表示槽归属变化，ASK 表示迁移临时重定向；  Gossip 维护拓扑与故障状态；  故障切换需要多数 Master 参与；  多 key/Lua 必须同槽，新增节点必须 reshard。思考题  为什么 Redis Cluster 选择 16384 个槽而不是一致性哈希环？  如果某个热点商品 key 打满单节点，如何优化？  Cluster 能否保证强一致？主从切换时会丢失数据吗？为什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。主从复制只提供副本，不提供自动故障切换。Sentinel 负责监控、通知、配置发现和自动切主，是中小 Redis 集群的高可用方案。18.1 Sentinel 架构        Sentinel1       Sentinel2       Sentinel3            |               |               |            +-------+-------+-------+-------+                    v               v                  Master --------&gt; Replica1                                  Replica2Sentinel 不代理业务流量，客户端仍然直连 Redis。它提供：  监控主从节点；  判断主观/客观下线；  选举 Leader Sentinel；  执行故障转移；  通知客户端新 Master 地址。18.2 部署要求至少 3 个奇数节点，且不要与 Redis Master 同机部署。sentinel.conf：port 26379sentinel monitor mymaster 192.168.1.10 6379 2sentinel auth-pass mymaster redis123sentinel down-after-milliseconds mymaster 5000sentinel failover-timeout mymaster 60000sentinel parallel-syncs mymaster 1参数说明：            配置      含义                  mymaster      master 名称，客户端使用              2      quorum，客观下线票数              down-after-milliseconds      无响应判定主观下线              failover-timeout      故障转移超时              parallel-syncs      切主后同时重连新主的从节点数      启动：redis-sentinel sentinel.conf# 或redis-server sentinel.conf --sentinel18.3 主观下线与客观下线主观下线 SDOWN单个 Sentinel 在 down-after-milliseconds 内未收到有效回复：Sentinel1 认为 Master down可能是网络分区或本机问题，不足以切主。客观下线 ODOWN至少 quorum 个 Sentinel 都认为主下线：quorum=2，两个 Sentinel 都 SDOWN -&gt; ODOWNquorum 不是执行切换所需票数，而是“客观下线判定票数”。18.4 Sentinel Leader 选举客观下线后，Sentinel 之间通过类 Raft 选举选出 Leader 执行故障转移：  每个 Sentinel 都可能成为候选；  需要获得多数 Sentinel 授权；  每轮纪元递增；  Leader 执行切主。因此通常部署至少 3 个节点，容忍 1 个 Sentinel 故障；5 个容忍 2 个。18.5 故障转移流程1. Master 被判定 ODOWN2. Sentinel 选举 Leader3. Leader 从可用 Replica 中选择新 Master4. 对选中的 Replica 执行 SLAVEOF NO ONE5. 其他 Replica 执行 SLAVEOF new-master6. 旧 Master 恢复后也变成 Replica7. Sentinel 更新配置与纪元8. 客户端感知新地址选择新 Master 时考虑：  与 Master 断开时长；  复制偏移量；  优先级 replica-priority；  Run ID。配置：replica-priority 1000 表示永不参与选举。18.6 客户端如何接入 SentinelJedis：Set&lt;String&gt; sentinels = Set.of(        "s1:26379", "s2:26379", "s3:26379");JedisSentinelPool pool = new JedisSentinelPool(        "mymaster", sentinels, "default", "redis123");Lettuce：RedisURI uri = RedisURI.Builder.sentinel("s1", 26379, "mymaster")        .withSentinel("s2", 26379)        .withSentinel("s3", 26379)        .withAuthentication("default", "redis123")        .build();客户端流程：连接 Sentinel -&gt; 查询 mymaster 当前 Master               -&gt; 直连 Master               -&gt; 订阅 switch-master 事件               -&gt; 切换后重建连接18.7 Sentinel 常用命令SENTINEL mastersSENTINEL master mymasterSENTINEL replicas mymasterSENTINEL sentinels mymasterSENTINEL get-master-addr-by-name mymasterSENTINEL ckquorum mymasterSENTINEL reset mymasterSENTINEL failover mymasterSENTINEL ckquorum 检查当前可用 Sentinel 是否满足故障切换多数派。18.8 脑裂与数据丢失异步复制下的风险：1. Master 与多数 Sentinel/Replica 网络分区2. 旧 Master 仍接受客户端写3. Sentinel 选出新 Master4. 网络恢复，旧 Master 降级为 Replica5. 旧 Master 分区期间写入丢失缓解配置：min-replicas-to-write 1min-replicas-max-lag 10含义：至少有 1 个从节点延迟小于 10 秒，Master 才接受写。它降低脑裂风险，但牺牲部分可用性。业务侧必须：  关键数据写数据库；  使用业务幂等；  接受 Redis 缓存可丢失；  监控切换事件和复制状态。18.9 故障演练初始:  Master A, Replica B/C动作:  kill A观察:  1. Sentinel SDOWN/ODOWN 日志  2. Leader 选举  3. B/C 谁成为 Master  4. 客户端是否自动切换  5. 写入是否恢复  6. A 恢复后是否变成 Replica恢复旧 Master：systemctl start redisSentinel 会自动将其配置为新 Master 的从节点。18.10 Sentinel 与 Cluster 怎么选            维度      Sentinel 主从      Cluster                  分片      不支持      支持 16384 slots              容量      单机内存上限      多节点水平扩展              客户端      Sentinel 协议      Cluster 协议/重定向              多 key 操作      无槽限制      通常需同槽              运维复杂度      较低      较高              适合      中等容量高可用      大容量高吞吐      如果单实例容量与写入压力足够，优先 Sentinel；超过单机瓶颈或需要水平拆分，再上 Cluster。本章小结  Sentinel 监控与切换，不代理业务流量；  quorum 判断客观下线，故障执行还需多数 Sentinel 选举 Leader；  至少部署 3 个奇数 Sentinel；  切换会选复制偏移较新、优先级合适的 Replica；  异步复制和脑裂仍可能丢最后写入；  中小规模高可用用 Sentinel，大规模分片用 Cluster。思考题  quorum=2 且只有两个 Sentinel，为什么仍然不推荐？  故障切换后客户端为什么会短暂报错？如何降低影响？  min-replicas-to-write 如何降低脑裂丢失风险？代价是什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。复制是高可用的基础：从节点提供副本，读写分离，并为故障切换和备份服务。本章讲全量同步、增量同步、复制积压缓冲、replid 与常见问题。17.1 复制的角色Master  |- Replica A  |- Replica B配置：# Replicareplicaof 192.168.1.10 6379masterauth passwordreplica-read-only yes查看：INFO replicationROLE主节点关键信息：role:masterconnected_slaves:2slave0:ip=...,port=...,state=online,offset=...master_replid:...master_repl_offset:...17.2 复制是异步的主节点执行写命令后：1. 本地执行2. 返回客户端3. 异步传播给 replica不是等从节点确认后才返回。因此：  主节点写入成功不代表从节点已收到；  主节点崩溃时，未同步写可能丢失；  读从节点可能读到旧数据。这是性能与一致性的核心取舍。17.3 全量同步流程Replica                     Master  | --- PSYNC ? -1 ----------&gt; |  |                           判断无法增量同步  | &lt;--- +FULLRESYNC replid offset  |                           BGSAVE 生成 RDB  | &lt;--- RDB 数据 -------------  | 加载 RDB  | &lt;--- 缓冲期间写命令 --------  | 追上 offset  | --- PSYNC replid offset -&gt; |  | 进入命令流同步触发场景：  从节点第一次连接；  复制 ID 无法匹配；  offset 已不在复制积压缓冲；  主节点认为无法安全增量同步。全量同步成本高：  主节点 BGSAVE；  网络传输全量 RDB；  从节点加载；  主节点缓存同步期间写命令。17.4 增量同步从节点断线重连后发送：PSYNC &lt;replid&gt; &lt;offset&gt;如果 offset 仍在主节点复制积压缓冲区内，只同步缺失命令：Master -&gt; CONTINUE       -&gt; 发送 offset 之后的数据配置：repl-backlog-size 256mbrepl-backlog-ttl 3600估算：backlog &gt;= 断线时长(s) * 主节点写流量(MB/s)例：平均写 20MB/s，目标容忍 60s 断线    -&gt; 至少 1.2GB17.5 replid 与 second replid每个主实例有复制 ID（replid），配合 offset 标识数据版本。故障切换后：旧 Master: replid=A, offset=1000Replica 提升为新 Master:  新 replid=B  second_replid=A  second_replid_offset=1000其他从节点如果还属于旧数据链，可通过 second replid 判断是否能部分重同步，减少全量同步概率。17.6 复制缓冲区主节点为每个 replica 维护输出缓冲：client-output-buffer-limit replica 256mb 64mb 60repl-timeout 60如果从节点消费慢：  主节点输出缓冲堆积；  内存增长；  超过限制断开 replica；  重连后可能触发全量同步，形成恶性循环。大 key、网络抖动、从节点磁盘/性能瓶颈都会放大这个问题。17.7 读写分离与一致性从节点默认只读：replica-read-only yes读写分离方案：写 -&gt; Master读 -&gt; Replica优点：分摊读流量。风险：  复制延迟导致读到旧值；  主从切换后可能读到更旧数据；  会话状态对一致性敏感时出错。适合：排行榜、商品缓存、统计类读。不适合：支付后立即查状态、权限变更、库存强一致判断。17.8 复制延迟监控INFO replication关注：            字段      含义                  master_repl_offset      主节点复制偏移              slave0:...offset      从节点已同步偏移              master_link_status      从节点连接状态              master_last_io_seconds_ago      最近主从 IO              slave_read_repl_offset      从节点读偏移      计算延迟：lag = master_repl_offset - replica_offsetoffset 差是字节数，不等于时间；可以结合时间序列监控增长趋势。17.9 无盘复制repl-diskless-sync norepl-diskless-sync-delay 5开启后，主节点不落 RDB 文件，而是直接通过网络发送给 replica。适合：  磁盘慢；  磁盘空间紧张；  多个从节点同时同步；  网络与内存资源充足。风险：占用主节点 CPU/网络，且不产生本地 RDB 备份。17.10 常见复制问题            问题      原因      处理                  频繁全量同步      backlog 小、网络断、缓冲超限      增大 backlog，修网络              从节点内存上涨      加载 RDB + 缓冲数据      控制实例大小，错峰同步              复制延迟持续增长      主写流量高、从节点慢      优化写、拆实例、扩容              MASTERDOWN      主不可达      检查网络/主健康，配合哨兵              从节点数据落后业务读失败      读写分离      强一致读走主或业务版本校验      17.11 生产建议一主两从起步；repl-backlog 按写流量与目标断线时间估算；从节点作为备份执行 BGSAVE；监控 offset 差和连接状态；避免把延迟敏感业务读到从节点；为哨兵或 Cluster 预留故障切换方案。本章小结  Redis 复制默认异步，主写入成功不等从确认；  首次或无法部分重同步时执行全量 RDB；  replid + offset 决定能否增量同步；  backlog 大小按写流量与断线容忍时间估算；  从节点消费慢会堆积复制输出缓冲；  读写分离必须评估业务对延迟的容忍度。思考题  主从复制能保证数据不丢吗？为什么？  从节点断线 30 秒，写流量 50MB/s，backlog 应至少多大？  为什么读从节点可能读到逻辑上已过期的数据？业务如何防护？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 是内存数据库，但可以持久化。RDB 快、小、易备份；AOF 丢失窗口小；混合持久化兼顾两者。本章讲清触发机制、文件格式、恢复流程与生产配置。16.1 为什么需要持久化没有持久化时，Redis 重启后数据全部丢失。这对纯缓存可以接受，但对锁任务、队列、计数、排行榜则可能是事故。两类文件：RDB: 某一时刻内存快照，二进制AOF: 写命令追加日志核心权衡：            维度      RDB      AOF                  文件大小      小      大              恢复速度      快      慢              数据丢失窗口      取决于触发周期      取决于 appendfsync              CPU/磁盘影响      fork+子进程写文件      持续追加与重写              可读性      二进制      文本命令      16.2 RDBsave 触发save 3600 1save 300 100save 60 10000含义：满足“秒内至少修改次数”条件时触发保存。手动：SAVEBGSAVELASTSAVE  SAVE：主线程执行，会阻塞，生产禁用；  BGSAVE：fork 子进程生成快照，常用。RDB 文件dbfilename dump.rdbdir /datardbcompression yesrdbchecksum yesRDB 保存二进制快照，加载时直接重建对象，比重放命令快。16.3 fork 与写时复制BGSAVE 流程：1. 主进程 fork 子进程2. 子进程遍历内存，写 RDB 临时文件3. 写完后原子替换旧 RDBfork 后父子进程共享物理内存页，采用 Copy-On-Write：子进程看到 fork 瞬间的数据视图父进程继续处理写命令被修改的页才复制风险：  实例越大，fork 复制页表越慢；  写入越多，COW 复制页越多，内存峰值越高；  低 overcommit_memory 可能导致 fork 失败；  容器内存 limit 未预留 COW 可能 OOM。16.4 AOF开启：appendonly yesappenddirname appendonlydirappendfilename appendonly.aofappendfsync everysec流程：命令执行成功  -&gt; 追加到 AOF 缓冲  -&gt; 根据策略写入文件  -&gt; fsync 到磁盘appendfsync 策略            策略      行为      丢失窗口      风险                  always      每条命令 fsync      最小      吞吐低、磁盘压力大              everysec      每秒一次      最多约 1 秒      推荐默认              no      交给 OS      不可控      通常不建议      everysec 后台线程执行 fsync。如果上一次 fsync 超过 2 秒仍未完成，Redis 会延迟写操作以避免无限堆积。16.5 AOF 重写AOF 会不断增长，重写生成一份只保留最终状态的新 AOF：旧 AOF:SET k 1SET k 2SET k 3INCR counterINCR counter重写后:SET k 3SET counter 2手动触发：BGREWRITEAOF自动触发：auto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 64mbaof-rewrite-incremental-fsync yespercentage=100 表示 AOF 比上次重写后大小增长一倍触发；同时需满足 min-size。16.6 混合持久化aof-use-rdb-preamble yes重写后的 AOF 文件：[RDB 格式基础数据][AOF 增量命令]优点：  RDB 部分恢复快；  AOF 部分保留更少丢失窗口；  文件小于纯 AOF。适合大多数需要持久化的生产实例。注意老版本 Redis 或兼容工具必须支持该格式。16.7 恢复流程Redis 启动时：1. 读取配置2. 检查 AOF 开启3. AOF 存在 -&gt; 优先加载 AOF4. AOF 不存在 -&gt; 加载 RDB5. 重建内存数据因此当 AOF 开启且文件存在时，RDB 不会作为最新数据源。相关配置：aof-load-truncated yesaof-timestamp-enabled noaof-load-truncated 允许加载末尾被截断的 AOF，通常用于断电恢复；仍应告警并尽快补齐数据。16.8 持久化对性能的影响RDB  fork 停顿；  子进程 CPU/磁盘 IO；  COW 内存增长。AOF  everysec 的磁盘写入；  重写期间磁盘 IO；  AOF 重写缓冲内存；  磁盘慢会拖慢写入。建议  RDB/AOF 文件放独立磁盘或高性能盘；  控制实例大小，降低 fork 时间；  避免多个大实例同时 rewrite；  监控 latest_fork_usec、aof_delayed_fsync；  关键业务用复制和外部备份兜底。16.9 备份策略# 生成 RDBredis-cli -a $PASS BGSAVE# 查看完成时间redis-cli -a $PASS LASTSAVE# 复制文件cp /data/dump.rdb /backup/dump-$(date +%F-%H%M).rdb更安全的方式：  在从节点执行 BGSAVE；  将 RDB/AOF 上传对象存储；  保留多版本；  定期演练恢复。恢复演练：mkdir /data/restorecp dump-20260825.rdb /data/restore/dump.rdbredis-server --port 6399 --dir /data/restore --appendonly noredis-cli -p 6399 DBSIZE16.10 生产配置模板缓存实例：save ""appendonly nomaxmemory-policy allkeys-lru允许分钟级丢失：save 900 1appendonly no允许秒级丢失：appendonly yesappendfsync everysecaof-use-rdb-preamble yesauto-aof-rewrite-percentage 100auto-aof-rewrite-min-size 1gb不能丢：appendonly yesappendfsync everysecaof-use-rdb-preamble yessave 3600 1 300 100 60 10000即使如此，异步复制下主故障仍可能丢失最后写入；“绝对不能丢”必须由数据库事务/消息系统/业务确认机制保证。本章小结  RDB 是快照，恢复快、文件小，但丢失窗口大；  AOF 是写日志，丢失窗口小，但文件大、恢复慢；  BGSAVE/BGREWRITEAOF 都依赖 fork 与 COW；  混合持久化是大多数实例的默认选择；  AOF 存在时优先加载 AOF；  备份必须定期演练恢复，不能只备份不验证。思考题  AOF everysec 一定最多丢 1 秒吗？哪些故障会导致更多丢失？  为什么大实例 RDB fork 期间可能出现内存增长？  纯缓存实例要不要开启 AOF？从恢复时间、磁盘和业务语义分析。</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。“Redis 是单线程”这句话既对也不对。本章讲清事件循环、IO 多线程、后台线程、阻塞点与慢命令，帮助你理解延迟从哪里来。15.1 总体架构                      ┌────────────────────┐客户端连接1...N ----&gt; | IO 多路复用 epoll     |                      └----------┬─────────┘                                 v                     事件循环主线程                       读取/解析/执行/写回                                 |                       命令执行器（单线程）后台线程 BIO:  close file / AOF fsync / lazy freeIO 线程(可选):  socket read/write 与协议解析核心原则：  命令执行由主线程串行执行；  网络读写可由 IO 线程并行辅助；  耗时释放与刷盘等任务放后台；  因此一个慢命令会拖住所有命令。15.2 事件循环简化流程：while true:    events = epoll_wait(...)    for event in events:        if readable:            read socket -&gt; parse command            execute command            write response to output buffer        try flush ready outputsRedis 将不同事件注册到多路复用器，根据平台选择 epoll/kqueue/select 等。单线程没有锁竞争、上下文切换和死锁问题，也更容易保持命令顺序。15.3 IO 多线程Redis 6.0 引入：io-threads 4io-threads-do-reads yes分工：主线程:  命令执行、数据结构修改IO 线程:  socket 读  协议解析/写回适用：  QPS 非常高；  网络读写和协议解析成为 CPU 热点；  多核机器资源充足。不适合：  瓶颈是慢命令；  大 key 导致输出缓冲巨大；  核数少或容器 CPU limit 低。通常建议 4 核以上再评估，io-threads 设为 2-4。15.4 后台线程 BIO后台线程负责：  关闭文件描述符；  AOF 刷盘；  异步释放大对象（lazy free）；  其他辅助任务。异步删除：UNLINK big-keyFLUSHALL ASYNCFLUSHDB ASYNC配置：lazyfree-lazy-eviction nolazyfree-lazy-expire nolazyfree-lazy-server-del noreplica-lazy-flush no这些配置表示对应场景是否使用异步释放。某些场景可改为 yes，但需要结合业务和内存峰值评估。15.5 哪些操作会阻塞主线程1. 大 key 命令KEYS *SMEMBERS huge-setHGETALL huge-hashLRANGE huge-list 0 -1ZRANGE huge-zset 0 -12. 大 key 同步删除DEL huge-key释放几十万元素的 Hash/Set 会占用明显时间。3. 复杂集合运算SINTER huge-set-a huge-set-bSDIFF huge-set-a huge-set-b4. Lua 长脚本死循环、大循环遍历、复杂排序。5. RDB/AOF 某些阶段虽然 Redis 做了 fork 和子进程处理，但 fork 复制页表、AOF 重写缓冲复制、fsync 阻塞仍可能造成停顿。6. 网络与输出缓冲慢客户端持续堆积输出，可能占用内存；Redis 会按配置断开。15.6 慢命令与延迟观测SLOWLOG GET 20LATENCY HISTORY commandLATENCY HISTORY event-loopLATENCY DOCTOR系统级工具：redis-cli --latencyredis-cli --intrinsic-latency 30perf toppidstat -p &lt;redis-pid&gt; 1区分：  Redis 事件循环慢：慢查询、CPU、fork；  网络慢：客户端到 Redis RTT；  客户端慢：连接池、GC、反序列化；  系统慢：CPU limit、swap、磁盘 fsync。15.7 客户端缓冲区普通客户端：client-output-buffer-limit normal 0 0 0副本复制缓冲：client-output-buffer-limit replica 256mb 64mb 60Pub/Sub：client-output-buffer-limit pubsub 32mb 8mb 60格式：hard-limit soft-limit soft-limit-seconds超过硬限制立即断开；超过软限制并持续指定秒数断开。慢消费客户端可能被断开，这是保护 Redis 的机制。15.8 单线程为什么仍然高吞吐  热数据全内存；  数据结构针对访问模式优化；  命令短小，执行路径简单；  epoll 避免线程阻塞等待网络；  pipeline 降低 RTT；  IO 线程分摊协议读写；  危险操作有替代命令或后台化路径。一旦违背“命令短小”的假设，性能会急剧恶化。这不是 Redis 不够快，而是用法破坏了它的模型。15.9 线程模型相关调优# 事件循环频率hz 10dynamic-hz yes# IO 多线程io-threads 4io-threads-do-reads yes# 异步释放lazyfree-lazy-eviction yeslazyfree-lazy-expire yeslazyfree-lazy-server-del yes# 慢查询slowlog-log-slower-than 10000slowlog-max-len 256调优顺序：  找慢命令；  治理大 key；  优化客户端/pipeline；  检查系统 CPU/swap/fsync；  最后评估 IO 线程。本章小结  Redis 命令执行由主线程串行，保证顺序和简化并发；  IO 多线程只加速网络读写和协议处理；  BIO 处理关闭、刷盘、异步释放等任务；  大 key、慢脚本、同步删除、集合运算是常见阻塞源；  SLOWLOG、LATENCY、系统指标要结合定位。思考题  开启 io-threads 能解决 HGETALL 大 Hash 慢的问题吗？为什么？  DEL 和 UNLINK 在线程模型上的差别是什么？  Redis 延迟升高但 SLOWLOG 为空，可能有哪些原因？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 是内存系统，所有设计最终都会碰到两个问题：key 什么时候过期，内存满了淘汰谁。本章把 TTL、删除策略、八种淘汰策略与生产风险讲透。14.1 TTL 的存储设置过期：EXPIRE key 60PEXPIRE key 60000EXPIREAT key 1735000000SET key value EX 60PERSIST keyRedis 维护一个过期字典：主字典:    key -&gt; value过期字典:  key -&gt; long 过期时间毫秒到期后，key 进入“逻辑过期”状态，访问时按删除策略处理。14.2 三种过期删除策略1. 定时删除每个 key 创建定时器，到期精确删除。内存友好，但大量 key 同时到期会占用 CPU 和定时器资源。2. 惰性删除访问 key 时检查是否过期：GET key  -&gt; 已过期 -&gt; 删除并返回 nil  -&gt; 未过期 -&gt; 返回 valueCPU 友好，但冷 key 过期后仍占内存。3. 定期删除后台周期抽样过期字典：  每轮从设置了 TTL 的 key 中抽样；  删除其中已过期 key；  如果过期比例超过阈值，继续本轮扫描；  控制单轮时间，避免阻塞。Redis 实际使用：惰性删除 + 定期删除。相关配置：hz 10dynamic-hz yeshz 越高，后台任务越频繁，过期清理更快，但 CPU 消耗越高。14.3 从节点如何处理过期副本不会自己删除大多数过期 key，而是等待主节点删除后同步删除命令，从而保持主从数据一致。因此可能出现：主节点已删除 -&gt; 从节点短暂仍存在从节点读取逻辑上已过期的数据时，返回逻辑过期结果；复制同步后会一致。业务若对 TTL 极度敏感，应读主或使用业务时间戳校验。14.4 maxmemory 与淘汰策略maxmemory 4gbmaxmemory-policy allkeys-lru查看：CONFIG GET maxmemoryCONFIG GET maxmemory-policyINFO memory当 used_memory 超过 maxmemory：  写命令触发淘汰；  具体行为由 maxmemory-policy 决定；  可能返回 OOM command not allowed。14.5 八种淘汰策略            策略      范围      算法      适合                  noeviction      不淘汰      内存满拒绝写入      事实数据/锁/队列              allkeys-lru      全部 key      近似 LRU      纯缓存              allkeys-lfu      全部 key      近似 LFU      长期热点缓存              allkeys-random      全部 key      随机      少用              volatile-lru      有 TTL 的 key      近似 LRU      混合实例              volatile-lfu      有 TTL 的 key      近似 LFU      混合实例              volatile-random      有 TTL 的 key      随机      少用              volatile-ttl      有 TTL 的 key      剩余时间短优先      快速释放        某些版本还提供别名或细节差异，具体以官方文档为准。核心选择只有三点：淘汰范围、频率依据、是否允许写入失败。14.6 近似 LRU/LFURedis 不是维护全局双向链表的精确 LRU，那会带来额外指针与锁开销。它采用采样：maxmemory-samples 5每次淘汰随机采样若干 key，从中选择最久未使用的。采样数越大越接近精确 LRU，CPU 开销也越高。LFU 使用概率计数：lfu-log-factor 10lfu-decay-time 1  lfu-log-factor：计数增长速度；  lfu-decay-time：访问频率衰减周期，单位分钟。LFU 适合“曾经的旧热点不应永远占据内存”的缓存。14.7 不同业务的策略选择纯缓存maxmemory-policy allkeys-lru所有 key 都可重建，内存满时淘汰最久未访问数据。访问频率更稳定的热点maxmemory-policy allkeys-lfu适合每日/长期热点明显的商品、话题、配置缓存。混合业务如果同一实例既有缓存也有业务状态：maxmemory-policy volatile-lru但要注意：没有 TTL 的 key 永远不会被 volatile-* 淘汰。如果全部写满，仍可能 OOM。更可靠的做法是业务拆实例，不把缓存和事实数据混放。状态类实例锁、队列、库存、订单超时任务等数据不能被随机淘汰：maxmemory-policy noeviction内存满时写入失败，业务立刻感知并熔断/扩容，比悄悄丢锁或丢任务安全。必须配套：  容量水位告警；  写入失败监控；  扩容流程；  数据生命周期治理。14.8 过期 key 集中过期的问题大量 key 同一时刻到期：  定期删除任务压力升高；  缓存同时失效引发数据库雪崩；  主从复制和 CPU 出现尖刺。TTL 随机化long ttl = 300 + ThreadLocalRandom.current().nextInt(60);redis.opsForValue().set(key, value, Duration.ofSeconds(ttl));分批预热活动开始前分批写入热点缓存，而不是零点一次性灌入。逻辑过期value 里包含业务过期时间，物理 TTL 可更长，后台异步刷新，避免同步等待重建。14.9 内存满的观测INFO memoryINFO stats关注：used_memorymaxmemorymaxmemory_policymem_fragmentation_ratioevicted_keysexpired_keys症状：            指标      说明                  evicted_keys 增长      正在淘汰              写命令 OOM      noeviction 或无候选 key              expired_keys 波峰      大量过期              碎片率异常      分配器碎片或 COW      14.10 生产检查清单[ ] maxmemory 显式设置，不超过机器安全水位[ ] maxmemory-policy 与数据类型匹配[ ] 缓存 key 尽量设置随机 TTL[ ] 状态 key 明确保留策略与容量[ ] 大 key 定期扫描[ ] evicted_keys / expired_keys / OOM 告警[ ] 混合业务评估拆实例[ ] 复制缓冲和客户端缓冲纳入容量评估本章小结  Redis 使用惰性删除 + 定期删除处理过期 key；  从节点主要跟随主节点删除，避免双方决策不一致；  maxmemory 满后按 policy 淘汰或拒绝写入；  纯缓存常用 allkeys-lru/lfu，状态数据常用 noeviction；  volatile-* 只淘汰设置了 TTL 的 key；  大量集中 TTL 会带来删除压力和缓存雪崩，需要随机化。思考题  为什么 Redis 不用定时器精确删除每个过期 key？  volatile-lru 实例中如果所有 key 都没 TTL，内存满会发生什么？  秒杀库存 Redis 应该使用哪种淘汰策略？为什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 的性能优势来自数据结构，内存成本也来自数据结构。理解对象编码，才能解释“为什么几个字段也占几十 KB”“为什么 Hash 突然变慢”“为什么 ZSet 内存这么大”。13.1 Redis 对象模型概览每个 value 在逻辑上都是一个 redisObject，包含：redisObject  type       数据类型：String/Hash/List/Set/ZSet...  encoding   底层编码：int/embstr/listpack/hashtable/skiplist...  refcount   引用计数（含共享对象优化）  lru/LFU    空闲或访问信息，用于淘汰  ptr        指向具体实现查看类型与编码：TYPE user:1001OBJECT ENCODING user:1001OBJECT FREQ user:1001OBJECT IDLETIME user:1001MEMORY USAGE user:1001type 是用户视角，encoding 是实现视角。 同一个 Hash，小数据可用 listpack 省内存，大数据切换 hashtable 提升操作稳定性。13.2 String 的编码intvalue 是整数且在范围内：SET counter 100OBJECT ENCODING counter# int优点：省内存、计数快。执行 APPEND 或改成非整数后，会转换成 embstr/raw。embstr短字符串使用 embstr：redisObject 与 SDS 内存连续分配，一次 malloc。阈值（典型版本）：len &lt;= 44 bytes -&gt; embstrlen &gt; 44 bytes -&gt; raw不同版本实现可能略有差异。embstr 只读，修改时会转为 raw 并重新分配。raw长字符串分成两块内存：redisObject 与 SDS 分开分配。SET article:1001 &lt;很长文本&gt;OBJECT ENCODING article:1001# raw13.3 SDS：简单动态字符串Redis 自定义 SDS，而不是直接 C 字符串：struct sdshdr {    len      已使用长度    alloc    总分配长度    flags    类型标记    buf[]    实际字符}优点：  O(1) 获取长度；  二进制安全，可存 \0；  预分配与惰性释放，减少频繁 realloc；  多种头部长度（sdshdr8/16/32/64），小字符串省内存。空间预分配简化规则：修改后 len &lt; 1MB -&gt; 额外分配 len修改后 len &gt;= 1MB -&gt; 额外分配 1MB因此频繁追加大字符串可能造成内存放大。13.4 Hash 编码：listpack 与 hashtablelistpack小 Hash 使用紧凑连续内存，每个 entry 顺序保存 field/value。HSET user:1001 name Tom age 28OBJECT ENCODING user:1001# listpack转换阈值由配置控制：hash-max-listpack-entries 512hash-max-listpack-value 64任一条件超出即转 hashtable：  field 数量超过 512；  任意 field/value 长度超过 64 字节。listpack 省内存，但查找是顺序扫描；因此 entry 上限不能过大。hashtableOBJECT ENCODING big-hash# hashtableO(1) 查找，支持渐进式 rehash。成本是：  指针开销；  桶数组与渐进迁移状态；  大量 field 内存远高于 listpack。13.5 List 编码：quicklist现代 Redis 的 List 由 quicklist 实现：多个 listpack 节点组成双端链表。RPUSH queue:task a b cOBJECT ENCODING queue:task# quicklist配置：list-max-listpack-size -2list-compress-depth 0  list-max-listpack-size：每个节点大小，负数表示按字节限制；  list-compress-depth：两端保留不压缩的节点数，中间可用 LZF 压缩。quicklist 兼顾：  两端 O(1) 推入弹出；  中间节点可控大小；  冷数据可压缩。13.6 Set 编码：intset 与 listpack/hashtable整数小集合可用 intset：SADD nums 1 2 3OBJECT ENCODING nums# intset配置：set-max-intset-entries 512set-max-listpack-entries 128set-max-listpack-value 64编码路径：全整数且少 -&gt; intset小集合短元素 -&gt; listpack超出限制或包含非整数 -&gt; hashtablehashtable Set 只关心 key，不存 value，但字典本身仍有指针和桶开销。13.7 ZSet 编码：listpack 与 skiplistlistpack小 ZSet 可用 listpack，按 score/member 排序存储。skiplist较大 ZSet 使用：dict: member -&gt; scoreskiplist: 按 score/member 排序因此 ZSet 同时支持：  O(1) ZSCORE（dict）；  O(logN) ZADD/ZRANK（skiplist）。配置：zset-max-listpack-entries 128zset-max-listpack-value 64跳跃表节点包含多层前进指针和 backward 指针，内存高于普通 Set。排行榜保存百万成员时必须测算内存。13.8 跳跃表为什么不用红黑树Redis 作者给出的工程理由大致是：  实现更简单，易调试；  范围查询按排序链遍历自然高效；  与字典组合后已满足 O(1) score 查询；  性能可控，内存可通过概率层数控制。跳表查找/插入/删除平均 O(logN)，最坏概率退化，实际工程参数稳定；红黑树最坏 O(logN)，但实现和范围操作复杂度更高。13.9 内存估算查看真实占用：MEMORY USAGE user:1001MEMORY USAGE rank:activity SAMPLES 5粗略理解：实际内存 =  key 字符串开销  + redisObject 头  + value 具体编码开销  + 字典/跳表索引  + 内存分配器碎片  + 复制缓冲/客户端缓冲等运行时开销例如一个小 Hash 的 5 个字段理论上不大，但也要计入：  key SDS；  redisObject；  listpack entry 元数据；  jemalloc size class 向上取整；  键空间字典入口。因此“1KB JSON”存 Redis 后，占用的不只是 1KB。13.10 jemalloc 与内存碎片Redis 常用 jemalloc，按 size class 分配。释放大量小对象后，可能出现：used_memory &lt; 当前 RSSmem_fragmentation_ratio &gt; 1.5查看：INFO memoryMEMORY STATSMEMORY DOCTOR重点字段：            字段      含义                  used_memory      Redis 分配器分配的数据内存              used_memory_rss      操作系统视角进程驻留内存              mem_fragmentation_ratio      RSS / used_memory              mem_allocator      分配器              allocator_frag_ratio      分配器碎片率      碎片常见于大量不同大小 key 的创建删除。活跃碎片整理配置：activedefrag yesactive-defrag-ignore-bytes 100mbactive-defrag-threshold-lower 10active-defrag-threshold-upper 100开启前要评估 CPU 影响，通常只在高碎片、高可用内存场景使用。13.11 编码调优实践  小对象优先保持 listpack/intset：hash-max-listpack-entries 128hash-max-listpack-value 32zset-max-listpack-entries 128调小阈值会降低单个 listpack 查找成本、增加转 hashtable 概率；调大省内存但可能提升小结构查询成本。  拆分大 Hash/ZSet：rank:activity:2026 -&gt; 按 10000 userId 分片rank:activity:2026:0rank:activity:2026:1  控制 key/value 长度：  key 保持语义且短；  缓存 DTO 只放必要字段；  大文本放对象存储，Redis 存引用。  冷热分离：  热点缓存留 Redis；  历史数据落 MySQL/ClickHouse/对象存储。13.12 大 key 的判断与治理参考阈值：            类型      建议关注                  String      value &gt; 10KB 谨慎，&gt; 1MB 重点治理              Hash      field 数 &gt; 5000 或内存 &gt; 10MB              Set/ZSet      元素 &gt; 5000 或内存 &gt; 10MB              List/Stream      长度大或单 entry 大      危害：  读写耗时高，阻塞单线程；  网络带宽放大；  删除/过期释放慢；  集群槽倾斜；  复制缓冲暴涨。治理：  按业务维度拆分；  HSCAN/SSCAN/ZSCAN 分页访问；  删除用 UNLINK；  设置 TTL 与最大长度；  大对象外部存储 + 引用；  上线前扫描评估。本章小结  type 是用户视角，encoding 是实现视角，可通过 OBJECT ENCODING 查看；  String 分 int/embstr/raw，底层 SDS 支持二进制安全和 O(1) 长度；  小 Hash/ZSet 常用 listpack，超出阈值转 hashtable/skiplist；  List 由 quicklist 组织多个紧凑节点；  ZSet 是 dict + skiplist，内存明显高于普通 Set；  实际内存包含对象头、索引、分配器碎片和运行时缓冲，不能只看业务数据大小。思考题  为什么把 Hash 的某个 field 改成 100KB 可能导致编码从 listpack 转 hashtable？  ZSet 为什么同时保存 dict 和 skiplist？  同样保存 100 万成员，Set 和 ZSet 哪个更省内存？为什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章把常见高并发场景写成可直接落地的 Redis 方案：计数、点赞、排行榜、限流、库存扣减、签到与 UV。12.1 计数器INCR read:article:1001INCRBY like:comment:2001 10DECR stock:sku:1001服务：public long increase(String key) {    Long value = redis.opsForValue().increment(key);    return value == null ? 0 : value;}分布式计数的一致性Redis 计数是实时准确的（单实例内），但若需要审计和可追溯，不能只保留一个数字。推荐：Redis: 高频计数缓存DB: 定期落盘流水/快照对账: 累计流水 vs Redis 计数避免 Redis 故障后计数完全丢失。12.2 点赞与取消点赞用 Set 记录点赞用户，防止重复：SADD like:article:1001 user:1SREM like:article:1001 user:1SISMEMBER like:article:1001 user:1SCARD like:article:1001原子点赞脚本：local liked = redis.call('SISMEMBER', KEYS[1], ARGV[1])if liked == 1 then    return 0endredis.call('SADD', KEYS[1], ARGV[1])redis.call('INCR', KEYS[2])return 1如果只需要数量、不要求精确名单，可用 HyperLogLog 或 Set + 定期归档。12.3 排行榜ZINCRBY rank:activity:2026 10 user:1001ZREVRANGE rank:activity:2026 0 9 WITHSCORESZREVRANK rank:activity:2026 user:1001ZRANGE rank:activity:2026 0 -1 WITHSCORES服务层：public List&lt;RankItem&gt; top10(String key) {    Set&lt;ZSetOperations.TypedTuple&lt;String&gt;&gt; tuples =            redis.opsForZSet().reverseRangeWithScores(key, 0, 9);    List&lt;RankItem&gt; result = new ArrayList&lt;&gt;();    int rank = 1;    for (var tuple : tuples) {        result.add(new RankItem(                rank++,                Objects.requireNonNull(tuple.getValue()),                tuple.getScore()));    }    return result;}分页ZREVRANGE rank:activity 0 9ZREVRANGE rank:activity 10 19排名并列需要业务规则。ZSet 分数相同时按 member 字典序排序，不一定符合“同分早到者靠前”。12.4 固定窗口限流key = rate:{subject}:{windowStart}每个请求 INCR，首次设置 TTL超过阈值拒绝Lua：local current = redis.call('INCR', KEYS[1])if current == 1 then    redis.call('PEXPIRE', KEYS[1], ARGV[1])endif current &gt; tonumber(ARGV[2]) then    return 0endreturn 1Java 调用：public boolean allowFixedWindow(String subject,                                int limit,                                long windowMillis) {    long window = System.currentTimeMillis() / windowMillis;    String key = "rate:" + subject + ":" + window;    Long result = redis.execute(fixedWindowScript,            List.of(key), String.valueOf(windowMillis), String.valueOf(limit));    return result != null &amp;&amp; result == 1;}优点：实现简单。缺点：窗口边界突刺，例如 59 秒和 61 秒可能集中双倍流量。12.5 滑动窗口限流用 ZSet 保存请求时间戳：local now = tonumber(ARGV[1])local window = tonumber(ARGV[2])local limit = tonumber(ARGV[3])local member = ARGV[4]redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, now - window)local count = redis.call('ZCARD', KEYS[1])if count &gt;= limit then    return 0endredis.call('ZADD', KEYS[1], now, member)redis.call('PEXPIRE', KEYS[1], window)return 1优点：边界更平滑。缺点：每个请求多记录一个 member，内存和清理成本更高。12.6 令牌桶限流思想：rate: 每秒生成令牌数capacity: 桶容量每次请求按当前时间补充令牌，尝试消费 1 个适合允许短时突发但平均速率受限的接口。生产常用 Redisson RRateLimiter，或网关层限流。选型：            算法      特点                  固定窗口      简单，边界突刺              滑动窗口      平滑，成本略高              令牌桶      支持突发，参数清晰              漏桶      强制平滑，常用于整形      12.7 秒杀库存扣减朴素 DECR 会超卖：库存 0A DECR -&gt; -1B DECR -&gt; -2Lua 保证检查和扣减原子：local stock = tonumber(redis.call('GET', KEYS[1]))if stock == nil then    return -1endlocal num = tonumber(ARGV[1])if stock &lt; num then    return 0endredis.call('DECRBY', KEYS[1], num)redis.call('LPUSH', KEYS[2], ARGV[2])return 1解释：  KEYS[1]：库存 key；  KEYS[2]：抢购成功队列；  ARGV[1]：购买数量；  ARGV[2]：订单请求 ID。完整链路：1. 前端/网关削峰，防止恶意刷请求2. Redis Lua 原子校验并扣减库存3. 扣减成功写入抢购成功队列/Stream4. 异步创建订单5. 数据库乐观锁 + 唯一订单号兜底6. 支付超时回补库存12.8 签到与连续签到按月 Bitmap：SETBIT sign:user:1001:202608 24 1GETBIT sign:user:1001:202608 24BITCOUNT sign:user:1001:202608Java：public void sign(long userId, LocalDate date) {    String key = "sign:user:%d:%s".formatted(            userId, date.format(DateTimeFormatter.ofPattern("yyyyMM")));    redis.opsForValue().setBit(key, date.getDayOfMonth() - 1, true);}连续签到可：  从今天向前逐位 GETBIT；  使用 BITCOUNT 判断月内次数；  复杂奖励规则落 DB 或事件表。12.9 UV 统计精确去重：SADD uv:page:1001 user:1001SCARD uv:page:1001估算去重：PFADD uv:page:1001 user:1001PFCOUNT uv:page:1001PFMERGE uv:all uv:page:1 uv:page:2选型：            需求      方案                  精确名单      Set / DB              精确人数      Set / DB              大规模估算      HyperLogLog              签到/活跃位      Bitmap      12.10 场景通用注意事项  所有写操作考虑幂等；  Redis 数据要明确可丢失等级；  高并发读要有本地缓存或多级缓存；  高并发写要评估单 key 热点；  定时任务要有分布式防重；  关键业务必须与数据库对账。本章小结  计数用 String，点赞去重用 Set，排行用 ZSet；  固定窗口简单但边界突刺，滑动窗口和令牌桶更平滑；  库存扣减必须把检查与扣减放入 Lua；  秒杀要前端削峰、Redis 原子扣减、异步建单、数据库兜底；  Bitmap/HyperLogLog 分别解决活跃标记与海量 UV 估算。思考题  为什么 DECR 后再判断小于 0 并回滚仍可能有问题？  分布式限流在 Redis 故障时应该拒绝所有请求还是放行？如何设计？  设计一个“活动排行榜 + 实时排名 + 历史月榜”的完整 key 结构。</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。分布式锁是 Redis 最常用也最容易出错的能力。本章从基础 SET NX 到 Redisson，再到 Redlock、故障切换与业务幂等，把正确性边界讲清楚。11.1 为什么需要分布式锁多个服务实例同时修改同一资源：实例A: 读库存=1实例B: 读库存=1实例A: 扣减成功实例B: 扣减成功 -&gt; 超卖单机锁只能保护一个 JVM，分布式锁让多个进程互斥：实例A -&gt; 尝试 SET lock NX 成功 -&gt; 进入临界区实例B -&gt; SET 失败 -&gt; 等待/返回实例A -&gt; 释放 -&gt; 实例B 可进入11.2 最基础的正确写法SET lock:order:1001 owner-uuid NX PX 30000必须满足：  NX：不存在才设置，保证互斥；  PX：设置过期，防止持有者崩溃导致死锁；  value 唯一：标识持有者，释放时只能释放自己的锁。错误写法：SETNX lock:order valueEXPIRE lock:order 30两步之间进程崩溃，锁可能永远存在。11.3 释放锁：不能直接 DEL直接 DEL 可能删掉别人的锁：A 获取锁 -&gt; GC/网络延迟超过 TTL锁过期 -&gt; B 获取锁A 恢复 -&gt; DEL lock -&gt; 删除了 B 的锁C 又能获取 -&gt; B/C 同时进入正确释放必须校验 value：if redis.call('GET', KEYS[1]) == ARGV[1] then    return redis.call('DEL', KEYS[1])else    return 0endJava 实现：String token = UUID.randomUUID().toString();Boolean ok = redis.opsForValue()        .setIfAbsent(key, token, Duration.ofSeconds(30));if (Boolean.TRUE.equals(ok)) {    try {        doBusiness();    } finally {        release(key, token);    }}11.4 锁超时与续期TTL 是保险丝，不是业务时长。设置过短会“锁过期但业务还在执行”；过长会导致持有者崩溃后长时间不可用。方案：  预估业务最大耗时，TTL = 最大耗时 + 缓冲；  使用 Redisson 看门狗自动续期；  记录锁获取、释放、业务耗时指标；  关键流程设置总超时。看门狗只能解决进程活着但执行慢的情况，不能解决代码死循环，因此要配合最大执行时间。11.5 Redisson 锁实现要点RLock lock = redisson.getLock("lock:order:" + orderId);if (!lock.tryLock(100, TimeUnit.MILLISECONDS)) {    return Result.busy();}try {    payOrder(orderId);} finally {    if (lock.isHeldByCurrentThread()) {        lock.unlock();    }}Redisson 底层：  使用 Lua 保证加锁/解锁原子；  Hash 结构记录持有者与重入次数；  无 leaseTime 时后台续期；  支持 waiting list 与公平锁。11.6 锁粒度设计            场景      建议 key                  订单支付      lock:order:{orderId}              用户资料更新      lock:user:{userId}              SKU 库存扣减      常直接 Lua，不一定加锁              全局配置刷新      lock:config:global              定时任务防重      lock:job:{jobName}:{yyyyMMddHHmm}      原则：  粒度尽量小，只锁真正冲突的资源；  不要用 lock:global 保护所有业务；  同一资源 key 规则统一；  锁名字要带业务语义，便于监控和排查。11.7 主从切换下的锁风险常见异步复制流程：1. 客户端A 在 Master 获取锁2. Master 崩溃，锁还没同步到 Replica3. Replica 提升为新 Master4. 客户端B 在新 Master 获取同一把锁5. A/B 同时持有这不是代码 bug，而是异步系统的一致性边界。可能的应对：  业务幂等：即使锁失效，重复执行也不造成资金/库存错误；  数据库乐观锁/唯一约束：把最终正确性放在事实源；  等待复制：降低丢失窗口，但牺牲可用性；  强一致协调服务：etcd/ZooKeeper/Consul，代价是吞吐与运维复杂度；  Redlock：多实例多数派，争议较多，需谨慎评估。生产系统最重要的是 1 和 2：锁用于减少冲突，不承担最终正确性。11.8 Redlock 简述Redlock 流程：1. 获取当前时间2. 依次向 N 个独立 Redis 实例加锁3. 多数实例成功且总耗时小于锁有效期，则视为成功4. 有效期 = 初始TTL - 获取耗时5. 失败则向所有实例释放它试图降低单实例故障导致锁失效的概率，但存在争议：  时钟跳变可能影响有效性判断；  长时间 GC/进程暂停仍可能在过期后继续执行；  部署要求 N 个独立实例，非一主多从；  不能消除“锁过期后业务继续执行”的古老问题。建议：  一般业务使用单实例 Redis 锁 + 业务幂等即可；  对正确性极其敏感的流程使用数据库约束/乐观锁，或 etcd/ZooKeeper；Redlock 只在你清楚其假设和代价时使用。11.9 锁与数据库乐观锁配合可靠订单支付示例：UPDATE ordersSET status = 'PAID', version = version + 1WHERE order_id = ? AND status = 'PENDING' AND version = ?;流程：1. 尝试 Redis 锁，减少并发请求2. 锁内读取订单3. 数据库条件更新，只有 PENDING 才支付成功4. 释放锁即使 Redis 锁异常失效，数据库条件更新也会保证只有一个请求成功。这是生产系统的推荐形态。11.10 分布式锁常见错误            错误      后果                  忘记 TTL      宕机后死锁              SETNX 后再 EXPIRE      中间崩溃造成永久锁              value 固定字符串      释放别人的锁              DEL 释放      同上              业务耗时大于 TTL      多实例同时执行              finally 中盲目 unlock      IllegalMonitorStateException              锁粒度过大      并发度下降              只靠锁保证正确性      主从切换后可能出错      11.11 可观测性指标：  lock_acquire_total；  lock_acquire_failure_total；  lock_wait_duration；  lock_hold_duration；  lock_timeout_while_running；  lock_release_failure_total。日志字段：lock_key, owner_id, wait_ms, hold_ms, result, trace_id出现 lock_timeout_while_running 必须告警，它意味着业务执行时间超过预期或锁被异常过期。本章小结  正确基础锁：SET key uniqueValue NX PX ttl；  释放必须用 Lua 校验 value 后删除；  锁要有 TTL、唯一持有者、超时与看门狗；  异步复制故障切换下，Redis 锁可能失效；  生产正确性由数据库约束/乐观锁/业务幂等兜底，锁用于降低竞争。思考题  为什么 value 必须是每个持有者唯一的随机值？  业务执行 5 秒但锁 TTL 3 秒，会发生什么？给出两种修复方案。  如果锁只能提高效率而不能保证绝对互斥，系统最终靠什么保证正确？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 把底层数据结构做得很强，Redisson 则把分布式常用模式封装成 Java 风格 API。本章讲 Redisson 的基础接入、锁、限流、延迟队列与使用边界。10.1 Redisson 是什么Redisson 是基于 Redis 的 Java 驻留对象框架，提供：  分布式锁与读写锁；  原子数、限流器；  分布式 Map/Set/List；  延迟队列与优先队列；  布隆过滤器；  发布订阅、信号量、闭锁；  Spring Cache 注解集成。它解决的是“通用工程组件”，不是替代业务代码里的所有逻辑。10.2 引入依赖&lt;dependency&gt;    &lt;groupId&gt;org.redisson&lt;/groupId&gt;    &lt;artifactId&gt;redisson-spring-boot-starter&lt;/artifactId&gt;    &lt;version&gt;3.27.2&lt;/version&gt;&lt;/dependency&gt;配置：spring:  redis:    redisson:      config: |        singleServerConfig:          address: "redis://127.0.0.1:6379"          password: redis123          database: 0          connectionMinimumIdleSize: 10          connectionPoolSize: 50          connectTimeout: 1000          timeout: 1000版本要与 Spring Boot、Redis Server、JDK 兼容矩阵匹配。10.3 基础使用@Autowiredprivate RedissonClient redisson;public void demo() {    RBucket&lt;String&gt; bucket = redisson.getBucket("user:1001");    bucket.set("Tom", Duration.ofMinutes(10));    System.out.println(bucket.get());    RMap&lt;String, Integer&gt; cart = redisson.getMap("cart:user:1001");    cart.put("sku:2001", 2);    RScoredSortedSet&lt;String&gt; rank =            redisson.getScoredSortedSet("rank:game");    rank.add(100, "player:1");}这些对象本质仍是 Redis 数据结构，只是提供 Java API。10.4 RedissonLockRLock lock = redisson.getLock("lock:order:1001");boolean locked = false;try {    locked = lock.tryLock(100, 10, TimeUnit.SECONDS);    if (!locked) {        throw new BusyException("try lock failed");    }    doBusiness();} finally {    if (locked &amp;&amp; lock.isHeldByCurrentThread()) {        lock.unlock();    }}参数：tryLock(waitTime, leaseTime, unit)  waitTime：最多等多久获取锁；  leaseTime：锁持有时间；  不传 leaseTime 时启用看门狗自动续期。10.5 看门狗机制默认锁超时时间是 30 秒：lockWatchdogTimeout=30000当使用：lock.lock();lock.tryLock(1, TimeUnit.SECONDS);没有显式 leaseTime 时，Redisson 会启动后台任务定期续期，通常每 10 秒重置到 30 秒。业务执行完成或客户端进程崩溃后，续期停止，锁最终自动过期。看门狗解决“业务执行时间不可预估”的问题，但也带来风险：  业务死循环会一直持有锁；  客户端假死可能长时间阻塞其他节点；  因此关键业务仍要设置最大执行时间与熔断。10.6 可重入锁Redisson 的 RLock 是可重入锁：RLock lock = redisson.getLock("lock:user:1001");lock.lock();try {    a(lock);} finally {    lock.unlock();}void a(RLock lock) {    lock.lock();    try {        System.out.println("reentrant");    } finally {        lock.unlock();    }}底层使用 Hash 记录锁标识与重入次数，必须由持有线程 unlock。10.7 公平锁与读写锁公平锁：RLock fairLock = redisson.getFairLock("lock:job");等待线程按请求顺序获取锁，公平性带来额外开销，吞吐低于普通锁。读写锁：RReadWriteLock rwLock = redisson.getReadWriteLock("lock:config");RLock readLock = rwLock.readLock();RLock writeLock = rwLock.writeLock();  读锁共享，多个读者可同时进入；  写锁互斥，写与读互斥；  适合读多写少且读操作可接受最终一致的配置/缓存场景。10.8 信号量与闭锁RSemaphore semaphore = redisson.getSemaphore("sem:export");semaphore.trySetPermits(5);if (semaphore.tryAcquire()) {    try {        doExport();    } finally {        semaphore.release();    }}限制并发任务数。闭锁：RCountDownLatch latch = redisson.getCountDownLatch("latch:batch");latch.trySetCount(3);latch.await();latch.countDown();适合等待多个节点/阶段完成，但要注意 await 超时与任务失败清理。10.9 分布式限流RRateLimiter limiter =        redisson.getRateLimiter("rate:login:user:1001");limiter.trySetRate(RateType.OVERALL, 10, 1, RateIntervalUnit.SECONDS);if (!limiter.tryAcquire()) {    throw new TooManyRequestException();}适用场景：  每用户登录/短信次数；  每接口 QPS；  每 API Key 调用限额。精确滑动窗口可用 ZSet 自行实现，窗口大小按业务选择。全局强限流还要考虑 Redis 故障时的降级策略。10.10 延迟队列RBlockingQueue&lt;String&gt; queue =        redisson.getBlockingQueue("queue:order:timeout");RDelayedQueue&lt;String&gt; delayedQueue =        redisson.getDelayedQueue(queue);delayedQueue.offer("order:1001", 30, TimeUnit.MINUTES);String orderId = queue.take(); // 到期后转移，再消费delayedQueue.destroy();适合订单超时关闭、重试延迟、提醒任务。生产还要处理：  消费失败重试；  幂等；  服务重启后的恢复；  队列监控与告警。大规模任务调度可评估 RocketMQ 延迟消息、Kafka + 定时器、ElasticJob/XXL-JOB。10.11 布隆过滤器RBloomFilter&lt;Long&gt; filter =        redisson.getBloomFilter("bloom:valid:product");filter.tryInit(1_000_000L, 0.01);filter.add(10001L);boolean mayContain = filter.contains(10002L);特点：  可能误判存在，不会漏判存在；  标准布隆过滤器不支持删除；  需要预估容量和误判率，超过容量后误判上升。适合缓存穿透防护、黑名单初筛、推荐去重初筛。10.12 使用边界Redisson 很方便，但不要把所有业务逻辑都搬进 Redis：  复杂状态流转仍应在数据库/服务层建模；  大量分布式对象操作可能形成大 key 和慢脚本；  Redis 故障时锁、限流、延迟队列都会受影响；  与 Lettuce/Jedis 混用时要统一连接池和监控；  所有分布式组件都要有超时、降级和告警。本章小结  Redisson 提供锁、限流、延迟队列、信号量、布隆过滤器等分布式组件；  RLock 支持可重入与看门狗续期，但必须正确 unlock；  无 leaseTime 才启用看门狗，显式 leaseTime 则到期自动释放；  延迟队列适合中小规模超时任务；  Redisson 是效率工具，不改变 Redis 的容量、故障与一致性约束。思考题  显式设置 leaseTime=5s 后看门狗还会续期吗？为什么？  Redisson 锁释放失败有哪些常见原因？  布隆过滤器为什么能防穿透，但不能完全替代数据库存在性校验？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 的“原子”有边界。本章讲 MULTI/EXEC、WATCH、Lua 脚本、函数、错误处理与脚本安全，帮助你写出真正可靠的复合操作。9.1 Redis 事务是什么MULTISET stock:sku:1001 99INCRBY stock:sku:1001 -1SET order:1001 okEXEC执行流程：  MULTI 开启事务；  后续命令进入队列，返回 QUEUED；  EXEC 时按入队顺序依次执行；  执行期间不会插入其他客户端命令。它提供的是串行执行，不是传统数据库的完整 ACID：  没有回滚计划；  运行时类型错误不会撤销已执行命令；  入队错误通常导致 EXEC 拒绝执行；  不支持条件等待和跨 key 逻辑。9.2 MULTI 的错误行为入队阶段错误MULTISET k vBADCOMMANDEXECBADCOMMAND 入队即报错，EXEC 会拒绝执行整个事务。执行阶段类型错误SET str-key helloMULTIINCR str-keySET other-key okEXECINCR str-key 运行时报错，但 SET other-key ok 仍执行。前面的错误不会导致后面取消，也不会回滚。9.3 WATCH：乐观锁WATCH 监视 key，如果事务执行前 key 被其他客户端修改，EXEC 返回 nil，事务不执行。WATCH stock:sku:1001val = GET stock:sku:1001if int(val) &lt;= 0:    UNWATCH    returnMULTIINCRBY stock:sku:1001 -1EXEC伪代码等价于 CAS。如果 EXEC 返回 nil，说明竞争失败，业务可重试。Spring 示例：public boolean deduct(String key) {    for (int i = 0; i &lt; 3; i++) {        redis.watch(key);        String value = redis.opsForValue().get(key);        if (value == null || Integer.parseInt(value) &lt;= 0) {            redis.unwatch();            return false;        }        redis.multi();        redis.opsForValue().decrement(key);        List&lt;Object&gt; result = redis.exec();        if (result != null &amp;&amp; !result.isEmpty()) {            return true;        }    }    return false;}高竞争场景 WATCH 重试率高，Lua 通常更合适。9.4 Lua 脚本Lua 脚本发送到服务端后作为一个整体执行，执行期间不被其他命令插入：EVAL "return redis.call('GET', KEYS[1])" 1 user:1001参数：  KEYS：脚本操作的 key 列表；  ARGV：普通参数；  第一个数字表示 key 数量。推荐所有 key 都通过 KEYS 传入，便于 Cluster 正确定位槽。库存扣减脚本local stock = tonumber(redis.call('GET', KEYS[1]))if stock == nil then    return -1endlocal num = tonumber(ARGV[1])if stock &lt; num then    return 0endredis.call('DECRBY', KEYS[1], num)return 1调用：EVAL "..." 1 stock:sku:1001 1脚本缓存SCRIPT LOAD "return 1"EVALSHA &lt;sha1&gt; 0SCRIPT EXISTS &lt;sha1&gt;SCRIPT FLUSH生产建议加载脚本并保存 SHA，客户端先 EVALSHA，收到 NOSCRIPT 再加载或降级 EVAL。9.5 Spring 执行 LuaDefaultRedisScript&lt;Long&gt; script = new DefaultRedisScript&lt;&gt;("""        local stock = tonumber(redis.call('GET', KEYS[1]))        if stock == nil then return -1 end        local n = tonumber(ARGV[1])        if stock &lt; n then return 0 end        redis.call('DECRBY', KEYS[1], n)        return 1        """, Long.class);public boolean deductStock(String key, int count) {    Long result = redis.execute(            script,            List.of(key),            String.valueOf(count));    return result != null &amp;&amp; result == 1;}脚本可以放资源文件中，启动时加载，避免代码里硬编码长字符串。9.6 Lua 的适用场景适合：  多步骤读写同一组 key；  需要条件判断；  高竞争下的库存、限额、状态机；  组合命令保持原子性。不适合：  长循环、大集合遍历；  外部 IO 或睡眠；  操作大量不相关 key；  Cluster 中跨槽 key 操作；  复杂业务逻辑（可读性和维护性差）。脚本越短越好，只做数据判断和修改，业务编排放在应用层。9.7 Lua 与 ClusterRedis Cluster 中，一个脚本访问的所有 key 必须落在同一个槽：EVAL "..." 2 user:1001 user:1002如果两个 key 槽不同，会报错。可以使用 hash tag 强制同槽：sec:{order-1001}:stocksec:{order-1001}:detail只有 {} 内的内容参与槽计算。但要小心：hash tag 会把相关数据固定到同一节点，过度使用会造成数据倾斜。9.8 脚本超时与 kill配置：busy-reply-threshold 5000# 旧名称 lua-time-limit脚本超过阈值后，Redis 开始拒绝新命令，但脚本本身仍可能继续执行：SCRIPT KILL如果脚本已经执行了写命令，只能：SHUTDOWN NOSAVE因此上线前必须评审脚本复杂度，禁止不可控循环。9.9 Redis FunctionsRedis 7 引入 Functions，把服务端逻辑作为可管理的库存储：#!lua name=mylibredis.register_function('my_hset', function(keys, args)    return redis.call('HSET', keys[1], args[1], args[2])end)加载：FUNCTION LOAD "..."FUNCTION LISTFCALL my_hset 1 user:1001 name Tom与 EVAL 相比，Functions 更适合版本化管理和复用服务端逻辑，但依然要遵守短脚本、同槽 key、无阻塞 IO 的约束。9.10 MULTI、WATCH、Lua 对比            方案      原子性      条件逻辑      适合                  单命令      强      弱      SETNX、INCR              MULTI/EXEC      串行执行      弱      固定命令序列              WATCH + MULTI      乐观并发      客户端判断      低竞争 CAS              Lua      强      服务端判断      高竞争复合操作              Functions      强      服务端管理      可复用脚本      本章小结  MULTI/EXEC 保证执行期间不插入其他命令，但不提供传统回滚；  WATCH 实现乐观锁，竞争高时重试成本上升；  Lua 是复合原子操作的首选，但必须短小、可控、key 全部显式传入；  Cluster 中脚本 key 必须同槽，hash tag 要谨慎使用；  Functions 提供服务端逻辑管理能力，但复杂业务不要都塞进 Redis。思考题  为什么 Redis 不支持运行时错误后的自动回滚？  库存扣减用 WATCH 和 Lua 哪个更好？从正确性、吞吐、代码复杂度分析。  一个 Lua 脚本遍历 10 万个 Set 成员会有什么后果？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Spring Boot 让 Redis 接入非常容易，但默认配置往往不适合生产。本章覆盖 RedisTemplate、序列化、缓存注解、连接配置、异常处理与常见坑。8.1 引入依赖&lt;dependency&gt;    &lt;groupId&gt;org.springframework.boot&lt;/groupId&gt;    &lt;artifactId&gt;spring-boot-starter-data-redis&lt;/artifactId&gt;&lt;/dependency&gt;&lt;dependency&gt;    &lt;groupId&gt;org.apache.commons&lt;/groupId&gt;    &lt;artifactId&gt;commons-pool2&lt;/artifactId&gt;&lt;/dependency&gt;默认客户端是 Lettuce。需要连接池时必须引入 commons-pool2。8.2 基础配置spring:  data:    redis:      host: 127.0.0.1      port: 6379      password: redis123      database: 0      timeout: 1s      connect-timeout: 500ms      client-name: order-service      lettuce:        pool:          enabled: true          max-active: 100          max-idle: 50          min-idle: 10          max-wait: 500msSpring Boot 3 使用 spring.data.redis.*；2.x 是 spring.redis.*。8.3 RedisTemplate最基础用法：@Service@RequiredArgsConstructorpublic class ProductService {    private final RedisTemplate&lt;String, Object&gt; redisTemplate;    public Product getProduct(String id) {        String key = "shop:product:" + id;        return (Product) redisTemplate.opsForValue().get(key);    }    public void save(Product product) {        redisTemplate.opsForValue()                .set("shop:product:" + product.getId(),                     product, Duration.ofMinutes(10));    }}不同数据结构：redisTemplate.opsForValue();     // StringredisTemplate.opsForHash();      // HashredisTemplate.opsForList();      // ListredisTemplate.opsForSet();       // SetredisTemplate.opsForZSet();      // ZSetredisTemplate.opsForStream();    // Stream8.4 序列化配置默认 JdkSerializationRedisSerializer 会把 key 变成乱码字节，且存在兼容与体积问题。推荐 key 用 String，value 用 JSON：@Configurationpublic class RedisConfig {    @Bean    public RedisTemplate&lt;String, Object&gt; redisTemplate(            RedisConnectionFactory factory) {        RedisTemplate&lt;String, Object&gt; template = new RedisTemplate&lt;&gt;();        template.setConnectionFactory(factory);        StringRedisSerializer keySerializer = new StringRedisSerializer();        GenericJackson2JsonRedisSerializer valueSerializer =                new GenericJackson2JsonRedisSerializer();        template.setKeySerializer(keySerializer);        template.setHashKeySerializer(keySerializer);        template.setValueSerializer(valueSerializer);        template.setHashValueSerializer(valueSerializer);        template.afterPropertiesSet();        return template;    }}GenericJackson2JsonRedisSerializer 会写入 @class 类型信息，便于反序列化为原类型。跨语言系统可去掉类型信息，改用显式 DTO 反序列化。8.5 StringRedisTemplate@Service@RequiredArgsConstructorpublic class CounterService {    private final StringRedisTemplate redis;    public long increase(String key) {        return redis.opsForValue().increment(key);    }}适合：  计数器；  分布式锁；  JSON 字符串；  与非 Java 客户端共享的数据。StringRedisTemplate 的 key 和 value 都是字符串，最不容易出现序列化黑盒。8.6 缓存注解启用：@EnableCaching@SpringBootApplicationpublic class Application { }使用：@Servicepublic class ProductQueryService {    @Cacheable(value = "product", key = "#id", unless = "#result == null")    public Product findProduct(String id) {        return productMapper.findById(id);    }    @CachePut(value = "product", key = "#product.id")    public Product update(Product product) {        productMapper.update(product);        return product;    }    @CacheEvict(value = "product", key = "#id")    public void delete(String id) {        productMapper.delete(id);    }}注解语义：            注解      行为                  @Cacheable      先查缓存，命中则不执行方法              @CachePut      执行方法并更新缓存              @CacheEvict      删除缓存              @Caching      组合多个缓存操作              @CacheConfig      类级别公共配置      TTL 配置@Configurationpublic class CacheConfig {    @Bean    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {        RedisCacheConfiguration config = RedisCacheConfiguration                .defaultCacheConfig()                .entryTtl(Duration.ofMinutes(10))                .disableCachingNullValues()                .serializeKeysWith(RedisSerializationContext.SerializationPair                        .fromSerializer(new StringRedisSerializer()))                .serializeValuesWith(RedisSerializationContext.SerializationPair                        .fromSerializer(new GenericJackson2JsonRedisSerializer()));        Map&lt;String, RedisCacheConfiguration&gt; configs = Map.of(                "product", config.entryTtl(Duration.ofMinutes(5)),                "rank", config.entryTtl(Duration.ofMinutes(1))        );        return RedisCacheManager.builder(factory)                .cacheDefaults(config)                .withInitialCacheConfigurations(configs)                .build();    }}8.7 缓存注解的坑1. 同类内部调用失效public Product query(String id) {    return this.findProduct(id); // AOP 代理不生效}解法：拆到另一个 Bean，或注入自身代理。2. key 表达式写错@Cacheable(value = "product", key = "#id")参数名编译后可能不可用，建议加 -parameters 或显式写参数位置：@Cacheable(value = "product", key = "#p0")3. null 缓存缓存 null 可以防穿透，但 JSON 序列化和业务语义要支持。建议用哨兵值或显式 value 对象。4. 返回对象是代理/懒加载对象MyBatis/JPA 动态代理序列化可能异常或体积异常。缓存 DTO，不要直接缓存 ORM 代理。8.8 手写缓存模板注解适合单对象方法，复杂业务建议显式控制：public Product findWithCache(String id) {    String key = "shop:product:" + id;    Product cached = redisForObjects.opsForValue().get(key);    if (cached != null) {        return cached;    }    Product product = productMapper.findById(id)            .orElseThrow(() -&gt; new NotFoundException("product not found"));    redisForObjects.opsForValue().set(            key, product, Duration.ofSeconds(300 + randomTtl(60)));    return product;}要点：  TTL 加随机值防雪崩；  miss 时做并发控制（第 25 章）；  数据库更新后按一致性策略失效；  指标记录命中率、miss 率、回源耗时。8.9 异常与降级缓存故障不应拖垮主流程，但也不能静默吞掉：public Product findWithFallback(String id) {    try {        Product cached = redis.opsForValue().get(key(id));        if (cached != null) return cached;    } catch (Exception e) {        metrics.counter("redis.error").increment();        log.warn("redis get failed, fallback db", e);    }    Product p = db.find(id);    try {        redis.opsForValue().set(key(id), p, Duration.ofMinutes(5));    } catch (Exception e) {        metrics.counter("redis.write.error").increment();    }    return p;}注意：  强依赖数据（锁、库存、token）不能简单降级；  降级后数据库要有限流；  每次降级必须可观测。8.10 测试@SpringBootTestclass RedisTemplateTest {    @Autowired    private StringRedisTemplate redis;    @Test    void shouldIncrement() {        redis.delete("test:counter");        assertThat(redis.opsForValue().increment("test:counter")).isEqualTo(1);    }}集成测试可用 Testcontainers：@Testcontainers@SpringBootTestclass RedisIntegrationTest {    @Container    static GenericContainer&lt;?&gt; redis =            new GenericContainer&lt;&gt;("redis:7.2").withExposedPorts(6379);}本章小结  Spring Boot 3 默认 Lettuce，连接池需 commons-pool2；  RedisTemplate 必须显式配置 key/value 序列化；  StringRedisTemplate 适合计数、锁和跨语言字符串；  缓存注解要理解 AOP 代理与 key 表达式；  生产缓存建议显式 TTL、随机化、指标和降级策略。思考题  @Cacheable 为什么同类调用不生效？  缓存 null 与 disableCachingNullValues 分别适合什么场景？  Redis 异常时是否所有业务都可以直接查库？哪些不能？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 服务端再快，客户端也可能成为瓶颈。本章讲 Jedis、Lettuce 的差异、连接治理、pipeline、订阅与序列化边界。7.1 Java 客户端怎么选            客户端      特点      适合                  Jedis      简单直接，同步 API，连接池      传统应用、简单同步场景              Lettuce      Netty 驱动，支持同步/异步/响应式，连接共享      Spring Boot 默认、高并发长连接              Redisson      分布式对象与锁，底层 Netty      需要锁、限流、延迟队列等高级能力      Spring Boot 3 默认集成 Lettuce；Redisson 常与 Lettuce 并存，一个负责基础数据访问，一个负责分布式能力。7.2 Maven 依赖&lt;dependency&gt;    &lt;groupId&gt;redis.clients&lt;/groupId&gt;    &lt;artifactId&gt;jedis&lt;/artifactId&gt;    &lt;version&gt;5.1.0&lt;/version&gt;&lt;/dependency&gt;&lt;dependency&gt;    &lt;groupId&gt;io.lettuce&lt;/groupId&gt;    &lt;artifactId&gt;lettuce-core&lt;/artifactId&gt;    &lt;version&gt;6.3.0.RELEASE&lt;/version&gt;&lt;/dependency&gt;7.3 Jedis 快速开始JedisPool pool = new JedisPool("127.0.0.1", 6379);try (Jedis jedis = pool.getResource()) {    jedis.auth("redis123");    jedis.set("user:1001", "Tom");    System.out.println(jedis.get("user:1001"));}推荐显式构建池：JedisPool pool = new JedisPool(        new JedisPoolConfig(),        "127.0.0.1",        6379,        2000,      // connection timeout        2000,      // socket timeout        "default", // user        "redis123",        0,         // db        "order-service");7.4 Lettuce 快速开始RedisURI uri = RedisURI.builder()        .withHost("127.0.0.1")        .withPort(6379)        .withAuthentication("default", "redis123")        .withDatabase(0)        .withTimeout(Duration.ofSeconds(2))        .build();RedisClient client = RedisClient.create(uri);StatefulRedisConnection&lt;String, String&gt; conn = client.connect();RedisCommands&lt;String, String&gt; sync = conn.sync();sync.set("user:1001", "Tom");System.out.println(sync.get("user:1001"));conn.close();client.shutdown();Lettuce 的连接是线程安全的，可以多个线程共享一个 StatefulRedisConnection。它的异步 API 返回 RedisFuture：RedisAsyncCommands&lt;String, String&gt; async = conn.async();RedisFuture&lt;String&gt; future = async.get("user:1001");future.thenAccept(System.out::println);7.5 连接池参数以 Jedis 为例：maxTotal=200maxIdle=100minIdle=20testWhileIdle=truetestOnBorrow=falsetimeBetweenEvictionRunsMillis=30000minEvictableIdleTimeMillis=60000blockWhenExhausted=truemaxWaitMillis=2000参数含义：            参数      建议                  maxTotal      按并发与 RTT 估算，不要盲目几千              maxIdle      接近 maxTotal，避免频繁建连              minIdle      保留常温连接，避免冷启动尖刺              maxWaitMillis      必须设置，防止无限等待              testOnBorrow      生产通常 false，借出检测会增加延迟      粗略估算：需要的连接数 ≈ 请求速率(RPS) * Redis 平均耗时(s)例：1 万 QPS，Redis 平均 2ms -&gt; 10000 * 0.002 = 20   预留峰值 3 倍 -&gt; 60 连接7.6 超时与重试必备四类超时：  连接超时：TCP 建连；  命令超时：等待 Redis 响应；  资源等待超时：连接池借连接；  请求整体超时：业务接口层兜底。Jedis 示例：Jedis jedis = pool.getResource();jedis.setTimeout(2000);Lettuce 示例：RedisURI uri = RedisURI.builder()        .withHost("host")        .withTimeout(Duration.ofMillis(500))        .build();重试原则：  只重试幂等命令或带有业务请求 ID 的操作；  读操作可快速重试 1-2 次；  写操作谨慎重试，避免重复扣减；  每层重试要有预算，避免重试风暴；  Redis 事务/Lua 执行结果不明确时，必须依赖业务幂等修正。7.7 Pipeline：减少网络往返一次网络往返可能消耗 0.5-2ms。100 个命令逐个执行，网络成本会远高于 Redis 执行成本。try (Jedis jedis = pool.getResource()) {    Pipeline pipe = jedis.pipelined();    for (int i = 0; i &lt; 1000; i++) {        pipe.set("key:" + i, "value:" + i);    }    pipe.sync();}Lettuce:conn.async().pipelined();  // 通过批量提交自动合并Pipeline 与事务不同：            特性      Pipeline      MULTI/EXEC                  减少 RTT      是      是              服务端原子执行      不保证      是              失败处理      每条命令独立      队列错误影响执行      批次建议：  每批 100-1000 条，按消息大小实测；  避免一个大 value 拖慢整批；  监控客户端响应时间与 Redis 输出缓冲；  大批量导入建议分批执行并 sleep。7.8 Pub/Sub 客户端JedisPubSub listener = new JedisPubSub() {    @Override    public void onMessage(String channel, String message) {        System.out.println(channel + " -&gt; " + message);    }};new Thread(() -&gt; jedis.subscribe(listener, "cache:invalidate")).start();注意：  Pub/Sub 消息不持久，订阅者不在线就丢；  消费慢时输出缓冲可能被断开；  要可靠投递，用 Stream；  集群模式可考虑 Sharded Pub/Sub。7.9 序列化策略Redis value 是字节数组，客户端序列化决定可读性、体积和兼容性：            格式      优点      缺点                  String      人类可读，调试方便      体积较大              JSON      跨语言，生态好      解析 CPU、schema 弱              JDK 序列化      Java 原生      体积大、安全风险、跨语言差              Kryo/Protobuf      体积小，速度快      schema 与调试成本      业务缓存建议：  key 可读，value 使用 JSON/Protobuf；  明确 schema 版本；  敏感数据加密；  缓存对象只放必要字段，不要整表塞进去。7.10 客户端常见异常            异常      常见原因      处理                  JedisConnectionException      网络断、实例挂、超时      检查健康、超时、重试              JedisDataException: NOAUTH      认证失败      检查密码/ACL              JedisExhaustedPoolException      池耗尽      排查慢命令、泄漏、池参数              RedisCommandTimeoutException      命令超时      看慢查询/CPU/网络              RedisBusyException      Cluster 迁移/重定向      客户端识别 MOVED/ASK              WRONGTYPE      类型不匹配      统一 key schema      资源泄漏典型错误：Jedis jedis = pool.getResource();jedis.set("k", "v"); // 异常后未归还正确写法必须 try-with-resources 或 finally归还。本章小结  Lettuce 适合 Spring Boot 与高并发共享连接，Jedis 简单直观；  连接池大小按 QPS * 平均耗时估算，并设置等待超时；  四类超时与有限重试是生产客户端底线；  Pipeline 降低 RTT，但不提供原子性；  Pub/Sub 只适合低可靠广播，可靠消息用 Stream。思考题  1000 QPS、Redis 平均 5ms，需要多少连接？如果命令变成 50ms 呢？  为什么写操作不能盲目自动重试？哪些写可以安全重试？  Pipeline 里执行 10 万条命令会有什么风险？如何分批？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。Redis 不只有五大基础类型。Bitmap、HyperLogLog、GEO、Stream 以及 Redis Stack/8.x 的 JSON、Search、TimeSeries，能把很多看似复杂的业务变成几条命令。6.1 Bitmap：位级空间优化Bitmap 建立在 String 上，通过 bit offset 操作：SETBIT sign:user:1001:202608 0 1GETBIT sign:user:1001:202608 0BITCOUNT sign:user:1001:202608BITPOS sign:user:1001:202608 1BITFIELD sign:user:1001:202608 GET u8 0每日签到key: sign:user:{userId}:{yyyyMM}offset: 日 - 1value bit: 1 表示签到连续签到判断可以用 BITCOUNT 与按天 offset 检查；复杂规则可结合业务表或脚本。活跃用户统计SETBIT active:20260825 1001 1BITCOUNT active:20260825BITOP AND active:both active:20260824 active:20260825BITOP OR active:any active:20260824 active:20260825优点：内存极小，1 亿用户约 12MB。限制：要求用户 ID 能稳定映射为 offset，且不适合需要遍历明细的业务。6.2 HyperLogLog：海量基数估算HyperLogLog 用极小内存估算集合基数，标准误差约 0.81%：PFADD page:1001:uv user-a user-b user-cPFCOUNT page:1001:uvPFMERGE site:uv page:1:uv page:2:uv适合：  页面 UV；  活动参与人数估算；  搜索词独立用户数；  大盘监控去重。不适合精确结算、抽奖名单、必须知道具体用户的场景。HLL 只回答“大约多少个”，不保存元素本身。6.3 GEO：地理位置GEO 基于 ZSet 与 geohash 编码：GEOADD shops:city:beijing 116.397128 39.916527 "shop:1001"GEOADD shops:city:beijing 116.410000 39.920000 "shop:1002"GEODIST shops:city:beijing shop:1001 shop:1002 kmGEOPOS shops:city:beijing shop:1001GEOSEARCH shops:city:beijing FROMLONLAT 116.40 39.91 BYRADIUS 3 km ASC COUNT 20附近门店1. 写入门店经纬度：GEOADD2. 用户发起请求：GEOSEARCH 半径/边界 + 排序 + 分页3. 获取门店 ID 后批量查详情缓存注意：  经度在前，纬度在后，容易写反；  地球两极与反经线附近查询需业务特殊处理；  超大范围搜索会返回大量 member，应限制半径和 COUNT。6.4 Stream：可靠消息流Stream 是 Redis 的日志型消息结构，支持消费组：XADD events:order * orderId 1001 status CREATED amount 99XLEN events:orderXRANGE events:order - + COUNT 10XREVRANGE events:order + - COUNT 1消费组XGROUP CREATE events:order order-service 0XREADGROUP GROUP order-service consumer-1 COUNT 10 STREAMS events:order &gt;XACK events:order order-service 1720000000000-0XPENDING events:order order-serviceXCLAIM events:order order-service consumer-2 60000 1720000000000-0XTRIM events:order MAXLEN 1000000核心概念：            概念      含义                  entry ID      时间戳-序号，单调递增              &gt;      只读未被该组消费的新消息              pending list      已读未 ack 的消息              consumer      组内具体消费者              XCLAIM      转移超时 pending 消息              MAXLEN      控制日志长度      与 List 队列对比            能力      List      Stream                  顺序      支持      支持              阻塞读      BLPOP/BRPOP      XREAD 阻塞              消费组      不支持      支持              ack/pending      不支持      支持              回放      弱      按位置读              适用      简单任务      事件流/轻量队列      Stream 不是 Kafka 的完整替代：容量受内存限制，持久化与分区伸缩能力也不同。适合中小规模事件流、延迟任务、服务内事件总线。6.5 JSON（Redis Stack / Redis 8）JSON 类型来自 RedisJSON 模块，支持路径读写：JSON.SET product:1001 $ '{"id":1001,"title":"Phone","price":4999,"tags":["new"]}'JSON.GET product:1001JSON.GET product:1001 $.priceJSON.SET product:1001 $.price 4899JSON.ARRAPPEND product:1001 $.tags '"hot"'JSON.OBJLEN product:1001 $适用：文档结构、局部路径更新、半结构化配置。如果只用开源核心 Redis，通常退化为 String JSON +业务端反序列化。6.6 Search 与 TimeSeriesRediSearchFT.CREATE idx:product ON HASH PREFIX 1 shop:product: SCHEMA title TEXT price NUMERICFT.SEARCH idx:product "phone" LIMIT 0 10FT.SEARCH idx:product "@price:[4000 5000]" SORTBY price适合轻量搜索、索引过滤和自动补全。复杂搜索、分词、排序权重、大规模检索仍建议 Elasticsearch/OpenSearch。TimeSeriesTS.CREATE cpu:host:1 RETENTION 86400000TS.ADD cpu:host:1 * 82.5TS.RANGE cpu:host:1 - + AGGREGATION avg 60000适合指标采样、设备监控、轻量时序数据。大规模监控平台仍常用 Prometheus/VictoriaMetrics/TDengine。6.7 类型复杂度速查            操作      复杂度      风险                  GET/SET/HGET      O(1)      低              HSET      O(1)      低              HGETALL      O(N)      N 大时危险              LPUSH/RPUSH      O(1)      单次元素多则 O(M)              LRANGE      O(S+N)      全量大列表危险              SADD/SISMEMBER      O(1)      低              SMEMBERS      O(N)      大 Set 危险              SINTER      O(N*M)      多个大集合危险              ZADD/ZSCORE      O(logN)      低              ZRANGE      O(logN+S)      大范围危险              BITCOUNT      O(N)      超长 bitmap 注意              PFCOUNT      O(1) 常见，合并时增加      通常低      本章小结  Bitmap 用极小内存解决签到与活跃位图；  HyperLogLog 适合海量 UV 估算，不保存具体元素；  GEO 支持附近位置与距离计算；  Stream 相比 List 提供消费组、ack、pending 与回放；  JSON/Search/TimeSeries 属于增强能力，需确认版本与部署支持；  高级类型也要评估命令复杂度和大 key 风险。思考题  为什么 HyperLogLog 不能用于抽奖名单？  Stream 的 pending 消息如何避免消费者宕机导致任务丢失？  如果要实现“每天登录人数 + 近 7 天连续登录人数”，你会组合哪些数据结构？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。数据结构选型决定性能、内存与代码复杂度。本章深入 String、Hash、List、Set、ZSet 的命令、语义与典型场景。5.1 String：不止字符串String 可以保存：  普通文本；  JSON/Protobuf 序列化对象；– 二进制数据（图片片段、序列化字节）；  整数，用于 INCR/DECR；  浮点数，用于 INCRBYFLOAT。常用命令SET user:1001 "Tom" EX 3600SETNX user:1001 "Tom"GET user:1001GETRANGE user:1001 0 2SETRANGE user:1001 0 "ABC"MSET a 1 b 2MGET a bINCR read:article:1INCRBY stock:sku:1 -5INCRBYFLOAT price:sku:1 12.5缓存对象SET shop:product:10001 '{"id":10001,"title":"Phone","price":4999}' EX 600优点：一次读写完整对象，实现简单。缺点：更新任意字段都要读改写整个 JSON，序列化成本随对象变大。计数器INCR counter:page:1001INCRBY counter:page:1001 10DECR counter:stock:sku:1计数操作原子，适合点赞、阅读、库存、限额。注意计数器的初始值与下限校验，避免超卖：DECR stock:sku:1如果原值为 0，会变成 -1。库存扣减应结合 Lua 或 DECR 后判断回滚，第 12 章给出完整方案。分布式锁基础SET lock:order:1001 owner-uuid NX PX 30000一个命令同时完成不存在才写入和 TTL，避免命令交错。完整锁方案见第 11 章。5.2 Hash：对象与字段映射Hash 适合对象属性、购物车、配置项：HSET user:1001 name Tom age 28 city BeijingHGET user:1001 nameHMGET user:1001 name ageHGETALL user:1001HLEN user:1001HEXISTS user:1001 ageHINCRBY user:1001 age 1HDEL user:1001 city局部更新更新昵称：HSET user:1001 name Jerry不需要读取和重写整个对象，也避免并发覆盖其他字段。购物车HSET cart:user:1001 sku:2001 2HINCRBY cart:user:1001 sku:2001 1HDEL cart:user:1001 sku:2001HGETALL cart:user:1001field 是 SKU，value 是数量，天然可增量修改。HSCANHSCAN user:events:1001 0 MATCH click:* COUNT 100大 Hash 不要 HGETALL 全量读取，应分批扫描或按业务拆分。5.3 List：双端列表List 是有序、可重复的序列，支持两端推入弹出：LPUSH queue:task a b cRPUSH queue:task x yLRANGE queue:task 0 -1LPOP queue:taskRPOP queue:taskLLEN queue:taskLINDEX queue:task 0LSET queue:task 0 new-valueLREM queue:task 1 valueLTRIM queue:task -100 -1简单队列LPUSH task:queue task-idBRPOP task:queue 5BRPOP/BLPOP 会阻塞等待，避免客户端空转。但如果要求 ack、消费组、pending 重试，应使用 Stream。最新列表LPUSH feed:following:user:1001 post:9001LTRIM feed:following:user:1001 0 999LRANGE feed:following:user:1001 0 20保持固定长度，避免 List 无限膨胀。5.4 Set：无序唯一集合SADD user:1001:tags java redis distributed-systemSISMEMBER user:1001:tags redisSREM user:1001:tags javaSCARD user:1001:tagsSMEMBERS user:1001:tagsSRANDMEMBER user:1001:tags 2SPOP user:1001:tags集合运算SADD user:1001:follow u1 u2 u3SADD user:2002:follow u2 u3 u4SINTER user:1001:follow user:2002:follow   # 共同关注SUNION user:1001:follow user:2002:follow   # 全部关注SDIFF user:1001:follow user:2002:follow    # 我关注但他未关注去重活动参与用户：SADD activity:2026:users user:1001SISMEMBER activity:2026:users user:1001SCARD activity:2026:users精确去重内存较大。若只要求估算 UV，HyperLogLog 更合适。5.5 ZSet：有序集合ZSet 的 member 唯一，每个 member 关联 score，按 score 排序：ZADD rank:activity:2026 100 user:1001 200 user:2002ZSCORE rank:activity:2026 user:1001ZINCRBY rank:activity:2026 50 user:1001ZRANGE rank:activity:2026 0 9 WITHSCORESZREVRANGE rank:activity:2026 0 9 WITHSCORESZRANGEBYSCORE rank:activity:2026 (100 200 WITHSCORES LIMIT 0 10ZRANK rank:activity:2026 user:1001ZREVRANK rank:activity:2026 user:1001ZREM rank:activity:2026 user:1001排行榜ZINCRBY rank:game 10 player:1001ZREVRANGE rank:game 0 9 WITHSCORESZREVRANK rank:game player:1001同分排序需要额外规则，可以把时间戳或自定义序号编码进 score，例如：score = 分数 * 10^10 + (MAX_TIME - 秒级时间戳)读取后再拆分，注意精度边界。延迟队列ZADD delay:tasks 1735000000 task:1001ZRANGEBYSCORE delay:tasks 0 &lt;now&gt; LIMIT 0 100ZREM delay:tasks task:1001只有成功 ZREM 的任务才执行，避免多消费者重复处理。5.6 类型选择决策表            需求      推荐类型                  缓存完整只读对象      String(JSON)              对象需要局部字段更新      Hash              计数器/库存数      String              双端队列/最新列表      List              去重集合/标签/交并差      Set              按分数排序/范围查询      ZSet              签到/活跃位标记      Bitmap              海量 UV 估算      HyperLogLog              附近位置      GEO              消息队列      Stream      5.7 类型混用与迁移同一个 key 只能有一种 value 类型：SET user:1001 "Tom"LPUSH user:1001 a# WRONGTYPE Operation against a key holding the wrong kind of value结构变更推荐：  新 key 使用新版本前缀：user:1001:v2；  双写或后台迁移；  读新失败读旧；  校验数量与抽样值；  稳定后下线旧 key。不要在线上直接边删边重建，除非业务明确允许丢失。本章小结  String 适合完整对象、计数、二进制与锁；  Hash 适合多字段对象与局部更新；  List 适合有序队列和最新列表，但可靠性弱于 Stream；  Set 适合唯一元素与集合运算；  ZSet 适合排序、范围查询与延迟任务；  大集合读取要分页/扫描，写入要评估元素数量和内存增长。思考题  商品详情页缓存用 String(JSON) 和 Hash 各有什么优缺点？  为什么延迟队列推荐 ZSet 而不是 List？  设计一个“共同好友 + 好友数量排行榜”，需要组合哪些 Set/ZSet 命令？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章从连接与认证开始，掌握日常开发最常用的命令、调试工具和危险命令，并理解 redis-cli 输出。4.1 连接与认证redis-cli -h 127.0.0.1 -p 6379带密码：redis-cli -h host -p 6379 --user default -a your-password更安全：export REDISCLI_AUTH=your-passwordredis-cli -h host -p 6379交互模式：AUTH your-passwordPINGECHO helloSELECT 0QUIT4.2 第一个 keySET user:1001 "Tom"GET user:1001STRLEN user:1001APPEND user:1001 " Cat"GET user:1001带过期时间：SET session:abc "user-1001" EX 1800TTL session:abcPTTL session:abcEXPIRE session:abc 600PERSIST session:abcDEL session:abc原子设置不存在才成功：SET lock:order:1001 owner-1 NX PX 300004.3 key 检索：不要用 KEYS危险命令：KEYS *KEYS user:*它会遍历 key 空间并在返回前阻塞服务。生产必须使用 SCAN：SCAN 0 MATCH user:* COUNT 100返回：1) "17"2) 1) "user:1001"   2) "user:1002"  第一项是下一次游标；  返回 0 表示一轮完成；  COUNT 是提示值，不是精确每批数量；  SCAN 只保证完整轮次内不遗漏，不保证实时一致。示例脚本：cursor=0while :; do  reply=$(redis-cli SCAN "$cursor" MATCH 'user:*' COUNT 200)  cursor=$(echo "$reply" | head -n 1)  echo "$reply" | tail -n +2 | grep -v '^$'  [ "$cursor" = "0" ] &amp;&amp; breakdone4.4 判断、重命名与类型EXISTS user:1001TYPE user:1001RENAME old-key new-keyRENAMENX old-key new-keyDEL user:1001UNLINK user:1001RANDOMKEYDEL 同步释放内存，大 key 可能阻塞；UNLINK 异步释放 value，更适合生产删除。4.5 批量操作MSET a 1 b 2 c 3MGET a b c优点：减少网络往返。风险：一次打包过多 key 或超大 value，会占用带宽并放大延迟。建议每批控制在几十到几百个，并实测延迟。4.6 查看信息与配置INFO serverINFO clientsINFO memoryINFO statsINFO replicationINFO persistenceINFO keyspace查看当前配置：CONFIG GET maxmemoryCONFIG GET maxmemory-policy运行期修改：CONFIG SET maxmemory 2gbCONFIG SET slowlog-log-slower-than 10000CONFIG REWRITECONFIG REWRITE 会把可持久化修改写回配置文件；前提是 Redis 对配置文件有写权限。4.7 客户端连接CLIENT LISTCLIENT INFOCLIENT IDCLIENT SETNAME order-serviceCLIENT GETNAMECLIENT KILL ID 123CLIENT NO-EVICT ONCLIENT LIST 重点字段：            字段      含义                  id      连接 ID              addr      客户端地址              name      连接名              age      连接存活秒数              idle      空闲秒数              db      当前 DB              cmd      最近命令              sub/psub      订阅数      生产服务建议设置连接名，方便定位流量来源。4.8 慢查询与延迟诊断SLOWLOG GET 10SLOWLOG LENSLOWLOG RESET返回包含：  唯一 ID；  发生时间戳；– 执行微秒数；  完整命令与参数；  客户端地址与名称。内置延迟监测：LATENCY HISTORY eventLATENCY DOCTORDEBUG SLEEP 0  DEBUG 命令可能造成阻塞或破坏状态，只能用于隔离的实验环境。4.9 内存与大 key 诊断MEMORY USAGE user:1001MEMORY USAGE rank:activity SAMPLES 0MEMORY DOCTORMEMORY STATSDBSIZE按类型统计：INFO keyspace输出示例：db0:keys=100000,expires=90000,avg_ttl=1200000注意 MEMORY USAGE 对大集合也要计算编码和采样，不要在高峰对大量 key 全量执行。4.10 危险命令清单            命令      风险                  KEYS *      阻塞主线程              FLUSHALL      清空所有数据              FLUSHDB      清空当前 DB              DEL bigkey      同步释放阻塞              HGETALL hugehash      大响应拖垮网络/客户端              SMEMBERS hugeset      同上              LRANGE key 0 -1      全量大列表              DEBUG/SHUTDOWN      运维危险操作      生产可通过 ACL 禁用或限制命令：ACL SETUSER app -keys -flushall -flushdb -shutdown4.11 常用命令速查            目的      命令                  连接测试      PING              设置字符串      SET key value EX 60              读取      GET key              删除      UNLINK key              扫描      SCAN cursor MATCH pattern COUNT 100              查看类型      TYPE key              查看内存      MEMORY USAGE key              查看信息      INFO memory              慢查询      SLOWLOG GET 10              客户端      CLIENT LIST              配置      CONFIG GET/SET      本章小结  redis-cli 是开发与排障第一工具，连接名与认证要规范化；  生产禁用 KEYS，扫描使用 SCAN；  大 key 删除用 UNLINK，读取避免全量命令；  INFO、SLOWLOG、CLIENT、MEMORY 是四大诊断入口；  批量操作能减少 RTT，但必须控制批次大小。思考题  SCAN COUNT 100 是否一定每次返回 100 个 key？为什么？  DEL 与 UNLINK 的区别是什么？为什么大集合更适合 UNLINK？  如果慢查询里出现大量 HGETALL，你会如何改造业务与数据结构？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章从安装、启动、配置文件、Docker 到多实例部署，把一套可用于学习和实验的 Redis 环境搭起来。3.1 安装方式怎么选            方式      适合                  包管理器      快速开发环境              Docker      最推荐的学习与本地实验              源码编译      阅读源码、指定版本、调试              云托管      生产省运维，但成本和迁移策略需评估      生产自建建议固定版本、统一配置模板、由配置管理工具部署，不要手工改一台忘一台。3.2 macOS / Linux 包管理器安装macOS：brew install redisbrew services start redisUbuntu/Debian：sudo apt updatesudo apt install redis-serversudo systemctl enable --now redis-serverRHEL/CentOS：sudo dnf install redissudo systemctl enable --now redis验证：redis-cli ping# PONG3.3 Docker 单实例docker run -d \  --name redis \  -p 6379:6379 \  redis:7.2 \  redis-server --requirepass redis123连接：redis-cli -h 127.0.0.1 -p 6379 -a redis123 ping挂载配置与数据：mkdir -p redis-datadocker run -d \  --name redis \  -p 6379:6379 \  -v "$PWD/redis.conf:/etc/redis/redis.conf" \  -v "$PWD/redis-data:/data" \  redis:7.2 \  redis-server /etc/redis/redis.conf注意 -a 会把密码暴露在 shell 历史中，学习环境可用，生产应使用 REDISCLI_AUTH 或 secret 注入：export REDISCLI_AUTH=redis123redis-cli ping3.4 源码编译git clone https://github.com/redis/redis.gitcd redisgit checkout 7.2make -j8make test./src/redis-server --version./src/redis-cli --version启动：./src/redis-server redis.conf调试构建：make OPTIMIZATION="-O0 -g"源码阅读与调试见第 29 章。3.5 配置文件核心项一个学习用 redis.conf：bind 0.0.0.0protected-mode yesport 6379requirepass redis123daemonize nologfile ""dir /data# 内存maxmemory 1gbmaxmemory-policy allkeys-lru# 持久化appendonly yesappendfilename "appendonly.aof"appenddirname "appendonlydir"appendfsync everysecaof-use-rdb-preamble yessave 3600 1 300 100 60 10000# 慢查询slowlog-log-slower-than 10000slowlog-max-len 256latency-monitor-threshold 100逐项理解：            配置      说明                  bind      监听地址，生产只绑内网网卡              protected-mode      无密码且非本机访问时保护              requirepass      兼容密码认证，新版建议 ACL              dir      RDB/AOF 工作目录，必须持久化且有权限              maxmemory      内存上限，超过后按策略淘汰              appendonly      开启 AOF              appendfsync      AOF 刷盘策略              slowlog-log-slower-than      微秒，默认 10000 = 10ms        注意：上例 allkeys-lru 适合纯缓存。如果 Redis 存放业务状态或锁，默认淘汰策略可能造成严重问题，第 14 章详细讨论。3.6 启动与停止前台启动便于观察：redis-server redis.conf守护进程：redis-server redis.conf --daemonize yes停止：redis-cli shutdown saveredis-cli -a xxx shutdown nosavesave 表示退出前生成 RDB；nosave 不保存。生产优雅停止必须确认持久化与复制策略。3.7 多实例部署同一台机器可按端口拆分实例，实现业务隔离：redis-6379 -&gt; 商品缓存redis-6380 -&gt; 会话redis-6381 -&gt; 排行榜启动：redis-server /etc/redis/6379.confredis-server /etc/redis/6380.confredis-server /etc/redis/6381.conf每个实例必须独立设置：portdirpidfilelogfiledbfilenameappenddirnamerequirepass / aclfilemaxmemory多实例共享 CPU、内存和网络，适合隔离故障域，不是无限扩容手段。3.8 搭建一主两从实验环境配置主节点 6379：port 6379requirepass redis123masterauth redis123从节点 6380、6381：port 6380replicaof 127.0.0.1 6379masterauth redis123requirepass redis123启动后查看：INFO replication主节点应看到：role:masterconnected_slaves:2测试：# 主SET replication:test ok# 从GET replication:test默认从节点只读：replica-read-only yes3.9 常见启动问题            现象      原因      处理                  连接被拒绝      未启动或端口/防火墙错      检查进程、bind、安全组              NOAUTH      未认证      配置密码或 ACL 用户              DENIED Redis is running in protected mode      未密码且远程访问      开启密码或只绑内网              AOF/RDB 写入失败      dir 无权限或磁盘满      检查路径与磁盘              内存告警      maxmemory 过小      容量规划与淘汰策略              启动后立刻退出      配置语法错      前台启动看日志      本章小结  Docker 是最快的学习环境；生产自建需统一版本与配置模板；  dir 决定持久化文件位置，是部署检查重点；  maxmemory 与 maxmemory-policy 必须显式设计；  多实例能隔离业务故障域，但共享机器资源；  一主两从环境是学习复制、哨兵与故障切换的基础。思考题  bind 0.0.0.0 加 requirepass 就等于安全吗？还缺什么？  为什么不建议多个业务共用一个 Redis 实例？  从节点默认只读，为什么这么设计？如果直接写从节点会造成什么问题？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。本章建立 Redis 的概念地图：key、database、value、TTL、编码、客户端连接与请求模型。这些名词会贯穿全书。2.1 Redis 的数据视图Redis 可以被看作一个巨大的 Map：┌──────────────── Redis Instance ─────────────────┐│ DB0                                              ││   "user:1001"      -&gt; Hash                      ││   "stock:sku:1"    -&gt; String("99")              ││   "rank:score"     -&gt; ZSet                      ││   "session:abc"    -&gt; String / JSON, TTL=1800   ││ DB1 ... DB15                                      │└──────────────────────────────────────────────────┘  key 是唯一的字符串；  value 是某种类型的数据结构；  key 可以设置 TTL，到期后不可访问；  默认有 16 个逻辑 DB（databases=16），通过 SELECT 切换。2.2 Key 设计规范Redis 没有表和库的强模型，key 设计就是“schema”。推荐：业务:对象类型:ID[:属性]示例：shop:product:10001shop:stock:sku:10001user:session:9f8c...rank:activity:2026:scorerate:login:user:1001命名建议  使用冒号分层，方便按前缀管理和检索；  长度控制在几十字节内，语义清晰但不要冗长；  不要使用随机超长 key；  版本化破坏性结构：user:profile:1001:v2；  避免一个 key 装下几十万字段的大 Hash。一个常见误解Redis 不会按 : 自动建立目录索引。SCAN shop:* 的匹配仍是扫描 key 空间，只是跳过不匹配项。想高效统计对象数量，应使用业务索引集合或外部元数据，而不是频繁 SCAN。2.3 逻辑 DB默认 DB 数量：databases 16客户端连接后默认在 DB0：SELECT 3FLUSHDB     # 只清当前 DBFLUSHALL    # 清所有 DB多 DB 只是命名空间隔离，不提供权限隔离，也不隔离内存和 CPU。因此：  单体应用可以用 DB 区分开发/测试数据；  生产多业务/多租户建议独立实例或 Cluster；  Redis Cluster 模式下通常只用 DB0。2.4 Value 类型总览            类型      典型语义      常用命令      场景                  String      字符串/整数/二进制      SET GET INCR APPEND      缓存对象、计数、token              Hash      字段到值的映射      HSET HGET HGETALL      对象属性、购物车              List      双端链表/快速列表      LPUSH RPOP LRANGE      队列、时间线              Set      无序唯一集合      SADD SISMEMBER SINTER      标签、好友、去重              ZSet      带 score 的有序集合      ZADD ZRANGE ZRANGEBYSCORE      排行榜、延迟队列              Bitmap      位图（String 上操作）      SETBIT BITCOUNT      签到、活跃标记              HyperLogLog      基数估算      PFADD PFCOUNT      UV 估算              GEO      地理位置      GEOADD GEOSEARCH      附近门店              Stream      消息流      XADD XREAD XGROUP      日志流、事件队列      选型原则：优先贴近业务语义，其次考虑命令复杂度和内存占用。不要把所有数据都 JSON 序列化成 String；也不要为了局部更新一个字段而创建上千个细碎 key。2.5 TTL 与过期时间SET session:abc "user-1001" EX 1800TTL session:abcEXPIRE session:abc 3600PERSIST session:abc要点：  TTL 以 key 为单位，不能只给 Hash 的某个 field 设置过期；  Redis 7.4+ 开始支持 HEXPIRE 等 field 过期能力，需确认版本；  TTL 到期后 key 逻辑上不可访问；  删除可能是惰性或周期性的，不代表到期瞬间一定立刻释放内存；  重写 value 或某些操作会保留 TTL，SET 默认清除 TTL，除非显式 KEEPTTL。详细机制见第 14 章。2.6 原子性：Redis 的单线程红利单条 Redis 命令在服务端串行执行，因此：INCR counter不会出现两个客户端同时读到 9、都写回 10 的交错。多个客户端的命令总有一个先后顺序。但这不等于“多命令事务”：INCR aINCR b两条命令之间可能插入其他客户端命令。要保证原子执行，需要：  MULTI/EXEC 入队后顺序执行；  Lua 脚本在服务端一次执行；  某些组合命令天然一步完成（如 SET ... NX PX）。见第 9 章。2.7 RESP 协议与请求模型客户端与 Redis 使用 RESP（REdis Serialization Protocol）通信。简化过程：Client                          Redis  | --- 命令文本/RESP 请求 ------&gt; |  |                             解析命令  |                             查找 key  |                             执行命令  | &lt;------ RESP 响应 ------------ |RESP2 常见响应类型：  +OK：简单字符串；  -ERR ...：错误；  :123：整数；  $5\r\nhello：批量字符串；  *2\r\n...：数组。RESP3 新增 map、double、boolean、push 等类型，让客户端可以拿到更丰富的语义，例如连接中断 push、哈希字段值类型提示。2.8 Redis 的线程模型概览Redis 6.0 之前通常被称为“单线程”，更准确地说：  命令执行始终由主线程完成；  网络 IO 在 6.0 后可配置多线程；  后台线程处理关闭文件、AOF 刷盘、异步删除等任务；  BIO/惰性释放避免主线程被昂贵释放操作阻塞。io-threads 4io-threads-do-reads yes因此，即使开启 IO 多线程，慢命令依然会阻塞整个实例。线程模型细节见第 15 章。2.9 持久化与高可用概念速览            概念      作用                  RDB      定期生成内存快照，恢复快、文件小              AOF      记录写命令，丢失窗口更小              混合持久化      RDB 头 + 增量 AOF，兼顾恢复速度与数据安全              主从复制      建立副本，读写分离与灾备              Sentinel      自动故障检测与主从切换              Cluster      分片、横向扩容、去中心化路由      这些机制分别在第 16-19 章展开。2.10 与 MySQL 的概念对照            MySQL      Redis      差异                  Database/Table      逻辑 DB / key 前缀      Redis 无严格 schema              Row      key-value      Redis value 是结构              Column      Hash field      Hash 更适合局部字段更新              Index      Set/ZSet/业务索引      需业务自己维护              SQL      Redis 命令      无 join/复杂查询              Transaction      MULTI/Lua      能力有限，非 SQL 事务              主从复制      replication      Redis 常见异步复制      把 Redis 当“可远程访问的内存数据结构服务”，比当“更快的 MySQL”更准确。本章小结  Redis 数据模型是 key 到结构化 value 的映射；  key 设计是长期可维护性的核心，推荐 业务:类型:ID；  逻辑 DB 只是命名空间，不隔离资源与权限；  TTL 以 key 为单位，删除有惰性和周期两种路径；  单条命令原子，多条命令需要 MULTI 或 Lua；  命令执行单线程，网络 IO 可多线程，慢命令仍是全局风险。思考题  为什么不建议所有对象都序列化成 JSON 字符串存 String？  如果要更新用户资料的昵称字段，String JSON 和 Hash 哪个更合适？为什么？  SCAN user:* 会不会因为使用了 : 分层而变得高效？为什么？</li>
  <li>这是《Redis 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开，代码块保留了原书可直接运行的版本。1.1 从一个真实场景开始假设商品详情页每次打开都要执行几十条 SQL：商品基础信息、SKU、库存、价格、优惠券、推荐位、评价统计。数据库能扛住每秒几千次查询，但大促时页面请求可能是每秒几万次，数据库会瞬间被打满。最直接的做法是加索引、拆库、读库扩容，但这些都改变不了核心事实：详情页的数据被反复读取，而每次读取的结果几乎相同。Redis 的价值在这里出现：请求 -&gt; 先查 Redis 命中 -&gt; 直接返回     -&gt; 未命中 -&gt; 查数据库 -&gt; 写入 Redis -&gt; 返回热点商品进入内存，数据库只处理冷数据和首次加载，读性能从毫秒级进入亚毫秒级。这是 Redis 最经典的用法，但它远不只是缓存。1.2 Redis 是什么Redis（REmote DIctionary Server）是一个基于内存的键值数据库，同时提供持久化和多种数据结构能力。三个关键词：  基于内存：数据主要存内存，读写延迟极低，单实例常见可达 10 万 QPS 以上；  键值模型：所有数据通过 key 定位，value 支持字符串、哈希、列表、集合、有序集合、位图、流等结构；  可持久化：通过 RDB/AOF 将内存数据落到磁盘，用于重启恢复和数据留存。Redis 的定位不是替代 MySQL。它更像一个“高速数据结构服务”：适合热点、临时、实时、可重建或可容忍有限丢失的数据；不适合作为复杂关系查询和超大容量的事实源。1.3 Redis 的历史与版本脉络Redis 由 Salvatore Sanfilippo（antirez）在 2009 年为解决意大利实时日志统计系统性能问题而开发，2010 年后逐渐成为缓存与实时系统的标准组件。            版本      时间      关键特性                  1.x      2009      基础数据结构与内存存储              2.x      2010-2013      复制、AOF、虚拟内存（后移除）              3.x      2015      Redis Cluster 分片              4.x      2017      Modules 生态、LRU 改进              5.x      2018      Stream 数据类型              6.x      2020      多线程 IO、ACL、RESP3、TLS              7.x      2022      Functions、Sharded Pub/Sub、AOF 改进              8.x      2025      集成 Search/JSON/TimeSeries 等能力，性能与可观测性增强        说明：Redis 8 将部分 Redis Stack 能力整合进主版本，但本书主线仍是开源通用的核心 Redis 能力；涉及增强模块时会明确说明。生产选型还应关注许可协议、团队运维能力和替代项目（如 Valkey）。1.4 Redis 为什么快面试常问，也真正决定你能不能调优。答案不是简单的“内存快”，而是一组设计选择：  内存存储：避免磁盘随机 IO，主路径只访问内存；  高效数据结构：SDS、哈希表、跳表、紧凑列表等针对不同 value 类型优化；  单线程命令执行：命令执行没有锁竞争和线程上下文切换，路径简单；  IO 多路复用：一个线程用 epoll/kqueue 管理成千上万连接；  RESP 协议轻量：文本协议易解析，RESP3 提供更多语义但依然高效；  避免昂贵操作：多数操作 O(1)/O(logN)，危险命令通过异步或拆分处理；  6.0+ IO 多线程：读写 socket 与协议解析可并行，命令执行仍单线程。速记：内存 + 高效结构 + 单线程执行 + epoll + 轻协议 + IO 多线程但单线程也意味着：一个慢命令会拖住整实例。KEYS *、超大集合的全量读取、误用 Lua 长循环，都是事故源。1.5 Redis 能做什么1. 缓存保存热点对象、页面片段、接口聚合结果，降低数据库压力。2. 会话存储Session、登录 token、验证码、设备状态。设置 TTL，天然适合短生命周期数据。3. 计数器阅读数、点赞数、库存扣减、限流窗口。INCR/DECR 原子执行。4. 排行榜ZSet 按 score 排序，支持实时更新与范围查询。5. 分布式锁用 SET key value NX PX ttl 做互斥；成熟方案常用 Redisson 看门狗与可重入锁。6. 消息与事件流Pub/Sub 适合低延迟广播；Stream 支持消费组、ack 与 pending 记录。7. 实时标签与画像Set 做交并差，Bitmap 做签到与活跃用户统计，HyperLogLog 做海量 UV 估算。1.6 Redis 不适合什么  大容量冷数据主存储：内存成本高，不适合 PB 级归档；  复杂关联查询：没有 join、group by、SQL 优化器；  强事务业务主库：事务能力有限，回滚不覆盖执行后的数据破坏；  高频大 key 读写：单线程会被慢命令阻塞；  对数据零丢失要求极高：异步复制与故障切换可能丢失最后一批写入，需业务或存储方案兜底。一句话：Redis 负责“快”，数据库负责“真”。 不要把事实源从数据库里轻易拿走。1.7 Redis、Memcached 与 KV 数据库对比            维度      Redis      Memcached      etcd      DynamoDB 类                  数据结构      丰富      仅 key/value 字符串      KV/lease      KV/文档/索引              持久化      RDB/AOF/混合      无      Raft 日志      云托管              复制      主从/哨兵/Cluster      客户端分片      强一致 Raft      服务托管              事务      MULTI/Lua，有限      无      MVCC 事务      事务支持              一致性      可调，常见最终一致      缓存语义      强一致      可配置              典型用途      缓存/实时结构      纯缓存      元数据/配置      云上 KV      Memcached 更简单纯粹；etcd 更强调强一致元数据；Redis 的优势是“内存 + 数据结构 + 生态完整”的组合。1.8 Redis 学习地图入门                实战                原理                 运维               架构01-06 章      -&gt;    07-12 章      -&gt;    13-19 章       -&gt;    20-24 章      -&gt;    25-29 章命令/数据类型       客户端/锁/并发       内存/持久化/HA        监控/调优/迁移       缓存/多活/生态初学者最常见的问题是“背命令”；中级工程师常见问题是“只会当缓存”；高级工程师必须理解：value 类型怎么选、内存怎么省、故障时怎么恢复、并发下是否正确。这本书会沿着这条路径推进。1.9 环境准备第 3 章前准备：  Linux/macOS/WSL2 或 Docker；  Redis 7.x/8.x 安装包或 Docker 镜像；  redis-cli 可执行文件；  Java 示例使用 JDK 17+、Spring Boot 3.x；  建议同时准备一个 MySQL，用于第 25-26 章缓存一致性与项目实战。本章小结  Redis 是基于内存的键值数据库，核心优势是内存 + 丰富数据结构 + 高效网络模型；  常用于缓存、会话、计数、排行、锁、限流、消息流和画像标签；  单线程命令执行让路径简单，也要求严格避免慢命令和大 key；  Redis 不适合大容量冷存储、复杂查询和强事务事实源；  学习 Redis 的核心是同时掌握“怎么用”“为什么快”“什么时候会坏”。思考题  你的系统中哪些数据适合放 Redis？哪些必须继续留在数据库？  Redis 单实例 10 万 QPS，是否意味着可以直接横向扩到 1000 万 QPS？瓶颈还会出现在哪里？  如果 Redis 宕机 5 分钟，哪些业务可以降级，哪些必须强依赖？</li>
</ul>
