ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

Redis生产实践:缓存一致性、分布式锁与主从复制避坑

Redis生产实践:缓存一致性、分布式锁与主从复制避坑 Redis 几乎是后端面试的必考科目缓存一致性、分布式锁、持久化、主从复制、内存淘汰网上成套的八股文背下来二十分钟能把面试官答得频频点头。但我带过的新人里面试分数最高的那个把 Redis 接进项目第一周就出了一次线上事故用户改完昵称刷新页面又变回旧的过了十几秒才恢复。排查下来代码里写的正是八股文标准答案——先更新数据库再删除缓存。问题不在结论而在于他把结论当成了万能公式完全没有考虑主从延迟、并发读写和删除失败的场景。这篇东西不打算再给你抄一遍面试题答案。我更想做的是把那些被压缩成一句话的标准答案重新展开还原它们成立的前提条件再补上真正落到生产环境时会遇到的边界情况。内容覆盖数据类型选择、缓存一致性方案、分布式锁的实现演进、RDB/AOF 与主从复制细节、大 key 热 key 治理以及安装部署和监控这些动手环节。不管你是正在准备面试还是已经接手了一个跑着 Redis 的项目都能从里面找到能直接用的判断依据。1. 背得滚瓜烂熟的答案为什么一上生产就变形1.1 八股答案的通用毛病结论正确前提被吃掉了八股文最大的问题是它把结论和前提一起压成了一句话而背诵的人往往只记住了后半截。比如Redis 是单线程的所以快这句话在 Redis 6.0 之前基本成立但真正的快来自内存操作、IO 多路复用和高效的数据结构而不是单线程这个属性本身Redis 6.0 之后网络 IO 已经多线程化了命令执行仍然是单线程。如果你在面试里只答单线程所以快遇到追问就答不上来如果在生产里按单线程所以不会并发问题去做设计那更危险——单线程指的是命令执行不是你的业务逻辑。再举一个例子Redis 支持事务。八股文里背的是 MULTI、EXEC、DISCARD、WATCH 四个命令。但真实的语义是Redis 事务不支持回滚某条命令执行失败其余命令照样执行它只是把命令打包一次性、按顺序执行中间不会被其他客户端插队。很多人第一次用事务是因为想实现检查余额再扣减这类逻辑结果发现并发下依然超卖。原因很简单事务的原子性是执行原子不是检查与执行之间的隔离。这类理解偏差就是八股答案最常见的失真方式。我自己的经验是每背一条 Redis 结论就强迫自己问三个问题这条结论依赖什么前提前提被破坏时会发生什么我在代码里怎么检测前提是否被破坏这三个问题问下来八股就变成了工程判断。1.2 我遇到的第一次翻车更新数据库和更新缓存的顺序回到开头那次事故。当时的代码逻辑是标准的 Cache Aside写请求先更新 MySQL再DEL对应的缓存 key读请求先查缓存未命中再查 MySQL 并回填。单机压测完全没问题线上却出现了旧值复活。原因藏在主从复制里。我们的 MySQL 是一主两从写走主库读走从库。写请求更新主库成功后立刻删缓存紧接着一个读请求进来缓存未命中去从库读——而从库的复制还没追上来读到的仍然是旧值然后这个旧值被回填进了缓存。之后所有读请求都命中这个旧值直到下一次写入把它删掉。整个过程不超过 100 毫秒但影响持续了十几秒。这个坑八股文里通常不会提因为八股文默认数据库读写都在同一个节点。一旦引入主从删除缓存和读库回填之间的窗口就被放大了。解决办法也不复杂要么读写相关的请求强制走主库牺牲一点扩展性要么给缓存设置一个较短的兜底 TTL让脏数据自己过期要么引入 binlog 订阅做缓存失效比如 Canal 这类方案。我最后选的是回填时加短 TTL 关键业务读写走主库的组合改动小风险可控。1.3 一份自查清单把八股答案翻译成生产约束踩过几次坑之后我整理了一份对照表每次评审涉及 Redis 的代码都会过一遍。它不复杂但能挡住大部分低级事故。八股说法生产里必须补上的前提常见的兜底手段先更新 DB 再删缓存读写是否走同一节点、删除是否可能失败短 TTL、binlog 订阅、重试删除Redis 是单线程的指的是命令执行业务逻辑仍并发用 Lua 或SET NX保证操作原子分布式锁用SETNX必须原子设置过期时间改用SET key val NX PX msAOF 更安全取决于appendfsync的取值一般用everysec权衡性能缓存能提升性能前提是命中率足够高监控命中率低于阈值就排查 key 设计主从能提高可用性存在复制延迟且不是自动故障转移主从 哨兵或直接上集群注意表格里的兜底手段没有一个是万能的它们的共同点是降低故障持续时间而不是杜绝故障。做缓存设计时先接受一定会有不一致窗口这个事实再考虑窗口有多长、能不能被业务容忍。2. 五种基础类型背后的真实选择逻辑2.1 先想清楚数据长什么样再决定用哪种类型八股文讲数据类型通常是一句String 存字符串Hash 存对象List 存列表Set 存集合ZSet 存有序集合。背是背下来了用的时候还是全用 String把对象序列化成 JSON 塞进去。这样也能跑但会白白浪费 Redis 的一个核心优势部分读写。举个具体场景。用户信息有 id、昵称、头像、积分、等级等十几个字段如果整体序列化成 JSON 存在 String 里要改一个昵称就得把整个 JSON 读出来、反序列化、改字段、再序列化写回去。这段时间里如果另一个请求改了积分就会互相覆盖。换成 Hash 之后HSET user:1001 nickname 新昵称只动一个字段互不干扰而且内存占用通常比 JSON 更小小 Hash 会使用紧凑编码。再比如排行榜。用 List 也能实现但每次查询排名都要遍历ZSet 天生带 score 和排名ZADD更新、ZREVRANGE取 Top N、ZREVRANK查排名都是 O(log N)。再比如统计某篇文章的独立访客这种去重需求Set 能做但用户量大了内存会爆HyperLogLog 更合适——误差 0.81%内存固定 12KB 左右。我的判断顺序是这样的先看数据是不是整体读写String、字段级读写Hash、有顺序且需要两端操作List / Stream、需要去重Set、需要按分数排序或范围查询ZSet。定下类型之后再考虑底层编码和内存。2.2 底层编码的切换阈值决定了你的内存账单同样是 ZSet元素少的时候用的是 ziplistRedis 7 里改叫 listpack元素多或者单个元素超过阈值就转成 skiplist。这两者的内存差距可能有三到五倍。八股文一般只会说小数据量用压缩列表大数据量用跳表但不会告诉你阈值在哪、怎么调。相关配置项在 redis.conf 里是这样的# ZSet 使用 listpack 编码的条件Redis 7.x 称谓 zset-max-listpack-entries 128 zset-max-listpack-value 64 # Hash hash-max-listpack-entries 128 hash-max-listpack-value 64 # List list-max-listpack-size 128 # Set元素全是整数且数量不超过阈值时用 intset set-max-intset-entries 512这些值不是越大越好。调大阈值小对象的内存占用会下降但单次操作时因为要整体重新分配内存延迟会上升。我做过一次实测把hash-max-listpack-entries从 128 调到 512某业务的内存下降了约 18%但 P99 写延迟从 0.4ms 涨到了 0.9ms。对延迟不敏感、内存紧张的场景值得调对延迟敏感的场景就别动。提示转换是单向的一旦从紧凑编码转成跳表或哈希表即使后来元素减少也不会自动转回去除非删除并重建 key。所以如果某个 key 会周期性膨胀考虑给它加上定时重建的逻辑。2.3 被八股文漏掉的那几个类型Bitmap、HyperLogLog、GEO、Stream基础五类型之外Redis 还提供了几个特殊结构它们在特定场景下能把复杂度和内存都降一个量级。Bitmap 本质是 String但可以用位操作。签到场景特别典型一个用户一年 365 天用 Bitmap 只需要 46 字节SETBIT sign:1001 20240315 1打卡BITCOUNT统计总天数BITOP做多用户聚合。如果用 Set 存日期字符串一个用户一年就要几十 KB。HyperLogLog 用来做基数统计PFADD添加、PFCOUNT估算。它的特点是内存固定、有误差、不支持删除单个元素。适合UV 统计搜索结果去重计数这类不要求精确的场景。要注意的是PFCOUNT在多 key 合并时会比较慢因为它要把多个 HLL 结构合并计算。GEO 是建立在 ZSet 之上的地理位置结构GEOADD存经纬度GEOSEARCH按半径或矩形查询。做附近的店铺这类功能不用自己算球面距离了。Stream 是 5.0 引入的消息队列结构有消费者组、消息 ID、ACK 机制。它比 List 实现的简易队列更完整但也不是专业 MQ 的替代品——没有复杂的重试策略、死信队列需要自己实现。这几个结构八股文里出现频率不高但面试里一旦问到你还知道哪些数据结构能讲清楚它们的适用边界比背定义有用得多。3. 缓存一致性三种方案的真实差异3.1 三种更新顺序的失效概率到底差在哪关于缓存和数据库的一致性网上流传的方案主要有三种先更新 DB 再删缓存、先删缓存再更新 DB、先更新 DB 再更新缓存。八股文的结论一般是用第一种但很少解释为什么。先看先更新缓存在更新 DB。这个方案的问题最明显两个并发写请求A 先更新缓存为值 2B 后更新缓存为值 3但数据库层面 B 可能先落库、A 后落库最终数据库是 2、缓存是 3长期不一致。而且如果缓存更新成功、数据库更新失败脏数据就留在缓存里了。再看先删缓存再更新 DB。这个方案在并发下有个经典漏洞请求 A 删除缓存然后去更新数据库在 A 更新完成之前请求 B 进来读缓存未命中读到数据库里的旧值回填缓存之后 A 才更新完数据库。结果缓存里是旧值数据库里是新值。要堵住这个漏洞需要在 A 更新完之后再删一次缓存也就是延迟双删。最后是先更新 DB 再删缓存。这个方案的失效窗口更小但依然存在就是我前面遇到的主从延迟问题删除缓存之后、主从同步完成之前的读请求会把旧值回填。理论上要出现这个情况需要读请求恰好在这个窗口内到达概率不高但现实中确实会发生。方案主要风险失效窗口是否需要额外机制先更新 DB 再更新缓存并发写覆盖、更新缓存的成本高大不建议采用先删缓存再更新 DB读请求回填旧值大需要延迟双删先更新 DB 再删缓存主从延迟导致回填旧值小短 TTL 或 binlog 订阅3.2 延迟双删的延迟时间怎么算不是拍脑袋很多人写延迟双删延迟时间直接写 500 毫秒或者 1 秒问他为什么答网上都这么写。这个值其实是可以算的。延迟时间要覆盖的是从第一次删缓存到数据库更新完成并被读到新值这段时间主要包括三部分主从复制的延迟、业务更新数据库本身的耗时、以及可能的网络抖动。前两项可以监控MySQL 的Seconds_Behind_Master或者SHOW SLAVE STATUS里的延迟加上业务 SQL 的 P99 耗时。假设主从延迟 P99 是 30ms更新 SQL 的 P99 是 20ms那么一次延迟删除至少要覆盖 50ms取两倍余量就是 100ms。如果主从延迟经常抖到几百毫秒那延迟双删本身就不适合因为你要延迟很久才能删第二次而且第二次删除还可能失败。我的做法是分两档主从延迟稳定的业务用 200ms 左右的延迟删除配合 5 到 10 分钟的缓存 TTL 兜底主从延迟不稳定的业务干脆把读请求强制路由到主库用一点性能换一致性。另外第二次删除一定要放在异步线程或者延时队列里做不能阻塞主流程并且删除失败要能重试。3.3 穿透、击穿、雪崩三个病的药方不能混用这三个词长得像但成因完全不同方案也不能互换。缓存穿透是查询一个数据库里也不存在的 key每次请求都穿过缓存打到数据库。典型来源是恶意刷接口或者业务上用了自增 ID 之外的随机标识。方案有两个一是把空结果也缓存起来设置较短的 TTL比如 60 秒二是用布隆过滤器提前拦截把所有可能存在的 key 预先放进去查询前先判断。注意布隆过滤器有误判率判断存在时可能出错判断不存在时一定准确。所以它的正确用法是说没有就一定没有说有不一定有。另外它不支持删除元素如果要支持删除得换成计数布隆过滤器或者定期重建。缓存击穿是某个热点 key 突然过期高并发请求同时打到数据库。方案有两个互斥锁只让一个请求去查库回填其他请求短暂等待或返回旧值逻辑过期key 本身不设 TTL而是把过期时间写在 value 里命中后判断是否过期过期则异步更新当前请求先返回旧值。缓存雪崩是大批 key 在同一时刻过期或者 Redis 实例整体不可用。前者通过给 TTL 加随机扰动解决比如基础 30 分钟加上 0 到 5 分钟的随机值后者要靠多级缓存、集群部署、限流熔断来解决本质上是可用性问题不是缓存设计问题。4. 分布式锁从 SETNX 到 Redlock 的实际演进4.1 从 SETNX 到 SET NX PX原子性这一步省不得分布式锁的经典八股写法是SETNX lock:order:1001 1 EXPIRE lock:order:1001 30这两条命令分开执行中间如果服务重启或者网络断开EXPIRE没执行成功锁就永远不过期后续所有请求全部阻塞。这个坑很多资料都会提正确写法是把两条合并成一条原子命令SET lock:order:1001 requestId NX PX 30000这里有几个细节值得展开。第一value 必须是唯一标识比如 UUID 或者机器标识 线程 ID因为解锁的时候要校验这把锁是不是自己加的。第二用PX而不是EX毫秒粒度更适合控制锁的过期时间。第三NX保证只有 key 不存在时才设置成功。4.2 锁续期、看门狗以及业务没执行完锁先过期设置了 30 秒过期如果业务执行了 40 秒锁会在第 30 秒自动释放此时其他线程就能拿到锁进入临界区两个线程同时操作共享资源锁形同虚设。这个问题有两种应对方式。第一种是给业务加超时控制保证执行时间远小于锁的过期时间。这种做法简单但前提是业务耗时可控涉及外部调用、批量处理时很难保证。第二种是锁续期。启动一个后台线程在锁快过期时比如剩余三分之一时间检查业务是否还在执行如果是就重新设置过期时间。Redisson 的看门狗机制就是这个思路默认锁过期时间 30 秒每隔 10 秒续期一次。用起来方便但要注意如果应用进程被 kill续期线程也跟着消失锁最多再存活 30 秒这是可以接受的但如果发生了长时间的 GC 停顿续期线程可能来不及执行锁提前过期这就是所谓的锁失效窗口。4.3 主从切换与 Redlock 的争议落到实践是什么样单节点 Redis 加锁有个致命问题如果主节点在锁还没同步到从节点时就宕机了故障转移后从节点升为主节点锁信息丢失第二个客户端就能拿到同一把锁。围绕这个问题Redis 作者提出了 Redlock 算法向多个独立节点加锁超过半数成功才算拿到锁。但 Redlock 也受到过质疑核心论点是它依赖各节点的时间假设在时钟漂移、进程停顿的情况下仍然可能失效而且它解决的是锁的互斥性解决不了持锁者对共享资源的操作是否安全。落到工程实践我的选择顺序是这样的场景推荐方案理由同一进程内的并发控制本地锁无需网络开销性能最好单实例、容忍极低概率失效单节点 Redis 锁 唯一 value Lua 解锁简单可靠满足绝大多数业务对一致性要求极高数据库唯一索引 / 乐观锁版本号依赖数据库事务不依赖缓存可用性跨机房、强一致基于共识算法的协调服务复杂度高但语义明确我自己的项目里订单创建这类不能重复的操作最终用的是数据库唯一索引兜底 Redis 锁做快速失败。Redis 锁挡掉 99% 的重复请求剩下的漏网之鱼由唯一索引拦截触发异常后返回请勿重复提交。这样即使 Redis 出问题业务也不会出错只是压力会打到数据库。4.4 Lua 校验解锁为什么不能直接 DEL解锁最容易被忽略的一点是不能直接DEL。因为如果 A 的业务超时了锁自动过期B 拿到了锁这时 A 执行完业务来解锁直接DEL就把 B 的锁删了。所以必须校验 value 是不是自己的而校验 删除这两步必须是原子的只能用 Lua-- KEYS[1]: 锁的 key -- ARGV[1]: 当前请求的唯一标识 if redis.call(GET, KEYS[1]) ARGV[1] then return redis.call(DEL, KEYS[1]) else return 0 end在 Java 里通过RedisTemplate.execute(RedisScript, keys, args)执行。很多人用if (get(key).equals(id)) del(key)这种写法在并发下依然会出问题因为GET和DEL之间锁可能刚好过期并被别人抢走。提示Redis 集群模式下执行 Lua 脚本所有 key 必须落在同一个槽位。如果锁的 key 是按业务 ID 拼的一般没问题但如果脚本里同时操作多个 key需要用 hash tag比如{order:1001}:lock强制它们同槽。5. RDB 与 AOF分工、代价和主从复制链路5.1 RDB 与 AOF 不是二选一而是分工八股文的对比表通常是RDB 快、文件小、可能丢数据AOF 安全、文件大、恢复慢。结论生产环境建议同时开启。这句话没错但没解释为什么同时开启更好。RDB 是某个时间点的数据快照适合做备份和全量恢复也适合主从复制的首次同步。AOF 记录的是写命令能保证更高的数据安全性但它会随着时间不断增长需要重写来压缩。Redis 4.0 之后引入了混合持久化AOF 重写时会把当前数据以 RDB 格式写到 AOF 文件开头后续增量命令以 AOF 格式追加。这样恢复时先加载 RDB 部分再重放增量命令速度比纯 AOF 快很多。关于appendfsync这个参数三种取值的安全性和性能差异很明显取值行为最多丢多少数据性能影响always每条命令都 fsync几乎不丢明显下降everysec每秒 fsync 一次约 1 秒影响很小no交给操作系统决定可能几十秒几乎无影响绝大多数业务用everysec就够了。只有对数据安全性要求极高的场景才考虑always而且要先用压测确认性能能扛住。5.2 fork 与写时复制内存为什么在 bgsave 时突然翻倍执行BGSAVE时Redis 会 fork 一个子进程来写文件主进程继续处理请求。很多人以为 fork 之后内存会翻倍其实不会立刻翻倍因为 fork 用的是写时复制Copy-On-Write父子进程共享同一份物理内存只有当某一方修改了某个内存页才会复制那一页。问题在于写入量。如果 fork 之后主进程的写入非常频繁被修改的页越来越多操作系统复制的内存就越来越多极端情况下内存占用会接近翻倍。这就是为什么在写入高峰期做BGSAVE容易触发 OOM。我踩过一次一个 20GB 的实例设置了每 10 分钟触发的自动快照某天流量翻倍内存直接冲到了 38GB触发了容器内存上限被杀。后来把自动快照的频率调低改成业务低峰期执行并且把实例内存从 32GB 提到了 64GB留出足够的 COW 余量。经验是实际内存峰值大约等于当前数据量乘以 1.5 到 2规划容量时别只看数据本身。注意vm.overcommit_memory这个内核参数需要设置为 1否则内核可能拒绝 fork 请求导致后台保存失败。5.3 主从复制的 runid、offset 与全量、增量同步主从复制的完整流程分三步。第一步从节点连接主节点发送PSYNC因为是首次同步主节点回复FULLRESYNC runid offset然后执行BGSAVE生成 RDB 文件发给从节点从节点清空自身数据后加载。这个过程中主节点的新写入会被记录在复制缓冲区里RDB 发送完之后再把这部分命令补发给从节点。第二步全量同步完成后进入命令传播阶段主节点每执行一个写命令就发给从节点同时维护一个 Offset 计数器主从双方通过比较 Offset 判断是否一致。第三步如果网络中断后重连从节点发送PSYNC runid offset主节点检查这个 runid 是不是自己的以及 offset 是否还在复制积压缓冲区repl-backlog范围内。都满足就只发缺失的那部分命令也就是增量同步否则退化成全量同步。这里有个关键参数repl-backlog-size默认 1MB在高写入量下很容易不够。一旦断开时间稍长offset 超出了缓冲区范围就会触发全量同步——主节点 fork、生成 RDB、传输、从节点加载整个过程对主节点压力很大。估算方式是平均写入速率 × 期望能容忍的断连时长比如写入 5MB/s 还想容忍 60 秒断连那至少要 300MB。5.4 主从搭建的实操顺序与几个必改参数以一台主、一台从为例从节点的配置里加上replicaof 192.168.1.10 6379 masterauth 主节点密码 replica-read-only yes repl-backlog-size 256mb repl-backlog-ttl 3600 repl-timeout 60启动后检查状态redis-cli -h 192.168.1.11 info replication # 关注 role:slave, master_link_status:up, master_repl_offset, slave_repl_offset几个容易忽略的点。第一masterauth一定不能漏否则连接会卡在master_link_status:down日志里会看到NOAUTH Authentication required。第二从节点默认read-only yes别为了图方便改成 no否则可能出现双向写入导致数据混乱。第三主节点也要设置repl-backlog-size这个参数配置在主节点上不是从节点。第四防火墙和安全组要放开 6379 端口的互访但这个端口绝对不能暴露到公网。如果要做自动故障转移单靠主从不够需要引入哨兵或者直接使用集群模式。哨兵负责监控、选主和通知客户端客户端通过哨兵获取当前主节点地址。6. 大 key、热 key 与内存治理6.1 过期删除与内存淘汰两组策略不是一回事这两个概念经常被混在一起。过期删除处理的是设置了 TTL 的 key 到期后怎么删内存淘汰处理的是内存达到 maxmemory 后删哪些 key。前者是定时任务后者是内存不足时的被动行为。过期删除用的是惰性删除 定期删除的组合访问 key 时检查是否过期过期就删同时每秒若干次随机抽查一部分设置了 TTL 的 key删掉其中过期的。这样设计是为了避免遍历所有 key 造成卡顿代价是有些过期 key 会短暂滞留。内存淘汰策略由maxmemory-policy决定策略淘汰范围适用场景noeviction不淘汰写入报错当数据库用不能丢数据allkeys-lru所有 key按最近最少使用纯缓存场景推荐allkeys-lfu所有 key按访问频率热点数据明显、有长期低频 keyvolatile-lru只淘汰设置了 TTL 的 key缓存和持久数据混用volatile-ttl优先淘汰剩余时间短的对过期时间敏感的场景allkeys-random随机淘汰访问分布均匀时可用LRU 在 Redis 里是近似实现通过maxmemory-samples控制采样数量默认 5。调大采样数会让淘汰更接近真实 LRU但会消耗更多 CPU。我们线上一般设成 10效果和开销比较平衡。注意把maxmemory-policy设成noeviction而maxmemory又设得偏小时写入会直接返回OOM command not allowed错误业务侧会报异常。要么给足内存要么选淘汰策略。6.2 大 key 怎么找、怎么拆大 key 的危害是多方面的单次操作耗时长可能阻塞其他请求删除时如果用的是DEL会同步释放内存造成卡顿主从复制和持久化时也会放大影响。找大 key 最直接的方式是redis-cli --bigkeys redis-cli --memkeys redis-cli -h host -p 6379 memory usage key--bigkeys是按类型采样统计的不会扫全库对线上影响可控。MEMORY USAGE可以精确查看单个 key 的内存占用默认采样 5 个元素估算。拆分思路要看数据结构。如果是 Hash可以按字段前缀拆成多个小 Hash如果是 List可以按时间或 ID 区间分段如果是 String先确认是不是存了序列化的大对象能拆成 Hash 就拆。删除大 key 一定要用UNLINK而不是DELUNLINK是异步释放不会阻塞主线程。另外如果业务里会批量扫描比如KEYS *或者HGETALL大 Hash一定要换成SCAN系列命令分批遍历。KEYS在生产环境应该被完全禁用有的团队甚至会在配置里改掉命令名来防止误用。6.3 热 key 与本地缓存热 key 指的是访问量极度集中的 key比如秒杀活动里的库存 key、首页配置 key。单个 key 的 QPS 太高会集中打到一个 Redis 节点上集群模式下成为瓶颈。发现热 key 可以用redis-cli --hotkeys但前提是淘汰策略为 LFU否则统计不到。也可以自己做客户端埋点统计每个 key 的访问次数。应对方式有几层。第一层是本地缓存用 Caffeine 这类工具在应用进程内缓存热点数据设置很短的 TTL比如 1 到 3 秒大部分请求根本不会到 Redis。这一层的代价是数据一致性会有秒级延迟要评估业务能否接受。第二层是 key 分散把hot:key复制成hot:key:1到hot:key:10读的时候随机选一个写的时候全部更新把压力分散到多个节点。第三层是限流降级当 Redis 响应变慢时直接走本地兜底数据保证核心链路可用。我们做秒杀时用的是第一层 第三层库存预热到本地缓存Redis 只承担扣减和最终校验Redis 出现抖动时直接拒绝新请求而不是排队等待。7. 安装部署与监控几个容易踩的实操点7.1 安装方式怎么选源码、包管理还是容器编排在 Linux 上装 Redis常见有三种方式。包管理器安装apt install redis-server或yum install redis最省事但版本通常偏旧而且默认配置不一定适合生产。源码编译安装可以指定版本和编译参数适合需要特定版本的场景缺点是升级麻烦。容器方式适合已经有编排平台的环境配置和版本都能用镜像固化下来。Windows 环境需要说明一下Redis 官方并不提供 Windows 版本官方文档里明确建议在 Windows 上通过 WSL2 运行 Linux 版本。网上流传的一些 Windows 移植版本通常停留在较老的版本功能和安全更新都跟不上只适合本地学习不要用在正式环境。如果用 Docker Compose 部署主从一个可参考的写法是services: redis-master: image: redis:7.2 container_name: redis-master command: [redis-server, /etc/redis/redis.conf] volumes: - ./master/redis.conf:/etc/redis/redis.conf - ./master/data:/data ports: - 6379:6379 restart: always redis-replica: image: redis:7.2 container_name: redis-replica command: [redis-server, /etc/redis/redis.conf] volumes: - ./replica/redis.conf:/etc/redis/redis.conf - ./replica/data:/data depends_on: - redis-master restart: always从节点的配置文件里写上replicaof redis-master 6379和masterauth。注意容器里用的是服务名而不是 IP因为容器重启后 IP 会变。数据目录一定要挂载出来否则容器重建后数据就没了。7.2 一份生产可用的配置骨架配置项很多但核心的就那些。下面这份是我从几个项目里提炼出来的骨架具体数值要按业务压测调整bind 0.0.0.0 protected-mode yes port 6379 requirepass 强密码 timeout 300 tcp-keepalive 300 # 持久化 save 900 1 save 300 10 appendonly yes appendfsync everysec aof-use-rdb-preamble yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 内存 maxmemory 8gb maxmemory-policy allkeys-lru maxmemory-samples 10 # 慢查询 slowlog-log-slower-than 10000 slowlog-max-len 512 # 危险命令重命名 rename-command KEYS rename-command FLUSHALL 几个必须强调的点bind不要写成0.0.0.0之后又把端口暴露到公网这是最常见的被入侵原因requirepass一定要设而且密码不能太弱rename-command把KEYS、FLUSHALL这类高危命令禁用掉能避免很多误操作slowlog-log-slower-than单位是微秒10000 表示 10 毫秒超过这个耗时的命令会被记录下来。7.3 可视化工具与监控指标命令行够用但排查问题时有个可视化工具会快很多。RedisInsight 是官方出品的界面清晰支持内存分析、慢查询查看和命令行。Another Redis Desktop Manager 是社区里口碑不错的客户端跨平台、启动快、支持集群和哨兵日常用起来很顺手。监控方面最重要的几个指标是命中率keyspace_hits / (keyspace_hits keyspace_misses)低于 80% 就要排查 key 设计或 TTL 设置内存使用率used_memory / maxmemory长期高于 80% 要警惕慢查询数量slowlog_len持续增长说明有耗时命令连接数connected_clients突增可能是连接池泄漏或客户端没复用连接主从延迟master_repl_offset - slave_repl_offset被拒绝的命令rejected_connections、errorstats里的各类错误计数这些指标通过INFO命令就能拿到接进 Prometheus 之后用 Grafana 做看板。我个人习惯是在看板上加一条内存增长曲线如果曲线斜率在业务低峰期也不下降基本可以确定有 key 没设 TTL 或者大 key 在堆积。最后分享一个我在排查线上问题时常用的小技巧当怀疑是某个 key 导致的问题先用redis-cli --bigkeys和--hotkeys快速定位再用MONITOR短时间抓一下命令流一定要短MONITOR本身开销很大长时间开启会拖慢实例最后用SLOWLOG GET 20看最近最慢的二十条命令。这三步走下来大部分 Redis 相关的线上问题都能定位到具体原因。如果后续要扩展我会优先把 binlog 订阅做缓存失效这条链路补上它比延迟双删更长治久安只是需要额外的中间件和运维成本。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表