Production War Stories

生产环境实战故事
线上问题排查纪实

三个真实的生产事故,完整还原从告警到修复的全过程。每个故事都包含真实的排查命令、日志分析和解决方案。

📝 3 个实战故事 🐛 真实线上故障 🔧 完整排查流程 ⏱️ 约 30 分钟阅读
🚨

Story 1: 凌晨2点的 OOM(内存溢出)

某天凌晨2点,线上服务突然挂了。手机被告警叫醒...

🧠 JVM 堆内存布局(OOM 根因分析)
JVM Heap — 最大 1GB
Eden 区
256MB
Survivor1
32MB
Survivor2
32MB
Old Gen(老年代)
680MB — 已满!
🔥 245万个对象堆积在 Old Gen,Full GC 回收不了 → OOM!
🐳 Docker 容器总内存限制: 1GB(Heap + Metaspace + 堆外内存 + 系统开销)
2026-03-20 02:05 — 告警触发
1.1 收到告警 — 钉钉机器人告警
🚨 服务告警

服务energy-system-api
状态DOWN
最后心跳2026-03-20 02:03:12
告警时间2026-03-20 02:05:00
服务器192.168.1.100
持续时间已持续 2 分钟
凌晨2点被手机震醒。看到 DOWN 告警,心跳漏了一拍。穿上拖鞋,打开电脑。
手机锁屏 — 告警推送
02:05 WiFi 🔋 87%
🚨
钉钉 刚刚
🚨 服务告警 — energy-system-api
状态: DOWN
服务器: 192.168.1.100
请立即处理!
📧
邮件 1分钟前
[Prometheus] Alert Firing
Instance 192.168.1.100:8080 is down

凌晨被叫醒排查问题,最忌讳脑子一片空白。深呼吸,然后按流程来:看容器 → 看日志 → 定位代码 → 修复

1.2 SSH 登服务器

第一时间 SSH 到告警服务器,先看容器状态。

Terminal — ssh ops@192.168.1.100
$ ssh ops@192.168.1.100
Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-67-generic x86_64)
Last login: Thu Mar 20 01:45:23 2026 from 10.0.0.5

$ docker ps
CONTAINER ID   IMAGE                      STATUS            NAMES
a1b2c3d4e5f6   nginx:latest              Up 3 hours        energy-system_nginx_1
f6e5d4c3b2a1   energy-system:latest   Restarting (1)  energy-system_app_1  ← 一直在重启!
b2c3d4e5f6a1   mysql:8.0                 Up 3 hours        energy-system_mysql_1
c3d4e5f6a1b2   redis:7-alpine           Up 3 hours        energy-system_redis_1
app 容器一直在 Restarting,说明启动就挂。其他服务(Nginx、MySQL、Redis)都正常。问题在 Java 应用本身。看日志。
1.3 看日志 — 找到 OOM 错误
Terminal — docker-compose logs
$ docker-compose logs app --tail=100 | grep -i "error"

2026-03-20 02:03:12 ERROR org.springframework.boot.SpringApplication - Application run failed
2026-03-20 02:03:12 ERROR java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3210)
    at java.util.ArrayList.grow(ArrayList.java:241)
    at java.util.ArrayList.grow(ArrayList.java:241)
    at com.energy.service.DataExportService.exportAll(DataExportService.java:45)
    at com.energy.service.DataExportService$$FastClassBySpringCGLIB$$a1b2c3d.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)

2026-03-20 02:03:14 WARN o.s.boot.SpringApplication - Unable to close application context
2026-03-20 02:03:30 INFO o.s.boot.SpringApplication - Starting application...
2026-03-20 02:03:45 ERROR java.lang.OutOfMemoryError: Java heap space
    at com.energy.service.DataExportService.exportAll(DataExportService.java:45)
找到了!OOM 发生在 DataExportService.exportAll 第45行。ArrayList 在疯狂 grow,说明往里塞了太多数据。
反复重启都在同一个地方挂掉,说明启动时有定时任务在跑 exportAll()
1.4 查看内存使用情况

