“每日八股”这个系列,我写到第三篇了。写它的初衷很朴素:Redis 算是后端岗位面试里性价比最高的一块,提问频率高、追问深,但市面上大多数八股清单只给你结论,不给判断依据。这一篇我把四类高频题聚在一起——持久化机制、缓存穿透/击穿/雪崩、分布式锁、底层数据结构,末尾又补了一块容易被跳过、但生产环境最容易摔跤的缓存一致性。准备面试的朋友可以拿来梳理思路,已经在业务里的朋友也可以对照一下自己是不是真把 Redis 用明白了。
1. 持久化别停在“RDB 和 AOF 的区别”,要能讲清数据恢复链路
1.1 面试官为什么总拿持久化开场
面试里“Redis 持久化”被问到的频率极高,一个重要原因是:这个问题可以从两个维度考察。基础层面是 RDB 和 AOF 有什么区别;深一层是 fork 子进程、写时复制、刷盘策略、数据恢复顺序,这些才是区分“背过”和“用过”的分水岭。
很多同学把 RDB 理解为“定期快照”,把 AOF 理解为“追加日志”,这样回答没错,但太薄了。我见过不少候选人能流利背出save 900 1这种触发条件,但被追问一句“主从全量同步时会不会触发 bgsave”,当场就懵了。
所以我的建议是:准备持久化问题,最好能从头到尾讲一条完整链路——Redis 突然宕机,重启之后数据从哪来、为什么能恢复、会丢多少。这一节就按这条链路展开,把这些逻辑弄清楚了,八股自然不用死背。
1.2 RDB 的两个隐藏坑:fork 阻塞与写时复制
RDB 本质是把内存中的全量数据以二进制快照形式写入磁盘。触发方式最常见的就是save m n配置,比如save 900 1表示 900 秒内发生至少 1 次写操作就生成快照。但真正需要理解的是它背后的执行机制。
save是同步生成快照,会阻塞主线程,生产环境基本不会用它;日常触发的是bgsave,由主进程 fork 一个子进程,子进程负责写文件,主进程继续服务客户端。这里有个非常容易忽略的坑——fork 本身要消耗时间,而且 fork 期间主线程会短暂阻塞。
阻塞时长取决于实例内存大小和系统压力。我用过一个内存接近 80G 的实例,INFO stats里的latest_fork_usec常年停留在几百毫秒到上千毫秒,高峰期极其难受。另一个坑是写时复制(Copy On Write):fork 出来的子进程和父进程共享内存页,如果父进程在 bgsave 期间收到写请求,涉及修改的内存页会先复制一份,导致内存瞬时上涨。也就是说,一个内存快满的实例,bgsave 很可能把它推入 swap 甚至触发 OOM。
这个坑在监控上很隐蔽。我后来总结的习惯是:RDB 生成前关注used_memory_rss和系统可用内存,而不是只看used_memory;集群环境下让不同从节点错峰备份,避免所有节点同时 bgsave。
1.3 AOF 的刷盘策略与重写机制
AOF 记录的是写操作命令,相当于给 Redis 做一份操作日志。它的可靠性关键在于appendfsync配置:
| 配置项 | 行为 | 丢数据风险 | 适用场景 |
|---|---|---|---|
| always | 每次写操作后立即 fsync | 几乎不丢 | 对数据可靠性要求极高的场景 |
| everysec | 每秒 fsync 一次 | 最多丢 1 秒左右 | 生产环境默认推荐 |
| no | 交给操作系统决定刷盘 | 可能丢多秒甚至分钟级 | 追求极致性能、能忍受丢失 |
提示:
everysec并不绝对意味着“最多丢一秒”。极端情况下比如主机断电,操作系统写缓存没落盘,实际丢失可能更多。如果业务对数据完整性极度敏感,还是得用always。
AOF 文件会随着持续写入越来越大,所以有 AOF 重写机制。注意,重写不是把旧文件压缩,而是 fork 子进程,根据当前内存中的数据重新生成一份只包含最少命令的 AOF 文件。触发条件是auto-aof-rewrite-percentage 100和auto-aof-rewrite-min-size 64mb,意思是文件超过 64MB 并且较上次重写增长了 100% 才触发。重写期间新写入的命令会先进入重写缓冲区,完成后追加到新文件再原子切换。理解了这套流程,你就知道为什么可以通过调大阈值来降低磁盘 IO 频率。
如果哪天 AOF 文件损坏了,Redis 启动时会拒绝加载。这时候可以用redis-check-aof --fix修复文件,再用修复后的文件启动,这是线上救急的常用手段。
1.4 混合持久化的加载优先级与生产配置
Redis 4.0 之后提供了混合持久化能力。aof-use-rdb-preamble yes开启后,AOF 文件开头部分直接是 RDB 格式的全量数据,之后才是增量命令。好处非常实在:纯 AOF 文件几 GB 时,启动回放可能要几分钟;混合持久化相当于先加载一个相对小的 RDB 段,再补少量增量命令,加载速度能快好几个量级,同时保留了较低的丢数据风险。
启动时的加载优先级值得记牢:只要开启了 AOF,Redis 首选 AOF 文件恢复数据;如果 AOF 是混合格式,先加载其中的 RDB 段,再用后续命令进行回放。只有 AOF 不存在或损坏且无法修复时,Redis 才会去加载 RDB 文件。很多同学以为 RDB 是默认加载源,这是一个非常常见的误区。
生产上的配置思路我一般分两种情况:主从架构里承担核心数据的节点,开启 AOF 且混合持久化,同时保留 RDB 做冷备;如果 Redis 纯做缓存、主要用于加速,可以只开 RDB 甚至什么都不开,把性能让出来。持久化配置没有统一标准,取舍依据永远是“业务能不能承受那么多数据丢失”。
2. 缓存穿透、击穿、雪崩:从背概念升级到“治理链路”
2.1 先分清三者,别让面试官觉得你在背定义
这三个词出现频率实在太高,但很多人回答时会混着讲。先记住核心差异:
- 穿透:查询的数据在缓存和数据库中都不存在,每次请求都绕开缓存直达 DB,最常见是恶意请求或空参攻击。
- 击穿:某个热点 key 在过期那一瞬间,大量请求同时打到 DB,像单点被击穿。
- 雪崩:大量 key 同时过期,或 Redis 整体不可用,导致 DB 请求量骤然放大。
三者共性确实是“缓存失效导致后端压力变大”,但治理思路完全不同。穿透要解决的是“空数据怎么挡在缓存门外”,击穿要解决“单点 key 过期时如何让只有少量请求回源”,雪崩要解决“批量失效怎么打散、系统整体怎么兜底”。方向清楚了,细节才铺得开。
2.2 穿透治理:布隆过滤器与空值缓存的取舍
穿透的标准应对方案之一是布隆过滤器。核心思路是在 Redis 前面再加一道过滤:请求进来先查布隆过滤器,过滤器说“不存在”就直接返回,避免把压力引到 Redis 和 DB。
布隆过滤器的原理是用多个哈希函数映射到 bit 数组,查询时只要有一个 bit 为 0,就说明数据一定不存在。它有两个参数需要提前定好:预期的数据量 n 和可容忍的误判率 p。常见计算公式是:bit 数组长度 m = -(n * ln(p)) / (ln 2)^2,哈希函数数量 k = (m / n) * ln 2。举个例子,预期 1000 万个 key,误判率 1%,算下来大约需要 11.4MB 的 bit 数组和 7 个哈希函数。误判本身问题不大,最多放进来一次空查询,DB 没有结果也不会产生脏数据。
但布隆过滤器有个硬伤:不支持删除。业务里如果大量删除 key,就得考虑重建,或者改用支持删除的变体,比如计数布隆过滤器、布谷鸟过滤器。所以我会把空值缓存作为第二道兜底——直接把不存在的 key 记成 null,给很短 TTL,几十秒就够了。代码上基本就是一个SET key null EX 60,能挡掉大部分重复空查询。
不过空值缓存也有代价:一是大量空 key 占内存,需要监控 key 数量;二是这些 null key 的 TTL 到期时会出现一个“穿透小高峰”。线上更稳的做法是布隆过滤器做第一道防线,空值缓存做第二道,两层配合。
2.3 击穿治理:互斥锁和逻辑过期的完整实现
击穿的经典场景是微博热搜、商品详情这类高热度 key。缓存里这个 key 本来存在,但正好在过期那一刻有大量请求进来,第一波请求发现缓存为空后同时去 DB,DB 很容易被打垮。
互斥锁方案比较直观:缓存失效时,第一个发现问题的线程尝试SET lock_key NX PX 30000,拿锁成功后才去 DB 查询并重建缓存;其他线程拿不到锁,要么直接返回旧值,要么 sleep 一小会再去查缓存。Java 侧的示意代码大致是:
String key = "hot:product:1001"; String lockKey = "lock:hot:product:1001"; String requestId = UUID.randomUUID().toString(); String value = cache.get(key); if (value == null) { Boolean locked = redis.set(lockKey, requestId, "NX", "PX", 30000); if (Boolean.TRUE.equals(locked)) { try { // 再加一次缓存查询,防止其他线程在等待期间已经重建 value = cache.get(key); if (value == null) { value = db.query(key); cache.set(key, value, 60); } } finally { // Lua 脚本释放锁,确认 requestId 是自己的 redis.eval("if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end", Arrays.asList(lockKey), Arrays.asList(requestId)); } } else { // 没抢到锁,短暂休眠后读缓存 Thread.sleep(50); value = cache.get(key); } } return value;互斥锁方案逻辑清晰,但有两个问题:一是热点 key 过期瞬间,没抢到锁的请求如果全部 sleep 等待,可能引发接口链路阻塞;二是锁服务本身如果出问题,比如 Redis 不可用,缓存操作也会跟着报错。所以它更适合并发峰值可控、下游 DB 有相应限流兜底的场景。
如果对实时一致性要求没那么高,可以用逻辑过期方案。缓存 value 里直接存一个对象,对象里带真正的 TTL 时间戳;请求进来发现逻辑过期后,尝试拿锁去重建,拿不到锁的请求继续返回旧值。这样回源的请求只有少量重建线程,其他请求零等待。代价是短暂的数据不一致,以及重建失败时旧值会一直沿用。像活动页固定文案这种读多写少、允许几秒延迟的场景,我非常推荐逻辑过期。
2.4 雪崩治理:随机过期时间只是开始,后面还有三板斧
雪崩的典型原因之一是批量写缓存时,所有 key 被设置了同一个过期时间。最基础的做法是在 TTL 上加入随机值:
SET key1 value1 EX 3600 SET key2 value2 EX 3600 + random(0, 300)这样做能把“同时过期”变成“先后过期”,压力被摊开。但它只能缓解,不能根治。如果业务在启动时批量预热 5 万个 key,就算 TTL 各不相同,热数据集中在同一批加载,DB 也一样会承压。
所以完整治理至少还要加三招:
- 本地缓存兜底:在应用层再加一层 Caffeine 之类的本地缓存,Redis 失效时至少还有本地副本顶着,等待回源。
- 请求限流熔断:在 DB 访问层加入线程池、信号量或限流组件,超出的流量直接返回默认值或排队等待。
- 热点 key 拆分:把一个高热度 key 拆成多个副本 key,比如
hot:1001:0到hot:1001:9,靠随机前缀分散到不同分片。
踩坑提醒一句:只做随机过期时间而忽略本地缓存兜底,雪崩时 Redis 大量 key 失效,DB 照样会被打崩。我之前在电商活动项目里就见过这种半吊子方案,后来加了本地缓存,DB 峰值 QPS 直接降了一个数量级。
3. 分布式锁:setnx 只是表象,边界条件才是深水区
3.1 一个合格的分布式锁,答案其实是四段式
面试里的分布式锁题,十个人有八个会脱口而出“用 setnx”。但只答到这里,基本会被追问“能保证可重入吗”“锁过期了怎么办”“释放时会把别人的锁删掉吗”。这四个问题才是这道题的分水岭。
一个能用于生产的 Redis 分布式锁,至少满足四点:
- 互斥性:同一时刻只有一个客户端能持有锁。
- 防死锁:必须设置过期时间,防止持有者崩溃后锁永远不释放。
- 防误删:释放锁时要校验持有者身份,避免删掉别人刚拿到的锁。
- 释放原子性:判断和删除必须在一个原子操作内完成。
这就是我常说的“四段式”。把这些点全说出来,面试官立刻知道你踩过坑,而不是只背过命令。
3.2 从 setnx 到 SET NX PX,再到 Lua 释放
最原始的写法是SETNX之后再单独执行EXPIRE。这个方案的问题很经典:两步操作不原子,如果SETNX成功后应用进程崩溃,EXPIRE永远没机会执行,锁就成了死锁。
正确写法是一条命令完成写入和过期设置:
SET lock:order:1234 6f8a2c9d-3b1e-4f88-9a7c-1c2d3e4f5a6b NX PX 30000value 必须是全局唯一标识,这样释放锁时才能确认“是我自己的锁”。释放锁也不能先 GET 再 DEL,因为 GET 和 DEL 之间如果另一个客户端刚拿到锁,你一个 DELETE 就把别人的锁删掉了。判断和删除必须绑定在同一个 Lua 脚本里:
if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end这个脚本几乎可以直接搬进代码库,是我在多个项目里实际用过的方案。到这里,一个“能用的锁”已经成立了:有互斥、有过期时间、不会误删。接下来进阶问题才是拉开差距的地方。
3.3 Redisson 看门狗续期:续的不是锁,是业务信心
锁过期时间的设置有个两难:设短了,业务还没执行完锁就到期,多个线程并发;设长了,持有者如果真崩溃,其他客户端要等很久。Redisson 用“看门狗”机制缓解这个问题。
Redisson 里,如果创建锁时不指定leaseTime,默认锁有效期为 30 秒,同时有一个后台线程每 10 秒检查一次锁是否仍被持有;如果业务还活着,就自动续期到 30 秒。这样既不用怼一个超长过期时间,也能降低“业务没执行完锁却到期”的概率。如果进程挂了,看门狗线程自然停掉,锁会在约 30 秒后被 Redis 回收。
这里有两个容易踩的细节。第一,看门狗只在你没有显式传leaseTime时生效;一旦你写了lock.lock(10, TimeUnit.SECONDS),就不会续期,10 秒后就过期。第二,看门狗续期走的是后台线程,如果把锁用到了异步线程里,非常容易出现 ThreadLocal 身份信息拿不到的问题。我当时排查过一起“锁没到期却被续期”的诡异故障,根因就是释放锁时校验身份失败,后续一查,锁对象被错用在了自己起的线程池里。
3.4 RedLock 争议与主从切换丢锁的实际选型
再升级一个层次:主从架构中,master 刚把锁写入内存,主从异步复制还没完成,master 挂了,slave 顶上后锁丢失,另一个客户端就可能拿到同一把锁。这就是主从切换丢锁问题。
听起来 RedLock 能解决:向 5 个独立 Redis 节点同时申请锁,超过半数返回成功才算获锁。但分布式系统社区对 RedLock 的争议一直很大,核心论点包括:它依赖“节点宕机不通知”这类人为假设,而真实场景中时钟跳跃、网络分区都可能导致锁失效;它并没有真正绕过同步问题,只是把单一故障变成了多数派故障。如果你面试时能说出 Martin Kleppmann 那篇文章和 Redis 作者回应的大致分歧,绝对加分。
实际选型上,我的观点比较务实:如果业务场景已经复杂到需要严格锁语义,大概率不应该只靠 Redis 锁。可以用数据库唯一约束表,或者用 ZooKeeper 临时顺序节点,后者靠 session 心跳保活,客户端崩溃后锁自动消失,语义更接近正规分布式锁。Redis 锁真正适合“性能优先、允许极端场景下短暂并发”的业务,比如秒杀库存防重、任务调度去重。几种方案对比:
| 方案 | 可靠性 | 性能 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| Redis 单节点锁 | 一般 | 很高 | 低 | 性能优先、允许极端并发 |
| Redisson 分布式锁 | 较高 | 高 | 中 | 大多数微服务业务首选 |
| RedLock | 理论争议大 | 中 | 高 | 仅在明确需要多数派容错时考虑 |
| ZooKeeper 临时节点 | 高 | 中 | 中 | 强一致、可靠性要求高的场景 |
我的建议是:默认 Redisson 就够了,RedLock 如果不是业务方明确要求,真不用刻意上。面试时把它当成“你知道争议点在哪”的加分话题来准备更合适。
4. 数据类型底层结构:面试官深挖到这里的概率很高
4.1 redisObject:每个 value 都有的“身份证”
Redis 里每个 key 和 value 都是一个redisObject,除了真正的数据指针ptr,还保存着类型type、编码方式encoding、引用计数refcount等信息。这个结构的意义在于:同一个命令在不同场景下可以用不同底层结构存储,从而在内存占用和访问效率之间做权衡。
面试时你不需要背出结构体每个字段,但至少要知道为什么 Redis 要搞一个 encoding 字段。最直接的原因是内存太贵。比如一个哈希对象只有两个字段,用完整哈希表存就很浪费;用紧凑的 listpack 存,可能只需要几十字节。这就是编码转换机制存在的价值。
4.2 String 编码切换与 SDS 为什么节省内存
String 类型最常被问,因为它有三种编码:
- int:value 能被解析为 64 位整数时使用,比如
SET age 25,直接存整数,连字符串指针都省了。 - embstr:字符串长度不超过 44 字节(Redis 3.2 之后),
redisObject和 SDS 头在连续内存块里,一次内存分配搞定。 - raw:字符串超过 44 字节,或者被
APPEND等修改导致原有结构无法继续承载时,切换为 raw 编码,需要多次内存分配。
这里有个容易追到的小细节:对 int 编码的字符串执行APPEND,它会直接变 raw,因为长度变了、内存布局被打散。所以线上如果某个 string 经常被 append 修改,编码大概率不在最优状态。这也是为什么 string 适合存小值、不适合频繁拼接。
SDS(简单动态字符串)相比 C 字符串的优势,核心是三点:O(1) 获取长度、二进制安全(中间可以包含 \0)、通过预分配空间减少扩容次数。用一句话说就是“比 C 字符串更懂内存”。
4.3 Hash、List、ZSet 的结构选择与转换时机
这一块是面试深水区的经典。几个常用类型的编码切换大致是:
| 类型 | 小型时编码 | 转换条件 | 大型时编码 |
|---|---|---|---|
| Hash | listpack(旧版叫 ziplist) | 元素数 > 128 或 单个 value > 64 字节 | hashtable |
| ZSet | listpack | 元素数 > 128 或 成员长度 > 64 字节 | skiplist + dict |
| List | quicklist(由多个 listpack 节点组成) | 节点大小和深度可控 | quicklist |
| String | int / embstr | 长度 > 44 或不可解析为整数 | raw |
面试官问 ZSet,几乎必问跳表。跳表本质上是一种多层链表,通过概率性的索引层实现 O(log n) 的读写复杂度。和红黑树比,跳表实现更简单,范围查询也方便——要查一个范围,直接用最底层链表顺序走就能拿到结果,而红黑树需要额外维护前驱后继。Redis 选跳表而不是红黑树,一个重要原因就是编码和维护成本低。
理解编码转换时机对性能调优很有帮助。一个本来只有几十个小字段的 Hash,如果后续慢慢变成几个大字段,它可能已经悄然切到 hashtable;此时再用HSET做写操作,内存开销明显上升。排查时先看一眼OBJECT ENCODING key,立刻确认。我踩过一次类似的坑:某个 Hash 缓存越写越大,最后单字段几十 KB,内存增长异常,才发现它早就从 listpack 切到了 hashtable;后来改成大字段单独做 key,内存才降下来。
4.4 渐进式 rehash 与 big key 排查
哈希表扩容时,Redis 不会一次性把所有元素搬进新数组,而是用渐进式 rehash:保留旧表和新表两个哈希表,每次读写操作顺便迁移一个桶,直到旧表清空。好处很明显,避免大表扩容瞬间阻塞主线程。
触发扩容的条件一般是负载因子超过 1,超过 5 会强制扩容。这个机制在生产排查里有实际意义:当实例在跑 bgsave 时,rehash 会被延后,因为写时复制会让内存 copy 放大;所以大实例上你会看到used_memory和used_memory_rss差值很大,往往是 rehash 加写时复制双重作用的结果。
big key 也要提一嘴。一个几 MB 甚至几十 MB 的 key,删除时用DEL会阻塞主线程几百毫秒;现在正确的做法是用UNLINK异步释放。排查可以用redis-cli --bigkeys扫描,但注意高版本推荐用--scan配TYPE和DEBUG OBJECT精确定位,避免全库扫描额外消耗。我处理线上一次 Redis 慢查询时,最终定位到罪魁祸首就是一个存 JSON 字符串的 big key,单 key 几百 KB,直接拖慢了客户端批量响应。
5. 缓存一致性:八股里最少提及、生产中最常摔跤的地方
5.1 先删缓存还是先更新 DB:两种顺序都做不到“绝对”
缓存一致性这个话题,面试里没有三兄弟问得那么频繁,但线上踩坑的人特别多。核心问题就一个:缓存和数据库是两套存储,写操作怎么编排,才能让缓存尽量不出现旧数据。
先删除缓存、再更新数据库:并发读的时候,读请求在写请求更新 DB 之前打进来,发现缓存没数据,于是把 DB 里的旧值写回缓存。这个旧值可能长期存在,一致性被破坏得比较严重。
先更新数据库、再删除缓存:是很多团队的默认方案,因为整体风险低一些。但它不是万无一失——如果删缓存这一步失败,比如网络抖动、请求超时,缓存里就一直是旧值。所以这个方案一定要在应用层把“删除缓存”做成可补偿的操作,最常见的补偿是把删除操作投递到消息队列,失败后重试。
5.2 延时双删的边界与实际效果
延时双删是对“先删缓存、再更 DB”方案的打补丁:写完 DB 后,等一段时间,比如 500ms,再删一次缓存。目的是覆盖“某个读请求在第一次删缓存之后、更新 DB 之前,把旧值写回缓存”的窗口。
能讲出延时双删的局限,比能讲出这个方案本身更加分。它有两个硬伤:
- 等待时间是经验值。业务读写耗时不同,延迟窗口不是固定的,设长了影响读性能,设短了又可能漏删。
- 它是概率性解决问题,不是可靠解决。如果那 500ms 里刚好有多个并发写读交织,仍然可能留下旧缓存。
所以延时双删适合“偶尔删缓存失败、且允许秒级不一致”的场景,比如非交易类页面文案、用户资料列表。真要是每笔数据都不能出错,那就不该用缓存方案了。
5.3 binlog 订阅方案的最终一致实践
更可靠的最终一致方案,是把缓存更新任务从业务代码里解耦,通过订阅数据库 binlog 来完成。MySQL 开启 binlog,用 row 格式记录变更;Canal 伪装成 MySQL 从节点,解析 binlog 里每步增删改,把变更事件推给消费者,消费者负责删除或更新对应缓存。
这个方案的好处很实在:
- 业务代码不用每个写操作都手动拼“删缓存”动作,减少遗漏。
- 删除失败可以天然重试,因为 binlog 消息不会因为一次消费失败就消失。
- 对缓存层不侵入,新增缓存字段不用改业务代码。
代价也明确:需要额外部署和维护 Canal、消息队列;缓存和 DB 的一致性被推到异步链路,极端情况下会出现秒级到几十秒的延迟。我在物流类系统里用过这个方案,业务能接受“状态更新慢几秒”,但长期不更新造成的客诉完全无法接受。最终通过 Canal 解决了大部分问题,还顺手用 binlog 事件做了历史变更审计,算是个副产物。
5.4 一致性没有银弹:工程本质是权衡
你会发现,缓存一致性本质上不是技术问题,而是权衡问题。真要强一致,最直接的办法就是不用缓存,所有读走 DB;如果一定要用缓存,就得接受一个约束:不可能同时做到强一致、高性能、高可用。
大多数业务系统最终都选了“最终一致”。工程目标不是把不一致概率降到零,而是压到业务可接受的范围,同时为异常准备补偿手段。比如过期时间本身就是一种补偿:就算中间出了岔子,TTL 一到,缓存被清掉,下次读就会自然从 DB 重新拿数据。我在设计缓存方案时,脑子里始终带着优先级:第一,不能打垮 DB;第二,尽量避免长时间脏数据;第三,极端情况允许短暂不一致,但不能丢数据。
面试官顺着缓存一致性继续追问,你把这套权衡讲出来,比单纯背“延时双删”“Canal”这些名词要打动人得多。
“每日八股”写到第三篇,我最想说的其实是:八股不是背概念,是要给每个知识点找到对应的生产场景。Redis 的每个机制设计背后都有具体痛点,常见面试问题基本是在帮你复盘那些线上事故。你工作中踩过的坑、调过的参数、看过的日志,才是面试时真正能拿去讲的素材。
下一篇我打算把主从复制、哨兵和 Cluster 集群的高频问题整理一下,尤其是复制延迟、脑裂、选主这类容易出事故的点。如果面试题催得紧,也欢迎在评论区告诉我你最想听哪个方向,我尽量优先写。