想象你经营一家餐厅:主厨(主库)负责所有新菜和改菜单(写操作),几位帮厨(从库)照着他的菜谱(binlog)提前复刻同样的菜,负责把现成的菜端给客人(读操作)。晚高峰时主厨突然晕倒——按常理餐厅只能停业,但一家有韧性的餐厅早备好了三道保险:帮厨立刻顶上(主从切换)、普通点单绕开主厨(读写分离)、服务员隔几秒再去催菜而不是挤爆后厨(连接重试)。 这篇文章就用这家餐厅做导游,把数据库韧性拆成七站:先看懂复制与切换,再学会路由与重试,最后用演练、监控和渐进演进攻克真实业务。你不需要懂数据库,只需要带着“开餐厅”的常识进来。

岗位关联

数据库韧性的真谛:故障总会发生,系统的价值取决于恢复的速度与优雅程度。掌握主从切换、读写分离与连接重试,是 SRE 与后端工程师守住核心链路的关键技能。

目录
  1. 可视化演示
  2. 1. 主从复制:帮厨照着菜谱复刻
  3. 2. 主从切换:主厨倒下,帮厨顶上
  4. 3. 读写分离:谁做菜、谁端盘
  5. 4. 连接重试:催菜要讲节奏
  6. 5. 故障演练:后厨的消防演习
  7. 6. 监控告警:后厨仪表盘与传呼机
  8. 7. 渐进演进:从路边摊到连锁餐厅
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

三步展示数据库韧性机制:主库到从库的同步复制、写主读从的读写分离路由、以及故障检测-主从切换-连接退避重试的自动恢复流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

主从复制 主:写入节点 从:副本节点 复制:数据同步 Replication 同步复制RPO=0保证零丢失。

1. 主从复制:帮厨照着菜谱复刻

餐厅的招牌菜全由主厨一人掌勺,可一旦主厨请假或病倒,生意就得停摆。聪明的老板会让主厨把每道菜的详细做法写成菜谱,几位帮厨照着菜谱提前把一样的菜复刻出来放在备餐台上。这样即使主厨不在,客人照样能点到“同款”。

数据库里的情况如出一辙。主库负责全部写操作,每次变更都记录成一份“菜谱”,也就是二进制日志(binlog);从库不停地拉取这份日志并回放,从而得到一份几乎相同的数据副本。这个机制就是主从复制(master-slave replication),最常见的是一主多从:一个主库干活,多个从库复制。

为什么要强调“几乎相同”?因为最常用的异步复制下,主库提交事务后立刻返回客户端,根本不等待任何从库确认。这样做主库写入快,代价是从库的数据总会滞后一小段时间,几毫秒到几秒不等。这个滞后就是复制延迟(replication lag),可以精确量化:

复制延迟决定了“帮厨端出来的菜有多新鲜”,更决定了下一站我们将看到的关键问题:当主厨突然倒下时,餐厅最多可能丢掉多少道菜。理解了延迟,你就已经握住了 RPO(允许丢失的数据量)的钥匙。

lag=master_pos−slave_pos\displaystyle \text{lag} = \text{master\_pos} - \text{slave\_pos}
复制延迟就等于主库最新的日志位点减去某个从库已经回放的位点,相当于“帮厨的菜比主厨的菜慢了多少步”。
  1. 主库位点 master_pos主库当前已经写到 binlog 的第几个字节,相当于菜谱最新写到了哪一行。
    master_pos\text{master\_pos}
  2. 从库位点 slave_pos某个从库已经回放到第几个字节,相当于帮厨已经照着菜谱复刻到哪一行。
    slave_pos\text{slave\_pos}
  3. 复制延迟 lag两者相减得到从库落后的数据量,除以回放速度就能换算成秒。
    lag=master_pos−slave_pos\text{lag} = \text{master\_pos} - \text{slave\_pos}

2. 主从切换:主厨倒下,帮厨顶上

晚高峰,主厨突然晕倒在灶台前。没有预案的餐厅只能关门谢客;而有预案的餐厅里,领班第一时间确认主厨失联,然后从帮厨中选出最资深的一位——往往是最新、最完整地复刻了菜谱的那位,宣布他升任主厨,并把客人的点单向新主厨引导。客人们只感觉上菜慢了半分钟。