确认一下是不是内存真的不够了。

Terminal — free & docker stats
$ free -h
              total    used    free    shared  buff/cache  available
Mem:           7.8Gi   6.9Gi   120Mi   256Mi   780Mi      760Mi
Swap:         2.0Gi   1.8Gi   200Mi

$ docker stats --no-stream
NAME                        CPU%   MEM USAGE / LIMIT
energy-system_nginx_1        0.02%  24MiB / unlimited
energy-system_app_1          0.0%   1.2GiB / 1.0GiB  ← 超了!
energy-system_mysql_1       2.1%  1.8GiB / 4.0GiB
energy-system_redis_1       0.5%  52MiB / 512MiB
1.2G
App 实际内存占用
1.0G
Docker 内存限制
120M
宿主机可用内存
内存超了 Docker 限制!JVM 最大堆只设了 1G,但 exportAll() 要把几百万条数据全加载到内存,直接撑爆了。
1.5 查看问题代码

打开 IntelliJ IDEA,定位到 DataExportService.java 第45行。

IntelliJ IDEA — DataExportService.java
DeviceController.java
DataExportService.java
DeviceDataMapper.java
UTF-8 | LF
📂 com.energy
📂 controller
📂 service
📄 DataExportService.java
📂 mapper
📄 DeviceDataMapper.java
📂 config
38public class DataExportService {
39
40    @Autowired
41    private DeviceDataMapper deviceDataMapper;
42
43    // 每天凌晨2点执行数据导出
44    @Scheduled(cron = "0 0 2 * * ?")
45  public void exportAll() {
46        // ← 一次查了全部数据!245万条!
47        List<DeviceData> allData = deviceDataMapper.selectAll();
48
49        // 全部加载到内存再导出...
50        List<ExportRow> rows = new ArrayList<>();
51        for (DeviceData data : allData) {
52            rows.add(convert(data)); // 数据量太大,OOM了!
53        }
54        excelWriter.write(rows);
55    }
56}

问题很清楚了:

1. selectAll() 一次把 245万条数据全加载到内存

2. 每条数据再转成 ExportRow 对象,内存翻倍

3. 凌晨2点的定时任务触发时,正好数据量从几万涨到了 245万

4. JVM 堆只有 1G,完全不够用,直接 OOM

应该分批查询,边查边写。

1.6 临时修复 + 永久修复

Step A: 临时止血 — 增大内存,先让服务跑起来

Terminal — 临时修改 JVM 参数
$ vim docker-compose.yml
# 修改 JVM 参数,增大堆内存
app:
    environment:
      - JAVA_OPTS=-Xmx1g -Xms1g
      - JAVA_OPTS=-Xmx2g -Xms2g
    deploy:
      resources:
        limits:
          memory: 1G
          memory: 2.5G

$ docker-compose up -d app
Recreating energy-system_app_1 ... done
✔ Service started successfully
临时修复:先增大内存让服务恢复,但这只是止血。245万条数据全加载到内存不是长久之计。

Step B: 永久修复 — 分批查询 + 流式写入

IntelliJ IDEA — 修复后的 DataExportService.java
DataExportService.java
DeviceDataMapper.java
UTF-8 | LF
43    @Scheduled(cron = "0 0 2 * * ?")
44    public void exportAll() {
45        int pageSize = 1000;
46        int page = 0;
47        excelWriter.createSheet("设备数据");
48
49        while (true) {
50            // 每次只查1000条
51            List<DeviceData> batch = deviceDataMapper.selectPage(
52                page * pageSize, pageSize);
53            if (batch.isEmpty()) break;
54
55            // 转换后立即写入,不在内存中堆积
56            List<ExportRow> rows = batch.stream()
57                .map(this::convert)
58                .collect(Collectors.toList());
59            excelWriter.writeBatch(rows);
60            page++;
61        }
62    }

Git Diff — 代码变更

