记得有一次生产环境凌晨报警一台Web节点CPU跑满后直接没响应整个站点卡了将近十分钟才缓过来。那次之后我彻底想明白一件事所谓高可用不是指望服务器不出故障而是要让故障发生时系统自己扛得住。高可用Web集群架构部署实验就是把这些意外事故提前在测试环境里演练一遍用负载均衡层、应用服务层、数据层三层结构搭一套完整集群然后人为拔电源、杀进程、断网线看它能不能自动完成故障转移对外服务始终不中断。这篇内容我按自己完整做过的实验流程来写包含了架构选型思路、Nginx与Keepalived的具体配置、MySQL主从复制搭建、Redis哨兵模式与会话共享、故障切换验证以及压测数据所有步骤都可以直接照着操作。适合刚接触集群架构的运维新人、想补全高可用知识栈的后端开发也适合准备架构师面试时用来做项目复盘的人。不管你是用虚拟机还是物理机这套方案都跑得起来。1. 实验背景与目标拆解1.1 为什么Web服务必须考虑高可用单机部署Web服务的逻辑很简单一个Nginx、一个应用进程、一个MySQL全部跑在同一台机器上。开发调试阶段完全没问题但一旦面对真实流量单点故障就成了悬在头上的刀。机器断电、磁盘损坏、内存泄漏、进程被OOM Killer杀掉任何一个环节出问题用户访问就直接失败。高可用架构的核心思想说白了就是消除单点。常规做法是把服务拆成多层每一层都放至少两个节点再通过某种机制让这些节点在故障时自动切换。常见的Web集群架构一般分三层负载均衡层负责流量分发和故障剔除应用服务层负责跑业务逻辑数据层负责存储状态。每一层都有自己的高可用手段层与层之间通过健康检查机制联动形成一套完整的故障转移链路。1.2 实验要解决的核心问题这个实验要验证的能力可以拆成三个明确的目标第一负载均衡节点本身挂掉时虚拟IP能在几秒内漂移到备用节点用户访问入口不失效第二后端某个Web应用节点挂掉时负载均衡器能自动把它从集群中摘除请求只转发给存活节点第三数据库主库异常时从库能被提升为新主库应用层的数据读写不受影响。三个目标对应三层高可用方案分别是Keepalived的VRRP协议实现VIP漂移、Nginx的upstream健康检查机制、MySQL主从复制加手动或自动化切换。每一层单独验证容易真正的难点在于三层联动时故障切换的顺序、时间窗口和数据一致性怎么保证。这个实验就是要把这三层完整搭起来逐层验证再叠加故障场景做整体演练。1.3 实验边界与验收标准考虑到多数人的实验环境资源有限我这次没有引入K8s、容器编排这些重量级组件用的是最直接的虚拟机方案。整个集群规划了6台节点2台负载均衡、2台Web应用、2台数据库外加一个可选的Redis节点用来做会话共享。操作系统选的是CentOS 7.9生产环境里这个版本用得最多参考资料也最好找。验收标准我定得比较实际负载均衡主节点宕机VIP漂移时间不超过5秒Web节点宕机Nginx自动摘除该节点剩余节点继续服务且无请求报错MySQL主库宕机通过手动或脚本方式将从库提升为主库业务读写恢复时间控制在可接受范围内。这个标准不算激进但能真实反映架构是否具备生产可用的底子。2. 技术选型与架构设计思路2.1 负载均衡层为什么选Nginx而不是LVS或HAProxy负载均衡层的可选方案很多LVS性能最强HAProxy对四层和七层支持都很均衡Nginx则在反向代理和Web服务能力上更有优势。我最终选择Nginx主要原因是它和业务层的亲和度最高配置upstream、SSL终止、HTTP层健康检查都非常方便一套配置既能做负载均衡又能兼任静态资源服务对实验场景来说性价比极高。实验里用的是Nginx的七层反向代理模式upstream块定义后端Web节点列表proxy_pass把请求转发过去。相比LVS的四层转发Nginx可以感知HTTP状态码比如后端返回502时主动摘除节点这对Web层服务来说判断更精准。代价是Nginx本身会消耗一定CPU做HTTP解析吞吐量低于LVS但对实验环境和中小规模业务来说完全够用。2.2 VIP漂移与健康检查Keepalived的核心原理Keepalived解决的负载均衡器自身的高可用问题。两台Nginx节点组成一个VRRP组共享一个虚拟IP正常情况下VIP绑定在主节点上主节点周期性向备节点发送VRRP通告报文。主节点挂了备节点在几个通告周期内收不到报文就会进入MASTER状态把VIP绑定到自己网卡上同时发送免费ARP刷新交换机上的MAC表项让后续流量自然切换到备用负载均衡器。Keepalived的配置重点有两个。第一个是VRRP实例里要指定VIP绑定的网卡名生产环境经常踩坑的地方就是多网卡机器上VIP绑错了接口第二个是必须写健康检查脚本定期检测Nginx进程是否存活不能等整个机器宕机才切换。比如Nginx进程异常退出但系统还活着如果没有脚本探活Keepalived认为节点正常VIP不会漂移流量就全部打到一台已经没有服务的机器上这种假死状态比直接宕机更难排查。2.3 数据层高可用方案对比数据层的高可用是整个实验里最不能拍脑袋决定的部分。我先后对比过三种方案MySQL主从加手动切换、MHA自动切换、MySQL Group Replication。MHA能做到主库故障时自动选出数据最完整的新主库并完成切换功能很强但部署复杂度和维护成本都不低。Group Replication是官方方案支持多主写入但要求所有节点网络延迟极低对实验环境来说不太友好。最终我选择了MySQL主从复制加半同步复制故障切换用脚本辅助完成。理由很直接主从复制是理解数据层高可用的基本功半同步复制能保证主库提交的事务至少同步到一台从库减少切换时的事务丢失。Redis在这一层的作用是会话共享配合哨兵模式防止缓存节点单点故障。关于Redis的哨兵模式和集群模式区别我后面单独展开两者解决的问题完全不同很多人在这里容易混淆。2.4 完整架构蓝图与流量走向整套架构的流量走向是这样的客户端请求先到达VIPVIP落在当前存活的主负载均衡节点上Nginx根据负载策略把请求转发给后端的Web节点Web节点处理业务逻辑时需要读取会话数据就访问Redis需要持久化数据就访问MySQL主库读操作可以分流到从库。整套链路里每一层都做了冗余任何一个节点故障流量都能在对应层完成切换。我在实验里给每台节点标注了角色和IP规划的时候刻意让负载均衡层的IP地址连续、数据层IP段独立这样后续写防火墙规则和配置文件时不会搞混。节点的硬件配置不用太高2核4G内存跑这些组件完全够用磁盘尽量用SSD尤其是数据库节点IO能力直接决定主从复制的延迟表现。3. 实验环境准备与基础部署3.1 节点规划与IP分配我用了6台虚拟机全部在同一网段操作系统CentOS 7.9关闭SELinux和防火墙。节点角色和IP分配如下lb01192.168.1.21主负载均衡节点运行Nginx与Keepalivedlb02192.168.1.22备负载均衡节点运行Nginx与Keepalivedweb01192.168.1.31Web应用节点1web02192.168.1.32Web应用节点2db01192.168.1.41MySQL主库db02192.168.1.42MySQL从库redis01192.168.1.51Redis节点同时作为哨兵监控节点VIP192.168.1.100对外提供服务的虚拟IPRedis节点我额外加了一台没有把它和Web节点混部原因是后续想验证哨兵机制时进程角色划分更清晰。如果实验环境紧张Redis可以和数据库节点复用一台机器但生产环境不建议这样干缓存和数据库混部会互相争抢内存和磁盘IO。3.2 基础环境初始化系统装完之后我第一时间统一做环境初始化。关闭SELinux用sed命令修改配置文件关闭防火墙用systemctl disable firewalld同时把hosts文件写好这样后面配置MySQL主从时可以直接用主机名通信。时间同步也很关键主从复制和Keepalived都对时间敏感我用了chrony服务指向内网时间源。依赖包方面Nginx需要gcc、pcre-devel、zlib-devel、openssl-devel这些编译依赖如果用yum安装预编译版本就不用自己编译了我还是坚持用源码编译主要想体验一下完整安装过程生产环境用yum会方便很多。MySQL用的是二进制包方式安装版本选5.7.38这个版本兼容性最好后面配置半同步复制也顺手。3.3 Web应用层部署与验证Web应用层我直接跑了一个轻量级的Python Flask应用每个节点返回自己的主机名和IP这样验证负载均衡时一眼就能看出请求被分发到了哪个节点。Flask应用代码很简单监听8080端口接口返回一行JSON格式的节点信息。应用的systemd服务配置写好后我分别在web01和web02上启动服务然后用curl验证了两个节点的接口都正常返回。这一步不要跳过如果应用层没起来就配置Nginx后面排查问题时会多一层干扰因素。应用层验证通过后我在两台Web节点上配置了Nginx代理到本机的Flask服务这样和前面负载均衡层的Nginx在逻辑上就区分开了都是Nginx但职责不同。4. 高可用核心组件部署与实现4.1 Keepalived与Nginx负载均衡配置先在lb01和lb02上安装Nginx然后配置upstream和反向代理。upstream块里定义web01和web02两个后端节点负载策略选least_conn因为实验里有短连接也有长连接least_conn比默认的轮询更容易均衡。后端健康检查用Nginx自带的max_fails和fail_timeout参数配合proxy_next_upstream开关后端节点返回502或504时自动把请求转发给下一个可用节点。Keepalived配置是这一层的关键我贴一下主节点lb01的核心配置片段global_defs { router_id LVS_MASTER } vrrp_script check_nginx { script /etc/keepalived/check_nginx.sh interval 2 weight -20 } vrrp_instance VI_1 { state MASTER interface eth0 virtual_router_id 51 priority 100 advert_int 1 authentication { auth_type PASS auth_pass 123456 } virtual_ipaddress { 192.168.1.100/24 dev eth0 label eth0:0 } track_script { check_nginx } }check_nginx.sh脚本里检查的是nginx进程是否存在不存在就返回1Keepalived会把priority下调20备节点优先级相对变高从而触发VIP漂移。这个脚本我单独提出来说是因为实际踩过坑最开始脚本里用了pgrep nginx发现机器上如果有其他用户态的nginx进程也会被匹配到后来改成ps -C nginx --no-heading判断匹配更准确。备节点lb02的配置和主节点基本相同区别是state为BACKUPpriority为90。还有一个细节是virtual_router_id必须一致同一网段里如果有多套Keepalived集群这个ID不能重复否则会出现VIP抢占的混乱现象。4.2 MySQL主从复制与半同步机制MySQL主从复制的原理本身不复杂主库开启binlog把数据变更写进二进制日志从库IO线程拉取日志写入本地的relay log然后SQL线程重放relay log完成数据同步。听起来简单实际操作中配置顺序和参数细节特别容易出错。主库db01的my.cnf关键配置[mysqld] server-id1 log-binmysql-bin binlog_formatrow gtid_modeON enforce_gtid_consistencyON sync_binlog1从库db02的my.cnf配置[mysqld] server-id2 relay-logrelay-bin read_onlyON gtid_modeON enforce_gtid_consistencyON配置完成后在主库创建专门的复制用户然后在从库执行CHANGE MASTER TO语句指定主库地址、用户名和密码用GTID模式后不需要再手动指定日志文件和偏移量同步位点由GTID自动管理比传统的binlog position方式省心很多。启动复制后立刻用show slave status来验证两个关键线程状态IO线程和SQL线程都显示Yes才算成功。为了让数据层故障切换时减少事务丢失我额外开启了半同步复制。半同步复制的原理是主库提交事务时必须等待至少一个从库确认收到binlog并写入relay log后才返回客户端提交成功。相比异步复制半同步多了一次网络往返写入性能会有损耗但对高可用架构来说这个代价是值得的。开启方式是在主从库分别安装半同步插件然后动态启用相关参数。Redis节点上的会话共享相比数据库主从要简单得多Web应用把session存储从本机文件改成RedisNginx就不需要设置ip_hash这种负载策略任意请求落在任意Web节点都能拿到同一份会话。我在Flask应用里配置了Redis作为session存储连接redis01的6379端口Web节点之间不再有会话状态的同步问题。4.3 Redis哨兵模式与集群模式的区别很多人把Redis哨兵模式和高可用混为一谈其实哨兵模式解决的是Redis节点的主从切换问题它本身不提供数据分片。实验里我部署了三个哨兵进程监控redis01这个主节点当主节点异常下线时哨兵集群通过投票机制选出新主节点并把客户端连接引导到新主节点上。哨兵本身至少部署三个实例避免哨兵自己出现脑裂时无法形成多数派投票。Redis集群模式则是另一回事它解决的是数据容量和吞吐量扩展问题通过哈希槽把数据分散到多个主节点上每个主节点还可以挂从节点提供高可用。哨兵模式适合数据量中等、主要是读写分离的场景集群模式适合数据量大、需要水平扩展的场景。实验里的Web应用会话共享用哨兵模式就够了强行上集群模式反而让配置复杂度超出验证目标。4.4 故障转移与自动切换验证所有组件配完之后我开始做故障验证。第一轮验证负载均衡层在lb01上直接停掉nginx进程观察VIP是否在几秒内漂移到lb02。用ip addr命令在lb02上能看到192.168.1.100出现同时在客户端机器上持续ping VIP整个过程网络不中断这就是Keepalived健康检查脚本生效的效果。第二轮验证Web节点故障把web01上的Flask应用进程杀掉然后从客户端连续请求VIP地址。正常情况下Nginx会在下一轮健康检查发现web01不可用自动把请求全部打到web02客户端看到的结果是请求全部由web02响应没有出现连接失败。这里有个细节max_fails和fail_timeout的乘积决定了后端节点被摘除的时间窗口如果设置太大故障切换会慢。第三轮验证数据库切换模拟主库宕机在db01上执行shutdown。此时从库db02还在运行但复制线程会断掉。执行show slave status能看到Slave_IO_Running变成No这时需要手动在db02上执行stop slave、reset slave all然后解除read_only限制把db02提升为新主库。应用层连接的数据库地址需要同步修改或依赖代理层切换实验里我直接改应用配置并重启Flask服务来验证读写恢复。5. 压测与故障演练实录5.1 压力测试工具与基础数据故障切换验证完我顺手做了压测评估这套集群在正常状态下的吞吐能力和故障态下的表现。压测工具用的是ab简单轻量适合模拟并发请求。测试请求是Web应用的一个查询接口背后会读一次Redis缓存不存在复杂计算主要考验网络链路和Nginx的转发能力。压测命令如下ab -n 20000 -c 200 http://192.168.1.100:80/api/test并发200总请求数20000这个量级对两台Web节点来说算中等偏低的压力。压测过程中我同时用top监控Web节点的CPU和内存用ss命令统计连接数确认没有出现连接队列溢出。5.2 故障演练过程记录压测基线跑完之后我开始做故障演练。为了让结果更有说服力演练分三组进行第一组是正常状态下压测记录吞吐量、平均响应时间、错误率第二组是停掉一台Web节点后压测观察吞吐量变化和请求是否全部收敛到存活节点第三组是停掉主负载均衡节点后压测验证客户端连接是否无感知切换。第二组演练的结果很有意思停掉web01后Nginx的健康检查在约3秒内完成了节点摘除期间的少量请求出现了短暂超时但大部分请求正常完成。这说明健康检查是有时间窗口的在窗口内的请求依然会尝试转发到已故障节点。这个现象在线上是高可用架构的固有特征只能通过缩短检查频率来降低影响无法完全消除。第三组演练停掉lb01后VIP漂移耗时约3.5秒客户端持续压测的请求出现了极短的中断接着恢复正常。原因是TCP连接建立后依赖VIP的ARP表项切换节点后新连接才能正常建立已经建立的长连接会断开。生产环境如果要做到连接不中断需要在负载均衡层之上再加一层四层入口比如DNS轮询或者云平台的SLB。5.3 指标记录与分析总结我把三组压测的关键指标整理成表格方便直观对比场景吞吐量req/s平均响应时间ms错误率正常双节点11250170%单Web节点6870290.12%负载均衡切换9860210.08%单独看数字停掉一台Web节点后吞吐量下降约39%这个比例符合预期因为两台节点变成了单台容量理论上减半加上健康检查期间的短暂排队下降幅度略高于50%。错误率从0变成0.12%原因就是前面说的健康检查时间窗口内请求转发到已故障节点。这套架构在正常态和故障态下的表现都符合预期验证了高可用设计目标。但压测也暴露了一个问题单台负载均衡节点在VIP切换瞬间会丢失存量长连接如果要追求更极致的可靠性需要在负载均衡层前端增加额外的接入层或者用云平台的负载均衡产品直接管理VIP和健康检查。6. 常见问题与排查技巧实录6.1 部署期高频问题速查表整个实验做下来我整理了一批最高频的问题和对应的排查方向直接做成速查表方便大家碰到类似情况时对照排查现象可能原因排查命令或思路VIP无法漂移Keepalived状态异常或日志无输出systemctl status keepalived、journalctl -u keepalived、检查VRRP实例的interface配置后端节点频繁被摘除Nginx健康检查超时或参数过小查看nginx error.log调整max_fails和fail_timeoutMySQL复制线程报错主从GTID不一致或网络中断show slave status查看Last_IO_Errno检查主库binlogRedis哨兵无法选出主节点哨兵实例数不足或quorum配置过大检查哨兵日志确认sentinel monitor参数应用会话丢失Redis地址配置错误或哨兵切换后未更新连接检查应用侧Redis连接配置启用哨兵模式连接方式同一符号检查时如果主从的server_id相同会导致复制异常这个问题很隐蔽建议初始化时统一用IP最后一个段位或者自定义编号确保唯一性。6.2 脑裂与Keepalived的伪健康陷阱Keepalived最常见的深坑就是脑裂也就是主备两台节点同时进入MASTER状态VIP同时出现在两台机器上。VRRP协议本身有优先级和通告机制正常网络抖动不会导致脑裂但网卡故障、防火墙丢弃组播报文、或者配置了不同的vrrp_instance名称都可能导致脑裂发生。排查脑裂的方法是分别在两台节点执行ip addr命令看VIP是否同时存在。生产环境防止脑裂的通用做法是增加一个额外的fence机制比如脚本检测到VIP同时存在时主动在备份节点上禁用网卡或关闭Keepalived服务。实验环境不需要这么复杂的fence但我还是建议在Keepalived配置里添加nopreempt参数避免优先级变化引起的频繁切换减少误判概率。另一个伪健康陷阱是只监控进程不监控服务能力。keepalived默认的vrrp_script只能告诉你进程还活着如果Nginx进程活着但已经假死比如worker进程全部卡死请求进来不响应脚本检测不到异常。生产级别的健康检查应该用curl访问本机的健康检查端口确认能返回正常HTTP状态码才算通过。实验环境我也改成了这种方式代价是多占一点CPU换来的是更真实的探活结果。6.3 监控、日志与日常维护建议实验收尾阶段我在每台节点上补了基本的监控和日志采集。没有引入Prometheus这类重量级监控体系只是用crontab写了个简单的shell脚本每隔30秒检查关键进程的状态异常时把告警信息追加到日志文件并在终端弹通知。对实验环境来说够用了生产环境至少要上Prometheus加Alertmanager指标采集要覆盖CPU、内存、磁盘、网络、进程存活、接口响应时间这些核心项。日志方面Nginx的access.log和error.log要定期切割防止磁盘写满。MySQL的error.log和binlog也要关注binlog如果开启不清理时间长了会占满磁盘。我习惯配置expire_logs_days参数自动清理过期binlog同时用crontab做每日配置文件的异地备份。高可用架构维护的核心思路是再完善的自动化切换机制也需要配合日常巡检才能做到故障发生时心里有底。这套集群在我本机保持运行了大约两周期间做了多次随机故障演练杀掉不同层的进程拔掉虚拟网卡模拟断网整体表现稳定。从最初的单机架构到现在的三层高可用集群最大的体会是架构设计不是堆组件而是要把每一层故障切换的细节想清楚健康检查的判定标准、切换时间窗口、数据一致性保证、日志和监控的覆盖范围这些才是真正决定高可用架构可靠性的关键。