数据库世界的这位“领班”叫哨兵(Sentinel)或 HA 控制器。主从切换(failover)的自动化流程正好三个环节。首先是故障探测:哨兵连续多次心跳收不到主库的回应,判定主库失联。然后是候选选主:从所有从库中依据复制位点、健康状态选出最接近主库的那个。最后是切换升级:新主库上线,旧主库降级,对外服务的入口被重新指向新主库。

整个过程要花多久?业界用 RTO(恢复时间目标)来度量。它不是一个拍脑袋的数字,而是三个耗时段的加总:

预案好不好,光靠纸面推演不算数,要靠一次次“演习”来掐表验证——这正是后文故障演练的伏笔。而连接池如何优雅地感知这次切换,就是第四站的催菜学问。

RTO=Tdetect+Tswitch+Treconnect\displaystyle \text{RTO} = T_{\text{detect}} + T_{\text{switch}} + T_{\text{reconnect}}
RTO 就是从故障发生到业务完全恢复的总时长,分成探测、切换、重连三段,哪一段拖沓,RTO 就变大。
  1. 故障探测时间心跳超时加上连续确认所需的时间,通常几秒到几十秒,取决于心跳间隔与次数。
    TdetectT_{\text{detect}}
  2. 选主切换时间选出候选从库、补齐与主库的数据差异并提升为主库的过程。
    TswitchT_{\text{switch}}
  3. 重连恢复时间应用的连接池发现新主库可用,重建连接并重新路由,业务恢复读写。
    RTO=Tdetect+Tswitch+Treconnect\text{RTO} = T_{\text{detect}} + T_{\text{switch}} + T_{\text{reconnect}}

3. 读写分离:谁做菜、谁端盘

餐厅里一多半客人点的都是提前复刻好的招牌菜(读请求),只有加菜、改菜单、结账这类操作才需要主厨亲手处理(写请求)。老板在门口安排了一位分菜员:点招牌菜的引导到帮厨台,涉及改菜单的才带进主厨厨房。这样主厨不用被每一道菜累垮。

数据库的读写分离(read-write splitting)就是在应用和数据库之间放这样一位“分菜员”,通常是路由中间件或客户端路由库。它的路由规则很明确:写语句(INSERT/UPDATE/DELETE)一律发送到主库;读语句(SELECT)按策略分发到各个从库,比如轮询、随机,或者按业务标记强制走主库。

把读写分开后,从库的读压力被摊薄,系统的总读吞吐随之提升。总的能力可以用各路读 QPS 相加来表达:

分菜员还要懂一条“一致性权衡”:客人刚让主厨改了菜单,转头就去帮厨台点老版本,很可能吃到旧菜。所以“刚写就读”的请求必须绕回主厨。这个权衡在面试中常被追问,是读写分离的灵魂。

QPSreadtotal=QPSreadmaster+∑i=1nQPSreadslavei\displaystyle \text{QPS}_{\text{read}}^{\text{total}} = \text{QPS}_{\text{read}}^{\text{master}} + \sum_{i=1}^{n} \text{QPS}_{\text{read}}^{\text{slave}_i}
系统能扛住的总读请求量,等于主库自己承担的读请求,加上 n 个从库各自承担的读请求之和。
  1. 主库读流量事务内的读、以及要求强一致的关键读,必须绕回主库执行。
    QPSreadmaster\text{QPS}_{\text{read}}^{\text{master}}
  2. 从库读流量求和对从库 1 到 n 的所有从库读 QPS 求和,n 是当前从库的数量。
    ∑i=1nQPSreadslavei\sum_{i=1}^{n} \text{QPS}_{\text{read}}^{\text{slave}_i}
  3. 总读吞吐两路相加,得到系统能支撑的最大读压力,n 越大读扩展能力越强。
    QPSreadtotal=QPSreadmaster+∑i=1nQPSreadslavei\text{QPS}_{\text{read}}^{\text{total}} = \text{QPS}_{\text{read}}^{\text{master}} + \sum_{i=1}^{n} \text{QPS}_{\text{read}}^{\text{slave}_i}