diff --git a/src/main/java/com/energy/service/DataExportService.java @@ -42,14 +42,22 @@      @Scheduled(cron = "0 0 2 * * ?")      public void exportAll() { -        List<DeviceData> allData = deviceDataMapper.selectAll(); -        List<ExportRow> rows = new ArrayList<>(); -        for (DeviceData data : allData) { -            rows.add(convert(data)); -        } -        excelWriter.write(rows); +        int pageSize = 1000; +        int page = 0; +        excelWriter.createSheet("设备数据"); +        while (true) { +            List<DeviceData> batch = deviceDataMapper.selectPage( +                  page * pageSize, pageSize); +            if (batch.isEmpty()) break; +            List<ExportRow> rows = batch.stream() +                  .map(this::convert).collect(Collectors.toList()); +            excelWriter.writeBatch(rows); +            page++; +        }      }

构建部署

Terminal — Build & Deploy
$ git add . && git commit -m "fix: DataExportService OOM - 分批查询替代全量加载"
[main 3f7a2b1] fix: DataExportService OOM - 分批查询替代全量加载
 1 file changed, 12 insertions(+), 6 deletions(-)

$ mvn clean package -DskipTests
[INFO] Scanning for projects...
[INFO] Building energy-system 1.0.0
[INFO] BUILD SUCCESS
[INFO] Total time: 23.4 s

$ docker-compose up -d --build app
Building app
Step 1/8 : FROM openjdk:17-jdk-slim
Step 8/8 : ENTRYPOINT ["java","-jar","app.jar"]
✔ Container energy-system_app_1 Started

$ docker ps | grep app
f6e5d4c3b2a1  energy-system:latest  Up 30 seconds  energy-system_app_1
                                        ↑ 不再 Restarting 了!
服务恢复!验证一下导出功能是否正常。
Terminal — 验证导出任务
$ docker-compose logs app --tail=20 | grep "export"
2026-03-20 02:35:00 INFO DataExportService - Export started, pageSize=1000
2026-03-20 02:35:01 INFO DataExportService - Exported page 0, 1000 rows
2026-03-20 02:35:01 INFO DataExportService - Exported page 1, 1000 rows
2026-03-20 02:35:02 INFO DataExportService - Exported page 2, 1000 rows
...
2026-03-20 02:38:15 INFO DataExportService - Exported page 2456, 789 rows
2026-03-20 02:38:15 INFO DataExportService - Export completed! Total: 2456789 rows

$ docker stats --no-stream | grep app
energy-system_app_1  2.3%  512MiB / 2.5GiB  ← 内存稳定在 512M
1.7 复盘 — OOM 排查流程
告警通知 docker ps 看日志 定位代码行 分析原因 临时止血 永久修复
OOM 排查流程:告警 → 看容器状态 → 看日志 → 定位代码行 → 分析原因 → 临时止血 → 永久修复。凌晨被叫醒不要慌,按步骤来。
02:05 — 告警触发
钉钉收到 DOWN 告警,手机推送叫醒值班
02:08 — SSH 登录
发现 app 容器 Restarting,其他服务正常
02:10 — 查日志
grep 日志发现 OOM 在 DataExportService.exportAll 第45行
02:15 — 看内存
free + docker stats 确认内存超出限制
02:20 — 看代码
找到根因:selectAll() 一次加载 245万条数据
02:30 — 临时修复
增大 JVM 堆到 2G,服务恢复
02:38 — 永久修复
改代码分批查询,构建部署,验证通过
🐍🐍🐍
🐌

Story 2: 接口突然变慢

下午3点,前端反馈页面加载很慢。接口从 200ms 变成了 5 秒...

