尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

多环境服务器排障实战:从磁盘IO瓶颈到系统性能优化

多环境服务器排障实战:从磁盘IO瓶颈到系统性能优化 之前维护一个内部项目环境时遇到过一件很有意思的事两台机器配置看起来差不多业务版本也一致但一台白天很稳定另一台每隔一两个小时就“卡”一下。群里有人开玩笑说这套测试服务器组合就像“马桶基地”什么稀奇古怪的问题都能遇到简称“b事多”。围绕这个问题我和同事一起做了一轮完整的排障最后问题倒是解决了但过程非常值得总结。这篇文章就以这次项目里的两个节点为背景代号分别叫“飞天”和“泰电”做一个对比式排查复盘。为什么会叫这两个名字一个是典型的云上节点底层运行的是大家比较熟悉的阿里云飞天体系另一个是机房里的老节点内部代号沿用项目里的叫法就叫“泰电”。两台机器处于同一套业务链路中结果表现天差地别。文章会覆盖环境说明、排查命令、问题定位、优化方案和工程建议适合刚接触服务器运维或者经常被环境问题折腾的开发者阅读。1. 背景与核心概念1.1 什么是“马桶基地”和“b事多”先说点轻松的。“马桶基地”并不是什么正式产品名而是大家对自己维护的一套测试集群的戏称。它的特点是节点不多、系统版本不统一、硬件类型五花八门但恰恰是这种环境最容易暴露真实生产环境里才会遇到的问题。时间久了大家就说这地方“b事多”也就是各种异常比较多。对做后端和运维的人来说环境越乱越需要一套标准的排查方法。如果只会重启或加日志很难定位到根因。所以这篇文章里的“飞天 vs 泰电”实际上是两类环境的对比飞天云主机虚拟化资源磁盘性能稳定网络链路相对简单。泰电机房旧节点硬件异构磁盘可能是普通机械盘网络经过多个交换机存在更多不确定性。1.2 为什么多环境问题难排查很多同学会问同样是部署一套代码为什么换台机器就不行根本原因在于服务器问题是由多个因素共同决定的底层虚拟化是否超卖。磁盘类型是 SSD 还是 HDD。内网带宽是否受限。系统内核参数是否一致。中间件版本和参数是否一致。业务请求量是否在节点间均匀分配。当应用出现报错时表象往往是“接口超时”“页面 5xx”“CPU 飙高”但真正的原因可能藏在磁盘 IO、网络握手、数据库连接池或者垃圾回收停顿中。多环境问题难排查就是因为同一个表象可能对应完全不同的根因。1.3 本次文章的目标读完这篇文章你至少能掌握多环境服务器排障的基本顺序。常用系统命令和输出含义。如何从应用层判断问题是否在调用链路上。针对磁盘、网络、连接池、慢 SQL 的优化思路。生产环境变更时的注意事项和最佳实践。2. 环境准备与版本说明在进入实操前先说明一下案例环境。下面的配置只是本次排障的示例不代表任何官方推荐配置。实际项目中请务必用真实环境的信息替换。2.1 节点配置项目飞天节点泰电节点角色应用节点 A应用节点 B系统CentOS 7.9CentOS 7.6CPU4 核4 核内存8 GB8 GB磁盘SSD 云盘机械盘网络云内网 VPC机房局域网部署服务Spring Boot NginxSpring Boot Nginx数据库共用 MySQL共用 MySQL缓存RedisRedis提醒如果系统版本、内核版本不同某些命令输出可能略有差异。比如free -h在 CentOS 7 和 CentOS 8 上显示逻辑就不同但不影响排查思路。2.2 软件版本本次涉及的软件版本大致如下Nginx1.20.xMySQL5.7.xRedis6.xJDK1.8Spring Boot2.x如果你的项目使用更高版本比如 MySQL 8.0 或 JDK 17部分参数和命令会有变化建议以官方文档为准。2.3 部署拓扑这是一个比较常见的部署形态[客户端 / 外部流量] | v [Nginx 负载均衡] | |-------- [应用节点 A飞天] | |-------- [应用节点 B泰电] | v [MySQL 主库] [Redis]理论上 Nginx 会把请求轮询分发到两个应用节点。既然配置一致按理说不应该出现“一个正常、一个慢”的情况但实际恰恰相反。接下来我们按层拆解排查过程。3. 多环境服务器的核心排查方向服务器排障最怕没有章法。我的习惯是分四层逐层往下看资源层CPU、内存、磁盘、负载。网络层连通性、延时、丢包、端口状态。中间件与应用层连接池、GC、线程状态、日志。数据库层慢查询、锁等待、连接数。下面每一层都会给出常用命令和判断思路。3.1 资源层排查首先用top查看整体负载。top重点关注三个值load average1 分钟、5 分钟、15 分钟平均负载。us用户态 CPU 占比。sy内核态 CPU 占比。wa等待 IO 的时间占比。如果wa很高说明系统可能卡在磁盘 IO 上。如果us和sy都很高则可能和业务计算或系统调用有关。继续查看内存和磁盘free -h df -h查看磁盘 IO 情况iostat -x 1 3重点看%util磁盘繁忙程度。长期接近 100% 说明磁盘是瓶颈。awaitIO 请求平均等待时间。机械盘通常比 SSD 高很多。rkB/s、wkB/s实际读写速率。如果节点使用的是机械盘高并发场景下出现%util100%、await几百毫秒的现象非常常见。后面泰电节点就是栽在这里。3.2 网络层排查网络层是另一个“事故高发区”。先看连通性和延时ping -c 10 目标IP再看路由路径traceroute -n 目标IP检查本机端口监听和连接状态ss -antlp | head -50统计 TCP 连接状态netstat -ant | awk {print $6} | sort | uniq -c | sort -nr如果出现大量TIME_WAIT通常和短连接过多有关。如果出现大量CLOSE_WAIT通常是应用没有正确关闭连接需要查代码。我们当时用ss -antlp发现泰电节点上的连接数比飞天节点高不少但还没到异常程度。真正的问题是在往下排查磁盘时才暴露的。3.3 中间件与应用层排查应用如果部署在 Java 环境可以重点关注 GC 和线程状态。查看 Java 进程jps -l查看线程概况top -H -p pid导出线程堆栈jstack pid thread_$(date %s).log查看 GC 情况jstat -gcutil pid 1000 10出现频繁 Full GC 时接口延时必然上升。可以结合jstat里的FGC和FGCT判断 GC 频率和耗时。如果我们只停留在应用层可能会绕很多弯路。因为两个节点的 JVM 参数基本一致GC 表现也接近但系统整体响应速度就是不一样。3.4 数据库层排查数据库也是常见瓶颈。先看慢查询日志有没有打开再看当前连接和锁等待。登录 MySQL 后执行SHOW PROCESSLIST;查看是否存在大量Waiting for table metadata lock或Lock wait timeout exceeded。确认慢查询日志配置slow_query_logON slow_query_log_file/var/log/mysql/mysql-slow.log long_query_time2实际排查中发现有部分请求慢不是慢 SQL 引起的而是泰电节点上部署的应用会去读数据库数据库和业务应用共用同一台物理机底层资源一旦磁盘 IO 被打满SQL 排队时间自然变长。4. 完整实战案例一次跨环境迁移排障4.1 问题现象业务方反馈在业务高峰期访问下单接口时偶尔出现超时。Nginx 返回 504部分请求需要重试才能成功。查看 Nginx 访问日志和错误日志后发现大量请求被打到了泰电节点。进一步确认飞天节点接口响应稳定。泰电节点接口响应时快时慢高峰时明显超时。4.2 系统资源排查先在两台机器上执行同样的命令形成对比。uptime top -bn1 | head -20 iostat -x 1 3飞天的输出load average 大约在 1.5 左右%util在 20% 附近。泰电的输出就很有代表性了avg-cpu: %user %nice %system %iowait %steal %idle 12.50 0.00 8.22 45.36 0.00 33.92%iowait达到 45%说明系统大量时间在等待磁盘 IO。再看iostat -xDevice: rrqm/s wrqm/s r/s w/s rkB/s wkB/s avgrq-sz avgqu-sz await r_await w_await svctm %util sda 0.00 2.30 98.00 32.04 788.52 598.36 11.04 4.62 56.24 12.33 132.11 8.31 100.00svctm8msawait56ms%util100%磁盘已经无法响应更多 IO 请求。再看df -hdf -h确认/data分区还有空间但磁盘类型是普通 SATA 机械盘。问题的“凶手”已经找到了一个磁盘 IO。4.3 网络链路排查虽然磁盘问题很明显但为了排除网络因素还是补充做了网络检查。ping -c 10 飞天内网IP ping -c 10 泰电内网IP结果两个节点延时都在 1ms 以内没有丢包。继续用curl测试应用接口curl -o /dev/null -s -w time_namelookup: %{time_namelookup}\ntime_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n http://泰电IP:8080/health在非高峰期调用接口响应还可以。到了高峰期time_starttransfer从几十毫秒涨到几百毫秒。这说明网络链路不是根因但接口响应变长与后端处理能力下降密切相关。4.4 数据库层排查查看 MySQL 慢查询日志tail -200 /var/log/mysql/mysql-slow.log确实发现几条慢 SQL但执行时间不算夸张。继续看连接数SHOW GLOBAL STATUS LIKE Threads_connected; SHOW GLOBAL STATUS LIKE Threads_running;连接数没有打满也没有锁等待。所以数据库本身不是“第一嫌疑人”。但有一个现象值得注意泰电节点上同时跑着业务应用和若干定时任务这些任务会在高峰时段批量扫描表数据。扫描过程产生大量磁盘读直接拖垮了机械盘的 IO。这才是问题的放大器。4.5 应用日志与 JVM 分析查看泰电节点上的业务日志发现不少接口耗时超过 3 秒。再查看 GC 日志jstat -gcutil pid 1000 10结果中 YGC 和 FGC 频率并不高内存也不紧张。进一步确认问题不是 JVM 年老代溢出或频繁 GC而是线程在等待底层 IO 完成后才继续执行。为了更直观可以抓一下线程栈jstack pid /tmp/jstack_$(date %s).log在线程栈里看到大量线程处于RUNNABLE状态但堆栈方法集中在文件读写和 Socket 读写上。和磁盘 IO 高的结论吻合。4.6 优化方案定位到问题后按优先级做了以下调整。第一迁移数据库数据目录把 MySQL 数据目录从机械盘迁移到 SSD 盘。迁移前先备份systemctl stop mysqld rsync -av /var/lib/mysql /data/mysql修改/etc/my.cnfdatadir/data/mysql确认目录权限chown -R mysql:mysql /data/mysql重启服务systemctl start mysqld第二优化 Nginx 上游超时检查 Nginx 配置中的代理超时时间。如果超时时间设置过短服务端在高峰期处理慢时Nginx 会提前返回 504。location /api/ { proxy_pass http://backend; proxy_connect_timeout 5s; proxy_read_timeout 30s; proxy_send_timeout 30s; }重新加载配置nginx -t systemctl reload nginx第三调整应用连接池Spring Boot 默认数据源连接池最大连接数一般不用调整但如果应用和数据库在同一台物理机上且 IO 资源紧张可以把连接池适当调小避免大量线程同时发起磁盘读写。示例application.yml片段spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 3000第四给热点数据加缓存把高频调用的数据放入 Redis减少数据库访问次数。这一步能明显降低磁盘读压力。第五定时任务错峰执行把凌晨和高峰期的定时任务调整到低峰时段或者限制任务并发数。4.7 对比结果说明优化后再次执行iostat -x 1 3泰电节点磁盘%util降到 30% 以内接口超时消失。业务高峰期再抓一次 Nginx 错误日志504 数量明显下降。这里的核心结论是飞天节点磁盘 IO 能力强即使业务负载波动也能兜住。泰电节点机械盘成为瓶颈业务压力一大延迟就放大。整个问题的根因不是应用代码而是硬件资源与业务负载不匹配。5. 常见问题与排查思路5.1 高频问题汇总问题现象常见原因解决思路接口偶发超时磁盘 IO 高使用iostat查看%util迁移数据目录Nginx 返回 504后端处理超时检查proxy_read_timeout定位后端耗时CPU 使用率过高应用死循环或 GC 频繁使用top -H和jstack定位线程CLOSE_WAIT 堆积应用未关闭连接检查 HTTP 客户端或数据库连接池配置连接数过多短连接太多使用连接池优化keep-alive慢查询出现缺少索引或锁等待开启慢查询日志分析 SQL 执行计划定时任务拖垮业务任务集中在高峰期错峰执行限制并发5.2 通用排查清单按照下面的顺序排查通常能定位大多数问题uptime看平均负载。top看 CPU、内存、wa。iostat -x 1 3看磁盘 IO。free -h看内存。df -h看磁盘空间。ss -antlp看端口和连接。jstack看 Java 线程状态。jstat -gcutil看 JVM GC。SHOW PROCESSLIST看数据库即时状态。看应用日志、Nginx 日志、慢查询日志。建议把每一步的截图和结果保存下来方便后续复盘。6. 最佳实践与工程建议排障只是结果避免重复“b事多”才是重点。这里总结几条可以落地的工程建议。6.1 环境差异要留档每个节点的系统版本、内核参数、磁盘类型、Java 参数、Nginx 版本都要记录在文档里。很多问题都是“环境不一致”造成的而不是代码问题。建议维护一张环境清单节点名称 所在机房/云账号 系统版本 CPU/内存 磁盘类型 部署服务 开放端口 关键参数 备注6.2 监控和告警一定要接入没有监控就等于在黑暗里排查。推荐先接入node_exporter Prometheus Grafana监控 CPU、内存、磁盘、网络。MySQL 慢查询和连接数监控。Spring Boot Actuator 暴露健康指标。Nginx 访问日志采集。告警规则可以先从磁盘 IO、CPU、内存、连接数、GC 耗时开始。不要一次性加太多否则会出现告警疲劳。6.3 变更前必须做备份与回滚预案无论调整my.cnf、Nginx 配置还是应用连接池都要先确认备份。尤其是数据库操作先备份再变更变更期间保留 rollback 步骤。示例备份命令mysqldump -u root -p --single-transaction --master-data2 业务库 备份文件_$(date %s).sql变更后观察至少一个业务周期确认没有问题再清理备份。6.4 压测是最有效的验证方式没有压测数据就不知道系统真正能扛多少量。部署后可以先用ab或wrk做一个简单压测wrk -t4 -c100 -d30s http://应用IP:8080/api/test记录下不同并发下的 QPS、P95、错误率。等下次业务增长时用历史数据做容量预估。6.5 生产环境注意权限与最小化操作排障时尽量使用只读命令如top、ss、iostat、jstack。需要变更时遵循最小权限原则。不要在业务高峰期直接改数据库配置尤其不要随意重启数据库。如果必须删除数据或修改线上数据先向负责人确认并且在测试环境完整演练一遍。6.6 日志规范很重要应用日志统一格式比如时间戳 | traceId | 应用名 | 级别 | 线程名 | 类名 | 信息有了 traceId才能把一次请求从 Nginx 到应用再到数据库串起来。如果日志里没有 traceId可以接入一个简单的过滤器来生成。7. 总结与学习路线这次“飞天 vs 泰电”的排障记录可以概括成三点收获两个节点配置再接近也要先确认底层硬件差异尤其是磁盘类型。排障要分层进行从资源层、网络层、应用层到数据库层不要上来就改代码。优化不是一次性的需要配合监控、压测和变更记录持续迭代。实操中建议你先掌握以下命令再逐步深入学习Linux 基础top、free、df、iostat、ss网络排查ping、traceroute、curl -wJava 应用排查jstack、jstat、jmap数据库排查SHOW PROCESSLIST、EXPLAIN、慢查询日志如果条件允许可以在自己的测试环境模拟一次 IO 压满的场景亲身体验负载升高、接口变慢、日志出现超时的全过程。当你亲手跑完一遍再遇到“马桶基地”这种什么环境都有、“b事多”不断的项目就会有底气说问题不大按流程查一遍就行。最后分享一个小习惯每次排障结束我都会把现场命令输出、结论、优化项和后续建议存到共享文档里。很多问题看起来是随机出现的但多对比几次环境就会发现规律。服务器不会说谎数据里永远藏着答案。
返回列表