4. 连接重试:催菜要讲节奏

主厨倒下的那一刻,如果一百个服务员同时冲进后厨大喊“我的菜呢?”,后厨只会彻底瘫痪。有经验的服务员会隔几秒来看一眼:第一次等半秒,第二次等一秒,第三次等两秒……间隔越拉越长。这不是消极怠工,而是给新主厨恢复的时间。

应用与数据库之间通过连接池维持着一组常驻连接。主库故障时,这些连接会迅速失败;客户端绝不能立刻疯狂重连,否则会把正在恢复中的数据库打爆。正确的做法是执行带退避的重试算法,业内叫指数退避(exponential backoff):

催菜要讲节奏,重试要讲退避。很多线上事故之所以被放大,不是因为第一次故障,而是因为无数客户端用同样猛烈的频率轰炸数据库,把“小感冒”捶成了"ICU"。

tn=min⁡(t0×2n,  tmax⁡)+rand(0,t0)\displaystyle t_n = \min(t_0 \times 2^{n},\; t_{\max}) + \text{rand}(0, t_0)
第 n 次重试前等待的时长:基础间隔 0.5 秒乘以 2 的 n 次方,封顶 8 秒,再加上一点随机抖动,避免所有客户端同时重试。
  1. 基础间隔 t0第一次重试前等待 0.5 秒,给系统一个喘息的机会。
    t0t_0
  2. 指数放大 2 的 n 次方第 n 次重试间隔翻倍:0.5、1、2、4、8 秒。
    t0×2nt_0 \times 2^{n}
  3. 上限与抖动 rand间隔超过 8 秒就不再翻倍;再加 0 到 0.5 秒的随机数,防止“惊群”式同步重试。最终得到第 n 次的等待时间。
    tn=min⁡(t0×2n,  tmax⁡)+rand(0,t0)t_n = \min(t_0 \times 2^{n},\; t_{\max}) + \text{rand}(0, t_0)

5. 故障演练:后厨的消防演习

真正的高手不会等主厨倒下才开始想对策。有经验的餐厅每季度做一次“主厨消失演习”:故意请走主厨,掐表看帮厨多久能顶上、哪几道菜受影响、哪些客人会流失。演习中暴露的流程漏洞,在真出事时就是省下来的真金白银。

数据库故障演练属于混沌工程(Chaos Engineering)的实践:在生产或预发环境人为注入故障,比如 kill 掉主库进程、模拟断网、制造磁盘变慢,来验证主从切换、连接重试、监控告警是否真的按预案工作。演练的目的不是制造混乱,而是在可控范围内提前暴露脆弱点。

演练结束后,要回答两个量化问题:RTO 是多少?RPO 是多少?RTO 我们已经认识;RPO(恢复点目标)指最多可能丢失多少数据——异步复制下,主库故障瞬间未同步的事务就丢了。这两个指标共同决定系统的可用性,可用性可以用经典公式估算:

消防演习的最终目标,是让“主厨倒下”从一部灾难片变成一场有惊无险的常规操作。而演习效果好不好,得靠后厨那块仪表盘来盯着。

A=MTBFMTBF+MTTR×100%\displaystyle A = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}} \times 100\%
系统的可用性等于平均无故障时间(MTBF)除以平均无故障时间与平均恢复时间(MTTR)之和。MTTR 越小,可用性越高。
  1. MTBF 平均无故障时间两次故障之间的平均间隔,靠线上统计和演练数据积累而来。
    MTBF\text{MTBF}
  2. MTTR 平均恢复时间从故障发生到恢复的平均时长,本质上就是多次演练测得的 RTO 均值。
    MTTR\text{MTTR}
  3. 可用性 A无故障时间占全部时间的比例。MTTR 每压缩一分钟,可用性就向 100% 靠近一步,这就是我们反复演练压缩 RTO 的意义所在。
    A=MTBFMTBF+MTTR×100%A = \frac{\text{MTBF}}{\text{MTBF} + \text{MTTR}} \times 100\%