2026-03-20 15:00 — 前端反馈
🔗 请求调用链时序图 — 接口为什么变慢?
🖥️ 前端
发起请求
200ms
🌐 Nginx
反向代理
800ms ← 慢!
⚙️ App Server
业务处理
1500ms ← 慢SQL!
🗄️ MySQL
数据库查询
✅ 前端 → Nginx: 200ms(正常)
⚠️ Nginx → App: 800ms(偏慢)
🔥 App → MySQL: 1500ms(慢SQL瓶颈!)
💡 瓶颈定位:MySQL 慢查询 4.5s → LEFT JOIN 缺索引,全表扫描导致接口变慢
2.1 收到反馈 — 前端群里喊慢
钉钉 — 研发群
💬 能源系统 - 研发群 8人在线
小李(前端)
设备列表接口好慢啊,要5秒才能加载出来 😫
页面转圈转半天,客户都在催了
小张(测试)
我这边也测了下,确实很慢。昨天还正常的。
小王(全栈)
收到,我来看下接口耗时
小李(前端)
👍 麻烦尽快,用户那边在投诉了
2.2 看接口耗时 — curl 计时

先自己测一下接口到底有多慢。

Terminal — curl 计时测试
$ curl -w "Time: %{time_total}s\n" -o /dev/null -s "http://localhost:8080/api/devices?page=1&size=20"
Time: 4.87s

$ curl -w "DNS: %{time_namelookup}s\nConnect: %{time_connect}s\nTTFB: %{time_starttransfer}s\nTotal: %{time_total}s\n" -o /dev/null -s "http://localhost:8080/api/devices?page=1&size=20"
DNS:            0.001s
Connect:         0.003s
TTFB:           4.85s  ← 服务端处理太慢!
Total:           4.87s
4.87s
当前接口耗时
~0.2s
正常耗时
24x
性能退化倍数
确实很慢!正常应该 200ms 以内。TTFB(首字节时间)4.85秒,问题在服务端处理,不是网络。
2.3 看应用日志 — SQL 慢查询
Terminal — 应用日志
$ docker-compose logs app --tail=50 | grep "devices"

2026-03-20 15:02:33 [http-exec-1] INFO  DeviceController - GET /api/devices - 4872ms
2026-03-20 15:02:33 [http-exec-1] DEBUG DeviceService - query took 4650ms
2026-03-20 15:02:38 [http-exec-2] INFO  DeviceController - GET /api/devices - 5120ms
2026-03-20 15:02:38 [http-exec-2] DEBUG DeviceService - query took 4980ms

$ docker-compose logs mysql --tail=50 | grep "slow"
2026-03-20 15:02:33 [slow_log] SELECT d.*, s.status FROM t_device d LEFT JOIN t_device_status s... duration: 4.5s
SQL 查询就花了 4.6 秒!问题在数据库层。Controller 层面总耗时 4.8 秒,SQL 就占了 4.6 秒。
2.4 查数据库 — EXPLAIN 分析

用 DataGrip 连上数据库,先看执行计划。

DataGrip — energy_system@mysql-prod
EXPLAIN SELECT d.*, s.status
FROM t_device d
LEFT JOIN t_device_status s ON d.id = s.device_id
WHERE d.type = 'sensor'
ORDER BY d.create_time DESC
LIMIT 20;
▶ EXPLAIN 结果 (1 row)
id select_type table type key rows Extra
1 SIMPLE d ALL NULL 2,456,789 Using where; Using join buffer; Using filesort
1 SIMPLE s eq_ref PRIMARY 1

问题一目了然:

1. type=ALL → 全表扫描!

2. key=NULL → 没走索引!

3. rows=2,456,789 → 扫了 245万行!

4. Using filesort → 排序还是文件排序,更慢

核心原因:type 字段没有索引。

2.5 检查索引 → 加索引
DataGrip — 检查索引
-- 查看 t_device 表的索引
SHOW INDEX FROM t_device;
▶ 索引列表 (2 rows)
Table Key_name Column_name Index_type
t_device PRIMARY id BTREE
t_device uk_sn device_sn BTREE
果然!只有主键和 device_sn 的索引,type 字段没有任何索引。查询条件 WHERE type='sensor' 走不了索引。

加索引

DataGrip — 加索引
-- 给 type 字段加索引
ALTER TABLE t_device ADD INDEX idx_type (type);

