做Linux运维这些年,最怕的就是半夜接到电话,说业务挂了、服务起不来了。很多故障其实不是硬件多复杂,而是单点部署——一台机器扛所有事,它一躺,整个业务跟着躺。最近我刚好帮客户从零搭建了一套双机热备,把主备切换、数据同步、故障演练整套流程都走了一遍,从踩坑到收敛,过程挺有代表性。这篇就系统聊聊Linux双机热备的思路和落地细节,从核心机制到实际配置,再到我实测的切换时间和故障排查,希望能给要上高可用的朋友一个能直接参考的范本。
1. 双机热备到底在“备”什么——先弄懂心跳、VIP和脑裂
1.1 单点故障绝对不是“坏了才修”的事
先说一个很扎心的场景:你的核心业务跑在一台Nginx或Tomcat服务器上,数据库、Redis、静态资源全在同一台机器里。一切正常时,一个小流量的应用根本看不出问题。但只要这台机器的主板烧了、硬盘挂了、内核panic,或者只是有人手滑敲了一条rm命令,整个服务的可用性立刻归零。更麻烦的是,数据可能跟着丢。
所以高可用的第一目标不是“让一台机器永远不坏”,而是“坏了之后业务不能断”。双机热备就是最基础、性价比最高的一套做法:两台服务器,一台主一台备,平时主节点干活,备节点待命;主节点一旦异常,备节点在几秒内接管,客户端的访问IP几乎是同一个,从用户视角看,业务几乎无感知。
这里要区别一下“热备”和“冷备”。冷备就是定期备份数据,出事后人工去恢复,恢复时间可能是半小时甚至几小时。热备则是自动故障转移,RTO(恢复时间目标)可以控制在秒级或分钟级,关键是整个过程不需要人工介入。对大多数中小型业务来说,双机热备已经是能花最少成本换取最大安全感的方式。
1.2 心跳、VIP漂移与脑裂,这三个词是双机热备的命根子
很多人看双机教程,满眼都是配置文件和命令行,但不理解背后的机制。我建议先弄懂三件事。
心跳(Heartbeat):主备节点之间需要不断确认“对方还活着”,这个确认动作就叫心跳。Keepalived默认每1秒通过VRRP协议发送一次通告,主节点连续3次没收到对方的通告,就认为对方挂了。注意“连续”这个词很关键,单独一次丢包是网络抖动,不会触发切换,否则生产环境会天天误切。
VIP漂移(VIP Failover):VIP是Virtual IP的缩写,也就是虚拟IP,或者叫浮动IP。整个双机热备对外只暴露一个VIP,平时绑定在主节点的网卡上,客户端访问的都是这个IP。当主节点故障时,备节点会立刻把VIP抢到自己身上,并通过免费ARP广播通知交换机“这个IP对应的MAC地址变了”。对客户端来说,IP没变,只是背后的服务器换了。
脑裂(Split-Brain):脑裂是双机热备里最让人头疼的问题。主备之间的心跳链路断了,但两台机器其实都是活的,各自认为自己才是主节点,结果两个节点同时拥有VIP,同时提供服务。一旦出现脑裂,客户端流量会被分流到两台机器上,数据如果在两边同时写入,后面同步就会出现严重分叉。解决办法通常有三条路:多一条独立的心跳线、增加第三方仲裁(比如探测网关投票)、或者配置仲裁盘。后面我会在故障排查章节再具体展开。
2. 方案选型:Keepalived还是Corosync/Pacemaker,别一上来就抄配置
2.1 Keepalived:轻量、成熟、上手快,基于VRRP协议
Keepalived是Linux上做双机热备最主流的方案,核心是VRRP(虚拟路由冗余协议)。VRRP最初是给路由器做故障切换设计的,后来被广泛移植到服务器场景。Keepalived把VIP管理、健康检查、故障切换集成在一套配置里,部署非常轻,不依赖数据库、不依赖消息总线,装完就能用。
Keepalived适合什么场景呢?两个节点、一套服务、一个虚拟IP,这几样加起来能覆盖九成以上的中小型业务。你在配置里写一个VIP,再写一段健康检查脚本,它就能自动完成“服务进程挂了就漂移VIP”的逻辑。实测下来,单纯依赖进程检查的切换时间通常在2~6秒之间,已经很够用了。
2.2 Corosync/Pacemaker:集群资源管理,适合复杂场景但不是万金油
再来看看Corosync和Pacemaker的组合。Corosync负责节点间通信,Pacemaker负责集群资源编排。它们不是简单的“IP漂移工具”,而是把服务进程、文件系统、虚拟IP、数据库主从等所有资源都纳入集群管理,支持复杂的启动顺序、多节点约束、资源互斥,还支持N个节点的仲裁。
听起来很强大,但代价也很明显:配置复杂,学习曲线陡峭,排错难度大。我见过不少团队把Corosync/Pacemaker部署翻车,最后查下来只是配置文件里一个括号写错。对只有两台服务器、只需要一个VIP的场景,上这套反而是在给自己挖坑。
2.3 我的选型经验:80%的日常场景直接用Keepalived
下面这张表是我在实际项目里做选型时的参考维度,分享出来供对比。
| 方案 | 部署复杂度 | 资源管理能力 | 故障检测方式 | 适用场景 |
|---|---|---|---|---|
| Keepalived | 低 | 仅VIP和服务健康检查 | VRRP心跳 + 自定义脚本 | 两个节点、一个VIP、进程级高可用 |
| Heartbeat | 中 | 资源脚本管理 | 心跳机制 + 资源代理 | 早期双机方案,新项目不建议 |
| Corosync/Pacemaker | 较高 | 多资源、多节点、启动约束 | 消息层心跳 + 资源代理 | 多节点集群、复杂依赖、共享存储高可用 |
简单说,如果你做双机热备只是为了“入口不中断”,Keepalived是首选。如果后面业务增长到需要多节点、多资源编排、数据库自动切换,再考虑Corosync/Pacemaker。商业HA软件我也接触过,本质逻辑和Keepalived差不多,真出问题时排查起来反而更费劲,所以我现在对外推荐基本都会先问清需求再定方案。
3. 实战部署:Keepalived + Nginx双机热备完整配置
3.1 环境准备:两台机器和一个VIP
开始动手前,先规划好环境。我这里用的是两台Rocky Linux 8服务器,一台为主节点、一台为备节点,具体信息如下:
| 角色 | 主机名 | 网卡 | 物理IP | 角色说明 |
|---|---|---|---|---|
| 主节点 | node1 | ens33 | 192.168.111.10 | 平时承载业务,优先级100 |
| 备节点 | node2 | ens33 | 192.168.111.20 | 待命接管,优先级99 |
| VIP | - | ens33 | 192.168.111.100 | 对外提供服务的浮动IP |
两台机器上提前装好Nginx并启动,保证本机通过IP能正常访问测试页。注意一点:生产服务器建议使用静态网络配置,不要依赖NetworkManager动态管理网卡。Keepalived漂移VIP时是直接对网卡做IP操作的,如果NetworkManager的托管策略和它冲突,会出现VIP消失或者IP绑定混乱的问题。我踩过一次这种莫名坑,后来一律建议把网卡配置写成固定的ifcfg文件。
3.2 Keepalived配置逐行拆解
安装Keepalived很简单,Yum源里直接就有:
yum install -y keepalived nginx systemctl enable keepalived nginx主节点的核心配置文件在/etc/keepalived/keepalived.conf,内容如下:
global_defs { router_id LVS_MASTER enable_script_security script_user root } vrrp_script chk_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 timeout 2 weight -20 rise 2 fall 2 } vrrp_instance VI_1 { state MASTER interface ens33 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.111.100/24 dev ens33 } track_script { chk_nginx } }这里每个参数都不是随便写的,逐个说明一下。
router_id就是给当前节点起个标识名,主备可以不同,用于日志识别。vrrp_script定义的是健康检查脚本块,脚本路径要写绝对路径,interval 2表示每隔2秒探测一次Nginx存活,timeout 2表示单次执行超过2秒算失败,fall 2是连续失败2次才认定服务故障,rise 2是连续成功2次才恢复状态,这两个参数能很好避免网络抖动导致的误切。
weight -20是关键,它表示健康检查失败时优先级降20分。主节点原本优先级100,失败后变成80,低于备节点的99,触发抢占切换。这也是主备切换的核心逻辑:不是直接让备节点接管,而是通过降低主节点优先级让备节点合法接管。有的朋友把weight配成正数,结果发现主节点恢复后反而抢不回VIP,就是因为优先级比较逻辑出了问题。
virtual_router_id 51是VRRP实例标识,同一局域网内多套Keepalived不能相同,取值范围1~255。advert_int 1是心跳通告间隔,1秒发一次VRRP报文。authentication里推荐用PASS,密码最多8位,注意不是加密,只是防止误入实例。
virtual_ipaddress里写的就是VIP,/24子网掩码按实际网段调整。track_script把前面的健康检查关联到实例上,没有这一步,脚本写得再好也不会生效。
3.3 健康检查脚本的写法与注意点
组件自带的check_nginx.sh内容很简单:
#!/bin/bash if ! pidof nginx >/dev/null 2>&1; then exit 1 fi exit 0脚本思路是“查到Nginx主进程就返回0,查不到返回1”。Keepalived执行脚本时,非零退出码就代表服务不健康。
这里有个常见问题:为什么不用curl直接访问本地80端口来做检测?原因是curl检测依赖HTTP响应,如果Nginx配置了复杂访问控制,或者页面本身需要后端资源,curl很容易因为超时或非200状态误判。进程检测虽然粒度粗一点,但它足够稳定,对于简单的“Nginx挂了就漂移”的需求已经完全够用。如果业务要求更细,比如检测某个URL返回码,可以在脚本里加判断,但一定要为curl加上--connect-timeout 2 --max-time 3这类参数,防止检测动作本身卡死。
写完脚本后必须加执行权限:
chmod +x /etc/keepalived/check_nginx.sh接下来把同样的配置复制到备节点,改三处:router_id改为备节点的名字,state改为BACKUP,priority改为99。注意virtual_router_id、auth_pass、VIP必须和主节点完全一致,否则两个节点互相不认识。
备节点配置示例:
global_defs { router_id LVS_BACKUP } vrrp_script chk_nginx { script "/etc/keepalived/check_nginx.sh" interval 2 timeout 2 weight -20 fall 2 rise 2 } vrrp_instance VI_1 { state BACKUP interface ens33 virtual_router_id 51 priority 99 advert_int 1 authentication { auth_type PASS auth_pass 1111 } virtual_ipaddress { 192.168.111.100/24 dev ens33 } track_script { chk_nginx } }3.4 启动验证:如何确认VIP已经生效
两台节点都启动服务:
systemctl start keepalived然后分别检查VIP位置:
ip addr show ens33正常情况下,主节点ens33上会多出一个192.168.111.100/24的地址,备节点上没有。再用systemctl status keepalived和journalctl -u keepalived -f看日志,主节点会显示进入MASTER状态,备节点显示BACKUP状态。
心跳是否正常也要验证一下。直接抓包看VRRP报文最直观:
tcpdump -i ens33 vrrp主节点会周期性发出目标地址为224.0.0.18的组播报文。如果你的网络环境不支持组播(比如某些公有云、跨网段部署),就得改用单播模式,在配置里加:
unicast_peer { 192.168.111.20 }两端互相指向对方IP。这是我后来在云环境部署时经常用到的功能,比组播省心很多。
4. 数据层怎么做双机热备——DRBD和数据库复制的关键细节
4.1 别让VIP漂移变成“漂了个寂寞”
很多人把Keepalived配完就宣告高可用完成,这是很大的误解。Keepalived只保证了“入口地址”的可用性,数据层的安全完全是另一回事。如果主节点的数据库或文件服务没有向备节点同步,主节点一宕,备节点接管了VIP,但备节点上的数据还是几小时前的旧版本,业务照样不可用。
所以数据同步环节必须在设计之初就想清楚。常见的选择有三类:共享存储、块级复制、业务层复制。
4.2 块级实时同步方案:DRBD
DRBD(Distributed Replicated Block Device)可以简单理解成“网络版RAID1”:底层块设备的数据通过网络实时镜像到另一台机器的磁盘上。主节点写什么,备节点同步写什么,数据粒度和主节点保持完全一致。
部署流程大致是:先准备好一块独立的磁盘或分区,比如/dev/sdb1,在/etc/drbd.d/global_common.conf中配置协议,生产环境我建议用protocol C,也就是主节点写入时必须等备节点确认落盘后才返回成功,这样主备数据一致性最强。然后在资源文件里定义节点名称、设备名、磁盘路径,执行:
drbdadm create-md r0 drbdadm up r0 drbdadm primary --force r0 mkfs.xfs /dev/drbd0 mount /dev/drbd0 /data查看同步状态用cat /proc/drbd,会显示UpToDate/UpToDate,这就是两边数据完全实时的状态。
DRBD做主备切换时有个非常容易出错的操作:切换前必须先把旧主节点上的文件系统正常卸载,执行drbdadm secondary r0,再在备节点上执行drbdadm primary r0。如果跳过卸载直接强制主,可能把还在写入的文件系统切到另一边,出现严重的数据异常。我自己第一次演练时就因为少了一步,直接把DRBD设备搞成需要重建,教训很深刻。
Keepalived和DRBD的联动时序也要设计好:一般顺序是先挂载文件系统,再启动业务服务,最后才把VIP切过去。如果服务没有成功启动就提前漂VIP,客户端访问的还是一个坏服务,等于白切。
4.3 业务层复制方案:MySQL/PostgreSQL主从
如果业务是数据库主导,直接在数据库层面做主从复制更合理。MySQL用主从同步(建议半同步复制),PostgreSQL用流复制,备库实时接收主库的WAL日志。Keepalived负责在检测到主库异常时把VIP切到备库机器,同时备库需要从只读模式切换成读写模式。
这类方案的关键点是“数据追平”。备库接管前必须确认它应用日志的延迟在可接受范围,否则切过去后数据会丢。半同步复制或者PostgreSQL的同步复制就是在主库提交事务时等备库确认收到,把丢失风险降到最低。如果切换逻辑要做得更精细,可以把keepalived的健康检查脚本升级成“检查主库是否可写、备库延迟是否可接受”的复合判断,这样从节点只在真正具备接管条件时才抢VIP。
5. 故障切换实测:拔电、杀进程、断网三种场景的真实反应
5.1 测试前的准备
配置不是写完就能上线的,故障演练必须做。我在客户环境里做了一轮完整切换测试,准备的检查项如下:
- 客户端持续ping VIP,观察丢包情况;
- 另一终端持续
curl访问Nginx测试页,记录中断时长; - 两端同时开启
journalctl -u keepalived -f日志跟踪; - 在客户端执行
ip neigh show 192.168.111.100,记录切换前后的MAC地址。
这套准备完成后,我依次做了三次故障模拟。
5.2 三次故障模拟的结果对比
第一轮:直接杀掉主节点的Nginx进程。
pkill -9 nginx客户机访问出现约4~6秒的中断。这段时间花在哪了?Keepalived的健康检查脚本2秒跑一次,连续失败2次才判定故障,这里已经耗掉4秒左右;判定后主节点优先级降到80,低于备节点的99,备节点抢占VIP并对外发送免费ARP。整体上符合预期,但能明显感觉到检测间隔对RTO的影响。
第二轮:直接停掉主节点的Keepalived服务。
systemctl stop keepalived这个场景更接近“主节点主动退出”的情况。备节点在连续3个通告周期没收到主节点的心跳后,大约3秒后抢占VIP,实际业务中断约3~4秒。比杀进程更快,因为这里省掉了健康检查脚本的轮询时间,直接走VRRP失效检测。
第三轮:模拟最严重的物理故障,直接给主节点断电,或者拔掉主节点的网线。
这里测得的中断时间大约在4秒左右。注意,拔网线是最容易触发脑裂的场景,因为主节点还活着但备节点收不到心跳,它会在超时后接管VIP。此时如果主节点心跳恢复,两边可能同时抢VIP。所以测试时我是在拔掉主节点网线的同时,把主节点上的Keepalived也停掉,模拟纯粹的物理故障节点,避免留下一个“半死不活”的主节点来捣乱。
5.3 切换时间背后的规律
三次测试的结果可以汇总成下表:
| 故障场景 | 业务中断时间 | 主要耗时点 |
|---|---|---|
| 杀掉Nginx进程 | 约4~6秒 | 健康检查轮询 + 优先级抢占 |
| 停止Keepalived服务 | 约3~4秒 | VRRP通告超时判定 |
| 主节点断电/断网 | 约4秒 | VRRP通告超时判定 |
可以看出,双机热备的RTO基本在10秒以内,用户体验上可能表现为一次短暂的页面加载超时,重试后就恢复了。如果你想把这个时间压缩得更短,有几个优化方向:把健康检查的interval从2秒降到1秒,把fall从2改成1,advert_int改成0.5秒。但代价是误判概率增加,建议在稳定的内网环境里用更短的参数,在跨公网的场景保持默认。
切换完成后,我在备节点上执行ip addr show确认VIP已经绑定,再用curl访问VIP确认业务正常。客户端的ARP缓存这时已经学习到新的MAC地址。现场还抓了一次包,能看到备节点主动发送了免费ARP报文,这正是VIP漂移能被局域网内迅速感知的原因。
6. 双机热备常见故障与排查技巧:避坑实录
6.1 脑裂:一旦发生,先止血再谈原因
脑裂的典型症状是两端日志里的状态都在乱跳,一会儿MASTER一会儿BACKUP,而且两个节点同时拿着VIP。发现这种苗头,第一要务不是查原因,而是止血:立刻停掉其中一个节点的Keepalived服务,比如停备节点的,让流量稳定在另一台机器上。确认业务恢复后,再检查心跳链路,逐个排查网线、交换机端口、防火墙规则、VRRP配置是否一致。
恢复阶段的顺序也很重要:先修好心跳,让备节点能正常收到主节点的通告,再启动备节点的Keepalived,它会自动进入BACKUP状态。如果把顺序搞反,先启动备节点再修心跳,备节点发现没有主节点通告会主动抢占VIP,直接二次脑裂。这个恢复顺序我建议写进运维手册,别靠临场记忆。
6.2 防火墙把VRRP组播拦了
Keepalived默认走VRRP组播地址224.0.0.18,很多系统防火墙默认是拦组播的。症状就是两端都以为对方死了,抢来抢去,和脑裂很像。处理方式很简单,在两端放行VRRP协议:
firewall-cmd --permanent --add-protocol=vrrp firewall-cmd --reload如果用iptables,也要允许proto 112的报文。这里最隐蔽的坑是:云环境或部分虚拟网络根本不支持组播,你在本地测试一切正常,一搬上云端就出问题。这种场景下,直接在配置里改单播模式(unicast_peer)是最省心的方案。
6.3 健康检查脚本坑了Keepalived
Keepalived配置的脚本如果写错,会导致服务直接起不来。常见错误有三类:脚本没有加执行权限、脚本路径写错、脚本内部语法错误。排查的时候看日志找script关键报错即可:
journalctl -u keepalived -f另外要注意,脚本里不要用pgrep nginx这种宽泛匹配,它可能把nginx相关的其他进程也匹配上。更严谨的写法是pgrep -x nginx或者用systemctl is-active nginx判断。我见过最离谱的一个案例,脚本里误写了死循环,导致Keepalived的子进程一直占满CPU,系统负载被拉满,业务Nginx反倒被拖死了。
6.4 交换机端口安全导致VIP切换后不通
VIP漂移的底层是MAC地址切换,交换机靠学习MAC表把流量转发到对应端口。如果交换机端口配置了端口安全、静态MAC绑定或者DHCP snooping,备节点接管VIP后发出的免费ARP会被交换机视为非法报文,客户端流量仍然被送往旧端口,结果就是VIP看着在备节点上,但流量根本不进去。
这个问题排查起来很绕,客户端能ping通VIP,备节点上也确实有VIP,但业务就是断的。最后看了交换机的MAC地址表才发现端口绑定问题。遇到这种情况,找网络同事清一下端口安全策略或老化时间,同时给客户端手动刷新ARP缓存:
ip neigh flush all6.5 两端时间不一致影响判断
日志、故障分析、脑裂判定都需要时间线一致。两台机器时间差太多,排查脑裂时你会发现两边的日志时间对不上,根本没法判断谁先抢的VIP。生产环境建议统一部署chrony做时间同步,几行配置的事,别留隐患。
6.6 排查三件套:日志、抓包、状态
真到排障环节,我一般按这个顺序来:先看Keepalived自带状态和日志,再抓包看VRRP报文,最后检查网络设备。常用命令列在这里:
systemctl status keepalived journalctl -u keepalived -f ip addr show ens33 tcpdump -i ens33 vrrp cat /proc/drbd ip neigh show最后再分享一个我的个人体会:双机热备上线前,一定要完整做一轮故障演练,而且演练的故障类型要覆盖杀进程、停服务、拔网线、断电四种,因为每一种场景的切换路径和耗时完全不同。别等项目真的宕机那天,才第一次认真看Keepalived的日志长什么样。