6. 监控告警:后厨仪表盘与传呼机

餐厅后厨挂着一块仪表盘,实时显示三类关键信息:出菜速度(QPS)、排队长度(连接数)、招牌菜复刻进度(复制延迟)。领班还给不同等级的异常配了不同的“传呼机”:冰箱温度偏高响铃提醒(P2),主厨失联直接拨 120(P0)。

数据库监控同样围绕关键指标展开:主从复制延迟、连接数与连接池活跃数、QPS 与错误率、慢查询、主库 CPU 与 IO。真正重要的是“可观测”——不仅能看当前数值,还能在故障后回溯完整的时间线,还原“先发生了什么、后发生了什么”。

有了指标,还要让告警自动触发动作。错误率是其中最通用的触发器:

监控不是“多画几张图”,而是把仪表盘变成能自动呼救、自动处置的哨兵。不过,这套精细的设备不是一开业就要配齐的——小餐馆和大连锁的打法,完全不同。

err_rate=error_counttotal_requests×100%\displaystyle \text{err\_rate} = \frac{\text{error\_count}}{\text{total\_requests}} \times 100\%
错误率等于统计窗口内的失败请求数除以总请求数。当错误率连续 3 分钟超过 1%,就触发 P1 告警并进入自动恢复流程。
  1. 错误次数一段时间窗口内,请求失败的次数,比如连接失败、超时、返回错误码。
    error_count\text{error\_count}
  2. 总请求数同一时间窗口内的全部请求数量,是计算比例的基数。
    total_requests\text{total\_requests}
  3. 错误率 err_rate两者相除再乘 100%,得到失败占比。阈值命中后,按 P0/P1/P2 分级告警,并联动自动切换、自动重连等恢复动作。
    err_rate=error_counttotal_requests×100%\text{err\_rate} = \frac{\text{error\_count}}{\text{total\_requests}} \times 100\%

7. 渐进演进:从路边摊到连锁餐厅

路边摊一个锅一个灶,硬搬连锁餐厅的管理制度只会累死自己。正确的路径是:先保证单灶能活——做好备份与恢复;客流上来了,加帮厨——部署只读从库;再引入分菜员——上读写分离路由;最后才是主厨接力预案、消防演习和仪表盘。

数据库韧性方案在不同规模团队的适配性截然不同。初创团队可能连专职 DBA 都没有,最合适的是云数据库托管,自带主从高可用与自动切换;中等团队引入自建主从加中间件读写分离,开始自己掌控切换流程;大规模团队则需要混沌工程平台、故障自愈和全链路可观测体系。

演进到什么程度才算够?回答这个问题靠的是 SLO(服务级别目标)。把可用性目标换算成“停机预算”,你就能直观地知道每年可以容忍多久的故障:

韧性不是一步登天,而是“先活下来,再变强”的迭代过程。路边摊有路边摊的打法,连锁店有连锁店的体系,关键是每一步都匹配当前的客流量和团队人手。

budget=365×24×60×(1−SLO)  min/yr\displaystyle \text{budget} = 365 \times 24 \times 60 \times (1 - \text{SLO}) \; \text{min/yr}
全年停机预算等于一年总分钟数乘以允许失败的比例。SLO 定 99.99% 时,一年最多只能停机约 52.56 分钟。
  1. 全年总分钟数365 天乘以每天 24 小时再乘以 60 分钟,得到一个固定的基数。
    365×24×60365 \times 24 \times 60
  2. SLO 可用性目标与业务方约定好的达标率,比如 99.9% 或 99.99%。
    SLO=0.9999\text{SLO} = 0.9999
  3. 停机预算 budget基数乘以失败占比,得到每年的“故障额度”。目标定得越高,留给故障的空间越小,对架构的要求也越高。
    budget=365×24×60×(1−SLO)  min/yr\text{budget} = 365 \times 24 \times 60 \times (1 - \text{SLO}) \; \text{min/yr}

AI 应用场景

电商大促的读多写少场景

阶段:在线交易系统的数据访问支撑