-- 给 create_time 也加个索引,优化 ORDER BY
ALTER TABLE t_device ADD INDEX idx_create_time (create_time);
✅ 执行成功 — 2 rows affected (0.8s)

再跑一次 EXPLAIN

DataGrip — 加索引后 EXPLAIN
EXPLAIN SELECT d.*, s.status
FROM t_device d
LEFT JOIN t_device_status s ON d.id = s.device_id
WHERE d.type = 'sensor'
ORDER BY d.create_time DESC
LIMIT 20;
▶ EXPLAIN 结果 (1 row)
id select_type table type key rows Extra
1 SIMPLE d ref idx_type 12,453 Using where; Backward index scan
1 SIMPLE s eq_ref PRIMARY 1
2,456,789
加索引前扫描行数
12,453
加索引后扫描行数
197x
扫描行数减少
2.6 验证 — 接口恢复
Terminal — 验证接口耗时
$ curl -w "Time: %{time_total}s\n" -o /dev/null -s "http://localhost:8080/api/devices?page=1&size=20"
Time: 0.18s

$ curl -w "Time: %{time_total}s\n" -o /dev/null -s "http://localhost:8080/api/devices?page=1&size=20"
Time: 0.15s

$ curl -w "Time: %{time_total}s\n" -o /dev/null -s "http://localhost:8080/api/devices?page=1&size=20"
Time: 0.16s
4.87s
修复前
0.18s
修复后
27x
性能提升
从 4.87s → 0.18s,快了 27 倍!问题解决了。
钉钉 — 研发群
💬 能源系统 - 研发群 8人在线
小王(全栈)
查到了,设备表 t_device 的 type 字段没索引。
数据量从 10万涨到了 245万,之前没索引还能忍,现在不行了。
已经加了索引,接口从 4.87s → 0.18s ✅
小李(前端)
牛!飞快!我这边验证了,秒开 👍
小张(测试)
👍 性能测试也通过了

根因分析:

t_device 表最初只有 10万条数据,没有索引也能在 200ms 内返回。随着业务增长,数据量达到 245万,全表扫描变得不可接受。

教训:数据量增长要提前预估,核心查询字段提前加索引。定期检查慢查询日志。

2.7 复盘 — 接口变慢排查流程
前端反馈 curl 计时 看应用日志 定位到 SQL EXPLAIN 分析 加索引 验证
接口变慢排查套路:curl 计时 → 看日志定位耗时在哪 → EXPLAIN 分析 → 加索引。关键技能:读懂 EXPLAIN 的 type、key、rows、Extra
🔥🔥🔥
🔥

Story 3: 发布后页面白屏

新版本发布后,用户反馈打开页面白屏。前端部署事故...

2026-03-20 16:30 — 发布后
3.1 收到反馈 — 用户白屏
钉钉 — 客服群
💬 能源系统 - 客服支持 12人在线
客服小陈
有客户说系统打不开了,白屏!
已经收到3个投诉了 🆘
客服小陈
刚才不是刚发版了吗?是不是有关系?
小王(全栈)
我看看,应该是前端部署的问题。
3.2 浏览器看 Console — 找到 404

打开 Chrome DevTools,看 Console 报错。

Chrome DevTools — Console
Elements Console Sources Network Performance
❌ GET https://energy.company.com/assets/index-CfG8zB9R.js
  net::ERR_ABORTED 404 (Not Found)

❌ GET https://energy.company.com/assets/vendor-Dk9x2LmP.js
  net::ERR_ABORTED 404 (Not Found)

❌ GET https://energy.company.com/assets/style-Ab3cDe4F.css
  net::ERR_ABORTED 404 (Not Found)

⚠️ Uncaught SyntaxError: Unexpected token '<' at index.html:1

