ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

MySQL高可用实战:MGR自动选主与Router读写分离故障转移

MySQL高可用实战:MGR自动选主与Router读写分离故障转移 前几天半夜处理完一个主库宕机的告警我一个人坐在工位前手动执行切换、检查从库延迟、再逐一改业务连接配置前后将近40分钟。当时我脑子里只有一个想法要是这套数据库能自己选主、自己把人接住该有多省心。后来在生产环境里真正落地了MySQL Router MGR这套组合我才确定这件事是可以做到的。这个组合里MGR就是MySQL官方提供的组复制方案Group Replication负责集群内部的数据一致性、自动选主和故障转移MySQL Router则是官方自带的路由中间件负责把客户端读写请求分发到正确的节点上并在主节点变化时自动调整路由。两者配合起来一套高可用集群的核心链路就完整了。本文适合正打算做MySQL高可用改造、或者手上有一堆MySQL实例想摆脱手动切换的DBA和运维同学里面所有命令和参数都来自我实际跑过的环境。1. 为什么是MGR配MySQL Router这套组合的分工与选型逻辑1.1 MGR到底解决了传统主从复制的什么痛点传统的一主多从架构最大的问题不是复制本身而是“切换”这件事非常依赖人工。主库挂了以后你需要手动去挑一个数据最完整的从库执行STOP SLAVE、RESET SLAVE ALL再把流量切过去期间还得处理VIP漂移、修改应用配置等一连串操作。这个过程既慢又容易出错尤其是在半夜被电话吵醒的时候。MGR解决的就是这个问题。它用Paxos协议在集群内部维护一份一致的成员视图每个节点都知道谁是主、谁是从、谁掉线了。当主节点不可用时集群会自动在剩余的从节点中发起选主不需要任何外部脚本。同时MGR对数据一致性有更强的保证事务提交时需要在多个节点间达成共识比传统异步复制更不容易丢数据。1.2 MySQL Router在哪个环节起作用MGR能做的是“集群内部”的自动切换但应用并不知道主节点已经变了。如果应用直连的是原来的主库IP那一换主之后连接就断了业务照样不可用。这时候就需要一个中间层告诉应用“你继续连这个地址就行具体连到集群里的哪台机器我来负责”。MySQL Router就是干这个的。它在集群前面提供一个稳定的接入地址分为读写端口和只读端口。应用写请求走读写端口Router会把它转发到当前的主节点读请求走只读端口Router会在所有从节点之间做负载均衡。当MGR完成选主后Router通过元数据感知到角色变化自动把写路由切换到新的主节点上。1.3 为什么不直接用VIP或者Keepalived很多传统方案会用VIP Keepalived做高可用但这类方案有两个隐患。第一VIP漂移依赖的是网络层的探活它只能告诉你“这个IP还通不通”并不能真正判断MySQL实例的健康状态。有时候MySQL进程还活着但已经无法响应请求Keepalived这边探活脚本写得不好就不会触发切换业务依然在受损。第二如果探活和实际主从切换没配合好可能出现VIP已经漂移走了、但旧主还在接收写入的情况这就形成了双主写入数据一致性立刻破功。MGR的优势在于它把“数据层面”的选主和“接入层面”的路由分开了选主结果由Paxos协议在多数派节点间确认Router再去跟着这个结果走而不是自己猜测主是谁。这样设计逻辑上非常干净也不容易出现两套系统意见不一致的情况。1.4 拓扑角色一览一个典型的MGR Router高可用架构包含三类角色角色数量建议职责MGR组节点3或5个奇数数据存储、组复制、自动选主MySQL Router至少1个建议2个读写路由、故障感知、连接分发应用客户端若干连接Router固定地址不必感知集群内部拓扑需要特别说明的是MGR单组最多支持9个节点但节点数越多每次事务提交在组内达成一致的开销就越大。我在生产环境里最基本的配置是3节点既保证了多数派超过1个节点存活即可继续服务又把性能损耗控制在可接受范围内。5节点适合对可用性要求更高、并且能容忍更多局部故障的场景3节点已经是大多数业务的合理起点。2. MGR的底层机制与关键参数看不懂这些就别动手2.1 组复制核心基于Paxos协议MGR的核心机制可以这样理解每个组内成员的变更操作都会在组内发起一次协议共识超过半数的节点确认后这个事务才算真正提交。这意味着写操作不是“主库自己定主意然后异步告诉从库”而是“大家一起投票决定这笔事务能不能生效”。这种机制带来两个直接结果。第一个结果是数据一致性显著提升传统异步复制可能丢事务但MGR在多数派节点确认后才返回成功丢数据的窗口被压到非常小。第二个结果是需要为共识付出延迟每个写事务的提交时间都要加上组内网络通信的开销所以我前面提醒过跨机房、高延迟网络场景下需要谨慎评估性能同一机房内通常没问题。2.2 单主模式与多主模式怎么选MGR支持两种运行模式单主模式组内只有一个节点允许写其他节点自动变成只读。主挂了以后自动选举新主。多主模式组内所有节点都可以写事务由Paxos协议保证一致性。我在生产环境里只用单主模式。原因很简单多主模式对应用的约束非常多比如自增主键冲突、外键级联操作的限制、DDL语句的执行限制等等稍不注意就会出现数据冲突。而且多主模式并不能解决写入瓶颈问题因为所有写操作最终都要在所有节点间达成共识单个节点的写能力仍然是性能上限。如果单主模式的写入量已经压满了上多主模式不会有本质改善正确的做法是分库分表或者引入分布式数据库方案。2.3 配置参数逐行解析这里给一份我在3节点MGR上实际使用的核心配置以MySQL 8.0版本为例[mysqld] # 基础标识每台机器必须不同 server_id 11 report_host 192.168.1.11 # GTID是MGR的前置条件 gtid_mode ON enforce_gtid_consistency ON # binlog相关MGR要求使用ROW格式 log_bin /data/mysql/logs/binlog binlog_format ROW binlog_checksum NONE log_slave_updates ON # 复制元数据存表 master_info_repository TABLE relay_log_info_repository TABLE # 写集提取用于组内事务冲突检测 transaction_write_set_extraction XXHASH64 # 组复制相关参数loose前缀表示允许未知参数时节点仍可启动 loose_group_replication_group_name 6a3a3f3e-1e7a-4e3b-9f24-76f3a2f4a001 loose_group_replication_start_on_boot OFF loose_group_replication_local_address 192.168.1.11:33061 loose_group_replication_group_seeds 192.168.1.11:33061,192.168.1.12:33061,192.168.1.13:33061 loose_group_replication_bootstrap_group OFF loose_group_replication_single_primary_mode ON loose_group_replication_enforce_update_everywhere_checks OFF有几个参数值得特别说明。gtid_modeON和enforce_gtid_consistencyON是MGR运行的前提没有GTID组复制根本无法启动。binlog_checksumNONE是组复制的硬性要求如果这个值不是NONE节点加入集群时会报错。log_slave_updatesON必须开启因为每个从节点要把自己接收到的变更继续记录到自己的binlog里供其他节点同步。loose_前缀是一个小技巧。这个前缀告诉MySQL如果当前版本不认识这个参数请忽略而不是拒绝启动。因为组复制参数定义在插件里MySQL实例先读取配置文件、后加载插件如果不加loose前缀启动阶段直接报“unknown variable”错误实例根本起不来。2.4 事务一致性验证与写集机制transaction_write_set_extractionXXHASH64也很关键。MGR要靠它为每个事务计算写集write set用来在多个节点之间做冲突检测。简单说两个事务如果修改了同一行数据那么这两个事务的写集会包含相同的行信息组内参与共识的节点发现这种冲突后会在多主模式下拒绝其中一个事务。单主模式下这个机制不会频繁触发但没有它整个MGR的底层逻辑就不成立。可以看到MGR对参数的要求是一环扣一环的少配一个后面的初始化就会报各种莫名其妙的错误。这也是为什么我一直强调搭建MGR不要上来就复制网上的某一段配置最好先理解每个参数在组复制链路中的位置出问题时才知道往哪个方向排查。3. 从三台裸机到MGR集群落地部署全过程3.1 环境规划与前置准备我习惯用三台独立的物理机或虚拟机来搭系统是CentOS 7.9MySQL版本8.0.30。三台机器的信息如下主机名IP地址角色mgr-node1192.168.1.11MGR节点mgr-node2192.168.1.12MGR节点mgr-node3192.168.1.13MGR节点组复制需要开放的端口有两类MySQL服务端口3306以及组内通信端口33061即group_replication_local_address里配置的那个端口。如果使用防火墙记得把这两类端口在三个节点间全部放行。我见过不少初始化失败的情况最后查下来就是防火墙漏了33061节点之间互相ping不通组通信端口导致加入集群时一直超时。此外三台机器的server_id和report_host必须各不相同。report_host的作用是让集群能够识别每个成员的地址如果不设置错误地使用默认主机名MGR在恢复数据时可能连不上对端节点。3.2 初始化MySQL实例与复制账号先安装好MySQL 8.0完成基本的mysqld --initialize初始化然后按上一节的配置修改my.cnf重启MySQL服务。接着登录实例创建组复制专用的恢复账号CREATE USER repl% IDENTIFIED BY YourPassw0rd; GRANT BACKUP_ADMIN, CLONE_ADMIN, GROUP_REPLICATION_ADMIN, REPLICATION_SLAVE, REPLICATION_CLIENT ON *.* TO repl%;这个账号不是给业务用的而是MGR内部节点之间同步数据用的。如果你的MGR节点是从一个已有实例通过CLONE方式补充进来的那BACKUP_ADMIN和CLONE_ADMIN缺一不可。即使你只使用binlog方式补充保留这两个权限也没有坏处。3.3 初始化组复制第一个节点与后续节点所有前置条件准备好之后第一步是在第一个节点mgr-node1上执行初始化操作SET GLOBAL group_replication_bootstrap_group ON; START GROUP_REPLICATION; SET GLOBAL group_replication_bootstrap_group OFF;bootstrap_group这个开关的意思是“告诉这个节点你是本集群的第一个成员请自己创建一个新组”。这里千万要注意这个操作只能执行一次并且只能在第一个节点上执行。如果同时在两个节点上开启bootstrap它们会各自形成独立组后面的数据关系就全乱了。第一个节点启动完成后直接用root账号执行STOP GROUP_REPLICATION正常情况不用但如果出现过错误需要重置然后回到第二个节点和第三个节点登录MySQL执行START GROUP_REPLICATION;后续节点不需要再开bootstrap它们会根据group_replication_group_seeds里配置的地址自动找到已有的组成员并申请加入。3.4 验证集群状态与读写访问集群是否真正建立起来看的是这张视图SELECT * FROM performance_schema.replication_group_members;正常的输出应该有三行每个节点的MEMBER_STATE都是ONLINE其中一个是PRIMARY另外两个是SECONDARY。如果看到某个节点状态是RECOVERING说明它正在同步数据等一会儿再看如果卡在ERROR多半需要去看错误日志针对报错做处理。这时可以做一个简单的验证在mgr-node1上建库建表并写入一条数据然后在其他两个节点上查询应该能立刻查到。同时在mgr-node1上执行SHOW VARIABLES LIKE read_only会看到OFF在另外两个节点上执行会看到ON这说明单主模式的读写角色分配已经生效。4. MySQL Router接入读写分离与故障感知4.1 Router的工作机制简述MySQL Router的角色可以理解成一个“戴着面具的接线员”。应用只认识Router给的固定地址和端口完全不关心集群内部有多少个节点、谁是主谁是从。Router自己会定期从某个MGR节点拉取集群元数据维护一张“哪个节点是什么角色”的映射表。Router只做转发不解析SQL也不缓存数据。它的性能损耗很低主要开销是建立连接和转发字节流。部署上它也不需要跟MySQL装在同一台机器可以单独找一两台机器跑。4.2 推荐使用bootstrap方式部署手工编写Router的配置文件虽然可行但很容易漏项。官方提供的--bootstrap方式可以直接从现有MGR集群自动生成一份完整配置强烈建议优先使用。假设我把Router装在一台新机器192.168.1.20上安装mysql-router以后执行mysqlrouter --bootstrap repl192.168.1.11:3306 --directory /data/mysqlrouter --usermysqlrouter --conf-use-gr-notifications1命令中指定的是MGR组里任意一个在线节点的地址Router会连接上去读取元数据。--conf-use-gr-notifications1这个参数一定要加它让Router订阅MGR的成员变更通知主节点切换时能立刻感知而不是干等默认的元数据缓存刷新周期。默认情况下Router会创建6446端口用于读写、6447端口用于只读也可以在后面加--read-write-port和--read-only-port来改成自己习惯的端口。4.3 验证Router的读写分离Router启动后用业务账号分别连接读写端口和只读端口验证路由是否正确mysql -h192.168.1.20 -P6446 -uapp_user -p mysql -h192.168.1.20 -P6447 -uapp_user -p连接成功后执行SELECT server_id;如果走的是6446端口server_id应该等于当前主节点的值走6447端口时应该等于某个从节点的值。多执行几次从端口查询如果你有多个从节点会发现结果在它们之间轮流变化这是Router在做负载均衡。这一步是上生产前必须验证的。如果读写端口返回的是从节点或者只读端口返回了主节点说明Router的元数据缓存与实际集群状态不一致最常见的原因是bootstrap时指定的节点角色有问题或者Router未开启组复制通知而缓存还没刷新。排掉这个再往上了连接池配置才能保证业务侧读写路径正确。4.4 Router如何感知MGR切换Router的元数据刷新有两种触发方式一种是主动订阅MGR的成员通知只要集群成员角色发生变化Router会立刻更新路由表另一种是兜底轮询即使通知链路失效Router也会在metadata_cache_ttl到期后重新查询默认60秒。这里要说清楚一个容易被忽略的事情如果你的应用连接池里的老连接已经绑定了旧主节点那么即使Router更新了路由表这些老连接并不会自动“搬家”。Router在检测到后端节点不可用之后会主动断开与之相关的客户端连接应用连接池感知到连接断开后重新创建连接时就会走到新主节点上。所以应用侧配置正确的连接池淘汰策略非常重要建议把连接的空闲存活时间设置得比Router踢掉失效连接的周期短一些或至少确保连接池会在拿到坏连接时主动重连。5. 故障切换实测主节点宕机后到底发生了什么5.1 故障模拟前的准备理论说再多不如亲手模拟一次故障。我在测试环境里的习惯做法是先确认当前集群的三个节点状态都是ONLINE且角色正常然后在一个终端持续循环查询某个计数器表的数值同时在另一个终端准备随时查看成员状态。我用的模拟命令很简单直接让主节点进程“消失”kill -9 $(pidof mysqld)用kill -9是为了模拟最极端的情况比如机器断电、进程崩溃。如果只是正常shutdownMGR有机会先广播“我要离开了”那属于优雅退出故障切换的触发条件会被掩盖。5.2 MGR自动选主的完整观察杀掉主节点后几秒钟内查看剩余节点的成员视图SELECT * FROM performance_schema.replication_group_members;原始主节点会显示UNREACHABLE或者直接不再出现两个剩余节点会短暂进入某种过渡状态然后其中一个节点的MEMBER_ROLE会从SECONDARY变成PRIMARY。关于新主是谁MGR选主算法实际上会选择在多数派中“最适合”的节点通常是最新的成员判定结果。整个过程不需要人工指定也没法人工干预你只需要关注最终选出来的新主数据是否完整。由于MGR的提交本来就是在多数派确认后才成功的新主上丢失已提交事务的概率极低但保险起见我会在切换后对新旧主节点的GTID集合做一次对比确认没有差异。5.3 Router自动感知与连接恢复主节点挂掉后观察Router侧的表现。如果开着--conf-use-gr-notifications1几乎在MGR完成选主的同时Router的日志里就会出现元数据刷新记录随后它会将连接旧主的客户端踢下线。此时再从应用侧执行查询第一次请求可能报连接错误连接池重试后写请求就会被转发到新选出的主节点上。整个过程中应用不需要修改任何配置。这也是我在实际生产中最看重的一点——对业务透明。5.4 给应用侧的重连建议虽然MGR和Router把切换这件事自动化了但应用连接池如果配置得不好依然会放大故障影响。两个核心参数需要把关一个是连接池的最大空闲连接数不要设置的太大否则Router踢连接时会有大量连接同时被销毁造成瞬间的连接风暴另一个是连接超时时间建议设置为5秒左右不要用默认的30秒否则故障期间应用线程会长时间阻塞等待。如果业务对延迟敏感可以考虑在应用层额外加一个“写请求重试一次”的机制。MGR切换期间应用第一次写请求失败属于正常现象立即重试第二次就能成功。不要对整个事务做无限重试那会把故障期间的错误流量放大到新主上。6. 踩坑实录与生产经验6.1 恢复通道的认证问题我第一套MGR环境初始化时其他两个节点怎么都加不进集群MEMBER_STATE一直卡在RECOVERING错误日志里报的是Authentication plugin caching_sha2_password cannot be loaded或者Access denied。原因是MySQL 8.0默认的认证插件是caching_sha2_password而组复制的恢复通道使用repl账号连接主节点同步binlog时默认认证方式对非SSL连接不友好。解决办法有两个在配置文件中添加group_replication_recovery_get_public_key ON让恢复通道主动向主节点获取RSA公钥或者干脆给repl账号指定旧版认证插件IDENTIFIED WITH mysql_native_password BY ...。我后期更推荐第一个方案因为mysql_native_password在新版本中逐渐被标记为废弃避免后续升级时再次踩坑。6.2 bootstrap_group参数误操作的后果这个坑我自己踩过也见别人踩过。在一个维护窗口里有人为了保险把三台机器的loose_group_replication_bootstrap_group都临时改成了ON然后依次执行START GROUP_REPLICATION。结果三台机器各自拉起了一个独立的单节点组虽然三个实例都在运行但互相无法感知。遇到这种情况没有快捷恢复方法只能把三个节点全部STOP GROUP_REPLICATION再在一个节点上正确bootstrap其他节点重新加入。所以我的经验是默认配置里就保持OFF初始化第一个节点时用SET GLOBAL临时开启用完之后立刻关掉绝不给误操作留余地。6.3 网络分区与多数派的关系MGR是少数服从多数的协议。3节点集群中如果出现网络分区分成1和2两组那么只有包含2个节点的多数派能继续工作孤立的那一个会被自动踢出集群并切换为只读不会脑裂。这个机制安全但也意味着你需要接受一个现实3节点集群最多容忍1个节点故障5节点集群最多容忍2个。如果业务要求“同时坏两台机器也不停服”那就要上5节点。同时网络设备层面的稳定性很重要MGR对组内通信延迟很敏感跨机房部署组复制时如果专线抖动明显会直接影响事务提交耗时。6.4 Router本身的单点问题最后提醒一个容易忽略的点Router替应用解决了MySQL层面的高可用但Router自己如果挂了应用同样会失去访问入口。生产环境建议至少部署两个Router实例前面用负载均衡设备或LVS给它们提供一个统一入口两个Router各自去bootstrap同一个MGR集群。平时两个Router都能接受流量任何一个发生故障时负载均衡自动把流量导到另一个上面。另外Router的connect_timeout和read_timeout两个参数建议从默认值调小一点。我自己习惯设置在5秒左右。这样当某个Router所在机器的网络出现异常时应用侧不会一直被半死不活的连接吊着而是快速失败迅速切换到另一个Router。6.5 关于SSL连接报错的一点点补充Redis热度词里有“mysql ssl连接错误”这个问题在Router场景下也会遇到。Router本身支持TLS加密但如果后端MySQL没有配置SSL或证书过期Router与MySQL之间的SSL握手会失败表现为应用通过Router连接时偶发报错。我的建议是在Router配置中单独管理好SSL相关参数测试环境可以用非SSL连接先跑通链路生产环境再把证书和加密开关一次性配置完整避免在故障排查时被多一层加密干扰。这套MGR Router的组合我已经在测试和生产环境里反复折腾过很多次。整体感受是选主快、切换稳、对应用友好比传统脚本切换方案的维护成本低得多。如果还要再给一个建议那就是——上生产前多演练几次故障切换把应用重连的行为观察透心里有底了再把它交出去。
返回列表