解决的问题:大促流量是平时的十余倍,商品详情、库存余量都是高频读请求,单库根本扛不住。传统“一台数据库硬扛”的做法,要么加机器上限也有限,要么主库故障时页面直接 502。引入读写分离后,读流量被分流到多个从库,主库只处理下单与支付等写操作;再配合主从自动切换与连接池退避重试,主库故障时订单链路依然可用。

具体案例:某电商平台大促前进行故障演练:脚本在低峰期 kill 掉主库进程,验证哨兵 30 秒内完成选主切换,连接池按 0.5s/1s/2s 退避重试后自动连上新的主库,全程下单成功率维持在 99.97%,页面无感。

RAG 知识库的高可用读取

阶段:大模型应用(RAG)的向量与元数据存储

解决的问题:RAG 应用中,用户的对话记录、知识库元数据读多写少;如果底层数据库故障,AI 助手会直接“哑火”。传统的单库架构没有容错能力,一旦宕机,知识库查询全部中断。采用一主多从加读写分离后,查询走从库、写入走主库,主库故障时自动切换并重试连接,让 AI 服务持续可用。

具体案例:某客服机器人知识库采用一主两从架构。一次主库所在物理机宕机,哨兵自动将最新的从库提升为主库,客户端连接池在退避重试 3 次后感知到新主库,知识库查询中断仅约 40 秒,机器人照常回答用户问题。

金融核心账务的季度混沌演练

阶段:核心系统的可用性合规保障

解决的问题:金融行业对 RTO/RPO 有硬性要求,但“从不演练”就意味着第一次实战必然手忙脚乱。传统方式靠人工年度灾备演练,周期长、覆盖窄,真实故障场景远比预案复杂。通过混沌工程平台定期注入主库故障、网络分区等场景,把恢复流程变成可重复、可度量的标准动作。

具体案例:某支付公司每季度在低峰期执行一次主库故障注入演练,脚本化验证切换、数据校验与回放流程,并用监控大盘全程记录。经过三个季度迭代,RTO 从最初的 8 分钟压缩到 45 秒,RPO 稳定在 2 秒以内。

Python 演示

python77 行
import randomimport time  class FakeDB(object):    """A tiny in-memory database with an up/down flag."""    def __init__(self, name):        self.name = name        self.up = True        self.value = 0     def write(self, data):        if not self.up:            raise ConnectionError("master down")        self.value = data        return True     def read(self):        if not self.up:            raise ConnectionError("slave down")        return self.value  class Router(object):    """Routes writes to master and reads to the first healthy slave."""    def __init__(self, master, slaves):        self.master = master        self.slaves = slaves     def write(self, data):        return self.master.write(data)     def read(self):        for slave in self.slaves:            if slave.up:                return slave.read()        return self.master.read()  def call_with_backoff(fn, base=0.5, max_delay=8.0, retries=5):    """Exponential backoff with jitter."""    for attempt in range(retries + 1):        try:            return fn()        except ConnectionError:            if attempt == retries:                raise            delay = min(base * (2 ** attempt), max_delay)            delay += random.uniform(0, base)            print("retry #%d after %.2fs" % (attempt + 1, delay))            time.sleep(delay)  def run_demo():    master = FakeDB("master")    slave1 = FakeDB("slave1")    slave2 = FakeDB("slave2")    router = Router(master, [slave1, slave2])     # write goes to master, reads go to healthy slaves    call_with_backoff(lambda: router.write(42))    print("read from slave:", call_with_backoff(lambda: router.read()))     # simulate master failure    master.up = False    print("master goes down, promote slave1")    router.master = slave1    router.slaves = [slave2]     # retry + failover keeps the business running    call_with_backoff(lambda: router.write(100))    slave2.value = slave1.value    print("read after failover:", call_with_backoff(lambda: router.read()))  if __name__ == "__main__":    run_demo()
目的:用一个内存版“假数据库”演示主从复制、读写分离路由与指数退避重试的组合拳:主库挂了,路由器重新指向新主库,客户端靠退避重试扛过切换窗口。
  1. class FakeDB(object):用带 up 标志的内存对象模拟数据库实例,故障就是把 up 置为 False。
  2. def call_with_backoff(fn, base=0.5, max_delay=8.0, retries=5):通用重试工具:传入任何会抛 ConnectionError 的函数。
  3. delay = min(base * (2 ** attempt), max_delay)指数退避核心公式:0.5 → 1 → 2 → 4 → 8 秒,并封顶。
  4. delay += random.uniform(0, base)加入随机抖动,防止所有客户端在同一时刻重试造成惊群。
  5. router = Router(master, [slave1, slave2])路由对象持有一主多从,业务方只跟路由器打交道。
  6. if slave.up: return slave.read()读请求优先发给健康从库,这是读写分离的落点。
  7. master.up = False模拟主库故障,相当于实验中拔掉电源。
  8. router.master = slave1主从切换的本质:把路由器的“主库指针”指向新主库。
  9. call_with_backoff(lambda: router.write(100))切换发生在重试间隙,重试成功后业务无感。

