
1. 我为什么把 MySQL MHA 和 Redis 哨兵放在一起聊先给结论MySQL 的高可用和 Redis 的高可用看起来都是“主从切换”背后是两套完全不同的思路。MySQL 主从复制只能保证数据有副本真出故障时要靠 MHA 这样的工具去“拼一把”完成切换Redis 则更直接官方推荐 Sentinel 哨兵把“谁当主、谁当从、谁来切”都固化成机制。我之所以把这两个绑在一起写是因为绝大多数业务系统的数据链路是“Redis 缓存 MySQL 落库”。如果你只把 MySQL 做了主从没有 MHA主库宕机时写服务会直接阻塞人工切换要十几分钟如果只把 Redis 做了哨兵MySQL 还是单点缓存再快也没有意义。把这套组合拆开看MHA 负责数据库层的自动化故障切换Sentinel 负责缓存层的自动选主最终目的都是让应用不需要人工介入就能继续工作。拿我自己经历的一次线上事故来说某个数据中台项目MySQL 主库是单机Redis 虽然部署了主从但切换依赖手工改配置。某个周末凌晨主库磁盘读写异常监控只看 CPU 和内存结果业务侧在早上八点高峰直接雪崩。所有写操作超时、缓存穿透打到数据库最终花了一个多小时才手工切到从库。这个事故不是说不用 MHA 就必然出事而是告诉我们只有当故障发生时一套经过演练的自动切换机制才是最靠得住的。这篇文章不打算讲那种“复制粘贴命令就能跑通”的教程我会先从原理讲清楚 MHA 和 Sentinel 各自的判断逻辑再给出完整的三节点拓扑部署过程和配置文件逐项说明最后专门聊高可用切换后后端代码里那些容易翻车的点。适合的人群是正在做架构升级的开发者、被主从复制坑过的 DBA、以及想在企业内网搭一套低成本高可用方案的运维同学。我也得提前说清楚这里讲的是中小规模、单机房内的经典方案。如果你已经上了云直接用云厂商的 RDS 和托管 Redis 会更省心如果是大规模、跨机房可能要考虑 MGR、Orchestrator 或 Redis Cluster。MHA 这套东西确实老了但它在很多传统企业里依然是存量主力理解它的原理对你迁移到新方案也有帮助。2. 拆开 MHA主库故障后它到底做了什么2.1 MHA Manager 与 MHA Node 的分工MHA 不是复制中间件它不参与 MySQL 的 SQL 转发平时的数据复制是 MySQL 本身的主从复制机制在跑。MHA 只做一件事监控和故障切换。所以它的部署分为两个角色MHA Manager管理端通常单独跑在一台机器上也可以放在某个从库上负责定时探测主库状态发起切换协调所有节点。MHA Node数据节点端所有跑 MySQL 的机器都要装切换过程中由 Manager 通过 SSH 调用这些 Node 上的脚本去完成日志补偿、relay log 清理、CHANGE MASTER 等操作。这种“Manager 发指令、Node 干脏活”的架构带来一个直接影响Manager 到所有 MySQL 节点的 SSH 免密是必须的而且建议使用独立低权限账号并限定可执行命令。很多人部署 MHA 卡住90% 是 SSH 环节没配好。2.2 一次完整的主库切换流程假设主库在凌晨 2 点突然 hang 住没有哨兵大家就靠 MHA。时间线大致是这样的Manager 默认每隔几秒由 ping_interval 控制通过mysqladmin ping去探主库。这里用到的探测方式是 ping_type可以选 SELECT 或 CONNECT。SELECT 能更真实地反映主库能否执行查询代价是每次都会向主库发送一条查询负载稍高。连续若干次失败后Manager 进入候选切换流程。它的第一件事不是直接切而是先尝试用 SSH 到主库确认同时通过 secondary_check_script 从另一台机器做二次探测。这是为了防误判避免网络抖动导致把活主库给切了。确认主库不可用后Manager 会找出所有存活的从库对比各自已执行的 binlog 位置或 GTID计算哪台的数据最新。从最新从库上找出差异的 relay log并把这些日志应用到其他从库尽量让所有从库的数据拉到同一个水位。这就是 MHA 常说的“补偿日志”也是它比普通脚本切换高明的地方。在日志补偿后按设定规则选一个候选主库把它提升为新主所有老从库CHANGE MASTER TO指向新主。执行 master_ip_failover_script 脚本把虚拟 IP 从老主漂移到新主。整个过程的这段时间应用是拿不到数据库连接的所以不是零闪断而是几十秒到分钟级的不可用窗口。MHA 的目标是“有人能自动把库切过来不要让业务等人工处理”不是“毫秒级无感知”。2.3 选主逻辑与 candidate_master 的意义MHA 在选新主的时候并不是随便挑一个数据最新的从库就算完。它有一套优先级配置里明确写了candidate_master1的从库优先即使这台数据略旧也会尽量通过日志补偿补齐后再选。如果没有指定候选就看各从库的 binlog 位置谁最靠后。正常情况下默认不选配置了no_master1的节点比如延迟特别高的机器。我见过很多团队把三台从库全不设 candidate_master结果故障切换时选出来一台配置最低的小机器当新主CPU 瞬间打满。合理做法是把硬件最好、和主库同机房的其中一台设为 candidate_master1并保证它半同步复制开启其它机器就是普通从库必要时也能顶上去但优先级靠后。这就是为什么要理解选主逻辑而不是仅仅复制一份模板配置。关于切换耗时千万别只盯着 ping_interval。真正影响整个切换耗时的往往是日志补偿和 relay log 应用阶段。如果从库文件特别大或 relay log 很多没清理MHA 花费的时间会远超你的预期。所以我在配置里会把 Manager 启动命令加上--wait_on_failover_error60这类容错参数同时优化 MySQL 的slave_net_timeout避免从库觉得主库断开后立刻把复制线程停掉导致日志补偿拿不到数据。这些参数在 MHA 默认模板里不会主动提属于“不演练根本发现不了”的部分。3. 落地部署三台 Rocky Linux 9 环境下的完整拓扑3.1 拓扑规划我用的是三台 4C8G 的 Rocky Linux 9 虚拟机内网互通端口全开。角色分布如下主机IP运行角色MySQL 角色mysql-node1192.168.1.11MySQL MHA Node初始 Mastermysql-node2192.168.1.12MySQL MHA NodeSlavecandidate_mastermysql-node3192.168.1.13MySQL MHA Node MHA ManagerSlaveredis-node1192.168.1.14Redis SentinelRedis Masterredis-node2192.168.1.15Redis SentinelRedis Slaveredis-node3192.168.1.16Redis SentinelRedis Slave如果你想省钱MHA Manager 也可以放在一台普通的非 MySQL 主机上但至少要和 MySQL 节点网络互通、SSH 可通。Redis 的哨兵节点需要独立投票所以哨兵至少三台否则选主时会因为票数不够而无法形成共识。这里我把 Redis 节点拆成了三台单独机器避免和 MySQL 抢资源。3.2 MySQL 8.0 主从复制准备主从复制是 MHA 工作的地基。MySQL 8.0 我建议直接用 GTID 模式它比基于 filenameposition 的复制在切换之后更容易判断数据位置。Master 和每个 Slave 的/etc/my.cnf里都要加[mysqld] server-id11 log_bin/var/log/mysql/mysql-bin binlog_formatROW gtid_modeON enforce_gtid_consistencyON relay_log/var/log/mysql/mysql-relay binlog_expire_logs_seconds604800 skip_name_resolveONserver-id 必须每个节点不同我习惯用 IP 最后一位来区分node1 是 11node2 是 12node3 是 13。skip_name_resolve这个参数建议打开否则 MHA 通过 IP 连接 MySQL 时如果 MySQL 反向解析 DNS 超时后续切换会变得很慢。主库上创建用于复制的账号CREATE USER repl% IDENTIFIED BY repl_pass; GRANT REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO repl%; FLUSH PRIVILEGES;从库上执行 CHANGE MASTERCHANGE MASTER TO MASTER_HOST192.168.1.11, MASTER_PORT3306, MASTER_USERrepl, MASTER_PASSWORDrepl_pass, MASTER_AUTO_POSITION1; START SLAVE; SHOW SLAVE STATUS\G这一步很容易忽略的是MHA 切主时需要从库 relay log 目录可写且不能被其它额外机制拦住建议在 slave 上把read_onlyON但 MHA 会用有 SUPER 权限的账号来临时关掉。同时可以打开半同步复制插件尽量降低故障切换时的数据丢失概率INSTALL PLUGIN rpl_semi_sync_master SONAME semisync_master.so; INSTALL PLUGIN rpl_semi_sync_slave SONAME semisync_slave.so; SET GLOBAL rpl_semi_sync_master_enabled ON; SET GLOBAL rpl_semi_sync_slave_enabled ON;MHA 在切换时能用日志补偿去捞数据但如果主库宕机的瞬间数据还没同步到任何从库那部分 binlog 就真丢了。半同步复制的作用是在主库提交事务时至少等一个从库确认收到日志能显著缩小这个窗口。3.3 安装 MHA Node 和 Manager以及我踩的编译坑MHA 从 2018 年后基本没有大的更新在 GitHub 上可以找到源码。Rocky Linux 9 上直接装旧版 RPM 会缺 perl 依赖我建议这样处理先在所有 MySQL 节点安装 Perl 依赖dnf install -y perl-DBD-MySQL perl-Config-Tiny perl-Log-Dispatch perl-Parallel-ForkManager perl-Time-HiRes编译安装 mha4mysql-nodecd /opt git clone https://github.com/yoshinorim/mha4mysql-node.git cd mha4mysql-node perl Makefile.PL make make install在 Manager 所在的机器编译安装 mha4mysql-managercd /opt git clone https://github.com/yoshinorim/mha4mysql-manager.git cd mha4mysql-manager perl Makefile.PL make make install这里我踩过的坑有两个。第一个是 Rocky Linux 9 默认的 Perl 版本比较新编译时偶尔会报Cant locate ExtUtils/MakeMaker.pm直接装perl-ExtUtils-MakeMaker就能解决。第二个是 mha4mysql-node 对旧版 DBD::mysql 有依赖如果你在配置 MySQL 8.0 时用了新的 caching_sha2_password 认证MHA 通过 DBD::mysql 连接可能认证失败稳妥做法是创建 MHA 专用的 MySQL 账号并指定mysql_native_password或者确认当前 DBD::mysql 版本支持 caching_sha2_password。MHA 账号的权限要给够因为它需要在切换时执行SET GLOBAL read_onlyOFF这类操作。我一般这样建CREATE USER mha% IDENTIFIED WITH mysql_native_password BY mha_pass; GRANT ALL PRIVILEGES ON *.* TO mha% WITH GRANT OPTION; FLUSH PRIVILEGES;生产环境如果不想给全局权限至少要保证 MHA 能 SELECT、能 CHANGE MASTER、能执行 SET GLOBAL。但说句实话MHA 官方脚本在切换过程中会做很多事情权限卡得太死最后坑的是自己。内网环境里给个大权限问题不大。3.4 app1.cnf 配置逐项说明包括超时时间MHA Manager 的核心配置在/etc/masterha/app1.cnf。我列一份实际能用的配置[server default] manager_workdir/var/log/masterha/app1 manager_log/var/log/masterha/app1.log master_ip_failover_script/etc/masterha/master_ip_failover ssh_userroot ssh_port22 ping_interval2 ping_typeSELECT secondary_check_script/usr/local/bin/masterha_secondary_check -s 192.168.1.13 -s 192.168.1.12 usermha passwordmha_pass repl_userrepl repl_passwordrepl_pass [server1] hostname192.168.1.11 master_binlog_dir/var/log/mysql [server2] hostname192.168.1.12 candidate_master1 master_binlog_dir/var/log/mysql [server3] hostname192.168.1.13 master_binlog_dir/var/log/mysqlMHA 的常见误解是配置顺序里的第一个节点必须是主库。其实 MHA 不认配置顺序它只看当前哪台是主库配置文件里的 server 列表是它要纳入监控的节点清单。即使你配置文件中 server1 写的是 master 的 IP故障切换后 Manager 会自动把新主库角色标记到对应 host甚至可以通过--remove_dead_master_conf把挂掉的节点从配置中移除。再说超时时间。MHA 的可调时间参数很多但日常最需要关注的是这几个ping_interval2Manager 探测主库的间隔默认是 3 秒。设置太短会在主库繁忙时误报太长会拖慢发现故障的速度。secondary_check_script二次确认脚本这个不算超时参数但它和超时配合使用能显著减少误切换。Manager 发起 failover 前还会对从库的日志补偿操作设置超时。如果你的从库文件特别大建议在 Manager 的启动命令里加上--wait_on_failover_error60这种容错参数或者调整 MySQL 层的net_read_timeout和slave_net_timeout避免从库觉得主库断开后立刻把复制线程停掉导致日志补偿拿不到数据。启动 Manager 的命令一般是nohup masterha_manager --conf/etc/masterha/app1.cnf --remove_dead_master_conf \ --ignore_last_failover /var/log/masterha/manager_run.log 21 注意--remove_dead_master_conf切换完成后自动把死主从配置文件里删掉避免 Manager 重启后认为死主还要监控。--ignore_last_failover是防止 MHA 因为上次切换后短时间内再次发生故障而拒绝切换。启动前一定先做自检masterha_check_ssh --conf/etc/masterha/app1.cnf masterha_check_repl --conf/etc/masterha/app1.cnfcheck_ssh检查 Manager 到所有节点的免密是否正常check_repl检查主从复制是否健康。这两条命令输出里出现OK才能继续否则排查完再往下走。我把 SSH 免密放在这里强调是因为它太重要了MHA Manager 需要免密登录所有 MySQL 节点执行命令一旦漏了一台切换时它会在那台机器上卡住整个切换时间无限拉长。4. MHA 真实切换验证从日志里看它怎么一步步切主4.1 手动制造主库故障部署完成后我习惯做一次“强制断电”型验证而不是用mysqladmin shutdown优雅关闭主库。现实中宕机往往不是优雅的直接kill -9mysqld 进程更能还原真实场景。验证步骤如下在 Manager 机器上盯日志tail -f /var/log/masterha/app1.log。登录 mysql-node1执行kill -9 mysqld_pid。观察 Manager 日志中Ping failed到Starting master failover的时间差。切换完成后连接 VIP 指向的机器确认新主是谁再到其它从库看SHOW SLAVE STATUS的 Master_Host 是否已指向新主。我自己实测时ping_interval2、secondary_check 打开的情况下从主库挂掉到新主可写耗时大约 25 秒到 40 秒。日志里最关键的一段是Mon Mar 4 02:00:03 2025 - [info] Ping failed, retrying... Mon Mar 4 02:00:05 2025 - [info] Connecting to secondary monitor ... Mon Mar 4 02:00:09 2025 - [info] Master 192.168.1.11 is not reachable from secondary monitor Mon Mar 4 02:00:09 2025 - [info] Starting master failover... Mon Mar 4 02:00:35 2025 - [info] Master failover to 192.168.1.12 completed successfully.如果你看到Ping failed之后马上进入Starting master failover说明你的secondary_check_script没生效或者没有配置二次探测这是很危险的一个短暂的网络抖动就可能触发误切换。我的经验是二次探测必须配哪怕只在另一台普通节点上跑一条mysqladmin ping也能过滤掉很多假故障。4.2 那些让切换失败的隐蔽问题在反复演练中我遇到过的坑主要有这几类relay log 清理过频。MHA 切换时要做日志补偿如果 MySQL 的 relay_log_purge1 且从库已经把 relay log 执行完并清空了MHA 就无法拿到旧 relay log 去比对差异。官方建议把 relay_log_purge 设为 0或者保证 relay log 保留一段时间。但这也会带来磁盘占用问题我是在低峰期手动清。SSH 探测卡顿。MHA 默认通过 SSH 到每个节点检查 mysqld 是否存活如果 SSH 连接因为 DNS 解析、防火墙策略慢整个切换会被拖得很长。检测方式很简单手动ssh root192.168.1.12看是否需要 3 秒以上才出提示符如果是则查/etc/ssh/sshd_config里的UseDNS no和GSSAPIAuthentication no。新主上的 old master 信息残留。MHA 切完后如果旧主库之后又恢复了它可能以原来的身份继续提供服务形成“双主写”风险。MHA 官方也建议在 failover 脚本里加上STOP SLAVE; RESET SLAVE ALL; SET GLOBAL read_onlyON;确保旧主回归后不会再写。还有一点容易忽略MHA 切换完成后Virtual IP 的漂移脚本要能正常工作。我的master_ip_failover脚本里核心逻辑是# 停掉老 IP ssh root$old_ip ip addr del 192.168.1.100/24 dev eth0 2/dev/null # 给新主绑定 VIP ssh root$new_ip ip addr add 192.168.1.100/24 dev eth0这只是最简版本生产环境建议用 keepalived 或云厂商的浮动 IP 脚本。如果 VIP 没有漂移应用端的 JDBC 连接串得用新主 IP 才能连库做不到透明切换。5. Redis 高可用主从加哨兵如何做到自动选主5.1 Redis 主从复制的基本姿势Redis 的高可用基础同样是主从复制。我在三台 Redis 机上各自装了 Redis 7.0然后建主从。主库 redis-node1 不需要特殊配置从库 redis-node2 和 node3 在redis.conf里写replicaof 192.168.1.14 6379 replica-read-only yes replica-priority 100我的经验是 Redis 主从的replica-priority一定要设哨兵在选新主时会优先选 priority 值小的节点。比如指定 node2 的 priority 为 50node3 为 100这样哨兵自动选主时会优先提升 node2。如果不改默认都是 100哨兵在多个从库间还要按 runid 排序结果不可控。另外replica-read-only yes要记得确认。曾经有人为了排查问题手工在从库set了一个 key导致主从数据不一致后面主从切换时这个脏 key 被带上了线很难排查。从库就别开放写所有人都不允许例外。5.2 Sentinel 判断故障的完整链路Sentinel 的高可用逻辑和 MHA 有一个核心区别它是“分布式判定”不会只靠单点探测就切换。每个 Sentinel 节点独立监听 Redis master。当某个 Sentinel 发现 master 超过down-after-milliseconds时间没有响应时它会在自己这里标记为主观下线subjectively down。随后这个 Sentinel 会向其它 Sentinel 广播这个消息如果达到quorum数量的 Sentinel 都确认 master 不可达才把 master 标记为客观下线objectively down然后触发故障转移流程。故障转移时会从 Sentinel 集群中选出一个 leader由它负责执行剩余步骤。选主其实就是选举一个新的 master之后其它从库replicaof到这个新 master。整个过程从业务层看就是 Redis 写入会失败几十秒切换后恢复。一个核心配置文件示例port 26379 sentinel monitor mymaster 192.168.1.14 6379 2 sentinel down-after-milliseconds 5000 sentinel failover-timeout 15000 sentinel parallel-syncs 1 sentinel auth-pass mymaster redis_passquorum2意味着最少要两个 Sentinel 都认为 master 挂了它才会发起切换。这里有个性能与安全的选择3 个 Sentinel 节点里 quorum 设为 2允许容忍一个节点挂掉如果你设成 3就要三个一起判断误判概率低但可用性会下降。down-after-milliseconds最短可以设到几千毫秒但没必要追求极致除非你可以容忍频繁切换。Redis 主库有大型 RDB 持久化或者阻塞命令时偶尔几十毫秒不响应很正常秒级判定比较稳。parallel-syncs1表示故障切换后同时只允许一个从库向新主做全量同步避免多个从库同时同步把新主压垮。验证 Sentinel 切换时我一般直接停掉当前 Redis 主进程然后观察哨兵日志sdown master mymaster 192.168.1.14 6379 odown master mymaster 192.168.1.14 6379 #quorum 2/2 switch-master mymaster 192.168.1.14 6379 192.168.1.15 6379看到switch-master就说明切换完成。这里给一个实用建议别在业务高峰做 Redis 主从切换演练因为切换期间会有一段不可用窗口读多写少的系统可以用本地缓存短暂兜底但写多读少的系统要提前和业务方对齐降级方案。5.3 为什么不推荐用 VIP 脚本管 Redis不少团队一开始给 Redis 也用 VIP keepalived 方案主备之间通过脚本切 VIP。这种做法在 Redis 场景下问题很明显Redis 的读压力通常分散在多个从库上你切完 VIP 只能保证主节点迁移了从库的拓扑关系还要靠外部脚本改。而 Sentinel 协议本身就把“发现主、发现从、自动改从”固化在了客户端 SDK 里应用通过哨兵地址就能感知当前主节点不需要额外脚本。所以我的建议是中小规模直接主从 Sentinel大幅降低运维成本数据量大、容量紧张的场景考虑 Redis Cluster让分片和高可用一起解决。这里不展开 Cluster但至少要知道 Cluster 自带高可用不需要再套 Sentinel。6. 高可用切换后后端代码必须重新审视的几个点6.1 数据库连接池max-lifetime 和 validation 要配套MHA 切换后应用侧的数据库连接池里还留着指向老主库的连接。VIP 漂移模式下老连接会表现为连接重置或 socket 超时如果连接池不回收这些连接新请求会在写库时突然异常。我常用的连接池配置是 HikariCPspring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 max-lifetime: 1800000 connection-timeout: 10000 validation-timeout: 3000 connection-test-query: /* ping */ SELECT 1max-lifetime一定要小于数据库侧的wait_timeout默认 8 小时否则连接池里的连接可能已经被数据库断掉但 pool 还认为它有效。我一般设 30 分钟既能定时换连又不会频繁建立连接。connection-test-query用来在取出连接时做轻量探测切换后这些坏连接会被更快清理。还有一点很重要连接池的initialization-fail-timeout不要设成 0否则启动时数据库不可达应用也会硬启动成功等真正请求来了才发现连不上。6.2 JDBC URL 里 useSSL 和 sslmode 别混用这个热搜词组合让我想起一个常见误区。在 MySQL Connector/J 中旧版参数是useSSLfalse到了 8.0.x官方更推荐sslModeDISABLED或者sslModeVERIFY_CA。而sslmode这个词其实是 PostgreSQL JDBC 的参数很多人把 PG 的 URL 习惯带到 MySQL写进jdbc:mysql://...?sslmodedisableMySQL 驱动根本不会认于是日志里出现一堆 SSL warning。在实际高可用切换的场景下我的建议不是一刀切关闭 SSL而是在内网网络可信的前提下显式关掉减少握手次数和故障排查干扰jdbc:mysql://192.168.1.100:3306/app_db?useSSLfalseconnectTimeout3000socketTimeout10000useServerPrepStmtstruerewriteBatchedStatementstrueconnectTimeout设置 3 秒防止主库挂掉时应用线程无限等待建立连接socketTimeout设 10 秒是为了让读取操作不至于一直挂着。这两个参数在高可用切换时非常重要老库 hang 住但 TCP 还通着如果不设置 socketTimeout业务线程会卡死在读操作上最终把线程池打爆。6.3 Redis 哨兵模式下客户端的自动感知Redis 哨兵切换后客户端并不需要感知具体哪个 IP 是新主而是自动通过 Sentinel 获取。以 Spring Boot 为例spring: redis: sentinel: master: mymaster nodes: - 192.168.1.14:26379 - 192.168.1.15:26379 - 192.168.1.16:26379 password: redis_pass连接 Redis 的时候客户端不是直接连接 Redis master IP而是连接 Sentinel询问“当前 mymaster 在哪”然后拿到真实的主节点地址。一旦发生故障转移Sentinel 会通知客户端重新发现主节点。这里客户端侧要注意两个参数Lettuce有个shutdown-timeout和连接校验配置建议在 Spring Boot 里开启spring.redis.lettuce.pool.enabledtrue并设置合理的test-on-borrow或test-while-idle否则切换期间客户端拿到的是失效连接。对于非 Spring 环境Jedis 的JedisSentinelPool要保证每个请求从 pool 里拿连接不要在应用里缓存一个静态的Jedis实例并在主从切换后继续使用。6.4 MHA 和 Sentinel 同时切换时分布式锁的脆弱性高可用场景下最容易翻车的是分布式锁。Redis 的 setnx 锁作为业界最常用的分布式锁在主节点故障切换的瞬间可能会出现“锁丢失”业务 A 在主节点上拿到了锁。主节点宕机Sentinel 把某从库提升为新主。这个从库上的数据并没有 A 的锁记录或者刚好复制延迟导致锁 key 丢了。业务 B 拿到同一把锁并发执行原本该互斥的逻辑不再互斥。处理方式不是扔掉 Redis 锁而是根据业务损失来判断。如果并发执行的结果影响可接受比如幂等接口那 Redis 锁继续用如果绝对不能并发放量就要引入数据库行锁或状态机作为第二位兜底。我在项目里的做法是锁粒度大的操作比如订单状态流转优先用 MySQL 行锁配合事务内的乐观锁版本号。锁粒度小、对偶发并发不敏感的操作比如防重提交用 Redis setnx且设置过期时间避免持有锁的实例宕机导致死锁。无论用哪种锁业务方法本身都要做成幂等接口层记录 request_id数据库加唯一索引写操作冲突时返回“已处理”。6.5 别让主从同步延迟变成“新主丢数据”的背锅侠MHA 和 Sentinel 都解决不了同步延迟带来的数据丢失。MySQL 半同步复制可以在一定程度上降低丢失概率但 MHA 切换时如果宕机的主库上还有未同步到从库的 binlog这部分数据就找不回来了。Redis 主从异步复制的延迟同理。所以在高可用场景下写后端代码时一定要有“主从切换会丢一点数据”的心里预期写成功但响应超时的请求客户端可能重试导致重复提交。接口幂等是必须的。数据库主从切换后读请求可能落到延迟的从库上出现“刚写就读不到”。如果你用了 MHA VIP 方案注意读流量不要直接打到 VIP 或新主而是单独配置从库路由。Redis 缓存和 MySQL 之间的缓存一致性在故障切换窗口内会放大。比如缓存里有旧值MySQL 已经切换到新主但缓存没失效。这时候业务要做的是接受短暂不一致而不是追求强一致。我在一次演练里就遇到过主库挂掉前写入了用户订单MHA 切换耗时约 30 秒应用层因为连接池没有快速回收一直报 SQL 异常等切换完成后用户重试提交结果订单表插入了重复记录。后来不是改了 MHA而是把接口改成“同用户同 request_id 幂等”这个问题才算根治。7. 从故障演练里得到的最终体会写到这里想分享几条我在反复演练中的切身体会。第一高可用架构如果没做过故障演练等于没有。MHA 配置看起来都正常check_repl也全绿但真正 kill 主库之后才会发现SSH 免密漏了、VIP 漂移脚本没执行权限、Manager 日志目录磁盘满、旧主库恢复后没设 read_only。这些坑没有一次演练是发现不了的。我强烈建议至少每月做一次主动 kill 演练选择核心业务低峰期执行。第二MHA 和 Redis Sentinel 的“切换时间”取决于你预留了很多细节参数。把 ping_interval、down-after-milliseconds、连接池的 socketTimeout 调优之后整个系统的主库故障恢复时间可以从十几分钟降到几十秒。但这是“配置调优”的结果不是装好就有的别期待默认参数下能有多快。第三高可用不是依赖某一层而是端到端配合。数据库 MHA 切好了Redis 哨兵也切好了但如果后端依然保留着静态数据库连接、缓存了 Redis 主节点地址、接口不幂等一切努力都会在真实故障瞬间被打回原形。最后说一句这套组合不是银弹。如果业务允许尽量使用云厂商托管数据库让专业平台处理故障切换如果必须自建MHA Sentinel 是一套经过大量生产验证的兜底方案值得投入人力和时间做完整演练。我个人的选择是把 MHA 保留在老项目里稳定运行新项目逐步往云原生托管服务和更现代的数据库高可用方案迁移但不管用什么方案上面这些故障切换后的连接管理、幂等、延迟容忍设计都永远逃不掉。