如何设计数据库高可用、读写分离与故障切换
不要先背主从和 RTO;先解释“主库挂了,有从库为什么还是切不过去”
这一页只解决一个问题: 主库故障时,业务数据库怎样继续服务,并且不读旧数据、不选错新主、不产生双主、不让新主被流量打死。高可用不是多几台库,而是一次可验证的切换设计。
Web 记账系统有一个主库、两个从库。主库所在机器故障后,应用连接池报错。团队说“我们有从库”,但从库不能直接写,谁升主、怎样切流量、旧主是否还能写都没有设计。
| 错觉 | 现实 | 逼出的设计 |
| 有从库就高可用 | 从库默认只是副本,不是新事实源 | 故障检测、候选主、升主 |
| SELECT 走从库就行 | 从库会延迟,刚写后可能读旧 | 读新鲜度路由 |
| 切 DNS / VIP 就结束 | 连接池可能连旧主,新主可能接不住 | fencing、拓扑版本、连接重连、降级 |
| 问题 | 例子 |
| 哪些功能必须写可用 | 新增账目优先,导出报表可以暂停 |
| 刚写后能不能读旧 | 账目详情不能旧读,月报可以短延迟 |
| 新主候选是否足够新 | 不能让落后 8 秒的副本接管关键写入 |
| 旧主怎样禁止继续写 | 网络分区时必须 fencing,防双主 |
| 应用怎样感知新拓扑 | Proxy、VIP、DNS、配置中心和连接池都要演练 |
| 切过去以后谁保护新主 | 连接风暴、强一致读、报表和批任务要限流或降级 |
db_topology
- topology_version
- primary_node
- replica_nodes
- term
- writable
- updated_at
write_response
- commit_lsn
- topology_version
read_route_rule
- read_freshness: strong | session | eventual
- min_replayed_lsn
吸收点: 读写分离要带“新鲜度”,故障切换要带“任期和拓扑版本”。否则系统不知道自己读的是哪条时间线、写的是哪个主。
| 边界 | 事故 | 设计动作 |
| 读新鲜度 | 刚记完账刷新却看不到 | 读主、等待从库追到 LSN,或会话短期读主 |
| 选主资格 | 落后副本升主导致已提交账目消失 | 按复制位点、延迟、容量和故障域选候选 |
| 旧主 fencing | 网络分区后旧主和新主同时写 | 撤销写权限、隔离旧主、拒绝旧任期写入 |
| 切后容量 | 所有应用重连,新主被连接风暴打死 | 退避重连、限流、关闭报表和后台批任务 |
关键判断: 高可用最怕的不是慢几秒,而是双主和历史分叉。防旧主继续写,比抢几秒 RTO 更重要。
| 样本 | 通过标准 |
| 写入后立刻查询详情 | 读到刚提交的账目,或明确等待从库追平 |
| 从库延迟 10 秒 | 强一致读不走落后从库 |
| 主库进程宕机 | 只选一个新主,写入在 RTO 内恢复 |
| 主库网络分区 | 旧主被 fencing,不出现双写 |
| 应用连接池不刷新 | 旧连接被 drain,新拓扑生效 |
| 旧主恢复 | 旧主先作为从库追平,不直接切回主 |
总结: 数据库高可用不是“有主从”。它要能证明:读写分离不会读旧,候选主足够新,旧主不能继续写,应用能切到新拓扑,新主接得住流量,旧主回来不会直接复位。做不到这些,从库只是备份感,不是高可用。