常见误区

误区我在主库写了数据,下一秒读从库,怎么读不到?肯定是代码写错了。不是说好了读写分离吗?

正解主从复制是异步的,从库拉取日志并回放需要时间,这就产生了复制延迟。读到旧数据是“最终一致性”的正常表现。关键操作(下单后立刻查订单)应该路由到主库;一般的商品列表、内容查询才适合走从库。

为何常见:直觉上“既然有副本就应该和主库一样”,忽略了数据同步需要一个传输加回放的过程,就像帮厨复刻招牌菜需要几分钟。

误区切换既然已经自动化了,用户应该完全无感知,而且一点数据都不能丢,否则就是方案不行。

正解异步复制下,主库故障瞬间尚未同步到从库的事务会永久丢失,这部分就是 RPO。想要减小 RPO,得用半同步或强同步复制,代价是主库写入性能下降。RTO 也不可能为 0,探测、选主、重连都需要时间。高可用是有量化代价的。

为何常见:各种自动化宣传让人误以为“高可用等于无感知”,实际上需要在 RTO、RPO 与性能之间做显式取舍。

误区数据库挂了,我应该用最短时间疯狂重连,这样它一恢复我就能立刻接上,多试几次总没错。

正解无退避的密集重试会雪上加霜。恢复中的数据库可能正要起来,却被一波连接风暴再次压垮。指数退避加随机抖动让重试节奏随恢复进度放缓,既不让请求丢失,也不给系统施压。

为何常见:“越快越好”是对客户端自身而言的直觉,却忽略了服务端的恢复压力——就像一百个服务员同时冲进后厨催菜。

误区生产环境做故障演练?万一真把系统弄挂了怎么办?不做最安全,出了问题再救火就行。

正解不演练的第一次“实战”才是最危险的。混沌工程强调在受控范围内注入小故障:选择低峰期、灰度环境,提前制定止血与回滚预案,观察监控并收集数据。演练是企业为真实故障买的“保险”。

为何常见:把演练等同于事故,忽略了“可控注入”与“预案兜底”这两道安全网,也低估了临时救火时的混乱成本。

误区读写分离很有效,那我加十个从库,读性能肯定翻十倍,数据库问题就全解决了。

正解从库增加会带来复制链路变长、延迟累积、成本上升、一致性窗口变大等问题。读写分离只解决读扩展,写瓶颈依然卡在主库;超过一定数量后收益递减。正确的路径是先量化瓶颈,再决定加从库还是做缓存、分片。

为何常见:线性外推的思维误区,以为读性能随从库数量线性增长,忽略了网络、复制与运维复杂度的现实约束。

面试题

Q1. 一主多从架构中,写操作(INSERT/UPDATE)应该路由到哪里?

单选题 · easy

  • A.任意一个从库节点
  • B.主库,从库只承担读← 正确答案
  • C.负载最低的数据库
  • D.所有数据库节点

答案:B。从库是异步复制出来的副本,写任意从库会丢失数据并破坏一致性,所以写操作只能发往主库。就像改菜单只能找主厨,帮厨没有权限。

Q2. 主从复制中“异步复制”的特点是?

单选题 · easy

  • A.主库提交后立刻返回← 正确答案
  • B.等从库确认才返回
  • C.每次写都同步落盘
  • D.由应用层手动触发