#  index.html 引用了带 hash 的新文件名,但服务器上没有这些文件
#  Nginx 找不到文件,返回了 HTML 格式的 404 页面
#  浏览器把 HTML 当 JS 解析,报 SyntaxError
JS 文件 404 了!文件名带 hash(如 index-CfG8zB9R.js),说明是 Vite/Webpack 打包后的文件。index.html 引用的是新 hash 文件名,但服务器上可能还是旧文件。
Chrome DevTools — Network
Elements Console Sources Network Performance
Name Status Type Size Time
index.html 200 document 1.2 KB 12ms
index-CfG8zB9R.js 404 script 8ms
vendor-Dk9x2LmP.js 404 script 6ms
style-Ab3cDe4F.css 404 stylesheet 5ms
logo-Bx2yZa1W.png 404 png 4ms
3.3 检查 Nginx — 旧文件还在

SSH 到服务器,检查前端文件。

Terminal — 检查前端文件
$ ssh ops@192.168.1.100
Welcome to Ubuntu 22.04 LTS

$ ls /opt/energy-system/frontend/dist/assets/
index-OLD_HASH.js    ← 旧文件还在!
vendor-OLD_HASH.js
style-OLD_HASH.css
favicon.ico

$ cat /opt/energy-system/frontend/dist/index.html | grep "js"
<script type="module" src="/assets/index-CfG8zB9R.js"></script>

# ↑ index.html 是新的,引用了新 hash 的 JS 文件 # 但 assets 目录下只有旧的 JS 文件! # 构建产物的 JS/CSS 文件没部署上去!
找到原因了!index.html 是新版(引用 index-CfG8zB9R.js),但 assets 目录下还是旧文件(index-OLD_HASH.js)。发布时只更新了 index.html,没更新 JS/CSS 文件。

再看 Nginx 配置

Terminal — Nginx 配置
$ cat /etc/nginx/conf.d/energy.conf

server {
    listen 80;
    server_name energy.company.com;

    location / {
        root /opt/energy-system/frontend/dist;
        try_files $uri $uri/ /index.html;
    }

    location /assets/ {
        root /opt/energy-system/frontend/dist;
        expires 1y;  ← 缓存1年!
        add_header Cache-Control "public, immutable";
    }
}

完整事故链:

1. CI/CD 构建了新的前端代码,生成带新 hash 的文件名(index-CfG8zB9R.js)

2. 部署脚本只复制了 index.html,没有复制 assets/ 目录下的新 JS/CSS 文件

3. Nginx 配了 expires 1y,强缓存 1 年

4. 浏览器请求新的 JS 文件 → 服务器上没有 → 404

5. JS 加载失败 → 页面白屏

3.4 修复 — 重新部署前端文件
Terminal — 重新部署
$ cd /opt/energy-system/frontend