答案:A。异步复制是主库提交成功即返回客户端,从库在后台拉取日志回放。B 是半同步/同步复制的行为,C 与复制无关,D 则完全不符合数据库自动复制的机制。

Q3. RTO(恢复时间目标)描述的是?

单选题 · mid

  • A.最多可丢失的数据量
  • B.故障发生到恢复服务的时间← 正确答案
  • C.主从复制的固定延迟
  • D.连接池重试的最大次数

答案:B。RTO 衡量“多久能恢复”,从故障发生到服务恢复的总时长;A 描述的是 RPO,C 是复制延迟,D 只是重试参数,都与 RTO 的定义不符。

Q4. 读写分离下,用户刚提交订单立刻查询却看到旧数据,最可能的原因是?

单选题 · mid

  • A.数据库连接池已满
  • B.从库复制存在延迟← 正确答案
  • C.路由规则配置错误
  • D.订单写入失败了

答案:B。刚写完主库就读从库,从库还没来得及回放最新事务,所以读到旧数据,这是复制延迟。连接池满、路由错误、写入失败都会表现为报错而不是旧数据。

Q5. 指数退避重试算法的核心目的是?

单选题 · mid

  • A.让重试立即成功
  • B.降低对恢复中系统的压力← 正确答案
  • C.提高单次请求响应速度
  • D.减少写入数据的延迟

答案:B。退避算法通过逐渐拉长重试间隔并加入抖动,保护正在恢复的数据库不被连接风暴打垮;它不是让重试变快,而是让系统在故障期更稳。

Q6. 关于半同步复制,正确的说法有?

多选题 · mid

  • A.需等待至少一个从库确认← 正确答案
  • B.可降低数据丢失的风险← 正确答案
  • C.对主库写入性能无影响
  • D.任何情况下都不丢数据

答案:AB。半同步要求至少一个从库确认后才返回成功,因此显著降低 RPO;但等待确认必然带来性能损耗,C 错;如果主库和已确认从库同时宕机,仍可能丢数据,D 错。

Q7. 某系统全年 SLO 为 99.9%,其每年可接受的停机时间是?

单选题 · hard

  • A.约 52 分钟
  • B.约 525 分钟← 正确答案
  • C.约 5250 分钟
  • D.约 0.5 分钟

答案:B。全年共 525600 分钟,99.9% 允许 0.1% 故障,即 525.6 分钟,约 525 分钟。52 分钟对应 99.99% 的 SLO,5250 分钟对应 99%,0.5 分钟对应近乎 99.9999%。

Q8. 数据库故障演练(混沌实验)的正确做法包括?

多选题 · mid

  • A.选择低峰期或灰度环境← 正确答案
  • B.提前制定止血和回滚预案← 正确答案
  • C.在生产环境直接拔电源
  • D.确保监控告警全程可见← 正确答案

答案:ABD。演练讲究“可控注入”:低峰期、有预案、全观测是三大前提。直接拔电源属于无准备的鲁莽操作,违背了混沌工程“受控验证”的原则,错误。

Q9. 哪些因素会影响主从切换后的 RPO?

多选题 · hard

  • A.复制方式是否半同步← 正确答案
  • B.故障前未同步的事务量← 正确答案
  • C.监控告警的灵敏度
  • D.从库使用的存储引擎

答案:AB。RPO 取决于故障瞬间已经提交但未同步到从库的数据量,半同步能显著缩小这个窗口,所以 A、B 都对。监控灵敏度影响的是 RTO;存储引擎不改变“已提交未同步事务会丢”的事实。

Q10. 监控告警“分级”的主要作用是?

单选题 · easy

  • A.让告警界面更好看
  • B.匹配不同响应速度与人员← 正确答案
  • C.减少监控数据的存储量
  • D.降低数据库的资源消耗

答案:B。告警按 P0/P1/P2 分级,是为了让不同严重度的问题匹配不同的响应速度与处理人员,例如 P0 自动拨打电话叫醒值班工程师。美观、存储、资源消耗都不是分级的目的。