$ rm -rf dist/*
# 清空旧文件

$ scp -r dev@build-server:/build-output/frontend/dist/* ./dist/
index.html                 100% 1209   1.2KB/s  00:00
assets/index-CfG8zB9R.js    100% 245KB   3.2MB/s  00:00
assets/vendor-Dk9x2LmP.js   100% 891KB   5.1MB/s  00:00
assets/style-Ab3cDe4F.css   100% 68KB    2.8MB/s  00:00
assets/logo-Bx2yZa1W.png    100% 12KB    1.5MB/s  00:00

$ ls dist/assets/
index-CfG8zB9R.js  ← 新文件到位!
vendor-Dk9x2LmP.js
style-Ab3cDe4F.css
logo-Bx2yZa1W.png

$ nginx -s reload
✔ Nginx reloaded
前端文件全部重新部署,Nginx reload。现在去浏览器验证。
3.5 验证 — 页面恢复正常
Chrome — energy.company.com
🔒 https://energy.company.com
⚡ 智慧能源管理系统
欢迎回来,管理员
首页 设备管理 数据监控 报表中心 系统设置
📊 数据概览
在线设备
1,245
正常率
99.2%
告警数
3
今日数据
8.5万
✅ 页面加载成功 — 已恢复正常
Chrome DevTools — Network (修复后)
Elements Console Sources Network Performance
Name Status Type Size Time
index.html 200 document 1.2 KB 11ms
index-CfG8zB9R.js 200 script 245 KB 32ms
vendor-Dk9x2LmP.js 200 script 891 KB 85ms
style-Ab3cDe4F.css 200 stylesheet 68 KB 18ms
logo-Bx2yZa1W.png 200 png 12 KB 14ms
全部 200!页面加载正常。从发现问题到修复,总共 15 分钟。
钉钉 — 客服群
💬 能源系统 - 客服支持 12人在线
小王(全栈)
已修复 ✅ 原因是部署时前端 JS 文件没更新到位。
已经重新部署,页面恢复正常。请让客户刷新一下。
客服小陈
好的!已经通知客户了。问题解决 👍
3.6 修复部署脚本 — 防止复发

根因是部署脚本不完整,修复部署脚本。

VS Code — deploy.sh
deploy.sh
docker-compose.yml
Shell | UTF-8
#!/bin/bash
# 前端部署脚本

DEPLOY_DIR="/opt/energy-system/frontend/dist"
BUILD_OUTPUT="dev@build-server:/build-output/frontend/dist"

# 1. 先删旧文件(防止残留)
echo
"🗑️ 清理旧文件..."
- scp -r $BUILD_OUTPUT/index.html $DEPLOY_DIR/ # 只复制了 index.html!
+ rm -rf $DEPLOY_DIR/*
+ echo "✅ 旧文件已清理"

# 2. 复制所有构建产物
echo
"📦 部署新文件..."
- # 之前少了这一步!!!
+ scp -r $BUILD_OUTPUT/* $DEPLOY_DIR/
+ echo "✅ 新文件已部署"

# 3. 验证文件完整性
echo
"🔍 验证..."
- # 没有验证步骤
+ # 检查 index.html 引用的文件是否都存在
+ grep -oP '(?<=src="|href=")/assets/[^"]*' $DEPLOY_DIR/index.html | while read f; do
+    if [ ! -f "$DEPLOY_DIR$f" ]; then
+        echo "❌ 缺少文件: $f"
+        exit 1
+    fi
+ done
+ echo "✅ 所有文件验证通过"

# 4. 重载 Nginx
sudo
nginx -s reload
echo
"🚀 部署完成!"
3.7 复盘 — 白屏排查流程
用户反馈 F12 Console 找到 404 检查服务器文件 重新部署 验证恢复
白屏排查套路:F12 看 Console → 找到报错(404/SyntaxError/CORS)→ 检查文件是否存在 → 检查 Nginx 配置 → 清理缓存。常见原因:部署不完整、缓存问题、路径错误。

防止此类问题再次发生:

1. 部署脚本改为 rm -rf dist/* && scp -r 新文件,避免残留

2. 加部署后验证:检查 index.html 引用的所有文件是否存在

3. Nginx 配置 try_files $uri =404,让 404 更明显而不是返回 index.html

📋📋📋
📊

生产问题排查心法

三个故事总结出的通用排查方法和常见问题速查表。

三步定位法

① 看日志

  • 应用日志 → 定位到哪个类哪一行
  • docker-compose logs app --tail=100
  • grep ERROR/WARN 快速过滤
  • 关注异常堆栈的第一行和 Caused by

② 看监控

  • CPU / 内存 / 网络 / 磁盘 → 定位资源瓶颈
  • free -h 看内存
  • docker stats 看容器资源
  • top 看进程 CPU

③ 看数据库

  • 慢查询日志 → EXPLAIN 分析索引
  • SHOW INDEX FROM table
  • EXPLAIN SELECT ...
  • 重点关注 type、key、rows、Extra

④ 看前端

  • F12 Console → JS 错误 / 404 / CORS
  • F12 Network → 请求状态码和耗时
  • Elements → DOM 结构是否正确
  • Application → localStorage / Cookie
常见问题速查表
Production Troubleshooting Cheatsheet
现象 第一反应 排查路径
🚨 服务挂了 docker ps 容器状态 → 日志 → OOM/异常
🐌 接口慢 curl 耗时 SQL 慢查询 → EXPLAIN → 索引
🔥 白屏 F12 Console 404/JS 错误 → Nginx/CDN/缓存
📈 CPU 飙高 top 命令 线程 dump → 死循环/加密计算
💾 内存涨 free + jstat 堆 dump → 内存泄漏对象
🔌 连接超时 netstat / ss 端口监听 → 防火墙 → 连接池满
EXPLAIN 字段速读
MySQL EXPLAIN Guide
type 值 含义 性能 说明
system/const 单行匹配 极快 主键/唯一索引精确匹配
eq_ref 唯一索引扫描 JOIN 时主键/唯一索引
ref 非唯一索引扫描 良好 普通索引匹配多行
range 范围扫描 一般 BETWEEN/IN/GT/LT
index 全索引扫描 较慢 扫描整棵索引树
ALL 全表扫描 最慢 逐行扫描全表,必须优化!
排查工具箱

🖥️ SSH 终端

  • 看日志:docker logs
  • 重启服务:docker-compose restart
  • 检查资源:free top df
  • 检查端口:netstat -tlnp
  • 检查进程:ps aux | grep java

🗄️ DataGrip

  • 查慢 SQL:SHOW PROCESSLIST
  • 执行计划:EXPLAIN SELECT ...
  • 加索引:ALTER TABLE ADD INDEX
  • 看索引:SHOW INDEX FROM table
  • 表状态:SHOW TABLE STATUS

☕ IntelliJ IDEA

  • 看代码、定位问题行
  • 全局搜索:Ctrl+Shift+F
  • 查看调用链:Alt+F7
  • Git Blame:查看谁改的
  • Debug 远程调试

🌐 Chrome DevTools

  • Console:JS 错误、API 报错
  • Network:请求状态、耗时
  • Application:缓存、Cookie
  • Performance:页面卡顿分析
  • Lighthouse:性能评分
常用排查命令速查
Command Cheatsheet
# ===== Docker =====
docker ps                     # 查看运行中的容器
docker logs --tail=100 <id>      # 看最近100行日志
docker stats --no-stream        # 查看容器资源占用
docker-compose restart app      # 重启服务

# ===== 系统资源 =====
free -h                    # 查看内存使用
top                         # 查看 CPU 占用
df -h                      # 查看磁盘空间
netstat -tlnp                # 查看端口监听

# ===== 日志分析 =====
tail -f app.log | grep ERROR     # 实时过滤错误日志
grep -rn "OutOfMemory" /var/log/ # 搜索 OOM 关键字
grep -A 10 "Exception" app.log    # 查异常后10行上下文

# ===== 网络 =====
curl -w "%{time_total}s" URL      # 测接口耗时
curl -I URL                  # 看响应头
nslookup domain.com           # DNS 解析检查

# ===== MySQL =====
SHOW PROCESSLIST;               # 查看当前执行的 SQL
EXPLAIN SELECT ...;             # 分析执行计划
SHOW INDEX FROM table;           # 查看索引
ALTER TABLE t ADD INDEX idx(f);  # 加索引
三个故事的教训

🚨 OOM 教训

  • 大数据量操作必须分批
  • 不要把全表数据加载到内存
  • 设置合理的 JVM 堆大小
  • 定时任务要有内存监控
  • 数据量增长要提前评估

🐌 慢查询教训

  • 核心查询字段提前加索引
  • 定期检查慢查询日志
  • 学会读懂 EXPLAIN
  • 数据量增长会影响性能
  • WHERE/ORDER BY 字段要索引

🔥 白屏教训

  • 部署脚本要完整可靠
  • 部署后自动验证文件完整性
  • 理解前端 hash 文件名机制
  • Nginx 缓存策略要正确
  • 发布后第一时间自己验证
生产环境出问题不可怕,可怕的是不知道怎么排查。记住三步定位法:看日志、看监控、看数据库。按流程来,不慌不忙,问题一定能找到。

生产排查的核心心法:先止血,再治病。先恢复服务,再定位根因,最后永久修复。