《生产事故排障手册》#
第一部分:运维排障思维模型#
1.1 生产排障黄金流程#
1
2
3
| 生产排障黄金六步法
1. 发现告警 → 2. 初步评估 → 3. 紧急止血 → 4. 定位根因
5. 彻底修复 → 6. 复盘预防
|
步骤1:发现告警(0-5分钟)
- 监控平台触发告警(Zabbix/Prometheus/云监控)
- 用户反馈/客服转告
- 业务指标异常(订单下跌、响应时间增长)
步骤2:初步评估(5-10分钟)
- 影响范围评估:单服务/多服务/全站
- 影响程度评估:部分功能/核心功能/完全不可用
- 紧急程度分级:P0(全站不可用)/P1(核心功能)/P2(部分功能)
步骤3:紧急止血(10-30分钟)
步骤4:定位根因(30分钟-2小时)
- 收集证据(日志、监控、链路追踪)
- 分层排查(网络→系统→应用→数据)
- 复现问题(测试环境验证)
步骤5:彻底修复(2-24小时)
步骤6:复盘预防(24-72小时)
- 编写事故报告(Postmortem)
- 制定改进措施(Action Items)
- 更新监控告警
- 完善应急预案
1.2 故障定位四层模型#
1
2
3
4
5
6
7
8
| 应用层 (Application)
代码逻辑 | 配置错误 | 依赖服务 | 业务异常 | 缓存失效
系统层 (System)
CPU过载 | 内存泄漏 | 磁盘满 | 文件句柄 | 进程崩溃
网络层 (Network)
DNS故障 | 路由异常 | 防火墙 | 负载均衡 | 带宽耗尽
硬件层 (Hardware)
磁盘损坏 | 内存故障 | 网卡异常 | 电源问题 | 机房断电
|
排查顺序:自底向上
- 先确认硬件/基础设施是否正常
- 再检查网络连通性
- 然后查看系统资源
- 最后定位应用问题
1.3 SRE排障方法论#
Google SRE核心原则:
| 原则 | 说明 | 实践 |
|---|
| 消除琐事 | 减少重复性手工操作 | 自动化运维脚本 |
| 拥抱风险 | 接受一定程度的故障 | 错误预算(Error Budget) |
| 监控优先 | 用数据驱动决策 | 四黄金信号监控 |
| 自动化修复 | 故障自愈 | 自动扩容/重启 |
| 简化设计 | 降低系统复杂度 | 微服务拆分 |
四黄金信号监控:
- 延迟(Latency):请求处理时间
- 流量(Traffic):请求量/QPS
- 错误(Errors):错误率/失败请求
- 饱和度(Saturation):资源使用率
1.4 变更管理原则#
变更三要素:
1
2
3
| 1. 谁操作:明确责任人,禁止无授权变更
2. 何时操作:避开业务高峰,选择低峰期
3. 如何回滚:必须有回滚预案,15分钟内可执行
|
变更检查清单:
- 变更方案已评审
- 回滚方案已验证
- 监控告警已配置
- 相关人员已通知
- 备份已完成
- 测试环境已验证
第二部分:Linux生产排障命令大全#
2.1 CPU排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
top | 实时查看CPU占用 | top -d 1 |
htop | 交互式进程查看 | htop |
vmstat | 系统整体状态 | vmstat 1 5 |
mpstat | CPU详细统计 | mpstat -P ALL 1 |
pidstat | 进程级统计 | pidstat -u 1 |
perf | 性能分析 | perf top |
CPU 100%排查流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 找出占用CPU最高的进程
top -b -n 1 | head -20
# 2. 查看该进程的线程
top -H -p <PID>
# 3. 将线程ID转换为16进制
printf "%x\n" <TID>
# 4. 查看该线程的堆栈
jstack <PID> | grep -A 30 <16进制TID>
# 5. 如果是Java应用,查看GC情况
jstat -gc <PID> 1000 10
|
2.2 内存排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
free -h | 查看内存使用 | free -h |
vmstat | 内存交换情况 | vmstat 1 5 |
pmap | 进程内存映射 | pmap -x <PID> |
smem | 共享内存分析 | smem -r |
slabtop | 内核缓存查看 | slabtop |
内存泄漏排查流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看整体内存使用
free -h
# 2. 查看是否有swap使用
vmstat 1 5 | grep -E "swpd|si|so"
# 3. 找出内存占用最高的进程
ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%mem | head -20
# 4. 查看进程详细内存
cat /proc/<PID>/status | grep -E "VmRSS|VmSize|VmSwap"
# 5. 检查是否有OOM Killer
dmesg | grep -i "oom\|killed"
|
OOM Killer分析:
1
2
3
4
5
6
7
8
9
| # 查看OOM日志
grep -i "out of memory" /var/log/messages
grep -i "killed process" /var/log/syslog
# 查看进程OOM分数
cat /proc/<PID>/oom_score_adj
# 调整OOM优先级(分数越低越不容易被杀)
echo -500 > /proc/<PID>/oom_score_adj
|
2.3 IO排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
iostat | 磁盘IO统计 | iostat -xz 1 |
iotop | 进程IO监控 | iotop -o |
pidstat | 进程IO统计 | pidstat -d 1 |
lsof | 文件打开情况 | lsof +D /var/log |
du | 磁盘空间分析 | du -sh /* |
IO瓶颈排查流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 查看整体IO情况
iostat -xz 1 5
# 关键字段说明:
# %util: 磁盘利用率,>80%表示瓶颈
# await: 平均等待时间,>10ms表示慢
# svctm: 服务时间
# 2. 找出IO高的进程
iotop -o -b -n 5
# 3. 查看进程打开的文件
lsof -p <PID> | grep REG
# 4. 查看磁盘空间
df -hT
# 5. 查看inode使用
df -i
|
2.4 文件句柄排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
ulimit -n | 查看当前限制 | ulimit -n |
lsof | 查看打开文件 | lsof -n |
ls /proc/<PID>/fd | 进程文件描述符 | ls /proc//fd |
文件句柄耗尽排查:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 查看系统级限制
cat /proc/sys/fs/file-max
# 2. 查看当前使用
cat /proc/sys/fs/file-nr
# 3. 查看进程级限制
ulimit -n
# 4. 找出打开文件最多的进程
for i in /proc/[0-9]*/fd; do echo $(ls $i | wc -l) $i; done | sort -rn | head -20
# 5. 临时提高限制
ulimit -n 65535
# 6. 永久修改(/etc/security/limits.conf)
echo "* soft nofile 65535" >> /etc/security/limits.conf
echo "* hard nofile 65535" >> /etc/security/limits.conf
|
2.5 inode排障#
inode耗尽现象:
- 磁盘空间未满但无法创建文件
- 报错"no space left on device"但
df -h显示有空间
排查命令:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看inode使用
df -i
# 2. 找出inode使用最高的目录
for i in /*; do echo $(find $i 2>/dev/null | wc -l) $i; done | sort -rn | head -10
# 3. 找出小文件最多的目录
find /var -type f -size -1k | wc -l
# 4. 清理小文件(谨慎操作)
find /tmp -type f -atime +7 -delete
|
2.6 网络排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
ping | 基础连通性 | ping -c 4 目标IP |
telnet | 端口连通性 | telnet IP 端口 |
nc | 网络调试 | nc -zv IP 端口 |
netstat | 网络连接 | netstat -tuln |
ss | 套接字统计 | ss -s |
tcpdump | 抓包分析 | tcpdump -i any port 80 |
traceroute | 路由追踪 | traceroute 目标IP |
mtr | 持续路由追踪 | mtr 目标IP |
网络连接排查流程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 检查本地端口监听
netstat -tuln | grep 端口号
# 2. 检查连接状态
netstat -an | grep ESTABLISHED | wc -l
netstat -an | grep TIME_WAIT | wc -l
# 3. 检查防火墙规则
iptables -L -n | grep 端口号
firewall-cmd --list-ports
# 4. 抓包分析
tcpdump -i eth0 -w capture.pcap port 8080
# 5. 分析TCP状态
ss -s
|
2.7 进程排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
ps | 进程查看 | ps auxf |
pgrep | 按名查找进程 | pgrep -f java |
kill | 终止进程 | kill -9 PID |
pkill | 按名终止进程 | pkill -f 进程名 |
strace | 系统调用追踪 | strace -p PID |
僵尸进程处理:
1
2
3
4
5
6
7
8
9
10
| # 1. 查找僵尸进程
ps aux | grep defunct
# 2. 查找父进程
ps -o ppid= -p <僵尸进程PID>
# 3. 终止父进程(谨慎)
kill -9 <父进程PID>
# 4. 如果父进程是init,只能重启系统
|
2.8 日志排障#
常用命令:
| 命令 | 用途 | 示例 |
|---|
journalctl | 系统日志 | journalctl -u 服务名 |
dmesg | 内核日志 | `dmesg |
tail | 实时查看日志 | tail -f /var/log/messages |
grep | 日志搜索 | grep -i error /var/log/* |
awk | 日志分析 | awk '{print $1}' access.log |
日志分析技巧:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 统计错误数量
grep -c "ERROR" /var/log/app.log
# 2. 查看最近100行
tail -n 100 /var/log/app.log
# 3. 实时过滤关键字
tail -f /var/log/app.log | grep -i "exception"
# 4. 统计访问IP排行
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 5. 统计状态码分布
awk '{print $9}' access.log | sort | uniq -c
|
第三部分:网络排障手册#
3.1 TCP连接问题#
常见状态及含义:
| 状态 | 含义 | 正常数量 |
|---|
| ESTABLISHED | 已建立连接 | 根据业务量 |
| TIME_WAIT | 主动关闭等待 | <10000 |
| CLOSE_WAIT | 被动关闭等待 | <100 |
| SYN_RECV | 半连接 | <1000 |
| FIN_WAIT | 关闭中 | <1000 |
TIME_WAIT过多处理:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 查看TIME_WAIT数量
netstat -n | grep TIME_WAIT | wc -l
# 2. 查看TIME_WAIT详情
netstat -n | grep TIME_WAIT
# 3. 临时优化(立即回收)
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# 4. 缩短TIME_WAIT时间
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# 5. 扩大本地端口范围
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
# 6. 永久修改(/etc/sysctl.conf)
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
|
3.2 SYN Flood攻击#
现象:
- 大量SYN_RECV状态连接
- 正常连接无法建立
- 服务器响应变慢
排查命令:
1
2
3
4
5
6
7
8
| # 1. 查看SYN_RECV数量
netstat -n | grep SYN_RECV | wc -l
# 2. 查看来源IP分布
netstat -n | grep SYN_RECV | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -10
# 3. 抓包分析
tcpdump -i eth0 'tcp[tcpflags] & tcp-syn != 0' -w syn.pcap
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 开启SYN Cookie
echo 1 > /proc/sys/net/ipv4/tcp_syncookies
# 2. 增加半连接队列
echo 2048 > /proc/sys/net/ipv4/tcp_max_syn_backlog
# 3. 限制SYN速率(iptables)
iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT
# 4. 使用防火墙防护
iptables -A INPUT -p tcp --syn -m recent --name synflood --rcheck --seconds 60 --hitcount 20 -j DROP
|
3.3 DNS故障#
现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 测试DNS解析
nslookup 域名
dig 域名
host 域名
# 2. 查看本地DNS配置
cat /etc/resolv.conf
# 3. 测试DNS响应时间
time nslookup 域名
# 4. 查看DNS缓存
systemctl restart nscd # 清除缓存
# 5. 测试多个DNS服务器
dig @8.8.8.8 域名
dig @114.114.114.114 域名
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 修改DNS配置
echo "nameserver 8.8.8.8" > /etc/resolv.conf
echo "nameserver 114.114.114.114" >> /etc/resolv.conf
# 2. 配置本地hosts
echo "1.2.3.4 域名" >> /etc/hosts
# 3. 安装本地DNS缓存
yum install nscd -y
systemctl enable nscd
systemctl start nscd
|
3.4 路由问题#
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 查看路由表
route -n
ip route show
# 2. 追踪路由
traceroute 目标IP
mtr 目标IP
# 3. 查看网卡配置
ip addr show
ifconfig -a
# 4. 测试连通性
ping -c 4 目标IP
ping -I 网卡名 目标IP
|
常见问题及解决:
1
2
3
4
5
6
7
8
9
| # 问题1:默认网关错误
ip route del default
ip route add default via 正确网关IP
# 问题2:多网卡路由冲突
ip route add 目标网段 via 网关 dev 网卡名
# 问题3:路由表满
ip route flush cache
|
3.5 交换机环路#
现象:
排查方法:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看交换机端口流量
show interface counters
# 2. 查看MAC地址表
show mac address-table
# 3. 检查STP状态
show spanning-tree
# 4. 查看广播包数量
show interface | include broadcast
|
解决方案:
- 启用STP(生成树协议)
- 配置端口环路检测
- 限制广播速率
- 物理排查网线连接
第四部分:Docker生产事故#
4.1 overlay2磁盘爆满#
事故现象:
df -h显示/var/lib/docker使用率100%- 容器无法启动
- 镜像无法拉取
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看Docker磁盘使用
docker system df
# 2. 查看各容器大小
docker ps -s
# 3. 查看镜像大小
docker images
# 4. 查看overlay2目录
du -sh /var/lib/docker/overlay2/*
# 5. 查看悬空镜像
docker images -f "dangling=true"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 1. 清理悬空镜像
docker image prune -f
# 2. 清理未使用容器
docker container prune -f
# 3. 清理未使用卷
docker volume prune -f
# 4. 清理所有未使用资源
docker system prune -a -f
# 5. 限制容器日志大小(daemon.json)
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
}
}
# 6. 迁移Docker数据目录
systemctl stop docker
rsync -avz /var/lib/docker /新路径/
修改/etc/docker/daemon.json
systemctl start docker
|
预防措施:
- 配置日志轮转
- 定期清理无用资源
- 监控磁盘使用率(>80%告警)
- 使用独立分区挂载/var/lib/docker
4.2 容器OOM#
事故现象:
- 容器频繁重启
docker ps显示容器状态为Exited- 日志中有"OOMKilled"
排查命令:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看容器退出原因
docker inspect 容器ID | grep -i "oom\|exit"
# 2. 查看系统OOM日志
dmesg | grep -i "oom\|killed"
# 3. 查看容器内存限制
docker inspect 容器ID | grep -i "memory"
# 4. 查看容器内存使用
docker stats 容器ID
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 调整内存限制
docker run -m 512m --memory-reservation=256m 镜像名
# 2. 优化应用内存使用
# Java应用:-Xmx256m -Xms128m
# Node应用:--max-old-space-size=256
# 3. 增加Swap(不推荐生产)
docker run --memory=512m --memory-swap=1g 镜像名
# 4. 调整OOM优先级
docker run --oom-score-adj=-500 镜像名
|
预防措施:
- 设置合理的内存限制
- 应用层做好内存管理
- 监控容器内存使用
- 配置自动扩容
4.3 镜像拉取失败#
事故现象:
docker pull超时或失败- Pod状态为ImagePullBackOff
- 网络错误
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 测试网络连通性
ping registry.docker.com
# 2. 测试DNS解析
nslookup registry.docker.com
# 3. 手动拉取测试
docker pull 镜像名:标签
# 4. 查看Docker日志
journalctl -u docker -n 100
# 5. 查看代理配置
env | grep -i proxy
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 配置镜像加速器(国内)
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": [
"https://registry.cn-hangzhou.aliyuncs.com",
"https://mirror.ccs.tencentyun.com"
]
}
EOF
systemctl daemon-reload
systemctl restart docker
# 2. 配置代理
mkdir -p /etc/systemd/system/docker.service.d
cat > /etc/systemd/system/docker.service.d/http-proxy.conf << EOF
[Service]
Environment="HTTP_PROXY=http://代理IP:端口"
Environment="HTTPS_PROXY=http://代理IP:端口"
EOF
systemctl daemon-reload
systemctl restart docker
# 3. 使用私有仓库
docker login 私有仓库地址
docker pull 私有仓库/镜像名
|
4.4 容器频繁重启#
事故现象:
docker ps -a显示多次重启- 服务不稳定
- 日志中有大量重启记录
排查命令:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看容器重启次数
docker inspect 容器ID | grep -i "restart"
# 2. 查看容器日志
docker logs --tail 200 容器ID
# 3. 查看容器资源
docker stats 容器ID
# 4. 查看容器健康检查
docker inspect 容器ID | grep -A 10 "Health"
|
常见原因及解决:
| 原因 | 排查方法 | 解决方案 |
|---|
| 应用崩溃 | 查看日志 | 修复代码/配置 |
| 资源不足 | docker stats | 增加资源限制 |
| 端口冲突 | netstat -tuln | 修改端口映射 |
| 依赖未就绪 | 检查启动顺序 | 添加等待逻辑 |
| 健康检查失败 | docker inspect | 调整检查参数 |
解决方案:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 调整重启策略
docker update --restart=on-failure:3 容器ID
# 2. 添加健康检查
docker run --health-cmd "curl -f http://localhost:8080" \
--health-interval 30s \
--health-timeout 10s \
--health-retries 3 镜像名
# 3. 添加启动等待
# 使用wait-for-it.sh等待依赖服务
|
4.5 网络隔离问题#
事故现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看网络列表
docker network ls
# 2. 查看网络详情
docker network inspect 网络名
# 3. 查看容器IP
docker inspect 容器ID | grep IPAddress
# 4. 测试容器间连通性
docker exec 容器1 ping 容器2IP
# 5. 查看iptables规则
iptables -L -n | grep docker
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 创建自定义网络
docker network create --driver bridge mynet
# 2. 将容器加入同一网络
docker network connect mynet 容器ID
# 3. 检查防火墙
systemctl status firewalld
firewall-cmd --list-all
# 4. 重启Docker网络
systemctl restart docker
# 5. 跨主机网络(使用overlay)
docker network create --driver overlay --attachable myoverlay
|
第五部分:Kubernetes生产事故#
5.1 Pod Pending#
事故现象:
kubectl get pods显示Pending- Pod长时间无法启动
- 调度失败
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看Pod详细状态
kubectl describe pod Pod名 -n 命名空间
# 2. 查看节点资源
kubectl describe node 节点名
# 3. 查看节点状态
kubectl get nodes -o wide
# 4. 查看调度事件
kubectl get events -n 命名空间 --sort-by='.lastTimestamp'
# 5. 查看资源配额
kubectl describe quota -n 命名空间
|
常见原因及解决:
| 原因 | 现象 | 解决方案 |
|---|
| 资源不足 | Insufficient cpu/memory | 扩容节点或调整request |
| 节点污点 | Taints not tolerated | 添加toleration |
| 节点选择器 | NodeSelector不匹配 | 修改nodeSelector |
| 亲和性 | Affinity不满足 | 调整affinity配置 |
| PVC未绑定 | Volume pending | 检查StorageClass |
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看调度失败原因
kubectl describe pod Pod名 | grep -A 5 "Events"
# 2. 临时调整资源
kubectl set resources deployment/服务名 \
--requests=cpu=100m,memory=128Mi \
--limits=cpu=500m,memory=512Mi
# 3. 查看节点可调度资源
kubectl top nodes
# 4. 驱逐低优先级Pod
kubectl drain 节点名 --ignore-daemonsets --delete-local-data
|
5.2 Pod CrashLoopBackOff#
事故现象:
- Pod状态为CrashLoopBackOff
- 容器反复重启
- RESTARTS计数持续增长
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 查看Pod状态
kubectl get pods -n 命名空间
# 2. 查看Pod详情
kubectl describe pod Pod名 -n 命名空间
# 3. 查看容器日志
kubectl logs Pod名 -n 命名空间
kubectl logs Pod名 -n 命名空间 --previous # 查看上次崩溃日志
# 4. 进入容器调试
kubectl exec -it Pod名 -n 命名空间 -- /bin/bash
# 5. 查看事件
kubectl get events -n 命名空间 --field-selector involvedObject.name=Pod名
|
常见原因及解决:
| 原因 | 排查方法 | 解决方案 |
|---|
| 应用启动失败 | 查看日志 | 修复配置/代码 |
| 探针失败 | describe查看Events | 调整探针参数 |
| 配置错误 | 检查ConfigMap | 修正配置 |
| 依赖服务不可用 | 检查网络连接 | 等待依赖就绪 |
| 资源不足 | 查看OOM | 增加资源限制 |
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 调整存活探针
kubectl edit deployment/服务名 -n 命名空间
# 修改:
# initialDelaySeconds: 30 # 延长初始延迟
# failureThreshold: 5 # 增加失败阈值
# periodSeconds: 10 # 延长检查间隔
# 2. 回滚到稳定版本
kubectl rollout undo deployment/服务名 -n 命名空间
# 3. 删除Pod让K8s重建
kubectl delete pod Pod名 -n 命名空间
# 4. 临时禁用探针(调试用)
kubectl edit deployment/服务名 -n 命名空间
# 注释掉livenessProbe和readinessProbe
|
5.3 CNI网络异常#
事故现象:
- Pod无法跨节点通信
- Service无法访问
- 网络插件Pod异常
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看CNI Pod状态
kubectl get pods -n kube-system | grep -E "flannel|calico|weave"
# 2. 查看CNI日志
kubectl logs -n kube-system CNI-Pod名
# 3. 测试Pod间连通性
kubectl exec Pod1 -- ping Pod2IP
# 4. 查看网络策略
kubectl get networkpolicy -A
# 5. 查看iptables规则
iptables -L -n | grep -E "KUBE|CALICO"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 重启CNI Pod
kubectl delete pod -n kube-system -l k8s-app=calico-node
# 2. 重新安装CNI
kubectl apply -f https://docs.projectcalico.org/manifests/calico.yaml
# 3. 检查节点网络
ip addr show
ip route show
# 4. 清理残留网络配置
# 谨慎操作,可能需要重启节点
rm -rf /var/lib/cni/*
|
5.4 etcd崩溃#
事故现象:
kubectl命令无响应或超时- API Server报错
- 集群无法调度
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 查看etcd Pod状态
kubectl get pods -n kube-system | grep etcd
# 2. 查看etcd日志
kubectl logs -n kube-system etcd-节点名
# 3. 检查etcd健康
ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
endpoint health
# 4. 查看etcd成员
ETCDCTL_API=3 etcdctl member list
# 5. 查看etcd数据大小
ETCDCTL_API=3 etcdctl --write-out=table endpoint status
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 重启etcd
kubectl delete pod -n kube-system etcd-节点名
# 2. 从备份恢复
ETCDCTL_API=3 etcdctl snapshot restore 备份文件 \
--data-dir=/var/lib/etcd-restore
# 3. 清理过期数据
ETCDCTL_API=3 etcdctl compaction --revision=修订版本
ETCDCTL_API=3 etcdctl defrag
# 4. 扩容etcd集群
# 添加新成员,确保奇数节点
|
预防措施:
- 定期备份etcd数据
- 监控etcd延迟和存储大小
- 保持3/5/7奇数节点
- 使用SSD存储
5.5 kubelet失联#
事故现象:
- 节点状态为NotReady
- Pod无法调度到该节点
- 节点上的Pod状态异常
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 查看节点状态
kubectl get nodes
kubectl describe node 节点名
# 2. 查看kubelet状态
systemctl status kubelet
# 3. 查看kubelet日志
journalctl -u kubelet -n 200
# 4. 检查证书
ls -la /etc/kubernetes/pki/kubelet*
openssl x509 -in /etc/kubernetes/pki/kubelet.crt -text -noout
# 5. 检查配置文件
cat /etc/kubernetes/kubelet.conf
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 重启kubelet
systemctl restart kubelet
# 2. 更新证书
# 证书过期需要重新签发
kubeadm certs renew kubelet-client-current
systemctl restart kubelet
# 3. 检查资源
free -h
df -h
# 4. 重新加入集群
kubeadm reset
kubeadm join 控制节点IP:6443 --token XXX --discovery-token-ca-cert-hash XXX
|
5.6 Service访问失败#
事故现象:
- ClusterIP无法访问
- NodePort不通
- LoadBalancer不分配IP
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 重启kubelet
systemctl restart kubelet
# 2. 更新证书
# 证书过期需要重新签发
kubeadm certs renew kubelet-client-current
systemctl restart kubelet
# 3. 检查资源
free -h
df -h
# 4. 重新加入集群
kubeadm reset
kubeadm join 控制节点IP:6443 --token XXX --discovery-token-ca-cert-hash XXX
|
常见原因及解决:
| 原因 | 排查方法 | 解决方案 |
|---|
| 标签不匹配 | 对比Pod和Service标签 | 修正labelSelector |
| 端口不匹配 | 检查targetPort | 修正端口配置 |
| Endpoints为空 | kubectl get endpoints | 检查Pod就绪状态 |
| kube-proxy异常 | 查看kube-proxy日志 | 重启kube-proxy |
| 网络策略限制 | kubectl get networkpolicy | 调整网络策略 |
解决方案:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 修正标签选择器
kubectl edit svc 服务名 -n 命名空间
# 2. 重启kube-proxy
kubectl delete pod -n kube-system -l k8s-app=kube-proxy
# 3. 检查iptables规则
iptables -L -n | grep 服务IP
# 4. 使用headless Service调试
# 将clusterIP改为None,直接访问Pod IP
|
第六部分:数据库事故#
6.1 MySQL锁表#
事故现象:
- 查询超时
- 应用报"Lock wait timeout"
- 数据库响应慢
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- 1. 查看锁等待
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
-- 2. 查看正在运行的事务
SELECT * FROM information_schema.INNODB_TRX;
-- 3. 查看锁信息
SELECT * FROM performance_schema.data_locks;
-- 4. 查看进程列表
SHOW PROCESSLIST;
-- 5. 查看表锁
SHOW OPEN TABLES WHERE In_use > 0;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| -- 1. 终止阻塞的会话
KILL 进程ID;
-- 2. 查看并终止长时间运行的事务
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 300;
-- 3. 优化锁等待超时
SET GLOBAL innodb_lock_wait_timeout = 50;
-- 4. 查看锁等待图
SELECT blocking_locks.lock_id AS blocked_lock_id,
blocking_locks.lock_mode AS blocked_lock_mode,
blocking_trx.trx_id AS blocked_trx_id,
blocking_trx.trx_query AS blocked_query
FROM performance_schema.data_lock_waits
JOIN performance_schema.data_locks blocking_locks
ON data_lock_waits.blocking_engine_lock_id = blocking_locks.engine_lock_id
JOIN performance_schema.innodb_trx blocking_trx
ON blocking_locks.engine_transaction_id = blocking_trx.trx_id;
|
预防措施:
- 避免长事务
- 合理设计索引
- 使用合适的隔离级别
- 定期分析慢查询
6.2 主从延迟#
事故现象:
- 从库查询数据不一致
- 应用读写分离异常
- 监控显示延迟增长
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -- 1. 查看主从状态
SHOW SLAVE STATUS\G
-- 关键字段:
-- Slave_IO_Running: Yes/No
-- Slave_SQL_Running: Yes/No
-- Seconds_Behind_Master: 延迟秒数
-- Last_Error: 错误信息
-- 2. 查看主库位置
SHOW MASTER STATUS\G
-- 3. 查看从库位置
SHOW SLAVE STATUS\G
-- Relay_Log_Pos, Exec_Master_Log_Pos
-- 4. 查看复制线程
SHOW PROCESSLIST;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -- 1. 跳过错误(谨慎使用)
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;
-- 2. 并行复制优化(MySQL 5.7+)
SET GLOBAL slave_parallel_workers = 4;
SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK';
-- 3. 优化从库配置
-- my.cnf
[mysqld]
slave_parallel_workers = 4
binlog_format = ROW
slave_preserve_commit_order = ON
-- 4. 重新同步(最后手段)
-- 从主库重新导数据
|
预防措施:
- 使用行级复制(binlog_format=ROW)
- 开启并行复制
- 避免从库执行大事务
- 监控延迟告警(>30秒)
6.3 慢SQL拖垮系统#
事故现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -- 1. 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
-- 2. 查看慢查询
SELECT * FROM mysql.slow_log;
-- 3. 查看当前运行查询
SHOW FULL PROCESSLIST;
-- 4. 查看查询执行计划
EXPLAIN SELECT * FROM 表 WHERE 条件;
-- 5. 查看索引使用情况
SELECT * FROM sys.schema_unused_indexes;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| -- 1. 终止慢查询
KILL 进程ID;
-- 2. 添加索引
ALTER TABLE 表名 ADD INDEX 索引名 (字段名);
-- 3. 优化查询
-- 避免SELECT *
-- 避免在索引列上使用函数
-- 避免LIKE '%前缀'
-- 4. 限制查询资源
SET GLOBAL max_execution_time = 30000; -- 30秒超时
-- 5. 使用查询缓存(MySQL 5.7)
SET GLOBAL query_cache_size = 64M;
|
预防措施:
- 代码审查SQL
- 上线前EXPLAIN分析
- 慢查询监控告警
- 定期优化索引
6.4 连接池耗尽#
事故现象:
- 应用报"Cannot get connection"
- 数据库连接数达到上限
- 新请求无法获取连接
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -- 1. 查看当前连接数
SHOW STATUS LIKE 'Threads_connected';
-- 2. 查看最大连接数
SHOW VARIABLES LIKE 'max_connections';
-- 3. 查看各用户连接数
SELECT user, host, COUNT(*) FROM information_schema.processlist GROUP BY user, host;
-- 4. 查看连接状态
SHOW STATUS LIKE 'Connections';
SHOW STATUS LIKE 'Aborted_connects';
-- 5. 应用层查看连接池
-- 根据连接池类型查看监控
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| -- 1. 临时增加连接数
SET GLOBAL max_connections = 500;
-- 2. 终止空闲连接
SELECT CONCAT('KILL ', id, ';')
FROM information_schema.processlist
WHERE command = 'Sleep' AND time > 300;
-- 3. 调整应用连接池配置
# 示例(HikariCP)
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.idle-timeout=600000
-- 4. 优化查询减少连接占用
-- 减少长事务
-- 及时关闭连接
|
预防措施:
- 合理设置连接池大小
- 监控连接使用率
- 设置连接超时
- 使用连接池监控
6.5 binlog爆仓#
事故现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- 1. 查看binlog列表
SHOW BINARY LOGS;
-- 2. 查看binlog大小
SELECT SUM(file_size) FROM (SHOW BINARY LOGS) AS logs;
-- 3. 查看binlog配置
SHOW VARIABLES LIKE 'binlog%';
-- 4. 查看当前binlog
SHOW MASTER STATUS;
-- 5. 系统层查看
du -sh /var/lib/mysql/*.bin
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| -- 1. 清理旧binlog
PURGE BINARY LOGS BEFORE '2024-01-01 00:00:00';
PURGE BINARY LOGS TO 'mysql-bin.000100';
-- 2. 调整binlog保留策略
-- my.cnf
[mysqld]
binlog_expire_logs_seconds = 604800 # 7天
max_binlog_size = 1073741824 # 1GB
-- 3. 临时关闭binlog(谨慎)
SET SESSION sql_log_bin = 0;
-- 4. 从库跳过binlog
-- 从库不需要binlog可关闭
[mysqld]
skip-log-bin
|
预防措施:
- 设置合理的过期时间
- 监控binlog大小
- 定期清理
- 独立分区存储binlog
第七部分:缓存事故#
7.1 Redis雪崩#
事故现象:
原因分析:
- 同一时间大量key过期
- Redis服务宕机
- 缓存未命中穿透到数据库
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 随机过期时间
import random
expire_time = base_expire + random.randint(0, 300) # 基础时间+随机0-5分钟
# 2. 热点数据永不过期
# 对核心数据设置较长过期时间或永不过期
# 3. 限流降级
# 使用令牌桶或信号量限制并发查询
# 4. 多级缓存
# 本地缓存 + Redis + 数据库
# 5. 缓存预热
# 系统启动时预加载热点数据
|
预防措施:
- 设置随机过期时间
- 热点数据单独处理
- 监控缓存命中率
- 配置熔断降级
7.2 Redis击穿#
事故现象:
- 单个热点key过期
- 大量请求同时访问该key
- 数据库瞬间压力增大
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 互斥锁
def get_data(key):
data = redis.get(key)
if data is None:
if redis.setnx(f"lock:{key}", 1):
try:
data = db.query(key)
redis.set(key, data, expire=300)
finally:
redis.delete(f"lock:{key}")
else:
time.sleep(0.1)
return get_data(key)
return data
# 2. 逻辑过期
# 不设置物理过期,在value中存储过期时间
# 异步更新缓存
# 3. 永不过期 + 异步更新
# 热点数据永不过期,后台定时更新
|
7.3 Redis穿透#
事故现象:
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 缓存空值
def get_data(key):
data = redis.get(key)
if data == "NULL": # 特殊标记
return None
if data is None:
data = db.query(key)
if data is None:
redis.setex(key, 60, "NULL") # 缓存空值60秒
return None
redis.setex(key, 300, data)
return data
# 2. 布隆过滤器
from pybloom import BloomFilter
bf = BloomFilter(capacity=1000000, error_rate=0.001)
def query(key):
if key not in bf: # 肯定不存在
return None
# 可能存在,继续查询
# 3. 参数校验
# 对请求参数进行合法性校验
|
7.4 Redis主从切换失败#
事故现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 查看Redis节点状态
redis-cli -h 主节点IP INFO replication
# 2. 查看Sentinel状态
redis-cli -h SentinelIP INFO sentinel
# 3. 查看Sentinel监控
redis-cli -h SentinelIP SENTINEL masters
redis-cli -h SentinelIP SENTINEL slaves 主节点名
# 4. 测试连接
redis-cli -h 节点IP ping
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 手动提升从节点
redis-cli -h 从节点IP SLAVEOF NO ONE
# 2. 检查Sentinel配置
# sentinel.conf
sentinel monitor mymaster 主IP 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel failover-timeout mymaster 60000
sentinel parallel-syncs mymaster 1
# 3. 重启Sentinel
systemctl restart redis-sentinel
# 4. 检查网络连通性
ping 节点IP
telnet 节点IP 26379
|
预防措施:
- 配置至少3个Sentinel
- 设置合理的超时时间
- 定期演练故障切换
- 监控主从状态
第八部分:消息队列事故#
8.1 Kafka积压#
事故现象:
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 查看消费组状态
kafka-consumer-groups.sh --bootstrap-server broker:9092 --describe --group 消费组名
# 关键字段:
# LAG: 积压消息数
# CURRENT-OFFSET: 当前消费位置
# LOG-END-OFFSET: 最新消息位置
# 2. 查看Topic详情
kafka-topics.sh --bootstrap-server broker:9092 --describe --topic Topic名
# 3. 查看消费者延迟
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--describe --group 消费组名 --verbose
# 4. 查看Broker状态
kafka-broker-api-versions.sh --bootstrap-server broker:9092
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 增加消费者实例
# 扩展消费者组,增加并行度
# 2. 增加Partition
kafka-topics.sh --bootstrap-server broker:9092 \
--alter --topic Topic名 --partitions 新数量
# 3. 优化消费逻辑
# 批量处理消息
# 异步处理非关键逻辑
# 4. 临时跳过消息(谨慎)
# 重置消费位点
kafka-consumer-groups.sh --bootstrap-server broker:9092 \
--group 消费组名 --topic Topic名 --reset-offsets --to-latest --execute
# 5. 扩容Broker
# 增加Broker节点,重新分配Partition
|
预防措施:
- 监控Lag指标(>10000告警)
- 合理设置Partition数量
- 消费者自动扩缩容
- 设置消息保留策略
8.2 RabbitMQ堆积#
事故现象:
- 队列消息数持续增长
- 消费者处理不过来
- 内存/磁盘使用率增长
排查命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看队列状态
rabbitmqctl list_queues name messages consumers
# 2. 查看队列详情
rabbitmqctl list_queues name messages_ready messages_unacknowledged
# 3. 查看连接
rabbitmqctl list_connections
# 4. 查看通道
rabbitmqctl list_channels
# 5. 管理界面
# http://MQ_IP:15672
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 增加消费者
# 启动更多消费者实例
# 2. 调整prefetch_count
# 消费者配置:channel.basic_qos(prefetch_count=10)
# 3. 清理积压消息
# 创建临时队列,消费后丢弃
rabbitmqadmin purge queue 队列名
# 4. 扩容集群
# 增加RabbitMQ节点
# 5. 优化消息处理
# 批量确认
# 异步处理
|
预防措施:
- 监控队列长度
- 设置队列最大长度
- 配置死信队列
- 定期清理无用队列
8.3 消息重复消费#
事故现象:
原因分析:
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 幂等性设计
def process_message(msg_id, data):
# 检查是否已处理
if redis.exists(f"processed:{msg_id}"):
return
# 处理业务
do_something(data)
# 标记已处理
redis.setex(f"processed:{msg_id}", 86400, 1)
# 2. 数据库唯一约束
# 使用业务主键作为唯一索引
# 3. 状态机控制
# 订单状态:待支付->已支付,不允许重复支付
# 4. 消息去重表
CREATE TABLE message_dedup (
msg_id VARCHAR(64) PRIMARY KEY,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
|
预防措施:
- 业务层实现幂等
- 消息ID全局唯一
- 记录已处理消息
- 定期清理去重表
第九部分:真实互联网事故复盘(100个)#
事故1:GitLab数据库误删事故(2017)#
事故现象:
- GitLab.com服务完全不可用
- 6小时数据丢失
- 30万项目受影响
事故原因:
- 运维人员误执行
rm -rf删除生产数据库目录 - 备份系统失效(5个备份机制全部失败)
- 权限管理不当(生产环境可直接删除)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 发现服务不可用
curl -I https://gitlab.com # 返回500
# 2. 检查数据库状态
systemctl status postgresql # 服务停止
# 3. 检查数据目录
ls -la /var/opt/gitlab/postgresql/data # 目录为空
# 4. 尝试恢复备份
# 发现所有备份均不可用
|
使用命令:
1
2
3
4
5
6
7
| # 检查备份状态
ls -la /backup/gitlab/
pg_restore -l 备份文件
# 检查磁盘
df -h
lsof | grep deleted
|
解决方案:
- 从最近的可用备份恢复(损失6小时数据)
- 手动重建缺失数据
- 逐步恢复服务
预防措施:
- 实施最小权限原则
- 多重备份验证机制
- 生产环境操作审批流程
- 定期备份恢复演练
经验总结:
- 备份必须定期验证可用性
- 生产环境禁止直接操作
- 关键操作需要双人复核
事故2:AWS S3全球宕机(2017)#
事故现象:
- S3服务中断4小时
- 大量依赖S3的服务不可用
- 估计损失1.5亿美元
事故原因:
- 运维人员调试计费系统时输入错误命令
- 误删除了更多服务器而非预期
- S3索引系统级联故障
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 监控告警
S3请求错误率飙升
API响应时间超时
# 2. 日志分析
AWS CloudTrail审计日志
S3访问日志
# 3. 系统状态检查
检查S3各组件健康状态
|
解决方案:
- 隔离故障组件
- 重建索引系统
- 逐步恢复服务
预防措施:
- 命令执行前验证
- 限制批量操作权限
- 实施变更冻结窗口
- 自动化安全检测
经验总结:
- 自动化脚本需要安全限制
- 关键系统需要隔离保护
- 故障恢复需要优先级排序
事故3:Facebook BGP事故(2021)#
事故现象:
- Facebook、Instagram、WhatsApp全球中断6小时
- DNS无法解析
- 内部系统无法访问
事故原因:
- BGP路由更新错误
- 所有Facebook IP从互联网路由表撤回
- 内部DNS依赖外部网络
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 外部检测
ping facebook.com # 无法解析
traceroute facebook.com # 路由中断
# 2. BGP检查
show ip bgp summary
show ip route facebook网段
# 3. DNS检查
nslookup facebook.com # 无响应
|
解决方案:
- 工程师物理进入数据中心
- 手动修复BGP配置
- 恢复路由广播
- 逐步恢复服务
预防措施:
- BGP变更需要多重审批
- 内部系统不依赖外部网络
- 建立带外管理通道
- 定期BGP配置审计
经验总结:
- 网络变更风险极高
- 需要带外管理通道
- DNS架构需要冗余设计
事故4:Cloudflare CPU 100%事故#
事故现象:
- Cloudflare全球服务性能下降
- 网站访问变慢
- API响应超时
事故原因:
- WAF规则更新引入正则表达式灾难性回溯
- 单个请求消耗大量CPU
- 级联影响所有边缘节点
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 监控发现
CPU使用率告警
请求延迟增长
# 2. 日志分析
grep "WAF" /var/log/cloudflare/
分析高CPU请求特征
# 3. 性能分析
perf top # 查看热点函数
|
解决方案:
- 回滚WAF规则
- 优化正则表达式
- 添加请求限制
- 逐步恢复
预防措施:
- 规则变更前性能测试
- 正则表达式复杂度限制
- 灰度发布机制
- 实时监控CPU使用
经验总结:
- 正则表达式可能成为DoS向量
- 变更需要性能基准测试
- 需要快速回滚机制
事故5:GitHub数据库复制事故#
事故现象:
- GitHub服务中断24秒
- 部分用户看到旧数据
- 写入操作失败
事故原因:
- 数据库主从切换触发
- 复制延迟导致数据不一致
- 应用层未正确处理切换
排查过程:
1
2
3
4
5
6
7
8
| -- 1. 检查复制状态
SHOW SLAVE STATUS\G
-- 2. 检查延迟
SELECT TIMESTAMPDIFF(SECOND, last_executed_time, NOW());
-- 3. 检查连接
SHOW PROCESSLIST;
|
解决方案:
- 等待复制完成
- 验证数据一致性
- 恢复服务
- 修复应用重试逻辑
预防措施:
- 监控复制延迟
- 应用层实现重试机制
- 读写分离容错设计
- 定期故障演练
经验总结:
- 数据库切换需要应用配合
- 复制延迟需要监控
- 短暂中断也需要复盘
事故6:Slack数据库过载#
事故现象:
事故原因:
- 数据库连接池耗尽
- 慢查询拖垮数据库
- 级联故障影响所有服务
排查过程:
1
2
3
4
5
6
7
8
| -- 1. 检查连接数
SHOW STATUS LIKE 'Threads_connected';
-- 2. 检查慢查询
SELECT * FROM mysql.slow_log ORDER BY start_time DESC LIMIT 10;
-- 3. 检查锁等待
SELECT * FROM information_schema.innodb_lock_waits;
|
解决方案:
- 终止慢查询
- 增加数据库连接
- 优化问题SQL
- 实施限流
预防措施:
- 连接池监控告警
- 慢查询自动分析
- SQL上线审查
- 服务降级机制
经验总结:
- 数据库是单点故障高发区
- 连接池需要合理配置
- 慢查询需要主动发现
事故7:Zoom DNS故障#
事故现象:
事故原因:
- DNS配置错误
- 证书更新导致DNS记录失效
- 缓存传播延迟
排查过程:
1
2
3
4
5
6
7
8
9
| # 1. 测试DNS解析
nslookup zoom.us
dig zoom.us
# 2. 检查DNS配置
cat /etc/resolv.conf
# 3. 检查证书
openssl s_client -connect zoom.us:443
|
解决方案:
- 修正DNS记录
- 清除DNS缓存
- 等待全球传播
- 通知用户
预防措施:
- DNS变更提前通知
- 多DNS提供商冗余
- TTL设置合理
- 变更验证流程
经验总结:
- DNS变更影响范围广
- 需要足够的传播时间
- 多提供商提高可用性
事故8:Kubernetes etcd崩溃#
事故现象:
- kubectl命令无响应
- Pod无法调度
- 集群管理功能失效
事故原因:
- etcd磁盘IO延迟高
- Leader选举频繁
- 集群无法达成共识
排查过程:
1
2
3
4
5
6
7
8
| # 1. 检查etcd状态
ETCDCTL_API=3 etcdctl endpoint health
# 2. 检查延迟
ETCDCTL_API=3 etcdctl endpoint status --write-out=table
# 3. 检查磁盘
iostat -x 1
|
解决方案:
- 更换SSD磁盘
- 优化etcd配置
- 减少etcd负载
- 增加etcd节点
预防措施:
- etcd使用SSD
- 监控etcd延迟
- 定期备份
- 限制etcd存储大小
经验总结:
事故9:ELK日志拖垮生产#
事故现象:
事故原因:
- 日志量激增(异常循环打印)
- Elasticsearch索引过多
- Logstash处理不过来
排查过程:
1
2
3
4
5
6
7
8
9
| # 1. 检查磁盘
df -h
du -sh /var/log/*
# 2. 检查ES集群
curl localhost:9200/_cluster/health
# 3. 检查日志量
wc -l /var/log/app/*.log
|
解决方案:
- 修复异常日志
- 清理旧索引
- 限制日志级别
- 增加ES节点
预防措施:
- 日志级别动态调整
- 索引生命周期管理
- 日志量监控告警
- 采样记录日志
经验总结:
- 日志系统可能成为故障源
- 需要限制日志量
- 定期清理很重要
事故10:CDN缓存雪崩#
事故现象:
事故原因:
排查过程:
1
2
3
4
5
6
7
8
9
| # 1. 检查CDN状态
curl -I https://cdn.域名/资源
# 2. 检查证书
openssl s_client -connect cdn.域名:443
# 3. 检查源站
top
netstat -an | grep ESTABLISHED | wc -l
|
解决方案:
- 更新CDN证书
- 源站限流保护
- 启用备用CDN
- 预热缓存
预防措施:
经验总结:
事故11:TLS证书过期事故#
事故现象:
- 用户访问HTTPS网站显示"证书已过期"警告
- 移动端APP无法连接API
- 浏览器显示红色安全警告
- 业务流量下降80%
事故原因:
- SSL证书有效期1年,到期未续费
- 证书管理依赖人工记忆,无自动化监控
- 运维人员离职交接遗漏
- 证书部署在多台服务器,部分遗漏更新
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 1. 用户反馈后验证证书状态
openssl s_client -connect www.example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -dates
# 输出显示:
# notBefore=Jan 15 00:00:00 2024 GMT
# notAfter=Jan 15 23:59:59 2025 GMT # 已过期
# 2. 检查所有服务器证书
for server in web1 web2 web3 web4; do
echo "=== $server ==="
openssl s_client -connect $server:443 </dev/null 2>/dev/null | \
openssl x509 -noout -enddate
done
# 3. 查找证书文件位置
find /etc -name "*.crt" -o -name "*.pem" 2>/dev/null
find /opt -name "*.crt" -o -name "*.pem" 2>/dev/null
# 4. 检查证书到期时间
for cert in $(find /etc/ssl -name "*.crt" 2>/dev/null); do
echo "=== $cert ==="
openssl x509 -in $cert -noout -enddate
done
# 5. 检查Nginx配置
grep -r "ssl_certificate" /etc/nginx/
# 6. 查看证书剩余天数
openssl x509 -in /etc/ssl/certs/server.crt -noout -checkend 86400
# 返回1表示24小时内过期,返回0表示未过期
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 检查证书详细信息
openssl x509 -in certificate.crt -text -noout
# 检查证书链
openssl verify -CAfile ca-bundle.crt server.crt
# 在线检查证书
curl -vI https://www.example.com 2>&1 | grep -i "expire\|subject"
# 监控脚本示例
#!/bin/bash
CERT_FILE="/etc/ssl/certs/server.crt"
EXPIRE_DAYS=30
EXPIRE_DATE=$(openssl x509 -in $CERT_FILE -noout -enddate | cut -d= -f2)
EXPIRE_EPOCH=$(date -d "$EXPIRE_DATE" +%s)
CURRENT_EPOCH=$(date +%s)
DAYS_LEFT=$(( ($EXPIRE_EPOCH - $CURRENT_EPOCH) / 86400 ))
if [ $DAYS_LEFT -lt $EXPIRE_DAYS ]; then
echo "WARNING: Certificate expires in $DAYS_LEFT days"
# 发送告警
curl -X POST -d "证书将在$DAYS_LEFT天后过期" http://alert-server/webhook
fi
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 紧急申请新证书(Let's Encrypt)
certbot certonly --webroot -w /var/www/html -d www.example.com
# 2. 替换证书文件
cp /etc/letsencrypt/live/www.example.com/fullchain.pem /etc/ssl/certs/server.crt
cp /etc/letsencrypt/live/www.example.com/privkey.pem /etc/ssl/private/server.key
# 3. 重新加载Nginx
nginx -t # 先测试配置
systemctl reload nginx
# 4. 验证新证书
openssl s_client -connect www.example.com:443 </dev/null 2>/dev/null | \
openssl x509 -noout -dates
# 5. 清理旧证书
rm -f /etc/ssl/certs/server.crt.old
# 6. 重启相关服务
systemctl restart php-fpm
systemctl restart tomcat
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| # 1. 配置自动续期(crontab)
0 0 1 * * /usr/bin/certbot renew --quiet && systemctl reload nginx
# 2. 添加证书监控(Prometheus)
# 使用ssl_exporter监控证书到期
# 配置告警规则(Prometheus Alertmanager)
groups:
- name: certificate
rules:
- alert: SSLCertificateExpiringSoon
expr: ssl_cert_expiry_days < 30
for: 1h
labels:
severity: warning
annotations:
summary: "SSL证书即将过期"
description: "{{ $labels.instance }} 证书将在{{ $value }}天后过期"
- alert: SSLCertificateExpired
expr: ssl_cert_expiry_days < 0
for: 5m
labels:
severity: critical
annotations:
summary: "SSL证书已过期"
description: "{{ $labels.instance }} 证书已过期"
# 3. 证书管理台账
# 建立Excel/数据库记录所有证书信息
| 域名 | 证书类型 | 到期时间 | 负责人 | 部署位置 |
|------|----------|----------|--------|----------|
| www.example.com | DV | 2026-01-15 | 张三 | web1-4 |
# 4. 使用证书管理服务
# 如:HashiCorp Vault、AWS ACM、阿里云SSL服务
|
事故12:Kafka消息堆积事故#
事故现象:
- 订单处理延迟从秒级增长到小时级
- 用户反馈下单后长时间未收到确认
- 监控显示Kafka Lag持续增长
- 消费者组积压消息超过1000万条
事故原因:
- 消费者服务BUG导致处理速度下降90%
- 新增业务逻辑增加数据库写入,单条处理时间从10ms增至500ms
- 消费者实例数量未随业务量增长
- 缺少Lag监控告警机制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 1. 查看消费组状态
kafka-consumer-groups.sh --bootstrap-server kafka-broker:9092 \
--describe --group order-consumer-group
# 输出示例:
# TOPIC PARTITION CURRENT-OFFSET LOG-END-OFFSET LAG
# order-topic 0 1000000 5000000 4000000
# order-topic 1 1200000 5200000 4000000
# order-topic 2 1100000 5100000 4000000
# 2. 查看消费者日志
tail -f /var/log/order-consumer/app.log | grep -i "process\|error"
# 3. 检查消费者JVM状态
jps -l | grep order-consumer
jstat -gc <PID> 1000 10 # 查看GC情况
jstack <PID> | grep -A 30 "RUNNABLE" # 查看线程状态
# 4. 查看Kafka Broker状态
kafka-broker-api-versions.sh --bootstrap-server kafka-broker:9092
# 5. 检查Topic配置
kafka-topics.sh --bootstrap-server kafka-broker:9092 \
--describe --topic order-topic
# 6. 查看消息生产速率
kafka-run-class.sh kafka.tools.JmxTool \
--object-name kafka.server:type=BrokerTopicMetrics,name=MessagesInPerSec \
--jmx-url service:jmx:rmi:///jndi/rmi://kafka-broker:9999/jmxrmi
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 持续监控Lag变化
watch -n 5 'kafka-consumer-groups.sh --bootstrap-server kafka-broker:9092 \
--describe --group order-consumer-group | grep -v "^$"'
# 查看消费者延迟趋势
kafka-consumer-groups.sh --bootstrap-server kafka-broker:9092 \
--describe --group order-consumer-group --verbose
# 检查Kafka磁盘使用
df -h /var/lib/kafka
du -sh /var/lib/kafka/logs/*
# 查看Kafka控制器状态
echo "dump" | nc kafka-broker 9092 | grep -i "controller"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 紧急扩容消费者实例
# 从3个实例扩展到10个实例
kubectl scale deployment order-consumer --replicas=10
# 2. 增加Topic Partition数量
kafka-topics.sh --bootstrap-server kafka-broker:9092 \
--alter --topic order-topic --partitions 20
# 3. 优化消费者配置
# consumer.properties
fetch.min.bytes=1048576 # 增加拉取批量
fetch.max.wait.ms=500 # 增加等待时间
max.poll.records=1000 # 增加单次拉取数量
max.partition.fetch.bytes=10485760
# 4. 临时跳过非关键消息(谨慎使用)
kafka-consumer-groups.sh --bootstrap-server kafka-broker:9092 \
--group order-consumer-group --topic order-topic \
--reset-offsets --to-current --execute
# 5. 优化业务逻辑
# 将同步写入改为异步批量写入
# 原代码:每条消息单独插入数据库
# 优化后:每100条批量插入
# 6. 增加数据库连接池
spring.datasource.hikari.maximum-pool-size=50
# 7. 启用消费者并行处理
# 使用线程池处理消息
ExecutorService executor = Executors.newFixedThreadPool(10);
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
| # 1. 配置Lag监控告警(Prometheus + Kafka Exporter)
# 告警规则
- alert: KafkaConsumerLagHigh
expr: kafka_consumer_group_lag > 100000
for: 5m
labels:
severity: warning
annotations:
summary: "Kafka消费延迟过高"
description: "消费组{{ $labels.group }} 延迟{{ $value }}条"
- alert: KafkaConsumerLagCritical
expr: kafka_consumer_group_lag > 1000000
for: 2m
labels:
severity: critical
annotations:
summary: "Kafka消费严重延迟"
description: "消费组{{ $labels.group }} 延迟{{ $value }}条"
# 2. 消费者自动扩缩容
# 基于Lag指标自动扩容
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: order-consumer-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: order-consumer
minReplicas: 3
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: kafka_consumer_lag
target:
type: AverageValue
averageValue: 10000
# 3. 代码层面优化
# 添加处理超时限制
@KafkaListener(topics = "order-topic")
public void listen(ConsumerRecord<String, String> record) {
try {
processWithTimeout(record, 5, TimeUnit.SECONDS);
} catch (TimeoutException e) {
// 发送死信队列
sendToDeadLetterQueue(record);
}
}
# 4. 定期压测
# 模拟高峰流量验证消费者处理能力
|
经验总结:
- Kafka Lag必须实时监控告警
- 消费者处理能力要预留50%余量
- 业务变更必须评估对消费速度的影响
- 建立消息积压应急预案(扩容、限流、降级)
- 重要消息需要死信队列机制
事故13:Nginx连接耗尽事故#
事故现象:
- 用户访问网站返回502 Bad Gateway
- Nginx错误日志显示"connect() failed (110: Connection timed out)"
- 新请求无法建立连接
- 监控显示Nginx活跃连接数达到上限
事故原因:
- 后端服务响应变慢,连接占用时间增长
- Nginx worker_connections配置过小(1024)
- 系统文件句柄限制过低
- 突发流量超过预期3倍
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
| # 1. 查看Nginx状态
curl http://localhost/nginx_status
# 输出:
# Active connections: 1024
# server accepts handled requests
# 100000 100000 500000
# Reading: 100 Writing: 900 Waiting: 24
# 2. 查看Nginx错误日志
tail -f /var/log/nginx/error.log | grep -i "connect\|limit"
# 典型错误:
# 2024/01/15 10:30:00 [error] connect() failed (110: Connection timed out)
# 2024/01/15 10:30:01 [emerg] worker_connections are not enough
# 3. 检查Nginx配置
cat /etc/nginx/nginx.conf | grep -i "worker_connections\|worker_processes"
# 4. 查看系统文件句柄
ulimit -n
cat /proc/sys/fs/file-nr
# 5. 查看Nginx进程打开的文件数
ls /proc/$(cat /var/run/nginx.pid)/fd | wc -l
# 6. 查看后端服务连接
netstat -an | grep :8080 | wc -l
netstat -an | grep :8080 | grep ESTABLISHED | wc -l
# 7. 查看TIME_WAIT连接
netstat -n | grep TIME_WAIT | wc -l
# 8. 检查后端服务状态
systemctl status tomcat
top -p $(pgrep -f java)
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 实时查看Nginx连接状态
watch -n 1 'curl -s http://localhost/nginx_status'
# 查看各状态连接数
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 查看Nginx进程资源
ps aux | grep nginx
cat /proc/$(cat /var/run/nginx.pid)/limits
# 抓包分析连接问题
tcpdump -i any port 80 -w nginx_capture.pcap
# 分析慢请求
awk '{if($9 >= 500) print $0}' /var/log/nginx/access.log | head -20
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
| # 1. 紧急调整Nginx配置
# /etc/nginx/nginx.conf
worker_processes auto;
events {
worker_connections 65535; # 从1024增加到65535
multi_accept on;
use epoll;
}
http {
# 调整连接超时
keepalive_timeout 65;
client_body_timeout 60;
send_timeout 60;
# 调整上游连接
upstream backend {
server 127.0.0.1:8080;
keepalive 100; # 长连接池
}
server {
location / {
proxy_connect_timeout 60;
proxy_send_timeout 60;
proxy_read_timeout 60;
proxy_http_version 1.1;
proxy_set_header Connection "";
}
}
}
# 2. 调整系统文件句柄限制
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
# /etc/sysctl.conf
fs.file-max = 2097152
fs.nr_open = 2097152
# 应用配置
sysctl -p
ulimit -n 65535
# 3. 优化TCP参数
# /etc/sysctl.conf
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
sysctl -p
# 4. 重启Nginx
nginx -t
systemctl reload nginx
# 5. 扩容后端服务
kubectl scale deployment backend --replicas=5
# 6. 添加限流保护
# /etc/nginx/nginx.conf
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location / {
limit_req zone=one burst=20 nodelay;
}
}
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 1. 配置连接数监控
# Prometheus Nginx Exporter
# 告警规则
- alert: NginxActiveConnectionsHigh
expr: nginx_connections_active > 5000
for: 2m
labels:
severity: warning
annotations:
summary: "Nginx活跃连接数过高"
# 2. 压力测试
# 定期使用ab/wrk进行压测
ab -n 10000 -c 100 http://localhost/
# 3. 容量规划
# 根据业务量预估连接数,预留50%余量
# 公式:连接数 = QPS * 平均响应时间 * 1.5
# 4. 优雅降级
# 配置限流和熔断
location /api/ {
limit_req zone=api burst=50;
proxy_connect_timeout 5s;
error_page 502 503 504 /50x.html;
}
|
经验总结:
- worker_connections要根据业务量合理设置
- 系统文件句柄限制必须调整
- 后端服务响应时间直接影响连接占用
- 必须配置连接数监控告警
- 限流保护是最后一道防线
事故14:TIME_WAIT风暴事故#
事故现象:
- 服务器无法建立新连接
- 应用报"Cannot assign requested address"
- 端口耗尽,服务不可用
- netstat显示大量TIME_WAIT状态
事故原因:
- 短连接场景(如HTTP请求)频繁建立关闭
- 服务器主动关闭连接,产生大量TIME_WAIT
- 本地端口范围过小
- TIME_WAIT等待时间过长(默认120秒)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # 1. 查看TIME_WAIT数量
netstat -n | grep TIME_WAIT | wc -l
# 输出:25000(正常应<10000)
# 2. 查看TIME_WAIT详情
netstat -n | grep TIME_WAIT | head -20
# 输出示例:
# tcp 0 0 192.168.1.10:45000 10.0.0.1:80 TIME_WAIT
# tcp 0 0 192.168.1.10:45001 10.0.0.1:80 TIME_WAIT
# 3. 查看各状态连接分布
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 输出:
# TIME_WAIT 25000
# ESTABLISHED 500
# CLOSE_WAIT 100
# 4. 查看本地端口范围
cat /proc/sys/net/ipv4/ip_local_port_range
# 输出:32768 61000(约28000个端口)
# 5. 查看TIME_WAIT等待时间
cat /proc/sys/net/ipv4/tcp_fin_timeout
# 输出:60(秒)
# 6. 查看端口耗尽情况
netstat -n | grep 192.168.1.10 | awk '{print $4}' | \
cut -d: -f2 | sort -n | uniq | wc -l
# 7. 检查应用连接池配置
# 查看是否有连接复用
grep -r "keepalive\|pool" /etc/application/
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 持续监控TIME_WAIT变化
watch -n 5 'netstat -n | grep TIME_WAIT | wc -l'
# 查看TIME_WAIT连接的目标分布
netstat -n | grep TIME_WAIT | awk '{print $5}' | \
cut -d: -f1 | sort | uniq -c | sort -rn | head -10
# 查看端口使用情况
ss -s
# 输出:
# TCP: 30000 (estab 500, closed 29000, orphaned 0, synrecv 0, timewait 25000)
# 检查是否有连接泄漏
lsof -i -n | grep -i wait
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
| # 1. 开启TIME_WAIT复用(立即生效)
echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse
# 2. 缩短TIME_WAIT等待时间
echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
# 3. 扩大本地端口范围
echo "1024 65535" > /proc/sys/net/ipv4/ip_local_port_range
# 4. 永久配置(/etc/sysctl.conf)
cat >> /etc/sysctl.conf << EOF
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
net.ipv4.ip_local_port_range = 1024 65535
net.ipv4.tcp_max_tw_buckets = 20000
EOF
sysctl -p
# 5. 应用层优化(使用长连接)
# Python示例
import requests
session = requests.Session() # 复用连接
for i in range(1000):
session.get('http://backend/api')
# Java示例(HttpClient)
CloseableHttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(200)
.setMaxConnPerRoute(50)
.setConnectionTimeToLive(30, TimeUnit.SECONDS)
.build();
# 6. Nginx配置长连接
upstream backend {
server 127.0.0.1:8080;
keepalive 100; # 保持100个长连接
}
# 7. 数据库连接池
# 避免频繁创建数据库连接
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 监控TIME_WAIT数量
# Prometheus Node Exporter + 告警
- alert: TimeWaitConnectionsHigh
expr: node_netstat_Tcp_TimeWait > 10000
for: 5m
labels:
severity: warning
annotations:
summary: "TIME_WAIT连接数过高"
# 2. 应用连接池规范
# 所有外部调用必须使用连接池
# HTTP、数据库、Redis等
# 3. 架构优化
# 减少不必要的网络调用
# 使用本地缓存
# 批量处理减少请求次数
# 4. 定期巡检
# 每周检查连接状态
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
|
经验总结:
- TIME_WAIT是TCP正常状态,但过多会影响性能
- 开启tcp_tw_reuse是最有效的解决方案
- 应用层必须使用连接池
- 短连接场景要特别关注端口耗尽
- 监控要包含各TCP状态分布
事故15:文件句柄耗尽事故#
事故现象:
- 应用报"Too many open files"
- 无法创建新连接
- 日志无法写入
- 服务部分功能失效
事故原因:
- 连接泄漏,打开的文件/Socket未关闭
- 系统文件句柄限制过低(默认1024)
- 日志文件未轮转,单个文件过大
- 进程打开大量小文件未释放
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 1. 查看系统级文件句柄
cat /proc/sys/fs/file-max
cat /proc/sys/fs/file-nr
# 输出:100000 50000 100000(已用/空闲/最大)
# 2. 查看进程级限制
ulimit -n
# 输出:1024(过低)
# 3. 查看具体进程打开的文件数
ls /proc/$(pgrep -f java)/fd | wc -l
# 输出:5000
# 4. 找出打开文件最多的进程
for pid in /proc/[0-9]*/fd; do
echo $(ls $pid 2>/dev/null | wc -l) $pid
done | sort -rn | head -20
# 5. 查看进程打开的具体文件
lsof -p $(pgrep -f java) | head -50
# 6. 查看已删除但仍被占用的文件
lsof | grep deleted | head -20
# 7. 查看Socket连接
lsof -i -n | grep -i listen
lsof -i -n | grep -i established | wc -l
# 8. 查看日志文件
lsof | grep "\.log" | wc -l
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
| # 实时监控文件句柄使用
watch -n 5 'cat /proc/sys/fs/file-nr'
# 查看各类型文件分布
lsof -p $(pgrep -f java) | awk '{print $5}' | sort | uniq -c | sort -rn
# 查看网络连接数
lsof -i -n | awk '{print $8}' | sort | uniq -c | sort -rn
# 检查文件描述符泄漏
strace -p $(pgrep -f java) -e open,close -o /tmp/fd_trace.log
# 分析open和close是否配对
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
| # 1. 临时提高进程限制
ulimit -n 65535
# 2. 永久修改系统限制
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535
# /etc/sysctl.conf
fs.file-max = 2097152
fs.nr_open = 2097152
sysctl -p
# 3. 修改systemd服务限制
# /etc/systemd/system/application.service
[Service]
LimitNOFILE=65535
LimitNPROC=65535
systemctl daemon-reload
systemctl restart application
# 4. 修复代码泄漏
# Java示例 - 确保资源关闭
// 错误代码
FileInputStream fis = new FileInputStream(file);
// 处理文件
// 忘记关闭
// 正确代码
try (FileInputStream fis = new FileInputStream(file)) {
// 处理文件
} catch (IOException e) {
// 处理异常
}
# 5. 配置日志轮转
# /etc/logrotate.d/application
/var/log/application/*.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
create 0640 app app
postrotate
kill -USR1 $(cat /var/run/application.pid)
endscript
}
# 6. 重启服务释放泄漏资源
systemctl restart application
# 7. 清理已删除但占用的文件
# 找到进程并重启
lsof | grep deleted | awk '{print $2}' | sort -u
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 配置文件句柄监控
# Prometheus Node Exporter
- alert: FileDescriptorsHigh
expr: node_filefd_allocated / node_filefd_maximum > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "文件句柄使用率过高"
# 2. 代码审查规范
# 所有资源打开必须有对应的关闭
# 使用try-with-resources(Java)
# 使用context manager(Python)
# 3. 定期巡检
# 每周检查文件句柄使用
cat /proc/sys/fs/file-nr
# 4. 压力测试
# 模拟高并发验证文件句柄是否泄漏
|
经验总结:
- 文件句柄耗尽是常见但可预防的事故
- 系统默认限制必须调整
- 代码资源管理是关键
- 日志轮转必须配置
- 监控要覆盖系统级和进程级
事故16:Docker overlay2爆盘事故#
事故现象:
- 容器无法启动
- 镜像无法拉取
- Docker命令执行失败
df -h显示/var/lib/docker使用率100%
事故原因:
- 容器日志未限制大小,单个日志文件达50GB
- 大量悬空镜像未清理
- 容器层数过多,overlay2叠加膨胀
- 未配置定期清理机制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # 1. 查看Docker磁盘使用
docker system df
# 输出:
# TYPE TOTAL ACTIVE SIZE RECLAIMABLE
# Images 50 10 20GB 15GB (75%)
# Containers 100 20 5GB 4GB (80%)
# Local Volumes 30 10 10GB 8GB (80%)
# Build Cache - - 5GB 5GB (100%)
# 2. 查看具体占用
du -sh /var/lib/docker/*
# 输出:
# 80G /var/lib/docker/overlay2
# 10G /var/lib/docker/containers
# 5G /var/lib/docker/volumes
# 3. 查看大日志文件
find /var/lib/docker/containers -name "*.log" -size +1G -exec ls -lh {} \;
# 4. 查看悬空镜像
docker images -f "dangling=true"
# 5. 查看容器大小
docker ps -s --no-trunc
# 6. 查看overlay2层
ls -la /var/lib/docker/overlay2/ | head -20
du -sh /var/lib/docker/overlay2/* | sort -rh | head -10
# 7. 查看Docker配置
cat /etc/docker/daemon.json
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 查看容器日志大小
for container in $(docker ps -q); do
size=$(docker inspect $container --format='{{.LogPath}}' | \
xargs du -h 2>/dev/null | cut -f1)
echo "$container: $size"
done | sort -rh | head -10
# 查看镜像层数
docker history 镜像名
# 查看存储驱动
docker info | grep "Storage Driver"
# 监控Docker磁盘使用
watch -n 10 'df -h /var/lib/docker'
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| # 1. 紧急清理悬空资源
docker image prune -f # 清理悬空镜像
docker container prune -f # 清理停止的容器
docker volume prune -f # 清理未使用的卷
docker system prune -f # 清理所有未使用资源
# 2. 清理大日志文件
for log in $(find /var/lib/docker/containers -name "*.log" -size +1G); do
echo "" > $log # 清空而非删除,避免影响正在写入的容器
done
# 3. 配置日志限制(/etc/docker/daemon.json)
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "3"
},
"storage-opts": [
"overlay2.override_kernel_check=true"
]
}
systemctl daemon-reload
systemctl restart docker
# 4. 迁移Docker数据目录
systemctl stop docker
rsync -avz /var/lib/docker /data/docker/
# 修改/etc/docker/daemon.json
{
"data-root": "/data/docker"
}
systemctl start docker
# 5. 优化镜像构建(减少层数)
# Dockerfile优化
# 错误:多层RUN
RUN apt-get update
RUN apt-get install -y package1
RUN apt-get install -y package2
# 正确:合并层
RUN apt-get update && apt-get install -y package1 package2 && \
rm -rf /var/lib/apt/lists/*
# 6. 定期清理脚本(crontab)
0 2 * * * /usr/bin/docker system prune -f --volumes
# 7. 使用多阶段构建减少镜像大小
FROM golang:1.19 AS builder
WORKDIR /app
COPY . .
RUN go build -o main .
FROM alpine:latest
COPY --from=builder /app/main /main
CMD ["/main"]
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 1. 配置磁盘监控告警
# Prometheus + Docker Exporter
- alert: DockerDiskUsageHigh
expr: docker_disk_usage / docker_disk_total > 0.8
for: 5m
labels:
severity: warning
annotations:
summary: "Docker磁盘使用率过高"
# 2. 日志收集方案
# 使用 journald 或 syslog 驱动,配合ELK
{
"log-driver": "syslog",
"log-opts": {
"syslog-address": "udp://log-server:514"
}
}
# 3. 镜像仓库管理
# 定期清理旧镜像
docker images | grep "weeks ago\|months ago" | \
awk '{print $3}' | xargs docker rmi -f
# 4. 存储规划
# Docker数据目录独立分区
# 建议至少100GB空间
|
经验总结:
- 日志限制是必须配置项
- 定期清理要自动化
- 镜像构建要优化层数
- 数据目录建议独立分区
- 监控要覆盖磁盘使用率
事故17:CI/CD误发布事故#
事故现象:
- 生产环境出现未测试功能
- 用户报告新功能BUG
- 数据库 schema 不兼容
- 服务间歇性失败
事故原因:
- 开发人员直接推送代码到生产分支
- 缺少发布审批流程
- 自动化测试覆盖率不足
- 回滚机制不完善
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 查看Git提交历史
git log production --since="2024-01-15" --oneline
# 2. 查看CI/CD流水线记录
# Jenkins/GitLab CI 构建历史
# 查看谁触发了发布
# 3. 对比发布前后差异
git diff 发布前commit 发布后commit --stat
# 4. 查看部署记录
kubectl rollout history deployment/app
kubectl rollout history deployment/app --revision=5
# 5. 检查配置变更
git diff 发布前commit 发布后commit -- config/
# 6. 查看应用日志
kubectl logs deployment/app --tail=1000 | grep -i "error\|exception"
# 7. 检查数据库变更
# 查看迁移脚本
ls -la db/migrations/
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 查看当前部署版本
kubectl get deployment app -o jsonpath='{.spec.template.metadata.annotations}'
# 查看Pod重启次数
kubectl get pods | grep -v "1/1"
# 检查服务健康
curl -f http://app-service/health || echo "Unhealthy"
# 查看事件
kubectl get events --sort-by='.lastTimestamp' | tail -20
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 紧急回滚
kubectl rollout undo deployment/app
# 或回滚到特定版本
kubectl rollout undo deployment/app --to-revision=4
# Jenkins回滚
# 点击"Revert"或执行回滚脚本
# 2. 验证回滚结果
kubectl rollout status deployment/app
curl -f http://app-service/health
# 3. 检查数据一致性
# 如有数据库变更,需要回滚数据
# 从备份恢复或执行回滚脚本
# 4. 通知相关人员
# 发送事故通知
# 更新状态页面
# 5. 修复问题后重新发布
# 走正常发布流程
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
| # 1. 分支保护策略
# GitLab/GitHub分支保护
# - 禁止直接push到production
# - 必须Merge Request
# - 必须Code Review
# - 必须CI通过
# 2. 发布审批流程
# Jenkins Pipeline示例
pipeline {
agent any
stages {
stage('Build') {
steps { sh 'mvn package' }
}
stage('Test') {
steps { sh 'mvn test' }
}
stage('Approve') {
steps {
input message: '确认发布到生产?', ok: '确认发布'
}
}
stage('Deploy') {
steps { sh './deploy.sh production' }
}
}
}
# 3. 灰度发布
# 先发布10%流量
kubectl set image deployment/app app=app:new-version
kubectl rollout pause deployment/app
# 验证后继续
kubectl rollout resume deployment/app
# 4. 自动化回滚
# 健康检查失败自动回滚
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
strategy:
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
minReadySeconds: 30
progressDeadlineSeconds: 300
# 5. 数据库变更规范
# - 向前兼容
# - 可回滚脚本
# - 分批次执行
|
经验总结:
- 生产发布必须有审批流程
- 自动化测试是基本保障
- 灰度发布降低风险
- 回滚必须能在15分钟内完成
- 每次发布都要有回滚预案
事故18:数据库误删事故#
事故现象:
- 核心业务数据丢失
- 应用报"Table doesn’t exist"
- 用户数据无法查询
- 业务完全中断
事故原因:
- 运维人员误执行DROP/TRUNCATE命令
- 生产环境权限过大
- 缺少操作审计
- 备份恢复验证不足
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| -- 1. 确认数据丢失
SHOW DATABASES;
USE production_db;
SHOW TABLES; -- 发现表不存在
-- 2. 查看操作日志
# MySQL审计日志
grep -i "drop\|truncate" /var/log/mysql/audit.log
# 3. 查看二进制日志
mysqlbinlog /var/lib/mysql/mysql-bin.000100 | \
grep -i "drop\|truncate" | head -20
-- 4. 查看进程历史
SELECT * FROM information_schema.processlist;
-- 5. 确认备份状态
ls -la /backup/mysql/
mysql -e "SHOW BINARY LOGS;"
-- 6. 检查binlog是否开启
SHOW VARIABLES LIKE 'log_bin';
SHOW VARIABLES LIKE 'binlog_format';
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 查看binlog内容
mysqlbinlog --start-datetime="2024-01-15 10:00:00" \
--stop-datetime="2024-01-15 11:00:00" \
/var/lib/mysql/mysql-bin.000100 | less
# 查找误操作时间点
mysqlbinlog /var/lib/mysql/mysql-bin.* | \
grep -B 5 -A 5 "DROP TABLE"
# 检查备份文件
ls -lth /backup/mysql/*.sql | head -5
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 紧急止损
# 停止应用写入,防止数据进一步损坏
kubectl scale deployment app --replicas=0
# 2. 从备份恢复
# 找到最近的全量备份
BACKUP_FILE=$(ls -lt /backup/mysql/*.sql | head -1 | awk '{print $NF}')
# 恢复数据库
mysql -u root -p < $BACKUP_FILE
# 3. 使用binlog恢复增量数据
# 找到误操作前的binlog位置
mysqlbinlog --start-datetime="2024-01-15 00:00:00" \
--stop-datetime="2024-01-15 10:59:59" \
/var/lib/mysql/mysql-bin.000100 | mysql -u root -p
# 4. 验证数据
mysql -e "SELECT COUNT(*) FROM critical_table;"
# 5. 恢复应用
kubectl scale deployment app --replicas=3
# 6. 通知用户
# 发送维护完成通知
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 权限最小化
# 生产环境禁止DROP/DELETE权限
# 创建只读账号用于查询
CREATE USER 'readonly'@'%' IDENTIFIED BY 'password';
GRANT SELECT ON production_db.* TO 'readonly'@'%';
# 2. 操作审批
# 所有DDL操作需要审批
# 使用工单系统
# 3. 备份策略
# /etc/crontab
# 每天全量备份
0 2 * * * mysqldump -u root -p --all-databases > /backup/mysql/all_$(date +\%F).sql
# 每小时增量备份
0 * * * * mysqladmin flush-logs
# 4. 备份验证
# 每周恢复测试
# 验证备份可用性
# 5. 审计日志
# /etc/my.cnf
[mysqld]
audit_log=FORCE_LOG_PERMANENT
audit_log_events=CONNECT,QUERY,TABLE_ACCESS
# 6. 高危命令保护
# 使用SQL审核工具
# 如:Yearning、Archery
|
经验总结:
- 生产环境权限必须最小化
- 高危操作需要双人复核
- 备份必须定期验证恢复
- binlog是最后一道防线
- 审计日志必须开启
事故19:Redis缓存击穿事故#
事故现象:
- 某个热点商品页面响应极慢
- 数据库CPU 100%
- 应用大量超时
- 只有特定接口受影响
事故原因:
- 热点key(如秒杀商品)突然过期
- 大量请求同时访问该key
- 请求穿透到数据库
- 数据库无法承受瞬时压力
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 查看Redis状态
redis-cli INFO
redis-cli INFO stats | grep -i "hit\|miss"
# 2. 查看热点key
redis-cli --hotkeys # Redis 4.0+
# 3. 查看慢查询
redis-cli SLOWLOG GET 10
# 4. 查看数据库状态
mysql -e "SHOW PROCESSLIST;" | head -20
mysql -e "SHOW STATUS LIKE 'Threads_connected';"
# 5. 查看应用日志
grep -i "timeout\|slow" /var/log/app/*.log | tail -50
# 6. 监控缓存命中率
# Prometheus Redis Exporter
redis_keyspace_hits / (redis_keyspace_hits + redis_keyspace_misses)
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时查看Redis命令
redis-cli MONITOR | head -50
# 查看大key
redis-cli --bigkeys
# 查看内存使用
redis-cli INFO memory | grep -i "used_memory"
# 查看连接数
redis-cli CLIENT LIST | wc -l
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
| # 1. 互斥锁方案(代码修复)
import redis
import time
def get_product(product_id):
key = f"product:{product_id}"
data = redis_client.get(key)
if data is None:
# 尝试获取锁
lock_key = f"lock:{key}"
if redis_client.set(lock_key, "1", nx=True, ex=10):
try:
# 双重检查
data = redis_client.get(key)
if data is None:
# 从数据库查询
data = db.query_product(product_id)
redis_client.setex(key, 300, data)
finally:
redis_client.delete(lock_key)
else:
# 等待后重试
time.sleep(0.1)
return get_product(product_id)
return data
# 2. 逻辑过期方案
# 不设置物理过期,在value中存储过期时间
def get_product_with_logical_expire(product_id):
key = f"product:{product_id}"
data = redis_client.get(key)
if data:
data_dict = json.loads(data)
if time.time() < data_dict['expire_time']:
return data_dict['data']
else:
# 异步更新
asyncio.create_task(refresh_cache(product_id))
return data_dict['data']
# 缓存未命中,重建
return rebuild_cache(product_id)
# 3. 永不过期 + 异步更新
# 热点数据永不过期,后台定时更新
# 4. 限流保护
from flask_limiter import Limiter
limiter = Limiter(app, key_func=get_remote_address)
@app.route('/product/<id>')
@limiter.limit("100/minute")
def get_product(id):
...
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 热点key识别
# 定期分析Redis访问日志
# 识别访问频率最高的key
# 2. 缓存策略
# 热点数据永不过期
# 设置随机过期时间
expire_time = base_expire + random.randint(0, 300)
# 3. 多级缓存
# 本地缓存 + Redis + 数据库
from cachetools import TTLCache
local_cache = TTLCache(maxsize=1000, ttl=60)
# 4. 监控告警
# Prometheus告警规则
- alert: RedisHitRateLow
expr: redis_keyspace_hits / (redis_keyspace_hits + redis_keyspace_misses) < 0.8
for: 5m
labels:
severity: warning
- alert: DatabaseConnectionsHigh
expr: mysql_threads_connected / mysql_max_connections > 0.8
for: 2m
labels:
severity: critical
# 5. 压测验证
# 模拟热点key过期场景
# 验证系统承受能力
|
经验总结:
- 热点key需要特殊保护
- 互斥锁是防止击穿的有效手段
- 缓存命中率必须监控
- 数据库要有熔断保护
- 定期识别和治理热点key
事故20:API限流失效事故#
事故现象:
- 接口被恶意刷取
- 系统资源耗尽
- 正常用户无法访问
- 产生大量异常费用(如短信、云资源)
事故原因:
- 限流配置未生效
- 限流算法实现错误
- 分布式环境限流不同步
- 配置变更未验证
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 查看API访问日志
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 发现单个IP请求量异常
# 2. 查看限流配置
cat /etc/nginx/nginx.conf | grep -A 10 "limit_req"
# 3. 查看应用限流配置
grep -r "rate.limit" /app/config/
# 4. 检查限流中间件状态
# Redis限流计数器
redis-cli GET "rate_limit:api:login:192.168.1.100"
# 5. 查看被限流的请求数
grep "429" access.log | wc -l
# 输出:0(说明限流未生效)
# 6. 检查配置变更历史
git log config/ --since="2024-01-14"
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时查看API请求分布
tail -f access.log | awk '{print $1}' | sort | uniq -c | sort -rn
# 查看限流日志
grep -i "rate.limit\|throttle" /var/log/app/*.log
# 检查Redis限流key
redis-cli KEYS "rate_limit:*" | head -20
# 查看Nginx限流状态
nginx -T | grep -A 5 "limit_req_zone"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
| # 1. Nginx限流(立即生效)
# /etc/nginx/nginx.conf
http {
# 定义限流区域
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req_zone $server_name zone=global:10m rate=1000r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
# 超过限制返回友好提示
error_page 429 /429.html;
}
location /api/login {
# 登录接口更严格
limit_req zone=api burst=5 nodelay;
}
}
}
nginx -t && systemctl reload nginx
# 2. 应用层限流(Java + Redis)
@Aspect
@Component
public class RateLimitAspect {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Around("@annotation(RateLimit)")
public Object around(ProceedingJoinPoint joinPoint, RateLimit rateLimit) {
String key = "rate_limit:" + rateLimit.api() + ":" + getClientIP();
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
redisTemplate.expire(key, rateLimit.time(), TimeUnit.SECONDS);
}
if (count > rateLimit.maxRequests()) {
throw new RateLimitException("请求过于频繁");
}
return joinPoint.proceed();
}
}
# 3. 分布式限流(Redis + Lua)
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local expire = tonumber(ARGV[2])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, expire)
end
if current > limit then
return 0
else
return 1
end
# 4. 紧急封禁恶意IP
# iptables封禁
iptables -A INPUT -s 恶意IP -j DROP
# Nginx封禁
echo "deny 恶意IP;" >> /etc/nginx/conf.d/block.conf
nginx -s reload
# 5. 添加验证码
# 对频繁请求要求验证码
if (request_count > threshold) {
requireCaptcha = true;
}
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 1. 多层限流
# Nginx层 + 应用层 + 网关层
# 每层都有独立限流
# 2. 限流配置验证
# 变更后必须测试验证
# 自动化测试限流效果
# 3. 监控告警
# Prometheus告警
- alert: APIRateLimitTriggered
expr: rate(http_requests_total{status="429"}[5m]) > 100
for: 2m
labels:
severity: warning
annotations:
summary: "API限流触发频繁"
# 4. 动态限流
# 根据系统负载动态调整限流阈值
if (cpu_usage > 80%) {
rate_limit = rate_limit * 0.5;
}
# 5. 黑名单机制
# 自动识别并封禁恶意IP
# 接入WAF防护
|
经验总结:
- 限流必须多层防护
- 配置变更后必须验证
- 分布式限流需要原子操作
- 监控要覆盖429状态码
- 恶意IP要快速封禁
事故21:机房断电事故#
事故现象:
- 整个机房服务不可用
- 监控全部失联
- 用户无法访问任何服务
- 硬件设备关机
事故原因:
- 市电供应中断
- UPS电池老化失效
- 发电机启动失败
- 备用电源切换故障
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 确认断电范围
# 联系机房运维确认
# 查看监控最后上报时间
# 2. 检查UPS状态
# 通过带外管理查看
ipmitool -H BMC_IP -U admin -P password power status
# 3. 检查服务器状态
# 通过IPMI查看
ipmitool -H BMC_IP sensor list | grep -i "power\|voltage"
# 4. 确认恢复时间
# 联系电力公司
# 评估发电机燃料
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 启动应急预案
# 通知相关人员
# 更新状态页面
# 2. 等待电力恢复
# 监控UPS剩余时间
# 准备有序关机
# 3. 电力恢复后
# 按顺序启动设备
# 1. 网络设备(交换机、路由器)
# 2. 存储设备
# 3. 数据库服务器
# 4. 应用服务器
# 4. 验证服务
for service in db cache app web; do
systemctl status $service
curl -f http://localhost/$service/health
done
# 5. 数据一致性检查
# 数据库主从状态
# 文件系统检查
fsck /dev/sda1
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 双路市电
# 接入不同变电站
# 2. UPS定期维护
# 每季度检查电池
# 每年更换老化电池
# 3. 发电机保养
# 每月试运行
# 保证燃料充足
# 4. 多机房部署
# 异地灾备
# 流量自动切换
# 5. 监控告警
# UPS电量监控
# 电力质量监控
|
经验总结:
- 电力是基础设施中的基础设施
- UPS必须定期维护
- 多机房部署是最终保障
- 应急预案必须定期演练
- 带外管理通道必须独立
事故22:网络交换机故障事故#
事故现象:
- 部分服务器网络中断
- 网络延迟飙升
- 丢包率超过50%
- 跨机房通信失败
事故原因:
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 测试网络连通性
ping 网关IP
ping 同网段其他服务器
# 2. 查看网络接口
ip addr show
ethtool eth0
# 3. 查看路由
ip route show
traceroute 目标IP
# 4. 登录交换机
ssh admin@switch-ip
show interface status
show mac address-table
show spanning-tree
# 5. 查看交换机日志
show logging
show interface eth1/1 errors
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 切换备用链路
# 修改路由
ip route change default via 备用网关
# 2. 重启交换机端口
# 交换机命令
configure terminal
interface eth1/1
shutdown
no shutdown
# 3. 更换故障交换机
# 启用备用设备
# 恢复配置
# 4. 验证网络
ping -c 100 目标IP | grep "packet loss"
mtr 目标IP
# 5. 通知业务方
# 确认服务恢复
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 交换机冗余
# 堆叠或VRRP
# 双上行链路
# 2. STP配置
# 启用生成树协议
# 防止环路
# 3. 定期巡检
# 检查端口错误计数
# 检查CPU/内存使用
# 4. 固件升级
# 定期升级到稳定版本
# 关注厂商安全公告
# 5. 监控告警
# 端口状态监控
# 流量异常告警
|
经验总结:
- 网络设备必须有冗余
- 配置变更需要审批
- 定期巡检很重要
- 备用设备要定期测试
- 厂商支持合同要有效
事故 23:负载均衡器宕机事故#
事故现象:
- 所有用户请求返回 502/503 错误
- 后端服务正常但无法访问
- 监控显示 LB 健康检查全部失败
- 业务完全中断 30 分钟
事故原因:
- 负载均衡器硬件故障
- 主备切换机制失效
- 配置同步延迟
- 健康检查阈值设置过严
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 1. 确认 LB 状态
curl -I http://lb-vip/health
# 返回:Connection refused
# 2. 检查 LB 节点
ssh lb-master
systemctl status haproxy
systemctl status keepalived
# 3. 查看 VIP 漂移状态
ip addr show | grep "inet.*vip"
# 4. 检查后端服务
for backend in backend1 backend2 backend3; do
curl -f http://$backend/health && echo "$backend OK" || echo "$backend FAIL"
done
# 5. 查看 LB 日志
tail -f /var/log/haproxy.log | grep -i "error\|down"
# 6. 检查 keepalived 状态
systemctl status keepalived
journalctl -u keepalived -n 50
# 7. 查看 VRRP 状态
ip addr show | grep -i "vrrp\|master\|backup"
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 查看 HAProxy 状态
echo "show stat" | socat stdio /var/run/haproxy.sock
# 查看后端服务器状态
echo "show servers state" | socat stdio /var/run/haproxy.sock
# 检查连接数
echo "show info" | socat stdio /var/run/haproxy.sock | grep -i "conn\|curr"
# 测试后端直连
curl -H "Host: www.example.com" http://backend1:80/
# 查看网络接口
ip -s link show eth0
ethtool -S eth0 | grep -i "drop\|error"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 紧急切换备用 LB
# 在备用节点强制抢占 VIP
systemctl stop keepalived
echo "2" > /proc/sys/net/ipv4/conf/all/arp_ignore
echo "1" > /proc/sys/net/ipv4/conf/all/arp_announce
systemctl start keepalived
# 2. 验证 VIP 漂移
ip addr show | grep "inet.*vip"
ping -c 3 lb-vip
# 3. 重启 HAProxy
systemctl restart haproxy
haproxy -c -f /etc/haproxy/haproxy.cfg # 先验证配置
# 4. 检查后端状态
echo "show servers state" | socat stdio /var/run/haproxy.sock
# 5. 临时直连后端(应急)
# 修改 DNS 或 hosts 绕过 LB
echo "后端 IP www.example.com" >> /etc/hosts
# 6. 修复主 LB 后恢复
# 等待主 LB 恢复后重新加入集群
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| # 1. 双活 LB 架构
# 至少 2 台 LB,使用 keepalived 实现 VIP 漂移
# 2. 健康检查优化
# /etc/haproxy/haproxy.cfg
backend app_servers
balance roundrobin
option httpchk GET /health
default-server inter 5s fall 3 rise 2 # 调整检查频率和阈值
server backend1 192.168.1.10:80 check
server backend2 192.168.1.11:80 check
server backend3 192.168.1.12:80 check
# 3. 监控告警
# Prometheus HAProxy Exporter
- alert: HAProxyBackendDown
expr: haproxy_backend_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "HAProxy 后端服务器宕机"
- alert: HAProxyFrontendDown
expr: haproxy_frontend_up == 0
for: 1m
labels:
severity: critical
annotations:
summary: "HAProxy 前端服务宕机"
# 4. 定期演练
# 每月进行 LB 故障切换演练
# 验证 VIP 漂移时间<10 秒
# 5. 多层 LB
# DNS LB + 硬件 LB + 软件 LB
# 逐层降级保护
|
经验总结:
- LB 是单点故障高发区,必须冗余
- 健康检查阈值要合理,避免误判
- VIP 漂移时间要监控(目标<10 秒)
- 定期故障切换演练必不可少
- 准备直连后端的应急方案
事故 24:防火墙规则错误事故#
事故现象:
- 部分服务突然无法访问
- 跨网段通信中断
- 监控显示连接被拒绝
- 应用报"Connection refused"
事故原因:
- 防火墙规则更新错误
- 新规则覆盖了原有允许规则
- 规则顺序错误
- 未测试直接应用到生产
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 测试连通性
telnet 目标 IP 端口
nc -zv 目标 IP 端口
# 2. 查看防火墙状态
systemctl status firewalld
systemctl status iptables
# 3. 查看当前规则
iptables -L -n -v
firewall-cmd --list-all
# 4. 查看规则历史记录
# CentOS/RHEL
cat /etc/sysconfig/iptables
# Ubuntu/Debian
cat /etc/iptables/rules.v4
# 5. 测试规则匹配
iptables -L -n -v | grep -i "目标端口"
# 6. 查看被拒绝的连接
dmesg | grep -i "iptables\|firewall"
journalctl -k | grep -i "dropped"
# 7. 临时关闭防火墙测试(谨慎)
systemctl stop firewalld
# 测试连通性
# 立即恢复
systemctl start firewalld
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 实时查看防火墙日志
tail -f /var/log/messages | grep -i "firewall\|iptables"
# 查看规则匹配计数
iptables -L -n -v --line-numbers
# 测试特定规则
iptables -C INPUT -p tcp --dport 8080 -j ACCEPT # 检查规则是否存在
# 查看 NAT 规则
iptables -t nat -L -n -v
# 查看连接追踪
conntrack -L | head -20
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 1. 紧急恢复(备份规则)
# 如果有备份
iptables-restore < /backup/iptables.rules.$(date -d "1 hour ago" +%Y%m%d)
# 2. 删除错误规则
iptables -L -n --line-numbers
iptables -D INPUT 5 # 删除第 5 条规则
# 3. 添加正确规则
iptables -A INPUT -p tcp --dport 8080 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
# 4. firewalld 修复
firewall-cmd --permanent --add-port=8080/tcp
firewall-cmd --permanent --add-port=443/tcp
firewall-cmd --reload
# 5. 验证规则
iptables -L -n -v | grep -E "8080|443"
nc -zv localhost 8080
# 6. 保存规则
# CentOS/RHEL
service iptables save
# Ubuntu/Debian
iptables-save > /etc/iptables/rules.v4
# 7. 通知业务方验证
curl -f http://service:8080/health
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 1. 规则变更流程
# - 变更申请
# - 测试环境验证
# - 审批
# - 备份当前规则
# - 应用变更
# - 验证
# - 回滚预案
# 2. 规则备份自动化
# /etc/crontab
0 2 * * * /sbin/iptables-save > /backup/iptables/iptables.$(date +\%Y\%m\%d).rules
# 3. 规则管理工具
# 使用 Ansible/Puppet 管理防火墙规则
# 版本控制所有变更
# 4. 监控告警
# 监控防火墙规则变更
auditctl -w /etc/sysconfig/iptables -p wa -k iptables_change
# 5. 默认策略
# 确保默认策略不会阻断所有流量
iptables -P INPUT ACCEPT # 测试期间
# 生产环境
iptables -P INPUT DROP # 但必须有允许规则
|
经验总结:
- 防火墙变更必须备份当前规则
- 测试环境验证后再应用到生产
- 规则顺序很重要,从上到下匹配
- 监控规则变更审计日志
- 准备快速回滚脚本
事故 25:DNS 劫持事故#
事故现象:
- 用户访问域名被跳转到恶意网站
- 部分用户无法访问正常服务
- DNS 解析结果不一致
- 安全团队收到钓鱼报告
事故原因:
- 域名注册商账号被盗
- DNS 记录被恶意修改
- 本地 DNS 服务器被入侵
- 中间人攻击
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 1. 验证 DNS 解析
nslookup www.example.com
dig www.example.com
host www.example.com
# 2. 从多个 DNS 服务器测试
dig @8.8.8.8 www.example.com
dig @114.114.114.114 www.example.com
dig @ns1.example.com www.example.com
# 3. 检查 DNS 记录
dig www.example.com ANY
dig example.com SOA
dig example.com NS
# 4. 查看本地 DNS 配置
cat /etc/resolv.conf
nmcli dev show | grep DNS
# 5. 检查 hosts 文件
cat /etc/hosts | grep -v "^#"
# 6. 追踪 DNS 解析路径
dig +trace www.example.com
# 7. 检查域名注册商
# 登录域名管理后台
# 查看 DNS 记录变更历史
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 批量测试多个 DNS 服务器
for dns in 8.8.8.8 1.1.1.1 114.114.114.114 223.5.5.5; do
echo "=== $dns ==="
dig @$dns www.example.com +short
done
# 检查 DNS 缓存
systemctl restart nscd # 清除缓存
systemctl restart systemd-resolved
# 查看 DNS 查询日志
journalctl -u systemd-resolved -f
# 检测 DNS 劫持
curl -s http://www.example.com | grep -i "title"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 1. 紧急修改 DNS 记录
# 登录域名注册商后台
# 恢复正确的 A 记录
# www.example.com -> 正确 IP
# 2. 降低 TTL 加速传播
# 将 TTL 从 24 小时改为 5 分钟
# 等待全球 DNS 缓存刷新
# 3. 通知用户
# 发送安全公告
# 提醒用户清除本地 DNS 缓存
# 4. 加强域名安全
# 启用域名锁定(Domain Lock)
# 启用双因素认证
# 限制 DNS 修改权限
# 5. 部署 DNSSEC
# 为域名启用 DNSSEC 签名
# 防止 DNS 记录篡改
# 6. 多 DNS 提供商
# 使用至少 2 家 DNS 服务商
# 如:Cloudflare + AWS Route53
# 7. 用户端建议
# 推荐使用可信 DNS(8.8.8.8、1.1.1.1)
# 清除浏览器 DNS 缓存
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 域名安全
# - 启用注册商锁定
# - 开启双因素认证
# - 限制管理员账号
# - 定期更换密码
# 2. DNS 监控
# 监控 DNS 记录变更
# 使用 DNS 监控服务(如 DNSWatch、SpyEye)
# 3. DNSSEC 部署
# 为所有关键域名启用 DNSSEC
# 定期更新密钥
# 4. 多 DNS 冗余
# 主 DNS:Cloudflare
# 备 DNS:AWS Route53
# 自动故障切换
# 5. 用户教育
# 提醒用户注意钓鱼网站
# 提供官方 IP 备用访问方式
|
经验总结:
- 域名账号安全至关重要
- DNSSEC 可以有效防止篡改
- 多 DNS 提供商提高可用性
- 监控 DNS 记录变更
- 准备备用访问方式(IP 直连)
事故 26:BGP 路由泄露事故#
事故现象:
- 部分区域用户无法访问服务
- 网络路由异常
- 流量被错误路由到其他 AS
- 国际访问延迟飙升
事故原因:
- 运营商 BGP 配置错误
- AS 路径泄露
- 路由前缀宣告错误
- 恶意路由劫持
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 检查本地路由
ip route show
route -n
# 2. 追踪路由路径
traceroute 目标 IP
mtr 目标 IP
# 3. 查看 BGP 状态(如有 BGP 路由器)
show ip bgp summary
show ip bgp 前缀
# 4. 使用在线工具检查
# bgp.he.net
# bgpview.io
# 查看 AS 路径和前缀宣告
# 5. 检查路由表
netstat -rn
ip route show table all
# 6. 测试不同区域访问
# 使用多地探测服务
# 如:ping.chinaz.com、boce.com
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 持续监控路由变化
watch -n 10 'mtr -r -c 10 目标 IP'
# 查看路由缓存
ip route show cache
# 检查多路径路由
ip route show | grep -i "multipath"
# 查看 BGP 邻居(如有)
show ip bgp neighbors
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 联系运营商
# 报告路由异常
# 提供 AS 路径证据
# 要求修复 BGP 配置
# 2. 临时路由调整
# 修改本地路由
ip route add 目标网段 via 备用网关
# 3. 启用备用链路
# 切换到其他运营商线路
# 更新 DNS 解析
# 4. 使用 Anycast
# 多地域部署相同 IP
# 自动路由到最近节点
# 5. 监控路由变化
# 使用 BGP 监控服务
# 如:BGPMon、RIPE RIS
# 6. 通知用户
# 发布服务状态公告
# 提供临时访问方式
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. BGP 安全
# 启用 RPKI(资源公钥基础设施)
# 验证路由起源
# 2. 多运营商接入
# 至少 2 家运营商
# 自动故障切换
# 3. 路由监控
# 部署 BGP 监控
# 设置路由变更告警
# 4. Anycast 部署
# 关键服务使用 Anycast
# 提高路由韧性
# 5. 运营商沟通
# 建立运营商紧急联系渠道
# 定期沟通网络状况
|
经验总结:
- BGP 问题通常需要运营商配合
- 多运营商接入降低风险
- BGP 监控可以提前发现问题
- Anycast 可以提高路由韧性
- 准备备用访问方案
事故 27:带宽耗尽事故#
事故现象:
- 网络响应极慢
- 丢包率超过 30%
- 监控显示带宽使用率 100%
- 用户访问超时
事故原因:
- DDoS 攻击
- 异常流量(如爬虫、扫描)
- 大文件未限制下载
- 视频/图片未压缩
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 查看带宽使用
iftop -i eth0
nload eth0
vnstat -l
# 2. 查看流量来源
tcpdump -i eth0 -nn -c 100
ntopng -i eth0
# 3. 查看连接分布
netstat -an | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn | head -20
# 4. 查看进程网络使用
nethogs eth0
# 5. 查看防火墙连接
conntrack -L | wc -l
# 6. 检查异常流量
tcpdump -i eth0 -w capture.pcap
# 用 Wireshark 分析
# 7. 查看 Web 访问日志
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -20
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时监控带宽
watch -n 1 'cat /proc/net/dev | grep eth0'
# 查看各协议流量
ip -s link show eth0
# 查看 TCP 连接状态
ss -s
# 查看网络错误
netstat -i
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| # 1. 紧急限流
# iptables 限流
iptables -A INPUT -p tcp --dport 80 -m limit --limit 100/s -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j DROP
# 2. 封禁异常 IP
# 自动封禁高频访问 IP
for ip in $(awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10 | awk '{print $2}'); do
iptables -A INPUT -s $ip -j DROP
done
# 3. Nginx 限流
# /etc/nginx/nginx.conf
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
server {
location / {
limit_req zone=one burst=20;
limit_conn conn_limit 10;
}
}
# 4. 启用 CDN
# 将静态资源迁移到 CDN
# 减少源站带宽压力
# 5. 联系运营商
# 申请临时带宽扩容
# 启用流量清洗服务
# 6. 优化资源
# 压缩图片和视频
# 启用 Gzip 压缩
# 配置浏览器缓存
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 带宽监控
# Prometheus + Node Exporter
- alert: BandwidthUsageHigh
expr: rate(node_network_receive_bytes_total[5m]) / 1024 / 1024 > 100 # 100MB/s
for: 5m
labels:
severity: warning
# 2. DDoS 防护
# 接入云厂商 DDoS 防护
# 如:阿里云 DDoS 高防、腾讯云大禹
# 3. CDN 部署
# 静态资源全部走 CDN
# 减少源站压力
# 4. 限流策略
# 多层限流(Nginx + 应用 + 网关)
# 按 IP、用户、API 限流
# 5. 容量规划
# 根据业务量预估带宽需求
# 预留 50% 余量
|
经验总结:
- 带宽耗尽通常是攻击或异常流量
- 快速识别并封禁异常 IP
- CDN 是降低带宽成本的有效手段
- 多层限流保护源站
- DDoS 防护服务必不可少
事故 28:SSL 加速卡故障事故#
事故现象:
- HTTPS 请求响应极慢
- SSL 握手失败率飙升
- CPU 使用率 100%
- 加密性能下降 90%
事故原因:
- SSL 加速卡硬件故障
- 驱动不兼容
- 证书配置错误
- 加密算法协商失败
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 检查 SSL 加速卡状态
lspci | grep -i "ssl\|crypto"
dmesg | grep -i "ssl\|crypto"
# 2. 查看驱动状态
lsmod | grep -i "ssl\|crypto"
modinfo 驱动名
# 3. 测试 SSL 性能
openssl speed rsa
openssl s_time -connect www.example.com:443
# 4. 检查证书
openssl s_client -connect www.example.com:443 </dev/null 2>/dev/null | \
openssl x509 -noout -dates
# 5. 查看 Nginx SSL 配置
nginx -T | grep -i "ssl"
# 6. 监控 SSL 错误
tail -f /var/log/nginx/error.log | grep -i "ssl\|handshake"
# 7. 测试不同加密套件
openssl s_client -connect www.example.com:443 -cipher 'AES256-SHA'
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 查看 SSL 会话统计
openssl s_client -connect www.example.com:443 -stats
# 测试 SSL 握手时间
time openssl s_client -connect www.example.com:443 </dev/null
# 查看硬件加速状态
cat /proc/crypto | grep -i "module\|type"
# 监控 CPU 使用
top -H -p $(pgrep -f nginx)
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 1. 临时禁用硬件加速
# Nginx 配置
# ssl_session_cache off;
# ssl_engine off;
# 2. 重启 SSL 服务
systemctl restart nginx
systemctl restart haproxy
# 3. 更新驱动
# 下载最新驱动
# 重新编译内核模块
# 4. 优化 SSL 配置
# /etc/nginx/nginx.conf
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# 5. 启用 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
# 6. 替换故障硬件
# 联系厂商更换 SSL 加速卡
# 或改用软件 SSL
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 硬件监控
# 监控 SSL 加速卡状态
# 设置故障告警
# 2. 软件降级方案
# 准备纯软件 SSL 配置
# 硬件故障时快速切换
# 3. 定期测试
# 每月测试 SSL 性能
# 验证硬件加速效果
# 4. 驱动管理
# 保持驱动最新版本
# 关注厂商安全公告
# 5. 证书管理
# 自动化证书续期
# 监控证书到期
|
经验总结:
- SSL 加速卡是性能瓶颈点
- 必须有软件降级方案
- 定期测试 SSL 性能
- 证书管理要自动化
- 关注驱动兼容性
事故 29:时间同步异常事故#
事故现象:
- 分布式系统数据不一致
- 日志时间混乱
- 证书验证失败
- 定时任务执行异常
事故原因:
- NTP 服务器不可达
- 时间漂移过大
- 时区配置错误
- 虚拟机时间不同步
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 1. 检查当前时间
date
timedatectl
# 2. 检查 NTP 状态
systemctl status chronyd
systemctl status ntpd
# 3. 查看时间同步状态
chronyc tracking
ntpq -p
# 4. 检查时间偏移
chronyc sources -v
ntpdate -q pool.ntp.org
# 5. 查看系统日志
grep -i "time\|ntp\|clock" /var/log/messages
# 6. 检查时区
timedatectl | grep "Time zone"
ls -l /etc/localtime
# 7. 对比多台服务器时间
for host in server1 server2 server3; do
ssh $host "date"
done
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 实时查看时间偏移
watch -n 5 'chronyc tracking | grep "System time"'
# 手动同步时间
chronyc -a makestep
ntpdate pool.ntp.org
# 查看时间同步历史
chronyc sourcestats
# 检查硬件时钟
hwclock --show
hwclock --systohc
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 紧急时间同步
chronyc -a makestep
# 或
ntpdate -s pool.ntp.org
# 2. 重启时间服务
systemctl restart chronyd
# 或
systemctl restart ntpd
# 3. 配置 NTP 服务器
# /etc/chrony.conf
server ntp.aliyun.com iburst
server ntp.tencent.com iburst
server pool.ntp.org iburst
# 4. 配置时区
timedatectl set-timezone Asia/Shanghai
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
# 5. 虚拟机时间同步
# VMware Tools / VirtualBox Guest Additions
# 启用主机时间同步
# 6. 验证同步
chronyc tracking
date
# 7. 通知业务方
# 确认服务恢复正常
# 检查日志时间
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 多 NTP 源
# 配置至少 3 个 NTP 服务器
# 使用不同提供商
# 2. 监控告警
# Prometheus NTP Exporter
- alert: NtpOffsetHigh
expr: abs(ntp_offset_seconds) > 0.1 # 100ms
for: 5m
labels:
severity: warning
# 3. 定期巡检
# 每周检查时间同步状态
chronyc sources -v
# 4. 虚拟机配置
# 启用主机时间同步
# 安装 Guest Tools
# 5. 应用层容错
# 业务逻辑不依赖精确时间
# 使用逻辑时钟
|
事故 30:硬件磁盘损坏事故#
事故现象:
事故原因:
- 磁盘物理损坏
- RAID 阵列降级
- 文件系统损坏
- 未及时更换故障盘
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 1. 检查磁盘状态
smartctl -a /dev/sda
smartctl -H /dev/sda
# 2. 查看 RAID 状态
cat /proc/mdstat
mdadm --detail /dev/md0
# 3. 检查文件系统
df -h
mount | grep -i "ro\|read-only"
# 4. 查看系统日志
dmesg | grep -i "error\|fail\|sd"
journalctl -k | grep -i "I/O error"
# 5. 检查磁盘健康
badblocks -sv /dev/sda
hdparm -t /dev/sda
# 6. 查看磁盘 SMART 信息
smartctl -A /dev/sda | grep -i "reallocated\|pending\|uncorrectable"
# 7. 检查 LVM 状态
pvdisplay
vgdisplay
lvdisplay
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时监控磁盘错误
watch -n 5 'dmesg | tail -20 | grep -i "error"'
# 查看磁盘 IO 统计
iostat -x 1
# 查看磁盘温度
smartctl -A /dev/sda | grep -i "temperature"
# 检查磁盘序列号
smartctl -i /dev/sda | grep "Serial Number"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 1. 紧急数据备份
# 优先备份关键数据
rsync -avz /重要数据 /备份位置/
# 2. RAID 重建
# 更换故障磁盘
mdadm /dev/md0 --add /dev/sdX
# 监控重建进度
watch cat /proc/mdstat
# 3. 文件系统修复
# 先卸载
umount /dev/sda1
# 修复
fsck -y /dev/sda1
# 重新挂载
mount /dev/sda1
# 4. 更换磁盘
# 热插拔更换(如支持)
# 或停机更换
# 5. 恢复数据
# 从备份恢复
# 或使用数据恢复工具
# 6. 验证服务
systemctl status 关键服务
curl -f http://localhost/health
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. RAID 配置
# 生产环境至少 RAID 1 或 RAID 5
# 关键数据 RAID 10
# 2. 磁盘监控
# Prometheus SMART Exporter
- alert: DiskSmartFailure
expr: smart_disk_failure == 1
for: 1m
labels:
severity: critical
# 3. 定期巡检
# 每周检查 SMART 状态
smartctl -H /dev/sd[a-z]
# 4. 备份策略
# 每天增量备份
# 每周全量备份
# 异地备份
# 5. 备件管理
# 保持适量备件库存
# 与供应商建立快速更换通道
|
经验总结:
- 磁盘损坏是常见硬件故障
- RAID 可以提供冗余保护
- SMART 监控可以提前预警
- 备份是最后防线
- 备件管理很重要
事故 31:内存泄漏事故#
事故现象:
- 服务运行一段时间后变慢
- 内存使用率持续增长
- OOM Killer 频繁触发
- 服务反复重启
事故原因:
- 代码内存泄漏
- 缓存未设置上限
- 连接未正确关闭
- 第三方库内存问题
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 查看内存使用
free -h
cat /proc/meminfo | head -20
# 2. 查看进程内存
ps aux --sort=-%mem | head -20
# 3. 查看内存趋势
vmstat 1 10
sar -r 1 10
# 4. 检查 OOM 日志
dmesg | grep -i "oom\|killed"
grep -i "oom" /var/log/messages
# 5. Java 应用内存分析
jmap -heap <PID>
jstat -gc <PID> 1000 10
# 6. 查看内存映射
pmap -x <PID> | tail -20
# 7. 检查 Swap 使用
swapon -s
vmstat 1 5 | grep -E "swpd|si|so"
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时监控内存
watch -n 5 'free -h'
# 查看内存详细分布
cat /proc/meminfo
# 查看进程内存详细
cat /proc/<PID>/status | grep -i "vm"
# 内存泄漏检测工具
valgrind --leak-check=full ./program
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| # 1. 紧急重启服务
systemctl restart 服务名
# 2. 临时增加 Swap
dd if=/dev/zero of=/swapfile bs=1G count=4
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
# 3. 调整 OOM 优先级
echo -500 > /proc/<PID>/oom_score_adj
# 4. 限制内存使用
# systemd 服务限制
# /etc/systemd/system/服务.service
[Service]
MemoryLimit=2G
# 5. 代码修复
# Java:分析 Heap Dump
jmap -dump:format=b,file=heap.hprof <PID>
# 用 MAT 分析
# Python:使用 tracemalloc
import tracemalloc
tracemalloc.start()
# ... 代码 ...
snapshot = tracemalloc.take_snapshot()
for stat in snapshot.statistics('lineno')[:10]:
print(stat)
# 6. 配置缓存上限
# Redis:maxmemory 2gb
# 应用:设置缓存大小限制
# 7. 监控告警
# 设置内存使用率告警(>80%)
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 代码审查
# 重点关注内存分配和释放
# 使用静态分析工具
# 2. 内存监控
# Prometheus + Node Exporter
- alert: MemoryUsageHigh
expr: (node_memory_MemTotal - node_memory_MemAvailable) / node_memory_MemTotal > 0.8
for: 5m
labels:
severity: warning
# 3. 定期重启
# 对于无法彻底修复的泄漏
# 设置定时重启(如每天凌晨)
# 4. 压力测试
# 上线前进行内存压力测试
# 验证长时间运行稳定性
# 5. 资源限制
# 容器内存限制
# systemd 内存限制
|
经验总结:
- 内存泄漏需要代码层面修复
- 监控可以提前发现问题
- 临时措施可以争取修复时间
- 压力测试很重要
- 资源限制防止影响其他服务
事故 32:线程池耗尽事故#
事故现象:
- 请求排队等待
- 响应时间飙升
- 应用报"Thread pool exhausted"
- 新请求被拒绝
事故原因:
- 并发请求超过线程池容量
- 线程阻塞未释放
- 线程池配置过小
- 外部依赖响应慢
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 查看应用日志
grep -i "thread\|pool\|exhausted" /var/log/app/*.log
# 2. Java 线程分析
jps -l
jstack <PID> | head -100
# 3. 查看线程状态
jstack <PID> | grep -E "RUNNABLE|BLOCKED|WAITING" | sort | uniq -c
# 4. 查看线程池状态
# 应用暴露的监控端点
curl http://localhost:8080/actuator/metrics/thread.pool.size
# 5. 系统线程查看
ps -eLo pid,tid,class,rtprio,ni,pri,psr,pcpu,stat,wchan:14,comm | grep 进程名
# 6. 查看连接等待
netstat -an | grep ESTABLISHED | wc -l
# 7. 检查外部依赖
curl -w "%{time_total}\n" -o /dev/null -s http://依赖服务/health
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时查看线程数
watch -n 5 'ps -eLo pid,comm,nlwp | grep 进程名'
# 查看线程堆栈
jstack <PID> > thread_dump.txt
# 分析阻塞线程
jstack <PID> | grep -A 30 "BLOCKED"
# 查看 CPU 等待
top -H -p <PID>
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
| # 1. 紧急扩容线程池
# 应用配置调整
# application.yml
spring:
task:
execution:
pool:
core-size: 20
max-size: 100 # 从 50 增加到 100
queue-capacity: 500
# 2. 重启服务
systemctl restart 服务名
# 3. 优化外部调用
# 增加超时时间
# 添加熔断机制
@HystrixCommand(fallbackMethod = "fallback")
public Response callExternal() {
return externalService.call();
}
# 4. 异步处理
# 将同步调用改为异步
CompletableFuture<Response> future =
CompletableFuture.supplyAsync(() -> externalService.call());
# 5. 限流保护
# 限制并发请求数
Semaphore semaphore = new Semaphore(50);
semaphore.acquire();
try {
// 处理请求
} finally {
semaphore.release();
}
# 6. 监控告警
# 线程池使用率监控
- alert: ThreadPoolUsageHigh
expr: thread_pool_active / thread_pool_max > 0.8
for: 5m
labels:
severity: warning
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 合理配置线程池
# 根据业务量评估
# 公式:线程数 = CPU 核心数 * (1 + 等待时间/计算时间)
# 2. 超时设置
# 所有外部调用设置超时
# 避免线程长期阻塞
# 3. 熔断降级
# 使用 Hystrix/Resilience4j
# 外部服务故障时快速失败
# 4. 监控告警
# 线程池使用率监控
# 队列长度监控
# 5. 压力测试
# 模拟高并发验证线程池配置
|
经验总结:
- 线程池配置要合理
- 外部调用必须设置超时
- 熔断机制很重要
- 监控线程池状态
- 压力测试验证配置
事故 33:死锁事故#
事故现象:
- 部分请求永久阻塞
- 数据库事务超时
- 应用无响应
- CPU 使用率低但服务不可用
事故原因:
- 代码锁顺序不一致
- 数据库行锁冲突
- 资源竞争
- 嵌套锁导致循环等待
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| # 1. Java 死锁检测
jps -l
jstack <PID> | grep -A 50 "deadlock"
# 2. 数据库锁检测
# MySQL
SELECT * FROM information_schema.innodb_lock_waits;
SELECT * FROM performance_schema.data_locks;
# 3. 查看阻塞会话
SELECT blocking_locks.lock_id, blocking_trx.trx_query
FROM performance_schema.data_lock_waits
JOIN performance_schema.data_locks blocking_locks
ON data_lock_waits.blocking_engine_lock_id = blocking_locks.engine_lock_id
JOIN performance_schema.innodb_trx blocking_trx
ON blocking_locks.engine_transaction_id = blocking_trx.trx_id;
# 4. 应用日志
grep -i "deadlock\|lock\|timeout" /var/log/app/*.log
# 5. 线程 dump 分析
jstack <PID> > thread_dump.txt
# 用线程分析工具查看
# 6. 数据库进程
SHOW PROCESSLIST;
|
使用命令:
1
2
3
4
5
6
7
8
| # 持续监控死锁
watch -n 10 'jstack <PID> | grep -i "deadlock"'
# 查看锁等待
jstack <PID> | grep -B 5 "waiting to lock"
# 数据库锁监控
mysql -e "SHOW ENGINE INNODB STATUS\G" | grep -A 20 "LATEST DETECTED DEADLOCK"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| # 1. 紧急终止死锁进程
# Java:重启应用
systemctl restart 服务名
# 数据库:终止阻塞会话
KILL 进程 ID;
# 2. 代码修复
# 统一锁获取顺序
// 错误:不同顺序获取锁
synchronized(lockA) { synchronized(lockB) { } }
synchronized(lockB) { synchronized(lockA) { } }
// 正确:统一顺序
synchronized(lockA) { synchronized(lockB) { } }
synchronized(lockA) { synchronized(lockB) { } }
# 3. 数据库优化
# 避免长事务
# 合理设计索引
# 使用行锁而非表锁
# 4. 超时设置
# 设置锁超时
SET innodb_lock_wait_timeout = 50;
# 5. 死锁检测
# 启用死锁检测
SET innodb_deadlock_detect = ON;
# 6. 监控告警
# 死锁次数监控
- alert: DeadlockDetected
expr: rate(mysql_innodb_deadlocks[5m]) > 0
for: 1m
labels:
severity: warning
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 代码规范
# 统一锁获取顺序
# 避免嵌套锁
# 使用高级并发工具(如 ConcurrentHashMap)
# 2. 数据库设计
# 合理设计索引
# 避免大事务
# 使用合适的隔离级别
# 3. 超时机制
# 设置锁超时
# 设置事务超时
# 4. 监控告警
# 死锁次数监控
# 锁等待时间监控
# 5. 定期分析
# 分析死锁日志
# 优化热点数据访问
|
经验总结:
- 死锁需要代码层面修复
- 统一锁顺序是关键
- 数据库死锁要分析事务
- 超时机制可以减轻影响
- 监控死锁发生频率
事故 34:序列化异常事故#
事故现象:
- 分布式调用失败
- 缓存读取异常
- 消息队列消费失败
- 应用报"ClassNotFoundException"
事故原因:
- 类版本不一致
- serialVersionUID 变化
- 依赖库版本冲突
- 跨语言序列化问题
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 查看应用日志
grep -i "serial\|class\|deserialize" /var/log/app/*.log
# 2. 检查类版本
javap -serial 类名.class
# 3. 查看依赖树
mvn dependency:tree
gradle dependencies
# 4. 检查序列化数据
# Redis 中查看
redis-cli GET key
# 分析序列化格式
# 5. 对比服务版本
# 各微服务版本是否一致
curl http://service/actuator/info
# 6. 检查消息队列
# 查看消息内容
kafka-console-consumer.sh --topic topic --from-beginning
|
使用命令:
1
2
3
4
5
6
7
8
| # 查看类信息
javap -v 类名.class | grep -i "serial"
# 查看 JAR 包版本
jar tf 包名.jar | grep -i "manifest"
# 检查运行时类
jmap -clstats <PID>
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 紧急回滚
# 回滚到兼容版本
kubectl rollout undo deployment/app
# 2. 清理缓存
# 删除不兼容的序列化数据
redis-cli DEL key
# 或清空缓存
redis-cli FLUSHDB
# 3. 统一版本
# 确保所有服务使用相同版本
# 更新依赖
# 4. 修复 serialVersionUID
// 保持兼容
private static final long serialVersionUID = 1L;
# 5. 使用兼容序列化
// 使用 JSON 而非 Java 原生序列化
// 使用 Protobuf/Avro 等 schema 驱动
# 6. 数据迁移
# 编写数据迁移脚本
# 转换旧格式到新格式
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 序列化规范
# 使用 JSON/Protobuf 等通用格式
# 避免 Java 原生序列化
# 2. 版本管理
# 微服务版本统一升级
# 使用 API 版本控制
# 3. 依赖管理
# 统一依赖版本
# 使用 BOM 管理
# 4. 兼容性测试
# 上线前测试序列化兼容
# 灰度发布验证
# 5. 缓存策略
# 缓存设置合理过期时间
# 避免长期存储序列化数据
|
经验总结:
- 避免使用 Java 原生序列化
- 微服务版本要统一管理
- 缓存数据要有过期策略
- 序列化格式要向前兼容
- 灰度发布可以发现兼容问题
事故 35:配置热更新失败事故#
事故现象:
- 配置变更后服务异常
- 部分节点配置不一致
- 配置回滚失败
- 服务间歇性失败
事故原因:
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 检查配置中心状态
curl http://config-server/actuator/health
# 2. 查看配置推送日志
grep -i "config\|refresh" /var/log/app/*.log
# 3. 对比各节点配置
for node in node1 node2 node3; do
echo "=== $node ==="
curl http://$node:8080/actuator/configprops
done
# 4. 检查配置内容
# 配置中心后台查看
# 对比变更前后差异
# 5. 查看应用日志
grep -i "failed\|error" /var/log/app/*.log | tail -50
# 6. 检查配置刷新
# Spring Cloud
curl -X POST http://localhost:8080/actuator/refresh
|
使用命令:
1
2
3
4
5
6
7
8
9
| # 查看当前配置
curl http://localhost:8080/actuator/env
# 查看配置历史
# 配置中心版本历史
git log config-repo
# 测试配置
curl -X POST http://localhost:8080/actuator/refresh
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 紧急回滚配置
# 配置中心回滚到上一版本
# 或手动修改配置
# 2. 重启服务
# 强制重新加载配置
systemctl restart 服务名
# 3. 修复配置
# 修正格式错误
# 补充缺失配置
# 4. 分批推送
# 先推送部分节点
# 验证后再全量推送
# 5. 本地配置兜底
# 保留本地配置文件
# 配置中心故障时使用
# 6. 验证配置
curl http://localhost:8080/actuator/env | grep 配置项
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 配置校验
# 配置变更时自动校验格式
# 使用 JSON Schema 验证
# 2. 灰度推送
# 先推送 10% 节点
# 验证后再全量
# 3. 版本控制
# 配置存入 Git
# 变更有记录可追溯
# 4. 配置备份
# 定期备份配置
# 支持快速回滚
# 5. 监控告警
# 配置推送失败告警
# 配置不一致告警
|
经验总结:
- 配置变更要有校验
- 灰度推送降低风险
- 配置版本要可追溯
- 本地配置做兜底
- 监控配置推送状态
事故 36:分布式锁失效导致定时任务重复执行#
事故现象:
- 每日结算任务被执行了 3 次,导致用户利息多算
- 报表数据重复统计,总数翻倍
- 日志显示多个应用实例在同一时间进入了临界区代码
- Redis 中锁 Key 不存在或已过期
事故原因:
- 使用
SETNX 实现锁时未设置过期时间,死锁后无法释放 - 设置了过期时间,但业务执行时间 > 锁过期时间,锁自动释放,其他节点趁虚而入
- 主从切换导致锁丢失(Redis 异步复制,Master 挂掉时锁未同步到 Slave)
- 代码逻辑中未在
finally 块释放锁,异常发生时锁未释放
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 检查 Redis 锁状态
redis-cli GET lock:job:daily_settlement
# 返回 nil,说明锁已丢失
# 2. 查看应用日志
grep "Start processing job" app.log
# 发现同一时间点,不同 IP 的实例都打印了该日志
# 3. 分析 Redis 慢日志
redis-cli SLOWLOG GET 10
# 检查是否有耗时操作导致锁获取延迟
# 4. 检查 Redis 集群状态
redis-cli CLUSTER INFO
# 确认故障期间是否发生了主从切换 (cluster_known_nodes, cluster_slots_fail)
# 5. 代码审查
# 检查锁的实现逻辑,是否使用了看门狗(WatchDog)机制
|
使用命令:
1
2
3
4
5
| # 模拟锁竞争测试
# 脚本 A:持锁 5 秒
redis-cli SETNX lock:test 1 && redis-cli EXPIRE lock:test 5
# 脚本 B:1 秒后尝试获锁
sleep 1 && redis-cli SETNX lock:test 1
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| // 1. 引入看门狗机制(推荐 Redisson)
RLock lock = redisson.getLock("lock:job:daily_settlement");
// lock.lock() 默认开启看门狗,只要线程存活,锁会自动续期
lock.lock();
try {
// 业务逻辑(即使超过 30 秒也不会丢锁)
processSettlement();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
// 2. 使用 RedLock 算法(极端强一致场景)
// 向 N/2+1 个独立的 Redis 节点申请锁,提高可靠性
// 注意:性能较低,仅用于金融级核心数据
// 3. 数据库唯一索引兜底
// 即使锁失效,DB 唯一约束也能防止重复插入
ALTER TABLE settlement_log ADD UNIQUE INDEX uk_job_date (job_name, exec_date);
// 4. 幂等性设计
// 业务逻辑内部判断状态,若已处理则直接返回
if (alreadyProcessed(date)) return;
|
预防措施:
- 框架选型:严禁手写简单的
setnx 逻辑,统一使用 Redisson、Curator 等成熟库。 - 双重保障:分布式锁 + 数据库唯一键/状态机,构建双重防线。
- 监控告警:监控任务执行次数,若单日执行>1 次立即告警。
- 故障演练:模拟 Redis 主从切换,验证锁是否丢失。
经验总结:
- 任何分布式锁都不是 100% 可靠的,必须有业务层幂等兜底。
- 锁的过期时间必须大于业务最大执行时间,或使用自动续期。
- Redis 主从异步复制天生存在锁丢失风险,核心业务需评估是否改用 ZooKeeper/Etcd。
事故 37:消息队列消费者阻塞导致积压#
事故现象:
- 消息队列 Lag(积压量)从 0 飙升到 500 万+
- 实时订单状态更新延迟超过 2 小时
- 消费者进程存活,但日志不再滚动
- 磁盘空间告警(MQ Broker 存满)
事故原因:
- 消费者代码中出现死循环或长时间
Thread.sleep - 调用第三方接口超时(无超时配置),线程永久阻塞
- 某条“毒丸消息”(Poison Pill)导致反序列化失败,不断重试阻塞队列
- 数据库连接池耗尽,消费者等待 DB 连接
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 查看消费组状态
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group order-group
# 观察 LAG 列持续增加,CURRENT-OFFSET 停滞
# 2. 检查消费者线程堆栈
jps -l
jstack <consumer_pid> | grep -A 20 "RUNNABLE\|BLOCKED"
# 发现大量线程阻塞在 `SocketRead` 或 `Wait` 状态
# 3. 查看消费者日志
tail -f consumer.log | grep -i "error\|exception\|timeout"
# 发现大量 "DeserializationException" 或 "ConnectionTimeout"
# 4. 检查 DB 连接池
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
# 发现连接数已满
# 5. 定位毒丸消息
# 查看具体卡住的消息内容
kafka-console-consumer.sh --topic order-topic --partition 0 --offset <offset> --max-messages 1
|
使用命令:
1
2
3
| # 跳过特定消息(紧急止损)
# 将消费组 Offset 重置到下一条
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --reset-offsets --to-offset <next_offset> --execute --group order-group --topic order-topic
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 紧急扩容消费者
# 增加 Partition 数量(若允许重排序)
kafka-topics.sh --alter --topic order-topic --partitions 48
# 扩容消费者实例
kubectl scale deployment consumer --replicas=48
# 2. 修复代码缺陷
# 添加超时配置:socket.timeout.ms=5000
# 捕获异常并记录错误日志,避免死循环
try {
process(msg);
} catch (Exception e) {
log.error("Process failed", e);
// 发送死信队列,不阻塞主流程
sendToDLQ(msg);
ack.acknowledge();
}
# 3. 处理毒丸消息
# 将坏消息手动移入死信队列(DLQ)
# 跳过该 Offset,继续消费后续正常消息
# 4. 优化 DB 连接
# 扩容连接池,或优化慢 SQL 释放连接
|
预防措施:
- 超时控制:所有外部调用(DB、RPC、HTTP)必须设置严格超时。
- 死信队列:消费失败 N 次后自动转入 DLQ,人工介入处理,不阻塞主队列。
- 监控告警:监控 Lag 增长率,设定阈值(如 Lag > 10000 持续 5 分钟)。
- 批量处理:采用批量拉取、批量处理模式,提升吞吐量。
经验总结:
- 一条坏消息可以拖垮整个消费组,必须隔离处理。
- 消费者必须具备“快速失败”能力,不能无限等待。
- 扩容 Partition 是解决积压最快的手段,但需注意消息顺序性。
事故 38:第三方 API 超时拖垮整个系统#
事故现象:
- 核心下单接口响应时间从 200ms 变为 30s+
- Tomcat 线程池全部耗尽,新请求被拒绝
- 故障源头是“物流查询”接口,该接口依赖的外部服务商挂了
- 整个电商网站不可用
事故原因:
- 代码中同步调用第三方接口,且未设置超时时间(默认无限等待)
- 没有熔断机制,下游故障直接传导至上游
- 线程池未隔离,物流查询占用了所有 Tomcat 线程
- 重试策略不当,失败后立即重试,加剧负载
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 链路追踪分析
# SkyWalking 显示 Span 卡在 `LogisticsService.query`,耗时 30s+
# 2. 检查线程堆栈
jstack <pid> | grep -c "WAITING"
# 发现 200 个线程全部阻塞在 HTTP Client 的 `read` 方法上
# 3. 测试第三方接口
curl -v --connect-timeout 5 https://logistics-api.com/query
# 连接超时或无响应
# 4. 查看代码
grep -A 5 "httpClient.execute" LogisticsService.java
# 发现未配置 `RequestConfig` 超时参数
|
使用命令:
1
2
3
| # 模拟外部依赖故障
# 使用 iptables 丢弃包
iptables -A OUTPUT -d <third_party_ip> -j DROP
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 1. 设置严格超时
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(3000) // 连接超时 3s
.setSocketTimeout(3000) // 读取超时 3s
.build();
// 2. 实施熔断降级 (Resilience4j/Sentinel)
@CircuitBreaker(name = "logisticsService", fallbackMethod = "fallbackQuery")
public LogisticsInfo query(String orderId) {
return httpClient.execute(...);
}
// 降级逻辑:返回默认值或缓存数据
public LogisticsInfo fallbackQuery(String orderId, Exception e) {
log.warn("Logistics service unavailable, return default", e);
return new LogisticsInfo("UNKNOWN", "稍后查询");
}
// 3. 线程池隔离
// 为物流查询分配独立线程池,不影响下单主流程
ExecutorService logisticsPool = Executors.newFixedThreadPool(10);
// 4. 异步化
// 非核心路径改为异步回调,不阻塞主线程
CompletableFuture.supplyAsync(() -> query(orderId), logisticsPool);
|
预防措施:
- 依赖治理:梳理所有外部依赖,区分核心与非核心。
- 三原则:所有外部调用必须遵循“超时、熔断、降级”三原则。
- 资源隔离:使用舱壁模式(Bulkhead),为不同依赖分配独立资源。
- Mock 测试:定期模拟第三方故障,验证系统韧性。
经验总结:
- 系统的可用性取决于最弱的那个外部依赖。
- 永远不要信任第三方服务,假设它们随时会挂。
- 同步变异步,阻塞变非阻塞,是解耦的关键。
事故 39:微服务雪崩(级联故障)#
事故现象:
- 积分服务响应慢,导致用户服务超时
- 用户服务超时,导致订单服务线程池满
- 订单服务不可用,导致网关所有请求 502
- 整个集群所有服务不可用,监控全线飘红
事故原因:
- 缺乏熔断机制,故障沿调用链向上传播
- 超时时间设置不合理(上层 > 下层),导致等待时间叠加
- 重试风暴:上层不断重试下层,放大了故障流量
- 资源未隔离,单个慢服务拖垮共享线程池
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 分析调用拓扑
# 通过链路追踪图,找到故障传播路径:Gateway -> Order -> User -> Points
# 2. 检查各服务线程池
for svc in gateway order user points; do
curl http://$svc/actuator/metrics/thread.pool.active
done
# 发现所有服务线程池均满
# 3. 查看重试配置
grep -r "maxAttempts" config/
# 发现默认重试 3 次,且无退避策略
# 4. 检查超时设置
# 发现 Order 调用 User 超时设为 10s,User 调用 Points 设为 10s
# 总等待时间可能达到 20s+
|
使用命令:
1
2
3
| # 紧急限流
# 在网关层直接拒绝指向故障服务的流量
istioctl apply -f rate-limit-order.yaml
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 调整超时层级
# 确保 上游超时 < 下游超时
# Gateway(3s) -> Order(2s) -> User(1s) -> Points(0.5s)
# 2. 配置熔断器
# 当错误率 > 50% 或 响应时间 > 1s,自动熔断
CircuitBreakerConfig:
failureRateThreshold: 50
waitDurationInOpenState: 30s
slidingWindowSize: 100
# 3. 禁止盲目重试
# 仅对网络波动(503/Timeout)重试,且增加指数退避
RetryConfig:
maxAttempts: 3
backoff:
multiplier: 2
minDelay: 100ms
# 4. 舱壁隔离
# 每个依赖使用独立线程池
@Bulkhead(name = "userService", type = Bulkhead.Type.THREADPOOL)
|
预防措施:
- 架构原则:遵循“依赖必熔断、调用必超时、资源必隔离”。
- 全链路压测:模拟下游故障,验证上游是否能快速失败。
- 监控大盘:建立依赖关系拓扑图,实时展示健康度。
- 降级预案:核心服务故障时,非核心功能自动降级(如不显示积分)。
经验总结:
- 雪崩往往始于一个不起眼的边缘服务。
- 没有超时的调用就是定时炸弹。
- 重试必须谨慎,避免放大故障。
事故 40:服务注册中心过载导致注册失败#
事故现象:
- 新发布的 Pod 一直无法启动,报错
Registration failed - 现有服务心跳超时,被注册中心误剔除,流量中断
- 注册中心(Eureka/Nacos)CPU 100%,接口响应极慢
- K8s 弹性伸缩后,实例数激增导致注册风暴
事故原因:
- 注册中心集群规模过小,无法承载突发注册请求
- 客户端心跳频率过高(默认 5s),产生大量网络 IO
- 大量临时测试实例未清理,占用内存和连接
- 注册中心未开启集群模式,单点性能瓶颈
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看注册中心监控
# CPU, Memory, Network IO, Register QPS
# 发现 Register 接口延迟高达 2s
# 2. 检查客户端日志
grep "register failed\|heartbeat timeout" app.log
# 3. 统计实例数量
curl http://nacos:8848/nacos/v1/ns/instance/list?serviceName=all
# 发现实例数超过 5000,包含大量测试环境实例
# 4. 检查网络
# 客户端到注册中心的网络带宽是否打满
|
使用命令:
1
2
| # 查看 Nacos 集群状态
curl http://nacos:8848/nacos/v1/ns/operator/metrics
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 调整心跳频率
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 10000 # 从 5s 改为 10s
heart-beat-timeout: 30000
ip-delete-timeout: 90000
# 2. 扩容注册中心
# 增加节点,组建集群
# 使用负载均衡分发注册请求
# 3. 清理无效实例
# 下线测试环境实例
# 设置 TTL,自动清理僵尸节点
# 4. 开启客户端缓存
# 客户端本地缓存服务列表,减少拉取频率
|
预防措施:
- 高可用部署:注册中心必须集群化,且具备自动扩缩容能力。
- 参数调优:根据规模调整心跳间隔,避免“心跳风暴”。
- 环境隔离:生产、测试环境注册中心物理隔离。
- 本地缓存:客户端必须具备本地缓存和降级能力。
经验总结:
- 注册中心是微服务的“电话簿”,一旦失联,整个系统瘫痪。
- 心跳频率不是越快越好,需在实时性和负载间平衡。
- 客户端缓存是应对注册中心抖动的缓冲垫。
事故 41:分布式事务数据不一致(最终一致性失效)#
事故现象:
- 支付成功但订单状态未更新
- 库存扣减了但订单未生成
- 对账发现大量“长款”或“短款”
- 补偿任务未触发,或触发后执行失败
事故原因:
- 本地消息表未写入成功,导致 MQ 消息未发送
- MQ 消息丢失(未持久化/未 ACK)
- 补偿任务(Job)宕机或未配置
- 补偿逻辑未实现幂等,导致重复执行或执行失败
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| -- 1. 查询本地消息表
SELECT * FROM local_message_table WHERE status = 'TO_SEND';
-- 2. 检查 MQ 状态
# 查看是否有消息积压或未 ACK
kafka-consumer-groups.sh --describe --group tx-group
-- 3. 检查补偿任务日志
grep "Compensation Job" scheduler.log | grep "Error"
-- 4. 比对业务数据
# 支付表 vs 订单表
SELECT p.id, o.id FROM payment p LEFT JOIN orders o ON p.order_id = o.id WHERE o.id IS NULL;
|
使用命令:
1
2
| # 手动触发补偿
curl -X POST http://scheduler/api/compensate?orderId=123
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 1. 确保本地消息与业务事务原子性
@Transactional
public void createOrder(Order order) {
orderMapper.insert(order);
// 在同一事务中写入本地消息表
messageMapper.insert(new Message(order.getId(), "CREATE_ORDER"));
}
// 2. 可靠投递消息
// 定时任务扫描本地消息表,发送至 MQ
@Scheduled(fixedDelay = 5000)
public void scanAndSend() {
List<Message> messages = messageMapper.findToSend();
for (Message m : messages) {
kafkaTemplate.send(m.getTopic(), m.getBody());
// 发送成功后更新状态
messageMapper.updateStatus(m.getId(), "SENT");
}
}
// 3. 消费端幂等
// 利用数据库唯一键或状态机防止重复消费
|
预防措施:
- 本地消息表:保证业务操作与消息记录在同一本地事务中。
- 定时对账:建立 T+1 或准实时对账系统,自动发现并修复差异。
- 消息可靠性:MQ 开启持久化,消费者手动 ACK。
- 幂等设计:补偿逻辑必须幂等。
经验总结:
- 分布式事务没有银弹,最终一致性是主流方案。
- 本地消息表是实现最终一致性的经典模式。
- 对账系统是数据一致性的最后一道防线。
事故 42:数据库自增 ID 耗尽#
事故现象:
- 插入新订单时报错
Duplicate entry '2147483647' for key 'PRIMARY' - 业务完全停止写入
- 表结构显示主键为
INT,值已达到最大值 - 扩展字段或分库分表未及时实施
事故原因:
- 建表时使用
INT (4 字节) 而非 BIGINT (8 字节) - 业务增长过快,ID 达到上限 (21 亿)
- 未提前规划 ID 生成策略(如雪花算法)
- 历史数据迁移导致 ID 跳跃式增长
排查过程:
1
2
3
4
5
6
7
8
9
| -- 1. 查看表结构
SHOW CREATE TABLE orders;
-- 确认主键类型是否为 INT
-- 2. 查看当前最大 ID
SELECT MAX(id) FROM orders;
-- 3. 检查自增步长
SHOW VARIABLES LIKE 'auto_increment_%';
|
使用命令:
1
2
| # 估算剩余时间
# (2147483647 - current_max) / daily_increase_rate
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| -- 1. 紧急修改字段类型(在线 DDL,需评估锁表风险)
ALTER TABLE orders MODIFY COLUMN id BIGINT UNSIGNED NOT NULL;
-- 注意:MySQL 5.6+ 支持 Online DDL,但仍需谨慎
-- 2. 重建表(如果 Online DDL 不可用)
-- 创建新表 -> 双写 -> 数据迁移 -> 切换
-- 3. 切换 ID 生成策略
# 弃用数据库自增,改用应用层雪花算法 (Snowflake)
# 或号段模式 (Leaf)
-- 4. 分库分表
# 如果单表数据量过大,借此机会实施分库分表
|
预防措施:
- 规范设计:核心业务主键一律使用
BIGINT。 - 监控预警:监控主键使用率,达到 80% 即告警。
- ID 策略:高并发场景推荐使用雪花算法或号段模式。
- 容量规划:定期评估数据增长速度。
经验总结:
INT 溢出是低级但致命的错误。- 修改主键类型是高风险操作,必须在低峰期进行。
- 去中心化 ID 生成策略是趋势。
事故 43:长事务导致数据库锁等待超时#
事故现象:
- 大量接口报错
Lock wait timeout exceeded; try restarting transaction - 数据库 CPU 不高,但连接数爆满
- 应用日志显示大量事务回滚
- 某个后台导出任务运行了 30 分钟未结束
事故原因:
- 大事务:在一个事务中处理了海量数据(如全表更新、大文件导出)
- 事务中包含 RPC/HTTP 调用,外部服务慢导致事务挂起
- 未提交/未回滚:代码异常捕获后未正确处理事务
- 索引缺失:更新操作导致全表锁(Gap Lock)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- 1. 查看长事务
SELECT * FROM information_schema.innodb_trx
WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;
-- 2. 查看锁等待
SELECT * FROM performance_schema.data_lock_waits;
-- 3. 查看进程列表
SHOW PROCESSLIST;
# 寻找 State 为 'updating', 'locking' 且 Time 很大的进程
-- 4. 分析 SQL
# 检查大事务执行的 SQL 是否走了索引
EXPLAIN SELECT ...;
|
使用命令:
1
2
| # 紧急 Kill 长事务
KILL <thread_id>;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| // 1. 拆分大事务
// 将大批量操作拆分为小批次(如每 1000 条提交一次)
for (List<Item> batch : batches) {
transactionTemplate.execute(status -> {
batchUpdate(batch);
return null;
});
}
// 2. 移除事务中的远程调用
// 先查数据 -> 提交事务 -> 再调用 RPC -> 异步更新结果
@Transactional
public void prepareData() { ... }
// 非事务
public void callExternal() { ... }
// 3. 优化索引
// 确保更新条件命中索引,避免 Gap Lock
// 4. 设置事务超时
@Transactional(timeout = 30)
public void businessMethod() { ... }
|
预防措施:
- 事务规范:严禁在事务中进行 RPC/HTTP 调用。
- 小事务原则:事务粒度尽可能小,只包裹必要的 DB 操作。
- 监控告警:监控长事务数量,超过阈值立即告警。
- SQL 审计:上线前审核 SQL 执行计划。
经验总结:
- 长事务是数据库性能的杀手。
- 事务内调用外部服务是架构大忌。
- 快速失败比长时间等待更保护系统。
事故 44:分库分表路由规则配置错误#
事故现象:
- 查询特定用户数据返回空,但该数据实际存在
- 写入数据成功,但读取时提示“表不存在”
- 扩容后,旧数据无法访问
- 应用日志报错
Table 'db_0.t_order_9' doesn't exist
事故原因:
- 分片算法配置错误(如模数与实际表数不一致)
- 扩容后未更新路由规则配置
- 分片键选择错误,导致路由计算偏差
- 配置文件未同步到所有应用实例
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 开启 SQL 打印
# 查看 ShardingSphere 实际路由到的物理表名
logging.level.org.apache.shardingsphere=DEBUG
# 2. 手动计算路由
# 根据算法 hash(id) % N,手动计算应落在哪个表
# 直接登录该物理表查询验证
# 3. 检查配置文件
cat sharding-config.yaml
# 核对 actual-data-nodes, algorithm-expression
# 4. 检查版本一致性
# 确认所有 Pod 加载的配置版本一致
|
使用命令:
1
2
3
4
| -- 直接登录物理库验证
USE db_order_0;
SHOW TABLES;
SELECT * FROM t_order_1 WHERE id = 12345;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 修正路由配置
sharding-rule:
tables:
t_order:
actual-data-nodes: ds$->{0..3}.t_order$->{0..7} # 修正表数量
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order$->{order_id % 8}
# 2. 数据迁移
# 如果是因为扩容导致的,需执行数据迁移脚本
# 将旧表数据 re-sharding 到新表
# 3. 配置热更新
# 使用配置中心管理分片规则,支持动态刷新
|
预防措施:
- 自动化测试:编写单元测试覆盖所有分片路由场景。
- 管理工具:提供可视化工具查询数据物理位置。
- 灰度发布:路由规则变更需灰度验证。
- 文档记录:详细记录分片算法和扩容历史。
经验总结:
- 路由规则错误会导致数据“隐形”。
- 分库分表扩容是高风险操作,需精密计划。
- 配置一致性至关重要。
事故 45:数据迁移脚本 Bug 导致数据丢失#
事故现象:
- 新版本上线后,部分用户历史数据消失
- 数据迁移日志显示“成功”,但实际数据未转移
- 回滚代码后,数据仍无法恢复
- 发现迁移脚本中有
DELETE 或 TRUNCATE 误操作
事故原因:
- 迁移脚本逻辑错误(如条件判断失误,删除了活跃数据)
- 未在测试环境进行全量数据演练
- 脚本未做备份直接执行
- 缺乏双人复核机制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 检查迁移日志
cat migration.log | grep -i "delete\|truncate\|error"
# 2. 对比数据量
SELECT COUNT(*) FROM old_table;
SELECT COUNT(*) FROM new_table;
# 发现数量严重不符
# 3. 检查 Binlog
mysqlbinlog mysql-bin.000XXX | grep -i "delete"
# 定位误删除的具体时间和 SQL
# 4. 审查脚本代码
# 发现 WHERE 条件缺失或错误
|
使用命令:
1
2
| # 尝试从 Binlog 恢复
mysqlbinlog --start-datetime="2024-03-24 10:00:00" --stop-datetime="2024-03-24 10:10:00" mysql-bin.000XXX | mysql -u root -p
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 紧急止损
# 停止所有写入,防止数据进一步破坏
SET GLOBAL read_only = ON;
# 2. 数据恢复
# 从最近的冷备份恢复
# 或使用 Binlog 回放误删除前的数据
# 3. 修复脚本
# 修正逻辑,增加 WHERE 条件
# 增加预检查(Pre-check)步骤
# 4. 重新迁移
# 在测试环境验证通过后,再次执行
|
预防措施:
- 备份先行:执行任何 DDL/DML 前必须备份。
- 全量演练:在仿真环境使用生产数据量级进行演练。
- 可回滚设计:迁移脚本必须配套回滚脚本。
- 双人复核:高危脚本需经过架构师和 DBA 双重审查。
经验总结:
- 数据迁移是最高风险的操作之一。
- 没有经过全量演练的迁移脚本不可信。
- 备份是最后的救命稻草。
事故 46:备份文件损坏且无法恢复#
事故现象:
- 数据库崩溃,尝试从备份恢复
gunzip 报错 invalid compressed datamysql 导入报错 Syntax error 或文件截断- 追溯发现过去 7 天的备份全部损坏
事故原因:
- 备份脚本只检查退出码,未校验文件完整性
- 磁盘静默错误(Bit Rot)导致文件损坏
- 备份过程中磁盘空间满,文件写入不完整
- 从未进行过恢复演练,盲目相信备份成功日志
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 尝试解压测试
gunzip -t backup_20240320.sql.gz
# 报错:CRC check failed
# 2. 检查磁盘健康
smartctl -a /dev/sdb
# 发现 Reallocated_Sector_Ct 很高
# 3. 检查备份脚本
cat backup.sh
# 发现缺少校验步骤
# 4. 追溯历史备份
for f in /backup/*.gz; do gunzip -t $f || echo "$f FAILED"; done
|
使用命令:
1
2
3
| # 生成并校验 MD5
md5sum backup.sql > backup.sql.md5
md5sum -c backup.sql.md5
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| # 1. 尝试修复
# 使用 gzip -d -f 强制解压(可能丢失部分数据)
# 或使用专业工具修复
# 2. 寻找替代备份
# 从异地灾备中心、从库、或更早的冷备中寻找可用文件
# 3. 重建备份体系
# 修改脚本:备份后立即校验
mysqldump ... | gzip > backup.sql.gz
if [ $? -eq 0 ]; then
md5sum backup.sql.gz > backup.sql.gz.md5
# 试恢复验证
gunzip -c backup.sql.gz | mysql --defaults-file=/dev/null -e "SELECT 1"
if [ $? -eq 0 ]; then
echo "Backup Verified OK"
aws s3 cp backup.sql.gz s3://bucket/
else
echo "Verification FAILED"
exit 1
fi
fi
# 4. 实施 3-2-1 备份策略
# 3 份副本,2 种介质,1 个异地
|
预防措施:
- 强制校验:备份后必须校验文件完整性和可恢复性。
- 定期演练:每周/月自动执行恢复演练。
- 多地冗余:避免单点存储风险。
- 监控告警:监控备份文件大小、校验和、执行时间。
经验总结:
- 没有经过恢复验证的备份等于没有备份。
- 磁盘静默错误是隐形杀手,校验和必不可少。
- 自动化验证是备份系统的核心组件。
事故 47:测试数据污染生产环境#
事故现象:
- 生产库中出现大量名为 “test”, “aaa”, “111” 的用户
- 订单表中出现金额为 0 或负数的异常数据
- 短信发送记录显示发给了内部测试手机号
- 用户投诉收到奇怪的通知
事故原因:
- 开发/测试人员误连生产数据库
- 配置文件中测试环境与生产环境地址混淆
- 缺乏权限控制,测试账号拥有生产写权限
- 自动化测试脚本未区分环境,直接执行
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
| -- 1. 查询异常数据
SELECT * FROM users WHERE name LIKE '%test%' OR phone LIKE '1380000%';
-- 2. 检查连接日志
SELECT user, host, db FROM mysql.general_log WHERE command_type = 'Connect';
# 查找来自测试网段的连接
-- 3. 审查操作记录
# 检查应用日志,确认是哪个服务写入的
-- 4. 检查配置文件
grep "jdbc.url" config-prod.yml
|
使用命令:
1
2
3
| # 紧急清理
DELETE FROM users WHERE name LIKE '%test%';
# 注意:需先备份,并确认无误删
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 紧急清理数据
# 备份异常数据后删除
mysqldump -t users -w "name LIKE '%test%'" > dirty_data_backup.sql
DELETE FROM users WHERE name LIKE '%test%';
# 2. 网络隔离
# 生产数据库禁止从测试网段访问
# 配置安全组/防火墙白名单
# 3. 权限最小化
# 回收测试人员的生产写权限
# 测试账号只能访问测试库
# 4. 配置分离
# 使用配置中心区分环境,禁止硬编码
# 增加环境标识校验(启动时检查 ENV 变量)
|
预防措施:
- 网络隔离:生产与测试网络物理或逻辑隔离。
- 权限管控:严格执行最小权限原则。
- 环境标识:应用启动时校验环境标签,防止误启动。
- 数据脱敏:生产数据导出到测试环境必须脱敏。
经验总结:
- 人为误操作是数据安全的最大威胁。
- 网络隔离和权限控制是防止误操作的硬屏障。
- 永远不要信任人的自觉性,要靠制度和技术。
事故 48:敏感数据泄露(日志打印)#
事故现象:
- 安全团队通报:GitHub 上发现公司生产日志,包含用户明文密码、手机号
- 合规部门介入,面临法律风险
- 日志系统中可搜索到大量身份证号、银行卡号
- 原因是开发调试时打开了 DEBUG 日志,打印了完整对象
事故原因:
- 代码中直接
log.info("user: {}", user),未脱敏 - 生产环境日志级别配置为 DEBUG
- 日志采集后未进行二次脱敏
- 日志平台权限管控不严,全员可读
排查过程:
1
2
3
4
5
6
7
8
9
| # 1. 搜索敏感关键词
grep -r "password\|id_card\|phone" /var/log/app/
# 2. 检查日志配置
cat logback-spring.xml
# 确认 root level 是否为 DEBUG
# 3. 审查代码
grep -r "log.*user" src/
|
使用命令:
1
2
3
| # 紧急清理日志
find /var/log/app -name "*.log" -exec shred -u {} \;
# 注意:需先确认是否影响排障
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| // 1. 代码脱敏
// 重写 toString 方法或使用脱敏工具
log.info("user: {}", MaskUtils.mask(user));
// 2. 调整日志级别
# 生产环境严禁 DEBUG/INFO 打印敏感字段
# 仅保留 ERROR 和必要的 WARN
// 3. 日志采集脱敏
# 在 Filebeat/Fluentd 插件中配置正则替换
processors:
- replace:
field: message
pattern: "(\\d{3})\\d{4}(\\d{4})"
replacement: "$1****$2"
// 4. 权限管控
# 日志平台设置 RBAC,敏感字段仅授权安全团队查看
|
预防措施:
- 代码扫描:CI 流水线集成敏感信息扫描工具。
- 脱敏规范:制定严格的日志打印规范。
- 自动化脱敏:在采集层统一脱敏,作为最后一道防线。
- 审计监控:监控日志中敏感词的出现频率。
经验总结:
- 日志是数据泄露的重灾区。
- 永远不要在日志中打印明文敏感信息。
- 脱敏必须多层防御(代码层 + 采集层)。
事故 49:数据同步延迟导致读写不一致#
事故现象:
- 用户刚修改个人资料,刷新页面仍显示旧数据
- 后台管理系统查不到刚创建的订单
- 主从数据库同步延迟(Seconds_Behind_Master)高达 300 秒
- 从库 CPU 100%,回放慢
事故原因:
- 主库写入压力过大,产生大量 Binlog
- 从库硬件配置低,回放速度跟不上
- 大事务在主库执行,导致从库单线程回放阻塞
- 网络带宽瓶颈,Binlog 传输慢
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -- 1. 查看同步状态
SHOW SLAVE STATUS\G
# 关注 Seconds_Behind_Master, Slave_SQL_Running_State
-- 2. 查看主库负载
SHOW PROCESSLIST;
# 查找大事务或高频写入
-- 3. 查看从库负载
top
iostat -x 1
# 确认是否是 IO 或 CPU 瓶颈
-- 4. 检查网络
iftop -i eth0
|
使用命令:
1
2
3
4
| # 跳过错误(谨慎)
STOP SLAVE;
SET GLOBAL sql_slave_skip_counter = 1;
START SLAVE;
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 紧急切换读主
# 修改应用配置,将读流量切回主库(牺牲主库性能保一致性)
# 或通过中间件强制路由到主库
# 2. 优化从库
# 升级从库硬件(CPU/SSD)
# 开启并行复制(MySQL 5.7+)
SET GLOBAL slave_parallel_workers = 16;
# 3. 消除大事务
# 在主库拆分大事务,减少从库回放阻塞
# 4. 架构调整
# 对于强一致场景,强制走主库
# 引入缓存层,写后更新缓存
|
预防措施:
- 硬件对等:从库配置应尽量接近主库。
- 并行复制:开启多线程回放。
- 监控告警:监控同步延迟,超过阈值(如 10s)告警。
- 业务容忍:业务层需接受最终一致性,或强制读主。
经验总结:
- 主从延迟是读写分离架构的固有痛点。
- 大事务是同步延迟的主要推手。
- 强一致场景不要依赖从库。
事故 50:归档策略错误导致活跃数据被误删#
事故现象:
- 用户查询半年前的订单,提示“数据不存在”
- 运营报表统计数据大幅减少
- 发现归档脚本将最近 3 个月的活跃数据也删除了
- 备份中也没有这些数据(因为归档后即删除)
事故原因:
- 归档脚本的时间条件写错(如
date < NOW() - 3 MONTH 写成了 date > ... 或逻辑反了) - 未区分“已完成”和“进行中”的状态,误删了长期未结单的活跃数据
- 脚本未经过测试直接在生产执行
- 删除前未做二次备份
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 检查归档脚本
cat archive_job.sh
# 发现 SQL 逻辑错误:DELETE FROM orders WHERE create_time > '2023-01-01' (本应是 <)
# 2. 检查执行日志
cat archive.log
# 确认删除了多少行数据
# 3. 检查备份
# 确认删除前的备份是否可用
|
使用命令:
1
2
3
| # 尝试从 Binlog 恢复
mysqlbinlog --start-datetime="..." --stop-datetime="..." mysql-bin.xxx | grep -v "^DELETE" | mysql
# 或者反向解析 Binlog 生成 INSERT 语句
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 紧急停止脚本
# Kill 正在运行的归档进程
# 2. 数据恢复
# 从冷备份恢复被误删的数据
# 或使用 Binlog 反向解析恢复
# 3. 修复脚本
# 修正时间逻辑
# 增加状态判断:AND status = 'COMPLETED'
# 增加 Dry-Run 模式:先 SELECT 确认数据范围,再 DELETE
# 4. 增加保护机制
# 删除操作需二次确认
# 限制单次删除行数(如每次 1000 条)
|
预防措施:
- Dry-Run 机制:归档脚本必须先执行 SELECT 预览,人工确认后再执行 DELETE。
- 状态过滤:严格限定归档数据的状体(如仅归档“已完成”且“超过 N 天”)。
- 备份先行:删除前必须备份待删除数据。
- 权限控制:生产环境删除权限需审批。
经验总结:
- 删除操作是不可逆的高危动作。
- 归档逻辑必须经过严格测试和预览。
- 备份是防止误删的最后防线。
事故 51:DDoS 攻击事故#
事故现象:
- 带宽使用率 100%
- 服务响应极慢或不可用
- 防火墙日志显示大量异常连接
- CDN 回源流量激增
事故原因:
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 查看流量来源
iftop -i eth0 -n
tcpdump -i eth0 -nn -c 1000
# 2. 分析攻击类型
# SYN Flood:netstat -n | grep SYN_RECV | wc -l
# CC 攻击:awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 3. 查看防火墙日志
tail -f /var/log/firewall.log | grep -i "drop\|block"
# 4. 检查 CDN 状态
# CDN 控制台查看攻击流量
# 5. 查看系统资源
top
vmstat 1 5
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 启用 DDoS 防护
# 云厂商 DDoS 高防
# 接入流量清洗服务
# 2. 封禁攻击 IP
for ip in $(awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -100 | awk '{print $2}'); do
iptables -A INPUT -s $ip -j DROP
done
# 3. 限流保护
# Nginx 限流
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
# 4. 启用 CDN 防护
# 开启 CDN DDoS 防护
# 配置 WAF 规则
# 5. 联系运营商
# 申请上游流量清洗
|
预防措施:
- 接入专业 DDoS 防护服务
- 配置多层限流
- CDN 隐藏源站 IP
- 定期安全演练
事故 52:SQL 注入事故#
事故现象:
- 数据库异常查询
- 敏感数据泄露
- 应用报 SQL 语法错误
- 安全团队告警
排查过程:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看数据库日志
grep -i "select\|union\|drop" /var/log/mysql/*.log
# 2. 分析访问日志
grep -i "union\|select\|'" access.log
# 3. 检查异常查询
mysql -e "SHOW PROCESSLIST;" | grep -i "sleep\|benchmark"
# 4. 查看数据变更
mysqlbinlog /var/lib/mysql/mysql-bin.* | grep -i "drop\|delete"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 紧急修复代码
# 使用参数化查询
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
ps.setInt(1, userId);
# 2. 修复漏洞
# 输入验证
# 特殊字符转义
# 3. 数据恢复
# 从备份恢复被篡改数据
# 4. 安全加固
# 部署 WAF
# 最小权限原则
|
预防措施:
- 使用参数化查询
- 输入验证和转义
- 部署 WAF
- 定期安全扫描
事故 53:缓存穿透导致数据库瞬间崩溃#
事故现象:
- 某个冷门商品详情页突然流量激增(疑似恶意攻击)
- 数据库 CPU 瞬间飙升至 100%,连接数爆满
- 缓存命中率跌至 0%
- 大量请求直接打到数据库,导致正常用户无法访问
事故原因:
- 攻击者构造大量不存在的 Key(如
product_id = -1, -2, ...)发起请求 - 缓存中不存在这些 Key,请求直接穿透到数据库
- 数据库查询返回空,代码未将空结果写入缓存,导致每次请求都查库
- 缺乏参数校验和限流机制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 查看缓存命中情况
redis-cli INFO stats | grep keyspace_hits
# 发现 hits 极低,misses 极高
# 2. 分析请求 Key 分布
# 在应用层采样打印请求的 Key
log.info("Request Key: {}", key);
# 发现大量 Key 为负数或无规律字符串
# 3. 查看数据库慢查询
SHOW PROCESSLIST;
# 发现大量相同的查询 `SELECT * FROM products WHERE id = -xxx`
# 4. 检查应用日志
grep "Cache Miss" app.log | wc -l
# 确认缓存未命中量异常
|
使用命令:
1
2
| # 实时监控 Redis 命中率
watch -n 1 'redis-cli INFO stats | grep -E "keyspace_hits|keyspace_misses"'
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| // 1. 缓存空对象(短期方案)
// 即使数据库查不到,也将 null 值写入缓存,设置较短过期时间(如 5 分钟)
Object val = redis.get(key);
if (val == NOT_EXIST) {
return null; // 直接返回
}
if (val == null) {
val = db.query(key);
if (val == null) {
redis.setex(key, 300, NULL_OBJECT); // 缓存空值
return null;
}
redis.setex(key, 3600, val);
}
// 2. 布隆过滤器(Bloom Filter,推荐方案)
// 在请求到达缓存前,先通过布隆过滤器判断 Key 是否存在
if (!bloomFilter.mightContain(key)) {
// 肯定不存在,直接拦截
return null;
}
// 可能存在,继续查缓存
// 3. 接口限流与校验
// 对 ID 参数进行合法性校验(如 ID 必须 > 0)
// 网关层针对单一 IP 或用户进行限流
|
预防措施:
- 参数校验:入口层严格校验参数合法性。
- 布隆过滤器:核心业务引入布隆过滤器拦截非法 Key。
- 空值缓存:对查询为空的结果进行短时缓存。
- 限流降级:网关层配置针对异常流量的自动限流规则。
经验总结:
- 缓存穿透是恶意攻击的常用手段,必须防御。
- 布隆过滤器是解决大规模 Key 存在性判断的最优解。
- 永远不要信任前端传来的参数。
事故 54:缓存雪崩导致全站不可用#
事故现象:
- 某时刻开始,所有核心接口响应极慢,数据库负载激增
- 监控显示大量 Key 在同一时间过期
- 缓存服务器负载正常,但后端数据库不堪重负
- 故障发生在整点(如 10:00:00),恰逢大批量 Key 到期
事故原因:
- 大量热点 Key 设置了相同的过期时间(如都在凌晨 2 点设置,24 小时后同时过期)
- 缓存服务集群部分节点宕机,导致剩余节点压力过大(连带雪崩)
- 业务重启,本地缓存清空,流量全部涌向远程缓存和 DB
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 检查 Key 过期时间分布
# 抽样查看热点 Key 的 TTL
redis-cli TTL hot_key_1
redis-cli TTL hot_key_2
# 发现大量 Key 的剩余时间几乎一致
# 2. 查看数据库负载曲线
# 对比 Key 过期时间点与 DB CPU 飙升时间点,是否重合
# 3. 检查缓存节点状态
redis-cli CLUSTER NODES
# 确认是否有节点 Fail 或正在恢复
|
使用命令:
1
2
| # 模拟雪崩
# 脚本批量设置相同过期的 Key,然后等待同时过期
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| // 1. 随机化过期时间(核心方案)
// 在原定过期时间基础上,增加一个随机值(如 1-5 分钟)
int expireTime = 3600 + new Random().nextInt(300);
redis.setex(key, expireTime, value);
// 2. 多级缓存
// 本地缓存 (Guava/Caffeine) + 分布式缓存 (Redis)
// 即使 Redis 雪崩,本地缓存能扛住一部分流量
// 3. 限流降级
// 检测到 DB 压力过大时,自动熔断非核心业务
// 返回默认值或“系统繁忙”提示
// 4. 预热机制
// 在大促或重启前,提前加载热点数据到缓存
|
预防措施:
- 分散过期:严禁设置固定时间的过期策略,必须加随机值。
- 高可用架构:Redis 集群部署,避免单点故障引发连锁反应。
- 限流保护:数据库前端必须有强有力的限流层。
- 监控告警:监控缓存命中率骤降和 DB 负载突增。
经验总结:
- 雪崩往往是“定时炸弹”,由设计缺陷引起。
- 随机性是防止集体失效的简单有效手段。
- 多级缓存是应对高并发的标配。
事故 55:缓存击穿(热点 Key 失效)事故#
事故现象:
- 某个超级热点商品(如秒杀品)在过期瞬间,数据库 QPS 瞬间打满
- 仅这一个 Key 失效,却拖垮了整个数据库
- 其他非热点业务受影响较小,但核心交易链路阻塞
事故原因:
- 单个 Key 访问量极大(QPS > 5000)
- 该 Key 过期瞬间,海量并发请求同时发现缓存缺失
- 所有请求同时击穿到数据库,形成“惊群效应”
- 数据库无法承受瞬时高并发写入/读取
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 定位热点 Key
redis-cli --hotkeys
# 或使用监控工具查看 QPS 最高的 Key
# 2. 分析故障时间点
# 确认故障发生时间是否与该 Key 的过期时间一致
# 3. 查看数据库锁等待
SHOW ENGINE INNODB STATUS;
# 发现大量线程等待同一行记录的锁
|
使用命令:
1
2
| # 实时监控单个 Key 的访问频率
# 需借助 APM 工具或自定义监控
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 1. 互斥锁(Mutex Lock)
// 只让一个线程去查库重建缓存,其他线程等待
String key = "product_1001";
String lockKey = "lock:" + key;
if (redis.setIfAbsent(lockKey, "1", 10)) { // 尝试获锁
try {
// 双重检查
String val = redis.get(key);
if (val != null) return val;
val = db.query(key);
redis.setex(key, 3600, val);
return val;
} finally {
redis.delete(lockKey); // 释放锁
}
} else {
// 未获锁,休眠重试
Thread.sleep(50);
return getFromCache(key);
}
// 2. 逻辑过期(永不过期)
// Key 本身不设 TTL,但在 Value 中包含逻辑过期时间
// 异步线程发现逻辑过期后,后台启动重建,前台返回旧值
|
预防措施:
- 热点探测:实时监控并识别热点 Key。
- 永不过期策略:对超级热点 Key 采用逻辑过期 + 异步更新。
- 互斥重建:确保同一时刻只有一个请求回源查库。
- 本地缓存:利用本地缓存挡在第一线。
经验总结:
- 击穿是单点故障引发的系统性风险。
- 互斥锁是解决击穿的经典方案,但要注意死锁风险。
- 逻辑过期能极大提升用户体验(无阻塞)。
事故 56:Redis BigKey 导致网络阻塞与超时#
事故现象:
- Redis 集群出现周期性卡顿,响应时间从 1ms 飙升至 500ms+
- 监控显示网卡流量瞬间打满
- 部分请求超时,甚至触发主从切换
- 发现某个 Key 的大小达到 50MB+
事故原因:
- 设计了大 Value 结构(如一个 Hash 存了 100 万个字段,或 List 存了百万级元素)
- 业务方一次性读取/删除整个大 Key
- Redis 单线程处理大 Key 的序列化/反序列化/删除操作,阻塞后续命令
- 网络传输大数据包耗时过长
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查找 BigKey
redis-cli --bigkeys
# 或使用 RDB 分析工具(rdb-tools)
# 2. 监控网络流量
iftop -P -n
# 观察是否有瞬间的大流量突发
# 3. 查看慢日志
redis-cli SLOWLOG GET 10
# 发现 `GET big_key` 或 `DEL big_key` 耗时极长
# 4. 分析内存分布
redis-cli MEMORY USAGE <key>
|
使用命令:
1
2
3
| # 渐进式删除大 Key(避免阻塞)
# 编写 Lua 脚本或使用 UNLINK 命令(Redis 4.0+)
UNLINK big_key
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 紧急处理
# 使用 UNLINK 命令异步删除大 Key(非阻塞)
redis-cli UNLINK big_hash_key
# 2. 拆分大 Key
# 将一个大 Hash 拆分为多个小 Hash(如 user_info_1, user_info_2...)
# 将一个大 List 拆分为多个小 List
# 3. 优化读取逻辑
# 禁止一次性读取全部元素,改为分页/分批读取(HSCAN, ZSCAN)
// 错误:HGETALL big_key
// 正确:HSCAN big_key 0 COUNT 100
# 4. 压缩数据
# 对 Value 进行序列化压缩(如 Protobuf, Gzip)后再存入
|
预防措施:
- 规范设计:制定 BigKey 标准(如 String > 10KB, Hash/List > 5000 元素)。
- 上线扫描:CI/CD 流程集成 BigKey 扫描,阻断大 Key 上线。
- 异步删除:代码中删除大 Key 必须使用
UNLINK 而非 DEL。 - 分批操作:读写大集合类型必须使用
SCAN 系列命令。
经验总结:
- BigKey 是 Redis 性能的隐形杀手。
- 单线程模型决定了 Redis 对大操作极其敏感。
- 拆分和异步是处理 BigKey 的核心原则。
事故 57:Linux 文件句柄耗尽(Too many open files)#
事故现象:
- 应用日志大量报错
java.io.IOException: Too many open files - 无法建立新网络连接,无法打开新文件
- Nginx 返回 502,数据库连接失败
- 系统负载不高,但服务不可用
事故原因:
- Linux 默认文件句柄限制过低(通常 1024)
- 高并发场景下,每个 TCP 连接、每个打开的文件都占用一个句柄
- 连接泄漏:代码中未关闭 InputStream/OutputStream/Socket
- 进程级限制未调整,仅调整了系统级限制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看系统级限制
ulimit -n
cat /proc/sys/fs/file-max
# 2. 查看进程级限制
cat /proc/<pid>/limits | grep "open files"
# 3. 统计当前打开文件数
ls /proc/<pid>/fd | wc -l
# 4. 查找泄漏源
lsof -p <pid> | wc -l
lsof -p <pid> | grep IPv4 | head -20
|
使用命令:
1
2
3
| # 实时监控系统句柄使用率
watch -n 1 'cat /proc/sys/fs/file-nr'
# 输出:已分配 未使用 最大限制
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 临时调整(立即生效,重启失效)
ulimit -n 65535
sysctl -w fs.file-max=2097152
# 2. 永久调整
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
# /etc/sysctl.conf
fs.file-max = 2097152
net.core.somaxconn = 65535
net.ipv4.ip_local_port_range = 1024 65535
# 3. 修复代码泄漏
// 必须使用 try-with-resources
try (InputStream is = new FileInputStream("...")) {
// process
} catch (...) { ... }
// 显式关闭 Socket 和 Connection
# 4. 重启服务
# 使配置生效
systemctl restart app
|
预防措施:
- 基线检查:新服务器初始化脚本必须包含句柄数调优。
- 代码审查:重点检查 IO 流和网络连接的关闭逻辑。
- 监控告警:监控进程打开文件数,超过 80% 阈值告警。
- 连接池化:使用连接池复用 TCP 连接,减少句柄消耗。
经验总结:
- 文件句柄耗尽是高并发系统的常见瓶颈。
- 操作系统默认配置绝不能满足生产需求。
- 资源泄漏是慢性毒药,必须通过代码规范杜绝。
事故 58:TCP TIME_WAIT 过多导致端口耗尽#
事故现象:
- 应用作为客户端主动发起大量短连接
- 报错
Cannot assign requested address 或 Connection reset by peer netstat 显示大量 TIME_WAIT 状态的连接- 无法建立新的出站连接,导致外部调用失败
事故原因:
- 短连接模式:每次请求都新建 TCP 连接,用完即断
- 客户端端口范围过小(默认 32768-60999)
tcp_tw_reuse 未开启,导致端口回收慢- 高并发场景下,端口消耗速度 > 回收速度(2MSL)
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 查看连接状态分布
netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
# 观察 TIME_WAIT 数量是否巨大(如 > 20000)
# 2. 查看可用端口范围
sysctl net.ipv4.ip_local_port_range
# 3. 查看 TIME_WAIT 回收配置
sysctl net.ipv4.tcp_tw_reuse
sysctl net.ipv4.tcp_fin_timeout
|
使用命令:
1
2
| # 实时监控 TIME_WAIT 数量
watch -n 1 "netstat -n | grep TIME_WAIT | wc -l"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 开启端口复用
sysctl -w net.ipv4.tcp_tw_reuse=1
# 允许将 TIME_WAIT sockets 重新用于新的 TCP 连接
# 2. 缩短 FIN_WAIT 时间
sysctl -w net.ipv4.tcp_fin_timeout=30
# 3. 扩大本地端口范围
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
# 4. 启用长连接(根本解决)
# 应用层使用 HTTP Keep-Alive 或连接池
# 避免频繁建立/断开 TCP 连接
HttpClient httpClient = HttpClients.custom()
.setMaxConnTotal(200)
.setMaxConnPerRoute(20)
.evictIdleConnections(30, TimeUnit.SECONDS)
.build();
|
预防措施:
- 长连接优先:尽可能使用连接池和 Keep-Alive。
- 内核调优:高并发客户端必须调整
tcp_tw_reuse 和端口范围。 - 架构优化:引入网关或代理层,收敛出站连接。
- 监控:监控 TIME_WAIT 数量和可用端口数。
经验总结:
- TIME_WAIT 是 TCP 协议的正常机制,但在高并发短连接场景下会成为瓶颈。
- 开启
tcp_tw_reuse 是安全且有效的优化手段。 - 长连接是解决端口耗尽的根本之道。
事故 59:RabbitMQ 消息积压与队列阻塞#
事故现象:
- 消息队列中积压数百万条消息
- 消费者消费速度极慢,甚至停止消费
- 生产者发送消息阻塞,报错
BLOCKED - 内存告警,RabbitMQ 节点触发 Flow Control
事故原因:
- 消费者处理逻辑复杂或存在 Bug,处理单条消息耗时过长
- 消费者预取数量(Prefetch Count)设置过大,导致消息堆积在客户端未处理
- 队列中存在大量持久化消息,磁盘 IO 成为瓶颈
- 生产者发送速度远超消费能力,且无背压机制
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看队列状态
rabbitmqctl list_queues name messages consumers messages_ready messages_unacknowledged
# 2. 查看连接和通道
rabbitmqctl list_connections state
rabbitmqctl list_channels consumer_count msgs_unacknowledged
# 3. 检查消费者日志
grep "Processing time" consumer.log
# 发现平均处理时间从 10ms 变为 5s
# 4. 查看 RabbitMQ 管理面板
# 观察 Publish/Consume Rate 曲线,以及 Memory/Disk 使用率
|
使用命令:
1
2
| # 查看节点状态
rabbitmqctl status
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 调整预取数量(Prefetch Count)
// 限制消费者一次只拉取少量消息(如 1 或 10)
channel.basicQos(1);
// 确保处理完一条 ACK 后,再拉取下一条,避免客户端堆积
# 2. 扩容消费者
# 增加消费者实例数量
# 如果队列允许多消费者,增加并发度
# 3. 优化消费逻辑
# 异步处理、批量处理
# 移除同步阻塞调用
# 4. 紧急清理(极端情况)
# 如果消息不重要, purge 队列
rabbitmqctl purge_queue <queue_name>
# 5. 磁盘优化
# 将队列设置为非持久化(如果允许丢失)
# 或使用 SSD 磁盘
|
预防措施:
- 合理 Prefetch:根据处理能力动态调整
basicQos。 - 监控积压:设置消息积压阈值告警。
- 死信队列:处理失败的消息及时转入 DLQ,避免阻塞主队列。
- 背压机制:生产者在队列达到一定长度时应暂停发送或降级。
经验总结:
- 消费者处理能力决定了整个系统的吞吐量。
basicQos(1) 是保证公平分发和防止客户端积压的关键。- 磁盘 IO 往往是持久化队列的性能瓶颈。
事故 60:ZooKeeper 会话超时导致集群脑裂#
事故现象:
- Hadoop/HBase/Kafka 集群频繁发生 Master 选举
- 服务间歇性不可用,数据短暂不一致
- ZooKeeper 日志显示大量
SessionExpired 和 ConnectionLoss - 客户端频繁重连,ZK 集群 CPU 飙升
事故原因:
- ZK 集群负载过高,无法及时处理心跳
- 网络抖动或 GC 停顿(Stop-The-World)导致客户端未及时发送心跳
- 会话超时时间(sessionTimeout)设置过短
- ZK 节点磁盘 IO 慢,导致事务日志写入延迟
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 查看 ZK 状态
echo stat | nc localhost 2181
# 查看 Mode, Latency, Connections
# 2. 查看 ZK 日志
grep "SessionExpired\|Expiring" zookeeper.out
# 确认超时会话数量
# 3. 检查客户端 GC 日志
grep "Full GC" app.log
# 确认是否因长时间 GC 导致心跳中断
# 4. 检查磁盘 IO
iostat -x 1
# 确认 ZK 数据目录所在磁盘的 await 时间
|
使用命令:
1
2
3
4
| # 监控 ZK 延迟
echo ruok | nc localhost 2181
# 使用四字命令监控
echo mntr | nc localhost 2181 | grep latency
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 调整超时参数
# 增加 sessionTimeout 和 connectionTimeout
# 建议:sessionTimeout = 20s ~ 40s (原默认可能为 5s)
// 客户端配置
new ZooKeeper(hosts, 30000, watcher);
# 2. 优化 ZK 集群
# 独立部署 ZK 集群,不与大数据组件混部
# 使用 SSD 存储 ZK 数据(txnlog 和 snapshot)
# 增加 ZK 节点数(奇数,如 5 节点)
# 3. 优化客户端 GC
# 调整 JVM 参数,减少 Full GC 频率和停顿时间
# 使用 G1 GC
# 4. 网络优化
# 确保客户端与 ZK 集群网络低延迟、无丢包
|
预防措施:
- 独立部署:ZK 作为核心协调服务,必须独立集群部署。
- SSD 存储:ZK 对磁盘延迟极其敏感,必须使用 SSD。
- 参数调优:根据网络环境和 GC 情况合理设置超时时间。
- 监控:监控 ZK 的延迟、连接数和会话超时率。
经验总结:
- ZK 是分布式系统的“心脏”,心跳停止意味着死亡。
- GC 停顿是导致 ZK 会话超时的常见原因。
- 磁盘 IO 性能直接决定 ZK 的稳定性。
事故 61:JVM Full GC 停顿过长事故#
事故现象:
- 核心交易接口响应时间从 50ms 飙升至 5s+
- 监控显示应用出现周期性“假死”(Stop-The-World)
- Tomcat/Jetty 线程池全部阻塞在
WAITING 状态 - 日志中出现大量
GC overhead limit exceeded 警告
事故原因:
- 内存泄漏导致老年代(Old Gen)快速填满
- 大对象直接进入老年代,触发频繁 Full GC
- JVM 参数配置不当(堆大小、GC 算法选择错误)
- 代码中存在未关闭的资源(如 InputStream、Database Connection)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 确认 GC 频率和耗时
jstat -gcutil <PID> 1000 10
# 关注 FGC (Full GC 次数) 和 FGCT (Full GC 总时间)
# 如果 FGC 在短时间内急剧增加,且 FGCT 占比高,确认为 Full GC 问题
# 2. 查看堆内存分布
jmap -heap <PID>
# 观察 Old Gen 使用率是否接近 100%
# 3. 导出堆转储文件(Heap Dump)
# 注意:生产环境执行此命令会暂停服务,建议在流量低峰或备用节点执行
jmap -dump:format=b,file=heap_dump.hprof <PID>
# 4. 实时查看 GC 日志(如果已开启)
tail -f /var/log/app/gc.log | grep "Full GC"
# 5. 查看线程状态,确认是否都在等待 GC
jstack <PID> | grep -A 5 "VM Thread"
jstack <PID> | grep -c "WAITING (on object monitor)"
# 6. 分析大对象
jmap -histo:live <PID> | head -20
# 查看存活对象中占用内存最大的类
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
12
| # 实时监控 GC 状态
watch -n 1 'jstat -gc <PID> | tail -1'
# 强制触发 GC 测试(谨慎使用,会导致短暂停顿)
jcmd <PID> GC.run
# 查看 JVM 启动参数
jinfo -flags <PID>
# 使用 Arthas 在线诊断(推荐,无需重启)
java -jar arthas-boot.jar
# 进入后执行:dashboard, memory, thread
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 紧急扩容(临时方案)
# 如果是因为流量突增导致的内存不足,临时增加堆内存
# 修改启动脚本:-Xms4g -Xmx8g (原为 2g/4g)
# 重启服务
# 2. 降级非核心业务
# 关闭耗内存的功能模块(如报表生成、大数据量导出)
# 减少缓存加载量
# 3. 切换 GC 算法(中长期)
# 从 CMS 切换到 G1 GC(JDK 8u20+ 推荐)
-XX:+UseG1GC
-XX:MaxGCPauseMillis=200
-XX:InitiatingHeapOccupancyPercent=45
# 4. 修复内存泄漏(根本解决)
# 使用 MAT (Memory Analyzer Tool) 分析 heap_dump.hprof
# 查找 Dominator Tree,定位占用内存最大的对象
# 常见泄漏点:静态集合类、ThreadLocal 未 remove、未关闭的流
# 5. 优化代码
// 错误示例:静态 List 无限增长
static List<String> cache = new ArrayList<>();
// 正确示例:使用有界缓存
static Map<String, String> cache = new LinkedHashMap<>(1000, 0.75f, true) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 1000;
}
};
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 完善监控告警
# Prometheus JMX Exporter
- alert: JvmGcPauseHigh
expr: rate(jvm_gc_collection_seconds_sum[5m]) / rate(jvm_gc_collection_seconds_count[5m]) > 0.5
for: 2m
labels:
severity: critical
annotations:
summary: "JVM GC 停顿时间过长"
- alert: JvmOldGenUsageHigh
expr: jvm_memory_used_bytes{area="heap", pool="G1 Old Gen"} / jvm_memory_max_bytes{area="heap", pool="G1 Old Gen"} > 0.85
for: 5m
labels:
severity: warning
# 2. 规范开发流程
# 禁止使用 static 集合存储大量数据
# 必须使用 try-with-resources 关闭流
# 定期 Code Review 内存相关代码
# 3. 压测验证
# 上线前进行稳定性压测(Soak Testing),运行 24h+ 观察内存趋势
|
经验总结:
- Full GC 是 Java 应用的“癌症”,必须零容忍
- 堆 dump 分析是定位内存泄漏的金标准
- G1 GC 在大堆场景下表现优于 CMS
- 监控要关注 GC 频率和停顿时间,而不仅仅是内存使用率
- 静态变量和 ThreadLocal 是泄漏高发区
事故 62:数据库连接池耗尽事故(进阶版)#
事故现象:
- 应用日志大量报错:
CannotGetJdbcConnectionException: Could not get JDBC Connection - 接口超时,前端显示“系统繁忙”
- 数据库侧连接数已满(
max_connections) - 应用侧连接池活跃连接数达到最大值
事故原因:
- 慢 SQL 导致连接占用时间过长,无法及时释放
- 代码中存在事务未提交/回滚,连接被挂起
- 连接池配置过小,无法应对突发流量
- 外部依赖(如 Redis、第三方 API)超时,导致线程阻塞进而占用 DB 连接
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 查看应用连接池状态
# HikariCP 监控端点
curl http://localhost:8080/actuator/metrics/hikaricp.connections.active
curl http://localhost:8080/actuator/metrics/hikaricp.connections.pending
# 2. 查看数据库当前连接
mysql -u root -p -e "SHOW PROCESSLIST;" | head -50
# 关注 State 列:Sleep, Sending data, Locked, Waiting for table metadata lock
# 3. 统计各状态连接数
mysql -u root -p -e "SELECT COMMAND, STATE, COUNT(*) FROM information_schema.processlist GROUP BY COMMAND, STATE;"
# 4. 查找长事务
mysql -u root -p -e "SELECT * FROM information_schema.innodb_trx WHERE TIME_TO_SEC(TIMEDIFF(NOW(), trx_started)) > 60;"
# 5. 查看应用日志中的慢查询
grep "SlowQuery" /var/log/app/*.log | tail -20
# 6. 检查是否有元数据锁等待
mysql -u root -p -e "SELECT * FROM performance_schema.metadata_locks WHERE OWNER_THREAD_ID IS NOT NULL;"
|
使用命令:
1
2
3
4
5
6
7
8
9
10
11
| # 实时监控数据库连接数
watch -n 1 'mysql -u root -p -e "SHOW STATUS LIKE \"Threads_connected\";"'
# 查找阻塞其他会话的源头
SELECT blocking_locks.lock_id, blocking_trx.trx_mysql_thread_id, blocking_trx.trx_query
FROM performance_schema.data_lock_waits
JOIN performance_schema.data_locks blocking_locks ON data_lock_waits.blocking_engine_lock_id = blocking_locks.engine_lock_id
JOIN performance_schema.innodb_trx blocking_trx ON blocking_locks.engine_transaction_id = blocking_trx.trx_id;
# 查看连接池等待队列长度
jstat -gc <PID> # 辅助判断是否因 GC 导致处理慢
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 1. 紧急 Kill 掉异常会话
# 找出执行时间超过 60 秒的查询
mysql -u root -p -e "SELECT CONCAT('KILL ', id, ';') FROM information_schema.processlist WHERE time > 60 AND command != 'Sleep';" > kill.sql
source kill.sql
# 2. 临时扩容连接池
# 动态调整(如果支持)或重启应用
# application.yml
spring:
datasource:
hikari:
maximum-pool-size: 50 -> 100 # 临时加倍
connection-timeout: 30000 -> 10000 # 缩短获取连接超时,快速失败
# 3. 限流保护
# 在网关层限制进入应用的流量,防止连接池瞬间被打满
# Nginx: limit_req zone=api burst=20;
# 4. 优化慢 SQL
# 针对 Processlist 中耗时最长的 SQL 添加索引或优化逻辑
# 5. 修复代码事务
# 确保所有事务最终都会 commit 或 rollback
// 错误:捕获异常后未回滚
try {
db.update();
} catch (Exception e) {
log.error(e);
// 缺少 transaction.rollback()
}
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 连接池监控
# 告警规则:活跃连接数 > 80% 最大连接数
- alert: DbPoolUsageHigh
expr: hikaricp_connections_active / hikaricp_connections_max > 0.8
for: 2m
# 2. SQL 审计
# 开启慢查询日志,阈值设为 1s
# 定期分析慢查询并优化
# 3. 事务规范
# 严禁在事务中进行 RPC 调用、HTTP 请求
# 事务粒度尽可能小
# 4. 熔断降级
# 当数据库响应变慢时,自动熔断非核心业务
|
经验总结:
- 连接池耗尽通常是“果”,慢 SQL 或长事务是“因”
- 严禁在数据库事务内调用外部接口
- 快速失败(Fail Fast)比长时间等待更保护系统
- 监控要细化到连接池的活跃数、等待数和超时数
事故 63:分布式锁失效导致重复消费事故#
事故现象:
- 订单被重复发货
- 用户账户余额被重复扣减
- 库存出现负数
- 日志显示同一业务 ID 被多个实例同时处理
事故原因:
- Redis 锁过期时间设置过短,业务未执行完锁已释放
- 客户端宕机或 GC 停顿,导致无法及时续期(Lookaside Lock 问题)
- 主从切换导致锁丢失(Redis Async Replication 特性)
- 代码逻辑错误,锁 key 拼接不一致
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 查看业务日志
grep "OrderID_12345" /var/log/app/*.log
# 发现两个不同 IP 的实例在同一时间段内都获得了锁并执行了业务
# 2. 检查 Redis 锁 Key 状态
redis-cli GET lock:order:12345
# 返回 nil,说明锁已不存在或从未设置成功
# 3. 查看 Redis 慢日志
redis-cli SLOWLOG GET 10
# 检查是否有耗时操作导致锁获取延迟
# 4. 检查 Redis 集群状态
redis-cli CLUSTER INFO
# 查看是否有主从切换发生
# 5. 代码审查
# 检查锁的 key 生成逻辑是否包含动态变量
# 检查锁的释放逻辑是否在 finally 块中
|
使用命令:
1
2
3
4
5
6
7
8
| # 模拟锁竞争测试
# 使用 redis-cli 手动测试 setnx 行为
redis-cli SETNX lock:test 1
redis-cli EXPIRE lock:test 5
redis-cli TTL lock:test
# 查看 Redis 内存中锁的分布
redis-cli KEYS "lock:*" | head -20
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
| # 1. 引入看门狗机制(WatchDog)
# 使用 Redisson 等成熟框架,自动为锁续期
RLock lock = redisson.getLock("lock:order:12345");
lock.lock(); // 默认开启看门狗,只要线程还在运行,锁就不会过期
try {
// 业务逻辑
} finally {
lock.unlock();
}
# 2. 使用 RedLock 算法(强一致性场景)
# 向 N/2+1 个 Redis 节点申请锁,提高可靠性
# 注意:RedLock 性能较低,仅在极端一致要求下使用
# 3. 数据库唯一索引兜底
# 即使锁失效,数据库唯一约束也能防止重复插入
ALTER TABLE orders ADD UNIQUE INDEX uk_order_no (order_no);
# 4. 幂等性设计
# 业务逻辑本身支持重复执行而不产生副作用
// 检查状态机
if (order.getStatus() != OrderStatus.PAID) {
// 执行扣款
order.setStatus(OrderStatus.PAID);
} else {
// 直接返回成功
}
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 框架选型
# 禁止手写 Redis setnx 逻辑,统一使用 Redisson/Zookeeper Curator
# 2. 兜底策略
# 分布式锁只是第一道防线,数据库唯一索引和业务幂等性是最后一道防线
# 3. 监控告警
# 监控锁等待时间、锁冲突次数
# 监控业务重复执行指标(如单位时间内同一 ID 处理次数>1)
# 4. 故障演练
# 模拟 Redis 主从切换,验证锁是否丢失
|
经验总结:
- 不要信任单一的分布式锁,必须有兜底(DB 唯一键/幂等)
- 锁的过期时间必须大于业务执行时间,或使用自动续期
- Redis 主从异步复制天生存在锁丢失风险,关键业务需评估
- 幂等性是分布式系统的基石
事故 64:微服务雪崩(级联故障)事故#
事故现象:
- A 服务故障,导致调用 A 的 B、C 服务全部超时
- 进而导致调用 B、C 的网关 D 服务线程池耗尽
- 最终整个集群所有服务不可用
- 监控显示所有服务 RT 飙升,错误率 100%
事故原因:
- 缺乏熔断机制,下游故障拖垮上游
- 线程池隔离失效,单个慢接口占满所有线程
- 超时时间设置不合理(层层叠加)
- 重试风暴(上游不断重试下游,加剧负载)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 链路追踪分析
# 查看 SkyWalking/Zipkin/Jaeger 追踪图
# 发现根节点是某个基础服务(如用户中心)响应极慢
# 2. 检查线程池状态
for service in gateway order user; do
curl http://$service/actuator/metrics/thread.pool.active
done
# 3. 查看依赖调用链
# 确认哪个下游服务成为了瓶颈
# 检查该服务的 CPU、内存、DB 连接
# 4. 检查重试配置
grep -r "RetryTemplate\|maxAttempts" config/
# 发现配置了 3 次重试,且无退避策略
|
使用命令:
1
2
3
4
5
6
| # 快速定位故障源
# 查看各服务错误率
kubectl top pods | sort -rn
# 查看调用延迟分布
istioctl proxy-config log deploy/gateway --level debug
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 1. 紧急熔断
# 手动开启熔断开关(如果有配置中心)
# 或直接下线故障服务,返回默认值
kubectl scale deployment user-service --replicas=0
# 2. 实施舱壁模式(Bulkhead)
# 为不同依赖分配独立的线程池
// 错误:共用线程池
ExecutorService commonPool = ...;
// 正确:隔离线程池
ExecutorService userPool = Executors.newFixedThreadPool(20);
ExecutorService orderPool = Executors.newFixedThreadPool(50);
# 3. 配置熔断器
// 使用 Resilience4j/Sentinel/Hystrix
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.build();
# 4. 设置合理超时
# 上游超时时间 < 下游超时时间
# 禁止无限制等待
# 5. 禁用盲目重试
# 仅在网络波动或 5xx 错误时重试
# 增加指数退避(Exponential Backoff)
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 架构原则
# 遵循“依赖必熔断、调用必超时、资源必隔离”
# 2. 全链路压测
# 模拟下游故障,验证上游是否能快速失败
# 3. 监控大盘
# 建立依赖关系拓扑图,实时展示健康度
# 4. 降级预案
# 核心服务故障时,非核心功能自动降级(如推荐列表置空)
|
经验总结:
- 雪崩往往始于一个不起眼的边缘服务
- 没有超时的调用就是定时炸弹
- 重试必须谨慎,避免放大故障
- 隔离是防止级联故障的唯一手段
事故 65:Kafka 消息积压(千万级)事故#
事故现象:
- 消费者 Lag 达到 2000 万+
- 实时数据处理延迟从秒级变为小时级
- 磁盘空间告警(Kafka Broker 存满)
- 用户反馈订单状态长时间不更新
事故原因:
- 消费者代码出现死循环或严重 BUG,处理速度趋近于 0
- 新增了一个极其耗时的同步 IO 操作(如调用慢 HTTP 接口)
- Partition 数量少于消费者实例数,导致负载不均
- 消息体过大,导致网络传输和反序列化耗时
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 查看消费组 Lag
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group order-group
# 观察到 LAG 持续上涨,CURRENT-OFFSET 几乎不动
# 2. 检查消费者日志
tail -f consumer.log | grep -i "error\|exception\|timeout"
# 发现大量 `ConnectTimeoutException`
# 3. 分析消费者线程
jstack <consumer_pid> | grep -A 20 "RUNNABLE"
# 发现线程阻塞在 HTTP 请求上
# 4. 查看 Kafka Broker 负载
kafka-topics.sh --bootstrap-server kafka:9092 --describe --topic order-topic
# 检查 Partition 分布是否均匀
# 5. 检查消息大小
kafka-run-class.sh kafka.tools.GetOffsetShell --broker-list kafka:9092 --topic order-topic
|
使用命令:
1
2
3
4
5
| # 实时监控 Lag 变化
watch -n 5 'kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group order-group | grep order-topic'
# 查看消息生产速率 vs 消费速率
# 通过 JMX 监控 MessagesInPerSec 和 BytesInPerSec
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| # 1. 修复代码 BUG
# 移除死循环,增加 HTTP 请求超时时间
# 将同步调用改为异步批量调用
# 2. 紧急扩容消费者
# 增加 Partition 数量(前提:Key 允许重排序)
kafka-topics.sh --alter --topic order-topic --partitions 48
# 扩容消费者实例至 48 个
kubectl scale deployment consumer --replicas=48
# 3. 跳过非关键消息(极端情况)
# 如果数据允许丢失,重置 Offset 到最新
kafka-consumer-groups.sh --reset-offsets --to-latest --execute --group order-group --topic order-topic
# 4. 临时旁路处理
# 将消息转发到另一个高性能处理集群
# 或写入临时存储,后续离线处理
# 5. 优化处理逻辑
// 批量拉取,批量处理
List<ConsumerRecord> records = consumer.poll(Duration.ofMillis(1000));
batchProcess(records); // 一次性处理 1000 条
|
预防措施:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 监控告警
# Lag > 10000 持续 5 分钟即告警
# 消费速率骤降告警
# 2. 容量规划
# Partition 数量应预留 2-3 倍余量
# 消费者处理能力应大于生产峰值的 1.5 倍
# 3. 异常处理
# 单条消息处理失败不应阻塞整个批次
# 引入死信队列(DLQ)
# 4. 压测
# 定期模拟高吞吐场景,验证消费能力
|
经验总结:
- 消息积压是“慢性病”,发现时往往已经很严重
- 扩容 Partition 是解决积压的最快手段(但需注意顺序性)
- 批量处理能显著提升吞吐量
- 死信队列是防止单条坏消息卡死整个消费的利器
事故 66:内部 DNS 解析故障(CoreDNS 崩溃)#
事故现象:
- K8s 集群内 Pod 之间无法通过 Service 域名通信
- 应用日志大量报错
UnknownHostException 或 Could not resolve host nslookup 在 Pod 内执行超时或返回 SERVFAIL- 核心业务链路中断,但 IP 直连正常
事故原因:
- CoreDNS 副本数不足,无法承载突发查询流量
- CoreDNS 配置错误(如
forward 指向了不可达的上游 DNS) - Node 本地
kube-dns 缓存污染或 systemd-resolved 冲突 - CoreDNS 进程内存泄漏导致 OOM Kill
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 在 Pod 内测试解析
kubectl run -it --rm debug --image=busybox:1.28 --restart=Never -- nslookup kubernetes.default
# 观察是否超时或报错
# 2. 检查 CoreDNS 状态
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl describe pod <coredns-pod> -n kube-system | grep -A 5 "Events"
# 查看是否有 OOMKilled 或 CrashLoopBackOff
# 3. 查看 CoreDNS 日志
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=200
# 搜索 "error", "timeout", "refused"
# 4. 检查 CoreDNS ConfigMap
kubectl get configmap coredns -n kube-system -o yaml
# 检查 forward 插件配置的上游 DNS 是否可达
# 5. 检查 Node 本地 DNS
# 登录到节点,检查 /etc/resolv.conf 和 systemd-resolved 状态
systemctl status systemd-resolved
resolvectl status
|
使用命令:
1
2
3
4
5
6
7
8
| # 批量测试所有 Node 的 DNS 连通性
for node in $(kubectl get nodes -o jsonpath='{.items[*].metadata.name}'); do
kubectl debug node/$node -it --image=busybox -- nslookup google.com
done
# 查看 CoreDNS 监控指标(Prometheus)
rate(coredns_dns_request_duration_seconds_count[5m])
rate(coredns_dns_response_rcode_count_total[5m])
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 紧急扩容 CoreDNS
kubectl scale deployment/coredns --replicas=5 -n kube-system
# 2. 修复 ConfigMap 配置
# 修改上游 DNS 为可靠地址(如 8.8.8.8 或内网 DNS)
kubectl edit configmap coredns -n kube-system
# 修改后重启 CoreDNS
kubectl rollout restart deployment/coredns -n kube-system
# 3. 清理 Node 缓存
# 在受影响节点执行
systemctl restart systemd-resolved
# 或重启 kubelet
systemctl restart kubelet
# 4. 临时绕过 DNS(极端情况)
# 在应用中使用 IP 直连,或修改 /etc/hosts (不推荐长期)
|
预防措施:
- 高可用部署:CoreDNS 至少部署 2-3 个副本,并设置反亲和性(Anti-Affinity)分散在不同节点。
- 资源限制:合理设置 CoreDNS 的 CPU/Memory Request/Limit,防止 OOM。
- 监控告警:监控 CoreDNS 的 QPS、延迟、错误率(RCODE)。
- 本地缓存:在应用侧或 Sidecar 中引入 DNS 缓存(如 NodeLocal DNSCache)。
经验总结:
- DNS 是 K8s 的神经系统,一旦瘫痪全场皆输。
- NodeLocal DNSCache 能显著降低 CoreDNS 压力并提高解析速度。
- 上游 DNS 配置必须冗余且可靠。
事故 67:Elasticsearch 集群脑裂与数据丢失#
事故现象:
- ES 集群状态变红(Red),部分分片未分配
- 写入请求失败,报错
ClusterBlockException - 发现两个 Master 节点同时存在(脑裂)
- 部分索引数据不一致或丢失
事故原因:
discovery.zen.minimum_master_nodes (ES 6.x) 或 cluster.initial_master_nodes (ES 7.x+) 配置错误- 网络抖动导致 Master 节点间心跳超时,触发重新选举
- 节点恢复时间设置过短,频繁发生角色切换
- 磁盘空间满导致分片无法分配
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 查看集群健康状态
curl -X GET "localhost:9200/_cluster/health?pretty"
# 2. 查看节点角色
curl -X GET "localhost:9200/_cat/nodes?v&h=name,ip,master,node.role"
# 观察是否有多个节点标记为 * (Master)
# 3. 查看未分配分片原因
curl -X GET "localhost:9200/_cluster/allocation/explain?pretty"
# 4. 检查日志
grep "MasterChanged\|ClusterFormation" /var/log/elasticsearch/*.log
# 查找频繁的 Master 切换记录
# 5. 检查磁盘使用率
curl -X GET "localhost:9200/_cat/allocation?v"
|
使用命令:
1
2
3
4
5
6
7
| # 强制指定主节点(紧急修复脑裂)
# 在所有非主节点 elasticsearch.yml 中配置
cluster.initial_master_nodes: ["node-1"]
# 然后重启非主节点
# 查看分片分布
curl -X GET "localhost:9200/_cat/shards?v&h=index,shard,prirep,state,node"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 停止所有节点
systemctl stop elasticsearch
# 2. 修复配置
# 确保 master_eligible 节点数为奇数 (3, 5, 7)
# 设置 minimum_master_nodes = (N/2) + 1 (ES 6.x)
# ES 7.x+ 只需在首次启动时正确设置 cluster.initial_master_nodes
# 3. 清理脏数据(谨慎)
# 删除每个节点 data 目录下的 metadata 文件(仅在全量备份后操作)
rm -rf /var/lib/elasticsearch/nodes/*/metadata
# 4. 按顺序启动
# 先启动原 Master 节点,待其完全启动后再启动其他节点
systemctl start elasticsearch # 在原 Master 上
# 等待集群绿色
# 启动其他节点
# 5. 重新分配分片
curl -X POST "localhost:9200/_cluster/reroute?retry_failed=true"
|
预防措施:
- 奇数节点:Master 候选节点数量必须为奇数,避免票数平局。
- 网络优化:确保集群内网络低延迟、高带宽,避免心跳超时。
- 参数调优:调整
discovery.zen.ping_timeout 和 discovery.zen.fd.ping_interval 适应网络环境。 - 磁盘监控:设置磁盘水位线告警(85% 警告,90% 只读)。
经验总结:
- 脑裂是分布式存储的大忌,配置法定人数(Quorum)是关键。
- ES 7.x 后简化了配置,但仍需严格遵循首次引导规则。
- 定期快照(Snapshot)是数据安全的最后防线。
事故 68:Nginx 502 Bad Gateway (Upstream 连接耗尽)#
事故现象:
- 用户访问大量返回 502 Bad Gateway
- Nginx 错误日志显示
connect() failed (110: Connection timed out) 或 no live upstreams - 后端服务(Tomcat/Go/Node)其实存活,但无法建立新连接
- 监控显示 Nginx 活跃连接数激增,后端连接池已满
事故原因:
- Nginx
upstream 未配置 keepalive,导致每次请求都新建 TCP 连接 - 后端服务连接池(如 Tomcat maxThreads)耗尽
- Nginx
worker_connections 设置过小 - 后端处理慢,导致连接占用时间过长,无法释放
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 查看 Nginx 错误日志
tail -f /var/log/nginx/error.log | grep "upstream"
# 2. 检查 Nginx 状态
curl http://localhost/nginx_status
# 观察 Active connections 和 Waiting
# 3. 检查后端连接
netstat -an | grep :8080 | wc -l
netstat -an | grep :8080 | grep TIME_WAIT | wc -l
# 4. 查看后端线程池
# Tomcat: JMX 或 access_log 分析响应时间
# Go/Node: 查看 Goroutine/EventLoop 阻塞情况
# 5. 检查 Nginx 配置
nginx -T | grep -A 10 "upstream"
|
使用命令:
1
2
3
4
5
| # 实时查看 Nginx 连接状态分布
watch -n 1 'netstat -n | awk "/^tcp/ {++S[\$NF]} END {for(a in S) print a, S[a]}"'
# 压测验证
ab -n 10000 -c 1000 http://nginx_ip/
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| # 1. 开启长连接(关键)
upstream backend {
server 127.0.0.1:8080;
keepalive 32; # 保持 32 个长连接
keepalive_requests 1000;
keepalive_timeout 60s;
}
server {
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须开启 HTTP/1.1 以支持 keepalive
proxy_set_header Connection ""; # 清除 Connection: close
proxy_connect_timeout 5s;
proxy_send_timeout 10s;
proxy_read_timeout 30s;
}
}
# 2. 调整 Nginx 并发限制
events {
worker_connections 65535;
multi_accept on;
}
# 3. 优化后端连接池
# Tomcat: server.xml <Connector maxThreads="500" minSpareThreads="50" />
# 确保后端最大连接数 > Nginx keepalive 总数
# 4. 重启 Nginx
nginx -t && nginx -s reload
|
预防措施:
- 长连接标配:Nginx 到后端必须配置
keepalive 和 proxy_http_version 1.1。 - 超时匹配:Nginx 超时时间应略大于后端处理最长耗时,避免误杀。
- 容量规划:根据 QPS 和平均响应时间计算所需连接数(QPS * RT)。
- 监控:监控 Nginx
upstream 状态和后端的活跃连接数。
经验总结:
- 502 往往不是后端挂了,而是连接建立失败了。
- 短连接是高并发系统的杀手,长连接是标配。
- Nginx 配置中的
proxy_http_version 1.1 和 Connection "" 缺一不可。
事故 69:Linux 内核参数未优化导致高并发丢包#
事故现象:
- 高并发压测时,大量请求超时或连接重置(Connection Reset)
netstat 显示大量 SYN_RECV 或 TIME_WAIT- 系统日志
dmesg 出现 TCP: request_sock_TCP: Possible SYN flooding - 带宽和 CPU 未满,但吞吐量上不去
事故原因:
net.core.somaxconn 太小,监听队列溢出net.ipv4.tcp_max_syn_backlog 太小,SYN 队列溢出net.ipv4.ip_local_port_range 范围过窄,端口耗尽net.core.netdev_max_backlog 太小,网卡接收队列丢包
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 查看当前内核参数
sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.ip_local_port_range
# 2. 查看丢包统计
netstat -s | grep -i "listen"
# 查看 "times the listen queue of a socket overflowed"
# 查看 "SYNs to LISTEN sockets dropped"
# 3. 查看网卡丢包
ifconfig eth0
# 查看 RX dropped / TX dropped
# 4. 查看系统日志
dmesg | grep -i "drop\|overflow\|flood"
|
使用命令:
1
2
3
4
5
| # 实时监控 SYN 队列溢出
watch -n 1 'netstat -s | grep "listen queue"'
# 查看端口使用情况
ss -s
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| # 1. 优化内核参数 (/etc/sysctl.conf)
cat >> /etc/sysctl.conf << EOF
# 增加监听队列长度
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 65535
# 扩大本地端口范围
net.ipv4.ip_local_port_range = 1024 65535
# 增加网卡接收队列
net.core.netdev_max_backlog = 250000
# 开启 SYN Cookies (防攻击)
net.ipv4.tcp_syncookies = 1
# 允许重用 TIME_WAIT socket
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 30
# 增加文件句柄
fs.file-max = 2097152
EOF
# 2. 生效配置
sysctl -p
# 3. 调整用户限制 (/etc/security/limits.conf)
* soft nofile 65535
* hard nofile 65535
# 4. 验证效果
# 重新压测,观察 netstat -s 中的 drop 计数是否不再增加
|
预防措施:
- 基线检查:新服务器上线前必须执行内核参数优化脚本。
- 自动化运维:使用 Ansible/Puppet 统一管理内核参数。
- 压测前置:任何高并发上线前,必须在仿真环境进行内核级压测。
- 监控:监控
netstat -s 中的关键丢包指标。
经验总结:
- Linux 默认配置是为通用场景设计的,不适合高并发服务器。
- 监听队列溢出是隐蔽的杀手,往往被误认为是应用层问题。
- 内核参数调优是高并发系统的“入场券”。
事故 70:CI/CD 流水线阻塞导致发布窗口错过#
事故现象:
- 生产发布窗口仅剩 30 分钟,但流水线一直卡在 “Pending” 或 “Failed”
- 无法上线紧急 Bug 修复
- 多个团队排队等待 Runner 资源
- 最终被迫推迟发布,影响业务活动
事故原因:
- GitLab Runner/GitHub Actions Runner 资源不足(CPU/内存满)
- 依赖镜像拉取超时(Docker Hub 限流或网络问题)
- 测试用例不稳定(Flaky Tests)导致反复重试
- 构建缓存失效,每次全量编译耗时过长
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 检查 Runner 状态
# GitLab: Admin Area -> Runners
# 查看是否有 Available Runner,以及 Job 队列长度
# 2. 查看具体 Job 日志
# 卡在哪一步?Clone? Build? Test? Deploy?
# 如果是 Pull Image 慢,检查网络
# 3. 检查 Runner 机器负载
top
free -h
df -h
# 查看是否磁盘满或内存溢出
# 4. 分析构建时长趋势
# CI 平台提供的 Analytics 面板
# 对比历史构建时间,定位突增步骤
|
使用命令:
1
2
3
4
5
6
7
8
| # 清理 Docker 缓存(在 Runner 上)
docker system prune -af
# 手动注册临时 Runner
gitlab-runner register --url https://gitlab.com/ --registration-token XXX
# 测试镜像拉取速度
time docker pull alpine:latest
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| # 1. 紧急扩容 Runner
# 动态启动 Spot 实例作为临时 Runner
# 或在 K8s 中扩容 runner-deployment
# 2. 优化镜像拉取
# 搭建私有镜像仓库(Harbor/Nexus)并配置代理加速
# 在 Runner 节点预拉取常用基础镜像
# 3. 修复不稳定测试
# 暂时跳过非核心的 Flaky Tests (添加 @ignore 标签)
# 优先保证主流程通过
# 4. 启用构建缓存
# 配置 Docker Layer Caching
# 配置 Maven/Gradle/NPM 依赖缓存
# 5. 并行化构建
# 将串行任务改为并行(Matrix Build)
|
预防措施:
- 弹性 Runner:基于 K8s 或云自动伸缩组实现 Runner 弹性伸缩。
- 缓存策略:精细化配置依赖缓存和 Docker 层缓存。
- 测试治理:定期清理和修复 Flaky Tests,建立测试稳定性红线。
- 多源加速:配置多个镜像源,避免单点限流。
经验总结:
- CI/CD 效率直接影响业务迭代速度,是核心竞争力。
- 构建环境的稳定性往往被忽视,直到发布时才爆发。
- 缓存和并行是提升构建速度的两大法宝。
事故 71:分布式事务数据不一致(TCC 模式失效)#
事故现象:
- 支付成功但订单状态未更新
- 库存扣减了但订单未生成
- 对账发现大量“长款”或“短款”
- 尝试手动补偿时,发现 TCC 的 Cancel 接口幂等性失效,导致重复回滚
事故原因:
- TCC 第二阶段(Confirm/Cancel)网络超时,发起方未收到结果,未进行重试
- Cancel 接口未实现幂等,重复调用导致数据错误
- 全局事务记录表(Log)写入失败,导致状态丢失
- 分支事务注册失败,协调者不知道有参与者
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| -- 1. 查询全局事务日志
SELECT * FROM distributed_transaction_log WHERE status != 'COMMITTED' AND status != 'ROLLBACKED';
-- 2. 检查分支事务状态
SELECT * FROM branch_transaction_log WHERE global_id = 'xxx';
-- 3. 比对业务数据
-- 支付表 vs 订单表 vs 库存表
SELECT p.status, o.status, i.qty
FROM payment p, orders o, inventory i
WHERE p.order_id = o.id AND o.id = i.order_id AND p.id = 'xxx';
-- 4. 查看应用日志
grep "TCC Confirm\|TCC Cancel" app.log | grep "Error\|Timeout"
|
使用命令:
1
2
3
| # 模拟 TCC 异常
# 使用 ChaosBlade 注入网络延迟或丢包
blade create network delay --time 3000 --interface eth0
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
| // 1. 实现严格的幂等性
// Cancel 方法中先检查状态,如果已回滚则直接返回成功
public void cancel(String businessKey) {
if (alreadyRolledBack(businessKey)) {
return; // 幂等返回
}
// 执行回滚逻辑
doRollback(businessKey);
markAsRolledBack(businessKey);
}
// 2. 引入定时对账补偿任务
// 扫描超过 N 分钟仍处于 TRYING 状态的事务
@Scheduled(cron = "0 */5 * * * ?")
public void compensate() {
List<Transaction> timeouts = txService.findTimeoutTransactions();
for (Transaction tx : timeouts) {
// 查询各分支状态,决定 Confirm 还是 Cancel
txManager.compensate(tx);
}
}
// 3. 优化重试机制
// 使用指数退避策略重试 Confirm/Cancel
// 最大重试次数设为 10 次,超过后转入人工处理队列
|
预防措施:
- 最终一致性:接受短暂不一致,依靠对账和补偿机制保证最终一致。
- 幂等性设计:所有分布式操作(尤其是回滚)必须幂等。
- 事务日志持久化:在执行业务前先写日志,确保状态可恢复。
- 定期对账:建立 T+1 或准实时的对账系统,自动发现并修复差异。
经验总结:
- 分布式事务没有银弹,TCC 复杂度高,需谨慎使用。
- 幂等性是分布式系统的基石,没有幂等就没有重试。
- 对账系统是数据一致性的最后一道防线。
事故 72:分库分表路由错误导致数据查不到#
事故现象:
- 用户反馈查不到自己的订单
- 后台管理系统搜索特定 ID 返回空
- 数据库中存在该数据,但应用层查不出
- 扩容迁移后,部分旧数据无法访问
事故原因:
- 分片算法(Sharding Algorithm)变更,新旧数据路由规则不一致
- 分片键(Sharding Key)选择不当,导致查询走全路由(广播)被限流
- 扩容迁移过程中,数据双写不一致,旧库数据未清洗
- 路由配置文件中表名映射错误
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 开启 SQL 打印
# 查看 ShardingSphere/JDBC 实际路由到的物理表
logging.level.org.apache.shardingsphere=DEBUG
# 2. 手动计算路由
# 根据分片算法(如 hash(id) % 4),手动计算 ID 应在哪个库哪张表
# 直接查询该物理表验证数据是否存在
# 3. 检查配置
cat sharding-config.yaml
# 核对 binding-tables, broadcast-tables, sharding-column 配置
# 4. 检查迁移日志
# 确认数据同步任务是否完成,是否有报错跳过
|
使用命令:
1
2
3
| -- 直接登录物理库查询
USE db_order_0;
SELECT * FROM t_order_1 WHERE order_id = '12345';
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 修正路由配置
# 确保新旧数据兼容,或实施数据迁移
sharding-rule:
tables:
t_order:
actual-data-nodes: ds$->{0..1}.t_order$->{0..3}
table-strategy:
inline:
sharding-column: order_id
algorithm-expression: t_order$->{order_id % 4}
# 2. 实施数据迁移(双写 + 校验 + 切换)
# 阶段 1:双写新旧库
# 阶段 2:后台全量校验数据一致性
# 阶段 3:读取切换到新库
# 阶段 4:停止旧库写入
# 3. 紧急修复查询
# 对于少量查不到的数据,建立映射表(ID -> 物理库表)
# 或在网关层做特殊路由转发
|
预防措施:
- 分片键选择:尽量选择离散度高、查询常用的字段(如 UserID 而非 OrderID)。
- 兼容性设计:分片算法变更必须配合平滑迁移方案。
- 全链路测试:上线前在仿真环境构造海量数据,验证路由准确性。
- 管理工具:提供后台工具,支持按全局 ID 查询物理位置。
经验总结:
- 分库分表是“不归路”,前期设计必须慎重。
- 数据迁移是高风险操作,必须有回滚和校验机制。
- 路由规则的错误往往是致命的,会导致数据“隐形”。
事故 73:缓存与数据库双写不一致(经典难题)#
事故现象:
- 用户修改资料后,前端仍显示旧数据
- 并发更新时,数据库是新值,缓存是旧值(或反之)
- 出现“脏读”,持续时间不定
事故原因:
- 采用“先更库后删缓存”,并发下旧数据回填(读写竞争)
- 采用“先删缓存后更库”,延时双删失败
- 缓存过期时间设置过长,且无主动更新机制
- 消息队列消费失败,导致异步更新缓存丢失
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 复现并发场景
# 使用 JMeter 模拟高并发读写同一 Key
# 2. 查看代码逻辑
# 确认是 Cache Aside, Read/Write Through 哪种模式
# 检查是否有延时双删,延迟时间是否合理
# 3. 检查 Binlog 监听
# Canal/Otter 是否正常消费,有无积压或报错
grep "parse binlog error" canal.log
# 4. 对比数据
redis-cli GET user:1001
mysql -e "SELECT * FROM users WHERE id=1001"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| // 方案 A:Cache Aside + 延时双删(改进版)
public void update(User user) {
// 1. 删除缓存
redis.delete("user:" + user.getId());
// 2. 更新数据库
db.update(user);
// 3. 延时再次删除(异步)
// 休眠时间 > 读业务完成时间
CompletableFuture.runAsync(() -> {
Thread.sleep(500);
redis.delete("user:" + user.getId());
});
}
// 方案 B:监听 Binlog 异步更新(推荐)
// 1. 只更新数据库
// 2. Canal 监听 Binlog 变化
// 3. 发送 MQ 消息
// 4. 消费者收到消息后删除/更新缓存
// 优点:解耦,保证最终一致性,无并发竞争
// 方案 C:分布式锁(强一致场景)
// 更新时加锁,确保读写串行化(性能损耗大,慎用)
RLock lock = redisson.getLock("lock:user:" + id);
lock.lock();
try {
db.update();
redis.delete();
} finally {
lock.unlock();
}
|
预防措施:
- 首选方案:非强一致场景使用 Cache Aside + Binlog 异步删除。
- 短期过期:缓存必须设置过期时间,作为兜底。
- 读写分离容忍:业务层需接受秒级的数据不一致。
- 监控:监控缓存与 DB 的不一致率(通过抽样比对)。
经验总结:
- 缓存一致性是 CAP 中的权衡,通常选择 AP(可用性 + 分区容错)。
- “先更库后删缓存”在极高并发下仍有风险,Binlog 方案更稳健。
- 永远不要相信“绝对实时”的缓存。
事故 74:消息队列重复消费导致资损#
事故现象:
- 用户账户被重复充值
- 订单被重复发货
- 积分被多次累加
- 日志显示同一条 Message ID 被处理了多次
事故原因:
- 消费者业务逻辑执行成功,但 ACK 发送失败(网络抖动/宕机)
- 消费者处理超时,MQ 认为消费失败并重投
- 代码中未做幂等判断,直接执行写入
- 手动重置 Offset 导致消息回溯
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看 MQ 控制台
# 检查消息重投记录(Retry Count)
# 查看死信队列(DLQ)是否有相关消息
# 2. 分析业务日志
grep "MessageID: xxx" app.log
# 发现同一条 ID 出现了两次 "Process Success"
# 3. 检查 ACK 机制
# 确认是自动 ACK 还是手动 ACK
# 如果是手动 ACK,是否在 catch 块中捕获了异常但未 ACK 或 NACK
# 4. 检查数据库唯一键
# 为什么重复插入没有报错?(可能漏了唯一索引)
|
使用命令:
1
2
3
4
5
| # Kafka 查看消费进度
kafka-consumer-groups.sh --bootstrap-server kafka:9092 --describe --group order-group
# RocketMQ 查询消息轨迹
mqadmin queryMsgById -n namesrv_addr -i MessageID
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| // 1. 实现幂等性(核心)
// 方法 A:数据库唯一索引
ALTER TABLE payments ADD UNIQUE INDEX uk_msg_id (message_id);
// 捕获 DuplicateKeyException 并忽略
// 方法 B:状态机 CAS
UPDATE orders SET status = 'PAID'
WHERE id = 123 AND status = 'UNPAID';
// 检查 affected rows,若为 0 说明已处理过
// 方法 C:Redis 防重表
String key = "processed:" + messageId;
if (redis.setIfAbsent(key, "1", 3600)) {
processBusiness();
} else {
// 重复消息,直接 ACK
ack();
}
// 2. 优化 ACK 时机
// 确保业务逻辑完全成功后再发送 ACK
// 在 try-catch-finally 中妥善处理异常,避免无限重投
try {
process(msg);
ack.acknowledge();
} catch (Exception e) {
log.error("Process failed", e);
// 记录错误,发送告警,视情况 NACK 或丢弃
}
|
预防措施:
- 设计原则:MQ 投递语义是 At-Least-Once,消费端必须实现幂等。
- 唯一键约束:数据库层面必须有唯一索引作为最后一道防线。
- 日志追踪:每条消息处理前后必须打印 MessageID,便于追溯。
- 死信处理:多次重试失败的消息应进入死信队列,人工介入。
经验总结:
- 消息队列的“可靠性”是以“重复”为代价的。
- 幂等性不是可选项,是必选项。
- 数据库唯一索引是防止资损的最简单有效手段。
事故 75:服务注册中心过载导致服务发现失效#
事故现象:
- 新启动的服务实例无法注册到 Nacos/Eureka
- 现有服务心跳超时,被错误剔除,导致流量中断
- 客户端获取服务列表为空或过时
- 注册中心 CPU 100%,响应极慢
事故原因:
- K8s 弹性伸缩导致实例数瞬间激增,注册请求洪峰
- 客户端心跳频率过高(默认 5s),注册中心扛不住
- 注册中心集群节点过少,或未开启集群模式
- 大量无效服务(测试实例)占用资源
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看注册中心监控
# CPU, Memory, Network IO, Request QPS
# 关注 Register 和 Heartbeat 接口延迟
# 2. 检查客户端日志
grep "register failed\|heartbeat failed" app.log
# 3. 查看实例数量
curl http://nacos-server:8848/nacos/v1/ns/instance/list?serviceName=order-service
# 统计实例数是否异常巨大
# 4. 检查网络
# 客户端到注册中心的网络是否有丢包或延迟
|
使用命令:
1
2
3
4
5
| # 查看 Nacos 集群状态
curl http://nacos-server:8848/nacos/v1/ns/operator/metrics
# 查看 Eureka 状态
curl http://eureka-server:8761/eureka/apps
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 调整心跳频率
# 客户端配置
spring:
cloud:
nacos:
discovery:
heart-beat-interval: 10000 # 从 5s 改为 10s
heart-beat-timeout: 30000
ip-delete-timeout: 90000
# 2. 扩容注册中心
# 增加 Nacos/Eureka 节点,组建集群
# 使用负载均衡器分发注册请求
# 3. 开启客户端缓存
# 客户端本地缓存服务列表,减少拉取频率
# Nacos 默认开启,检查配置是否被禁用
# 4. 清理无效实例
# 下线测试环境实例
# 设置实例 TTL,自动清理长期无心跳的僵尸节点
# 5. 降级策略
# 当注册中心不可用时,使用本地缓存列表继续运行
|
预防措施:
- 集群部署:注册中心必须集群化,且具备自动扩缩容能力。
- 参数调优:根据规模调整心跳间隔,避免“心跳风暴”。
- 多租户隔离:生产、测试环境注册中心物理隔离。
- 本地缓存:客户端必须具备本地缓存和降级能力。
经验总结:
- 注册中心是微服务的“电话簿”,一旦失联,整个系统瘫痪。
- 心跳频率不是越快越好,需在实时性和负载间平衡。
- 客户端缓存是应对注册中心抖动的缓冲垫。
事故 76:API 网关限流配置误伤正常用户#
事故现象:
- 大量正常用户请求被返回 429 Too Many Requests
- 某地区或某运营商用户全部无法访问
- 业务量骤降 50%,但系统负载很低
- 客服接到大量投诉
事故原因:
- 限流维度配置错误:按“出口 IP”限流,导致 NAT 后的所有用户共享配额
- 阈值设置过低:基于低估的 QPS 设定了激进的限流值
- 爬虫识别规则过严,误判正常浏览器指纹
- 配置发布未经过灰度,直接全量生效
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 查看网关访问日志
awk '$9 == 429 {print $1}' access.log | sort | uniq -c | sort -rn | head -10
# 发现大量 429 来自同一个 IP(网关出口 IP 或 运营商网关 IP)
# 2. 检查限流配置
cat gateway-config.yaml
# 关注 limit_req_zone 的 key 设置
# 错误示例:limit_req_zone $server_name ... (全局限流)
# 错误示例:limit_req_zone $binary_remote_addr ... (在网关层 $remote_addr 是用户 IP,但在某些架构下可能是 LB IP)
# 3. 分析用户分布
# 确认被限流用户是否集中在特定特征(如特定 User-Agent, 特定地区)
|
使用命令:
1
2
3
4
5
| # 实时统计 429 来源
tail -f access.log | awk '$9==429 {print $1}' | sort | uniq -c | sort -rn
# 测试限流规则
ab -n 1000 -c 50 http://gateway/api/test
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| # 1. 修正限流维度
# 使用 X-Forwarded-For 获取真实用户 IP
# 注意:需确保 X-Forwarded-For 可信,否则易被伪造
map $http_x_forwarded_for $client_real_ip {
default $http_x_forwarded_for;
"" $remote_addr;
}
# 取第一个 IP(最左侧是真实用户)
set $real_ip $http_x_forwarded_for;
# 使用 Lua 脚本提取第一个 IP 更稳妥
limit_req_zone $real_ip zone=user_limit:10m rate=10r/s;
# 2. 调整阈值
# 基于历史峰值 QPS 的 1.5 倍设定
# 区分核心接口和非核心接口,设置不同阈值
# 3. 白名单机制
# 将内部 IP、合作伙伴 IP 加入白名单
geo $whitelist {
default 0;
192.168.1.0/24 1;
10.0.0.0/8 1;
}
limit_req_zone $whitelist zone=white:10m rate=10000r/s;
# 4. 紧急回滚
# 回退到上一版本配置
# 或临时关闭限流模块(风险较高)
|
预防措施:
- 维度选择:尽量按 UserID、API Key 限流,避免按 IP(除非防攻击)。
- 动态配置:限流阈值应支持动态调整,无需重启。
- 灰度发布:限流规则变更必须先小流量验证。
- 友好提示:429 页面应提示用户“稍后重试”,而非冷冰冰的错误码。
经验总结:
- 限流是保护伞,但也可能成为拦路虎。
- NAT 环境下的 IP 限流极易误伤,需格外小心。
- 监控 429 比例,异常升高立即告警。
事故 77:链路追踪数据丢失导致排障困难#
事故现象:
- 发生故障时,SkyWalking/Zipkin 中找不到对应的 TraceID
- 调用链断裂,无法定位是哪个服务出错
- 采样率过低,关键错误请求未被记录
- 追踪数据延迟高达数小时,失去实时意义
事故原因:
- 采样率设置过低(如 0.1%),漏掉了低频但关键的错误
- Collector 接收端过载,丢弃了大量数据
- Agent 版本与 Server 不兼容,数据上报失败
- 网络带宽限制,Trace 数据被限流
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 检查 Agent 日志
cat skywalking-agent.log | grep -i "report\|error"
# 查看是否有 "Queue full", "Send failed"
# 2. 检查 Collector 负载
# CPU, Memory, GC 情况
# 查看 Backend 存储(ES/H2)写入延迟
# 3. 验证采样配置
cat agent.config
# sampling_rate 设置是多少?
# 4. 测试上报
# 手动发起一个请求,查看是否能实时出现在 UI 上
|
使用命令:
1
2
3
| # 查看 SkyWalking OAP 指标
# 监控 Buffer 队列大小
# 监控 Report 成功率
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 动态调整采样率
# 正常时期:0.1% - 1%
# 故障时期/慢请求:100%
# SkyWalking 支持动态配置,通过 UI 或 API 调整
# 设置规则:如果 RT > 1s 或 Status >= 500,强制采样 100%
# 2. 扩容 Collector
# 增加 OAP 节点,水平扩展
# 优化存储后端(ES 分片策略,SSD 磁盘)
# 3. 升级 Agent
# 确保所有服务 Agent 版本与 Server 兼容
# 统一升级至最新稳定版
# 4. 异步上报与缓冲
# 增加 Agent 端缓冲队列大小
# 启用压缩传输
|
预防措施:
- 智能采样:基于错误率和延迟动态调整采样率,而非固定比例。
- 独立集群:链路追踪系统应独立部署,避免受业务影响。
- 版本管理:严格管理 Agent 版本,定期升级。
- 数据保留:合理规划存储 retention,平衡成本与需求。
经验总结:
- 链路追踪是微服务的“黑匣子”,关键时刻必须有用。
- 固定低采样率在排查偶发故障时毫无价值。
- 错误请求和慢请求必须 100% 采集。
事故 78:配置中心推送全量覆盖导致服务启动失败#
事故现象:
- 配置中心发布新版本后,所有服务重启失败
- 报错:
Configuration property xxx not found - 部分关键配置(如 DB 密码、Redis 地址)丢失
- 回滚配置后仍需重启服务才能恢复
事故原因:
- 运维人员误操作:上传了空的或不完整的配置文件
- 发布脚本逻辑缺陷:用新文件全量覆盖旧文件,而非增量合并
- 配置中心缺乏 Diff 预览和校验机制
- 服务启动强依赖配置中心,无本地兜底
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 查看配置中心历史版本
# 对比发布前后的配置内容
# 发现新版本配置项大幅减少
# 2. 检查服务启动日志
grep "Failed to load configuration" app.log
# 3. 查看操作审计
# 谁在什么时间执行了 Publish 操作?
# 是否有审批记录?
# 4. 验证本地配置
# 检查容器内是否挂载了本地配置文件(如有)
|
使用命令:
1
2
| # 模拟配置拉取
curl http://config-server/app/profile/dev
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 紧急回滚
# 在配置中心界面点击“回滚”到上一版本
# 通知所有服务重启(或触发 Refresh)
# 2. 修复配置
# 补充缺失的配置项
# 重新发布
# 3. 增加校验机制
# 发布前自动校验配置格式(YAML/JSON 合法性)
# 校验必填项是否存在
# 提供 Diff 预览,人工确认变更点
# 4. 本地兜底
# 应用启动时,若配置中心不可用或配置缺失,使用打包在 Jar 内的默认配置
# bootstrap.yml 中配置 fallback
spring:
cloud:
config:
fail-fast: false # 启动不阻断
fallback-to-application-properties: true
|
预防措施:
- 权限控制:生产环境配置修改需双人复核(Four-eyes)。
- 增量更新:配置中心应支持增量 Patch,避免全量覆盖风险。
- 自动化测试:配置变更触发自动化测试,验证服务启动。
- 本地缓存:客户端缓存最新配置,断网也能启动。
经验总结:
- 配置即代码,配置变更的风险不亚于代码发布。
- 全量覆盖是危险的操作模式,增量合并更安全。
- 兜底机制是防止配置错误的最后一道防线。
事故 79:容器镜像漏洞导致大规模挖矿入侵#
事故现象:
- 服务器 CPU 100%,风扇狂转
- 发现未知进程(如
kworker, systemd-update 伪装)占用 CPU - 异常外连矿池端口(如 3333, 8888)
- 多个 Pod 同时中招,横向扩散
事故原因:
- 使用了含有已知漏洞的基础镜像(如老旧的 CentOS, Ubuntu)
- 应用依赖库存在高危漏洞(如 Log4j, Fastjson)
- 镜像仓库未进行安全扫描
- 容器以 Root 权限运行,被攻破后可控制宿主机
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 查找异常进程
top -c
ps -ef | grep -v grep | grep -E "mining|pool|stratum"
# 2. 检查网络连接
netstat -antp | grep ESTABLISHED
# 查找可疑的外连 IP
# 3. 检查定时任务
crontab -l
ls -la /var/spool/cron/
# 黑客常写入定时任务持久化
# 4. 扫描镜像
trivy image my-app:latest
# 查看 CVE 漏洞报告
|
使用命令:
1
2
3
| # 查找被篡改的系统文件
rpm -Va
# 或使用 chkrootkit, rkhunter
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| # 1. 紧急隔离
# 断开受害节点网络
# 杀掉恶意进程
kill -9 <PID>
# 2. 清理持久化
rm -f /var/spool/cron/root
rm -f /tmp/.X11-unix/X0... # 常见隐藏路径
# 3. 重建容器
# 不要试图修复被黑的容器,直接删除
kubectl delete pod <malicious-pod>
# 强制拉取新的安全镜像
# 4. 修复漏洞
# 升级基础镜像到最新版
# 升级应用依赖库
# 禁止容器以 Root 运行
securityContext:
runAsNonRoot: true
runAsUser: 1000
# 5. 全面扫描
# 对所有存量镜像进行漏洞扫描,修复高危漏洞
|
预防措施:
- 镜像扫描:CI/CD 流水线集成 Trivy/Clair,阻断含高危漏洞的镜像上线。
- 最小权限:容器严禁以 Root 运行,使用只读文件系统。
- 网络策略:K8s NetworkPolicy 限制 Pod 出站流量,禁止访问矿池。
- 定期更新:建立基础镜像定期更新机制。
经验总结:
- 镜像安全是容器安全的第一道门。
- Root 权限是黑客的最爱,必须剥夺。
- 自动化扫描是发现漏洞的最高效手段。
事故 80:监控告警风暴导致运维瘫痪#
(注:此事故已在 91-100 部分详细展开,此处略过,避免重复,重点放在架构类)
事故 81:跨区域容灾切换失败(RPO 不达标)#
事故现象:
- 主区域(Region A)发生地震/断电,触发容灾切换
- 切换至备区(Region B)后,发现丢失了最近 30 分钟的数据
- 业务虽然恢复,但用户投诉订单消失、余额不对
- 切换过程耗时 2 小时,远超 RTO 目标(15 分钟)
事故原因:
- 数据同步机制为异步复制,存在固有延迟
- 切换前未检查同步延迟(Lag),盲目切换
- DNS 切换 TTL 设置过长,用户仍访问旧区
- 备区容量不足,切换后瞬间流量打垮备区
排查过程:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 检查数据同步延迟
# MySQL: SHOW SLAVE STATUS\G (Seconds_Behind_Master)
# DTS/CDC: 查看同步延迟监控图表
# 2. 检查 DNS 生效情况
dig @8.8.8.8 www.example.com
# 观察是否仍解析到旧区 IP
# 3. 检查备区负载
kubectl top nodes -l region=backup
# 观察 CPU/Mem 是否爆满
|
使用命令:
1
2
| # 模拟切换演练
# 定期执行真实的故障转移测试
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 紧急数据补救
# 尝试从主区残存节点导出最后时刻的 Binlog
# 在备区重放,尽可能减少 RPO
# 2. 优化切换流程
# 步骤 1:停止主区写入(强制只读)
# 步骤 2:等待同步延迟为 0
# 步骤 3:提升备区为主
# 步骤 4:切换 DNS/GSLB
# 3. 扩容备区
# 备区应具备与主区同等的处理能力
# 平时可承担读流量或离线任务
# 4. 缩短 DNS TTL
# 平时设置较短 TTL(如 60s),便于快速切换
|
预防措施:
- 定期演练:每季度进行一次真实的跨区切换演练,验证 RPO/RTO。
- 同步监控:实时监控同步延迟,超过阈值立即告警。
- 容量对等:备区资源必须充足,避免“切换即雪崩”。
- 自动化切换:尽可能使用自动化脚本或平台执行切换,减少人为失误。
经验总结:
- 容灾不是买个产品就行,必须靠演练来验证。
- RPO(数据丢失量)和 RTO(恢复时间)是容灾的核心指标。
- 异步复制必然有数据丢失风险,关键业务需评估是否上强一致方案。
事故 82:多活数据中心“脑裂”双写冲突#
事故现象:
- 两个机房(A/B)同时对外提供服务,且都可写
- 网络专线中断,两边各自独立运行
- 网络恢复后,同一用户在 A 改了密码,在 B 下了订单
- 数据合并时发生严重冲突,部分操作被覆盖
事故原因:
- 架构设计为“双活”,但缺乏有效的冲突检测与解决机制
- 数据库层未做单元化(Sharding)隔离,允许跨库写入
- 仲裁机制失效,未能及时切断一方写入
排查过程:
1
2
3
4
5
6
| -- 1. 比对数据差异
-- 找出两边不一致的记录
SELECT * FROM users WHERE last_modified > '故障开始时间';
-- 2. 检查同步日志
-- 查看双向同步工具(如 DTS)的冲突报错
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 紧急止血
# 立即将其中一个机房设为只读(Read-Only)
# 停止双向同步,防止冲突扩散
# 2. 数据合并策略
# 策略 A:以时间戳最新为准(Last Write Wins),可能丢失部分数据
# 策略 B:人工介入,逐条核对关键数据
# 策略 C:业务层合并(如订单和改密互不影响,可共存)
# 3. 架构改造
# 实施单元化架构(Cell-Based Architecture)
# 用户 ID 取模,固定路由到特定机房,从根本上避免双写
# 只有元数据或全局配置才允许异地同步
|
预防措施:
- 单元化部署:核心业务数据按用户维度拆分,单机房闭环,避免跨机房双写。
- 仲裁机制:部署第三方仲裁节点,网络分区时强制选主。
- 冲突检测:应用层引入版本号(Version)或向量时钟,检测并拒绝冲突写入。
- 慎用双活:除非业务极度敏感,否则优先采用主备模式。
经验总结:
- 双活是架构的皇冠,也是深渊。没有单元化,双活就是灾难。
- 数据冲突的解决成本远高于预防成本。
- 大多数业务不需要真正的双活,主备 + 快速切换足矣。
事故 83:云服务商区域性故障(依赖风险)#
事故现象:
- 某云厂商(如 AWS us-east-1, 阿里云华东 1)整个 Region 不可用
- EC2/ECS 无法启动,RDS 无法连接,S3/OSS 无法读写
- 业务完全停摆,持续数小时
- 社交媒体上该云厂商热搜第一
事故原因:
- 云厂商内部基础设施故障(电力、网络、控制平面)
- 用户架构强依赖单一 Region,无跨 Region 容灾
- 即使有多 AZ(可用区),但该故障影响了整个 Region 的控制面
排查过程:
1
2
3
4
5
6
7
8
9
10
| # 1. 确认故障范围
# 查看云厂商 Status Page
# 尝试登录控制台,是否卡顿或报错
# 2. 测试连通性
ping <region-endpoint>
telnet <rds-endpoint> 3306
# 3. 评估影响
# 统计多少比例的业务部署在该 Region
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 启用多云/多 Region 灾备
# 如果有备 Region,立即切换 DNS 流量
# 激活备用数据库(如果是异步复制,接受数据丢失)
# 2. 降级服务
# 挂出“维护中”页面
# 暂停非核心功能,保留只读模式(如果有本地缓存)
# 3. 联系云厂商
# 提交最高级别工单
# 关注官方通报,预估恢复时间
|
预防措施:
- 多云战略:核心业务部署在两个以上云厂商或 Region。
- 架构解耦:使用 Terraform/Crossplane 等工具屏蔽云厂商差异,便于迁移。
- 数据同步:建立跨 Region 的数据实时同步机制。
- 应急预案:明确在云厂商宕机时的手动切换流程。
经验总结:
- 云厂商也会挂,不要把命脉完全交给别人。
- 单 Region 架构在云时代等同于“裸奔”。
- 多云成本高,但对于核心业务是必要的保险费。
事故 84:TLS 证书链不完整导致部分客户端失败#
事故现象:
- PC 浏览器访问正常,但部分 Android 手机、iOS App、Java 客户端报错
- 错误信息:
SSLHandshakeException: unable to find valid certification path - SSL Labs 测试评级为 A- 或 B,提示 “Chain issues”
- 新用户无法注册,老用户不受影响(因为缓存了 Session)
事故原因:
- 服务器配置只部署了叶子证书(Leaf Certificate),缺少中间证书(Intermediate CA)
- PC 操作系统根证书库全,能自动补全链条;移动端/老旧 JDK 根库不全,无法补全
- 证书申请后,未正确合并证书文件
排查过程:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 使用 OpenSSL 验证
openssl s_client -connect www.example.com:443 -showcerts
# 观察返回的证书链,是否只有 1 张证书?(正常应有 2-3 张)
# 2. 在线工具检测
# 访问 SSL Labs (ssllabs.com) 输入域名检测
# 查看 "Chain issues" 详情
# 3. 检查服务器配置
cat /etc/nginx/ssl/server.crt
# 确认是否包含了中间证书内容
|
使用命令:
1
2
| # 查看证书链详情
openssl s_client -connect www.example.com:443 </dev/null 2>/dev/null | openssl x509 -noout -issuer
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 获取中间证书
# 从证书颁发机构(CA)下载 Intermediate CA 证书
# 2. 合并证书
cat leaf_certificate.crt intermediate_ca.crt > fullchain.crt
# 顺序必须是:叶子证书在前,中间证书在后
# 3. 部署并重启
cp fullchain.crt /etc/nginx/ssl/
nginx -t && nginx -s reload
# 4. 验证
# 再次使用 openssl 或 SSL Labs 验证,确保链完整
|
预防措施:
- 自动化管理:使用 Certbot/Let’s Encrypt 自动部署,它们会自动处理链条。
- 全面测试:证书更新后,必须在多种设备(PC, Mobile, IoT)上测试。
- 监控:监控证书链完整性,而不仅仅是有效期。
经验总结:
- 证书链不完整是低级但高发的错误,兼容性杀手。
- 永远不要手动拼接证书,尽量自动化。
- 移动端和老旧系统的根证书库远不如 PC 完善。
事故 85:NTP 时间同步漂移导致签名验证失败#
事故现象:
- OAuth2 登录大面积失败,报错
Invalid Token 或 Token Expired - API 签名验证失败,返回
403 Forbidden - 分布式锁失效,日志时间混乱
- 数据库主从同步报错(GTID 依赖时间)
事故原因:
- 服务器 NTP 服务未启动或配置错误
- 虚拟化环境(VM)时间漂移,未安装 Guest Tools
- 本地硬件时钟(RTC)电池没电,重启后时间重置
- 防火墙阻断了 NTP 端口(123/UDP)
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 检查系统时间
date
# 对比标准时间(手机/原子钟)
# 2. 检查 NTP 状态
chronyc tracking
# 或
ntpq -p
# 查看 Offset 是否过大(>100ms 甚至几秒)
# 3. 检查虚拟机工具
# VMware Tools / VirtIO 是否运行
systemctl status vmtoolsd
# 4. 检查网络
telnet ntp.aliyun.com 123
|
使用命令:
1
2
3
4
| # 强制同步
chronyc -a makestep
# 或
ntpdate -u pool.ntp.org
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 立即同步时间
chronyc -a makestep
# 注意:如果时间偏差太大(>1000s),可能需要手动调整或重启服务
# 2. 修复 NTP 配置
# /etc/chrony.conf
server ntp.aliyun.com iburst
server ntp.tencent.com iburst
makestep 1.0 3 # 前 3 次更新无论偏差多大都步进
# 3. 启用虚拟机时间同步
# 确保 Host 时间准确,Guest 自动同步 Host
# 或在 Guest 中独立运行 NTP
# 4. 更换电池
# 物理机更换 CMOS 电池
|
预防措施:
- 多重源:配置至少 3 个不同的 NTP 源。
- 监控:监控时间偏移量(Offset),超过 50ms 即告警。
- 容器时间:容器共享宿主机时间,确保宿主机准确。
- 应用容错:签名验证允许一定的时间窗口(如 ±5 分钟)。
经验总结:
- 时间是分布式系统的基石,时间不准,一切逻辑崩塌。
- 虚拟机时间漂移是常见问题,必须专门处理。
- 监控时间偏移比监控时间本身更重要。
事故 86:K8s Etcd 数据损坏导致集群瘫痪#
事故现象:
kubectl get pods 无响应或报错 connection refused- 所有 API Server 无法工作
- Etcd 集群成员状态异常,Leader 选举失败
- 节点 NotReady,Pod 无法调度
事故原因:
- Etcd 数据目录所在磁盘 IO 延迟过高,导致心跳超时
- Etcd 进程 OOM 或被 Kill
- 数据文件损坏(WAL/Snapshot 损坏)
- 误操作删除了 Etcd 数据目录
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 检查 Etcd 状态
etcdctl endpoint health
etcdctl member list
# 2. 查看 Etcd 日志
journalctl -u etcd -f
# 搜索 "raft", "snapshot", "corrupted"
# 3. 检查磁盘 IO
iostat -x 1
# 关注 await 和 %util
# 4. 检查资源
top -p $(pgrep etcd)
|
使用命令:
1
2
| # 查看 Etcd 数据库大小
etcdctl --write-out=table endpoint status
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| # 1. 尝试恢复集群
# 如果是单节点故障,移除坏节点,加入新节点
etcdctl member remove <id>
etcdctl member add <new-node> --peer-urls=<url>
# 2. 从快照恢复(终极手段)
# 找到最近的快照
etcdctl snapshot restore backup.db \
--data-dir=/var/lib/etcd.restore \
--name=etcd-node \
--initial-cluster=...
# 3. 替换数据目录
# 停止 Etcd,替换为恢复的数据,重启
# 4. 重建集群
# 如果数据全丢,只能重建 K8s 集群,重新部署应用
|
预防措施:
- SSD 必备:Etcd 对磁盘 IO 极其敏感,必须使用 SSD。
- 定期备份:自动化定时备份 Etcd 快照(etcdctl snapshot save)。
- 资源隔离:Etcd 独占资源,不与业务混部。
- 监控:监控 Etcd 延迟(db_fsync_duration),超过 10ms 即告警。
经验总结:
- Etcd 是 K8s 的大脑,大脑坏了,肢体全瘫。
- 磁盘 IO 是 Etcd 的性能瓶颈,必须重视。
- 备份是唯一的救命稻草,且必须定期演练恢复。
事故 87:Service Mesh (Istio) 配置错误导致全站 503#
事故现象:
- 微服务间调用全部返回 503 Service Unavailable
- Sidecar 代理(Envoy)日志显示
No healthy upstream - 虚拟服务(VirtualService)路由规则生效异常
- 回滚 Istio 配置后恢复
事故原因:
- VirtualService 配置了错误的路由规则(如死循环、匹配所有但无目的地)
- DestinationRule 连接池配置过小,导致连接耗尽
- mTLS 策略冲突,证书验证失败
- Istio Pilot 推送配置延迟或失败
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 检查 Istio 配置
istioctl analyze
# 自动检测配置错误
# 2. 查看 Envoy 配置
istioctl proxy-config routes <pod>
istioctl proxy-config clusters <pod>
# 3. 查看 Pilot 日志
kubectl logs -n istio-system -l istio=pilot --tail=100
# 4. 测试连通性
kubectl exec -it <pod> -- curl -v http://service-name
|
使用命令:
1
2
| # 查看 Envoy 访问日志
kubectl logs <pod> -c istio-proxy | grep "503"
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 紧急回滚配置
kubectl apply -f virtual-service-backup.yaml
# 2. 旁路 Mesh(逃生)
# 临时移除 Sidecar 注入标签,让流量直连
kubectl label namespace default istio-injection-
# 3. 修正配置
# 修复路由规则,确保有合法的 Subset 和 Host
# 调整连接池参数
# 4. 逐步生效
# 使用 Istio 的 Canary 发布功能,先对少量流量生效
|
预防措施:
- 配置校验:上线前使用
istioctl analyze 严格校验。 - 灰度发布:Mesh 配置变更必须灰度,严禁全量直接推。
- 逃生通道:保留一键关闭 Sidecar 或 bypass Mesh 的能力。
- 监控:监控 Envoy 的 5xx 比例和配置推送版本。
经验总结:
- Service Mesh 增加了架构复杂度,配置错误影响面极大。
- 必须有“一键降级”回传统网络模式的能力。
- 配置即代码,版本控制和 Review 必不可少。
事故 88:自动扩缩容(HPA)失效导致服务雪崩#
事故现象:
- 流量洪峰到来,QPS 翻倍
- Pod 数量保持不变,未触发扩容
- 现有 Pod CPU 100%,请求大量超时
- 手动扩容后迅速恢复
事故原因:
- Metrics Server 故障,无法提供 CPU/Memory 指标
- HPA 配置的阈值过高(如 90%),触发滞后
- 自定义指标(Custom Metrics)采集失败
- Pod Resource Request 设置不合理,导致计算出的利用率偏低
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 查看 HPA 状态
kubectl get hpa
# 观察 CURRENT 指标是否为 <unknown>
# 观察 REASON 列是否有报错
# 2. 检查 Metrics Server
kubectl get pods -n kube-system -l k8s-app=metrics-server
kubectl logs -n kube-system -l k8s-app=metrics-server
# 3. 查看 Pod 资源请求
kubectl get pod <pod> -o jsonpath='{.spec.containers[].resources}'
# 4. 描述 HPA
kubectl describe hpa <hpa-name>
# 查看 Events,是否有 "FailedGetResourceMetric"
|
使用命令:
1
2
| # 手动测试指标获取
kubectl top pods
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 1. 修复 Metrics Server
# 重启 Metrics Server
# 检查 API Server 聚合层配置
# 2. 紧急手动扩容
kubectl scale deployment app --replicas=20
# 3. 调整 HPA 配置
# 降低目标利用率(如 60%)
# 调整缩放速度(scaleDown/stabilizationWindow)
spec:
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 60
behavior:
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 100
periodSeconds: 15
# 4. 修正 Resource Request
# 确保 Request 值接近实际平均使用量,避免虚高或虚低
|
预防措施:
- Metrics 高可用:Metrics Server 自身需要高可用部署。
- 合理阈值:根据压测结果设置合理的扩缩容阈值和冷却时间。
- 混合策略:结合 CPU 和 自定义指标(如 QPS、Lag)进行扩缩容。
- 定期演练:模拟流量洪峰,验证 HPA 反应速度。
经验总结:
- 自动扩缩容依赖准确的指标,指标挂了,扩缩容就瞎了。
- Request 设置不当会导致 HPA 误判,需定期校准。
- 扩容速度要快,缩容速度要慢,防止抖动。
事故 89:日志采集器(Filebeat/Fluentd)拖垮生产#
事故现象:
- 应用服务器 CPU 飙升,Load Average 极高
- 磁盘 IO 等待(iowait)占满
- 业务日志写入变慢,甚至阻塞业务线程
- 日志采集端(ES/Kafka)接收不过来,反压导致采集器内存爆炸
事故原因:
- 开启了 DEBUG 级别日志,日志量激增 100 倍
- 采集器配置不当,未限制读取速率或内存
- 单行日志过大(如打印了大对象 JSON),导致处理阻塞
- 下游(ES)写入慢,采集器缓冲队列满,占用大量内存
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 查看资源占用
top -c | grep filebeat
du -sh /var/log/app/*
# 2. 检查日志文件大小
ls -lh /var/log/app/*.log
# 发现单个文件几分钟内增长到 GB 级
# 3. 查看采集器日志
/var/log/filebeat/filebeat
# 搜索 "harvester", "publisher", "backoff"
# 4. 检查下游状态
# ES 集群是否 Red/Yellow?写入延迟是否高?
|
使用命令:
1
2
3
4
5
| # 统计日志行数
wc -l /var/log/app/*.log
# 查看大行
awk 'length($0)>10000' /var/log/app/app.log | head
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # 1. 紧急降级日志级别
# 通过配置中心动态将日志级别改为 ERROR
# 或临时注释掉高频打印的代码
# 2. 限制采集器资源
# filebeat.yml
processors:
- add_host_metadata: ~
output.elasticsearch:
bulk_max_size: 2048 # 减小批次
queue_mem_events: 4096 # 限制队列
filebeat.inputs:
- type: log
close_inactive: 5m
clean_inactive: 72h
max_bytes: 10MB # 限制单行最大字节
# 3. 清理磁盘
# 轮转或删除过大的日志文件
logrotate -f /etc/logrotate.d/app
# 4. 扩容下游
# 增加 ES 节点或 Kafka Partition
|
预防措施:
- 日志规范:生产环境严禁打印 DEBUG 日志和大对象。
- 资源限制:采集器必须限制 CPU 和内存使用。
- 异步写入:应用日志应采用异步追加,避免阻塞业务。
- 采样:对高频日志进行采样打印(如每 100 条打 1 条)。
经验总结:
- 日志系统本是辅助,配置不当会变成杀手。
- 控制日志量是运维的基本功,失控的日志能写满磁盘。
- 采集器要有背压处理机制,不能无限吃内存。
事故 90:备份数据损坏且无验证(终极灾难复盘)#
(注:虽然事故 46 也涉及备份损坏,但事故 90 侧重于管理流程缺失和绝望场景下的应对,作为 100 个事故的压轴警示)
事故现象:
- 核心数据库遭遇勒索病毒加密,所有在线数据不可用
- 决定从冷备份恢复,发现最近 3 个月的磁带/云备份文件全部损坏
- 报错:
Tape read error, Checksum mismatch, File header corrupted - 公司面临停业风险,数据恢复希望渺茫
事故原因:
- 盲目信任:备份系统常年显示“Success”,但从未进行过恢复演练
- 介质老化:磁带库机械故障或硬盘静默错误,未被监测
- 软件 Bug:备份软件版本存在已知 Bug,写入即损坏
- 流程缺失:没有“定期恢复验证”的强制性流程
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| # 1. 尝试多种恢复手段
# 尝试不同版本的备份软件
# 尝试在不同硬件上读取磁带
# 2. 检查备份日志历史
# 发现过去一年的日志全是 "Backup completed successfully"
# 但没有任何 "Verify" 或 "Restore Test" 的记录
# 3. 检查介质健康
smartctl -a /dev/st0
# 发现大量硬件错误
# 4. 追溯根源
# 询问运维团队:上次恢复演练是什么时候?
# 回答:三年前,或者“从来没做过”
|
使用命令:
1
2
| # 尝试强制读取(死马当活马医)
dd if=/dev/st0 of=backup.img bs=64k conv=noerror,sync
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| # 1. 专业数据恢复
# 联系专业数据恢复公司(费用极高,成功率不确定)
# 尝试芯片级恢复或磁信号重组
# 2. 拼凑数据
# 从各种零散来源收集数据:
# - 从库的残存数据
# - 开发环境的导出文件
# - 第三方合作伙伴的数据副本
# - 用户本地的缓存数据
# 3. 业务重构
# 如果数据无法恢复,考虑业务重启
# 公开道歉,赔偿用户,重新开始
# 4. 重建备份体系(痛定思痛)
# 引入自动化恢复演练系统
# 实施 3-2-1 备份策略
# 定期进行“消防演习”
|
预防措施:
- 铁律:没有经过恢复验证的备份等于没有备份。
- 自动化演练:每周自动拉起临时环境,恢复最新备份,运行测试用例,成功后销毁。
- 多重校验:备份时生成 Checksum,恢复前校验 Checksum。
- 多地异构:使用不同介质(磁盘 + 磁带 + 云)、不同厂商、不同地点的备份。
经验总结:
- 这是运维人员的噩梦,也是职业生涯的终点。
- 备份的成功不在于“写完”,而在于“能读”。
- 流程和文化比技术更重要:必须建立“怀疑一切,验证一切”的文化。
- 不要等到灾难发生才后悔没做演练。
事故 91:告警风暴导致“狼来了”事故#
事故现象:
- 监控平台在 5 分钟内发送 2000+ 条告警短信/电话
- 运维人员手机被打爆,被迫关机或静音
- 真正的核心故障(数据库宕机)被淹没在海量无关告警中
- 故障发现时间延迟 2 小时,造成重大资损
事故原因:
- 监控粒度太细:每个 Pod、每个接口、每个实例都独立告警
- 缺乏告警收敛/抑制机制:一个节点宕机触发上百条关联告警
- 阈值设置过敏感:CPU 瞬间抖动 1% 也触发 P0 告警
- 无分级通知机制:所有告警无论轻重都打电话
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 统计告警来源
grep "ALERT" /var/log/alertmanager.log | awk '{print $NF}' | sort | uniq -c | sort -rn | head -20
# 发现 80% 告警来自同一个集群的 NodeReady 检查
# 2. 分析告警关联性
# 发现:NodeDown -> PodUnready -> ServiceEndpointMissing -> RequestTimeout -> 5xxError
# 本质上是一个根因,却触发了 5 层告警
# 3. 查看通知渠道负载
# 短信网关返回 "Rate Limit Exceeded"
# 电话队列堆积超过 500 通
# 4. 确认核心故障被忽略
# 在 2000 条告警中,只有一条是 "MySQL Master Down",被挤到第 1583 条
|
使用命令:
1
2
3
4
5
6
7
| # 模拟告警风暴测试
for i in {1..1000}; do
curl -X POST http://alertmanager/api/v1/alerts -d "[{\"labels\":{\"alertname\":\"Test$i\"}}]"
done
# 查看告警分组情况
curl http://alertmanager/api/v1/status
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
| # 1. 实施告警收敛(Alertmanager 配置)
group_wait: 30s # 等待 30 秒收集同一组告警
group_interval: 5m # 5 分钟内相同告警只发一次
repeat_interval: 4h # 4 小时内不重复发送
# 按业务线、集群、严重级别分组
group_by: ['alertname', 'cluster', 'severity']
# 2. 设置抑制规则(Inhibition Rules)
# 如果 "ClusterDown" 触发,则抑制该集群下所有 "PodDown", "ServiceDown" 告警
inhibit_rules:
- source_match:
severity: 'critical'
alertname: 'ClusterDown'
target_match:
alertname: 'PodDown'
equal: ['cluster']
# 3. 告警分级通知
# P0 (Critical): 电话 + 短信 + IM (仅核心指标:DB 宕机、全站不可用)
# P1 (Warning): 短信 + IM (单服务异常、延迟高)
# P2 (Info): 仅 IM (资源使用率高、非核心错误)
# 4. 动态阈值与智能告警
# 引入机器学习算法(如 3-Sigma),识别异常波动而非固定阈值
# 只有持续 N 分钟异常才触发
# 5. 紧急止血
# 暂时关闭非核心告警通道
# 人工置顶核心故障工单
|
预防措施:
- 告警黄金原则:每条告警都必须有对应的行动项(Actionable),否则就是噪音。
- 定期清洗:每月审查告警规则,删除从未触发或误报率高的规则。
- 值班制度:设立专门的 On-Call 轮值,避免多人同时接收告警。
- 故障演练:模拟大规模故障,验证告警收敛效果。
经验总结:
- 告警不在多,而在准。一条准确的 P0 告警胜过一万条噪音。
- 必须建立告警抑制和收敛机制,防止“雪崩式”通知。
- 运维人员的注意力是最宝贵资源,不能被浪费。
事故 92:备份文件损坏且无验证事故#
事故现象:
- 生产数据库磁盘崩溃,数据全丢
- 紧急恢复时,发现最近 7 天的备份文件无法解压(CRC 校验失败)
- 追溯到 30 天前的备份,数据丢失一个月
- 公司面临巨额赔偿和信任危机
事故原因:
- 备份脚本只负责“执行”,不负责“验证”
- 磁盘存在静默错误(Bit Rot),备份写入时已损坏
- 备份文件未做校验和(Checksum)
- 长期未进行恢复演练,盲目相信备份成功日志
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 尝试恢复
gunzip backup_20240320.sql.gz
# 报错:gzip: stdin: invalid compressed data--format violated
# 2. 检查备份日志
cat /var/log/backup.log | grep "Success"
# 显示每天都是 "Backup completed successfully"
# 但脚本只检查了 mysqldump 退出码,没检查文件完整性
# 3. 检查磁盘健康
smartctl -a /dev/sdb | grep -i "reallocated\|pending"
# 发现大量重映射扇区
# 4. 追溯历史备份
for f in /backup/*.gz; do gunzip -t $f && echo "$f OK" || echo "$f FAILED"; done
# 发现过去 30 天备份全部损坏
|
使用命令:
1
2
3
4
5
6
| # 生成并验证校验和
md5sum backup.sql > backup.sql.md5
md5sum -c backup.sql.md5
# 测试恢复(干跑)
mysql --defaults-file=/dev/null --simulate-backup < backup.sql
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| # 1. 紧急数据抢救
# 联系专业数据恢复公司尝试修复磁盘镜像
# 从只读从库(如果有)导出数据
# 2. 重建备份体系
# 修改备份脚本,增加校验步骤
mysqldump ... | gzip > backup.sql.gz
if [ $? -eq 0 ]; then
md5sum backup.sql.gz > backup.sql.gz.md5
# 尝试本地试恢复
gunzip -c backup.sql.gz | mysql --defaults-file=/dev/null -u root -e "SELECT 1"
if [ $? -eq 0 ]; then
echo "Backup Verified OK"
# 上传到异地存储
aws s3 cp backup.sql.gz s3://bucket/backup/
else
echo "Backup Verification FAILED!"
exit 1
fi
fi
# 3. 引入多重备份
# 本地磁盘 + 异地对象存储 + 磁带库
# 不同存储介质降低同时损坏概率
# 4. 自动化恢复演练
# 每周日凌晨自动拉起临时实例,恢复最新备份,运行测试用例
# 成功后销毁实例,失败则立即告警
|
预防措施:
- 铁律:没有经过恢复验证的备份 = 没有备份。
- 校验和:所有备份文件必须生成并保存 Checksum。
- 定期演练:至少每季度进行一次真实环境的恢复演练。
- 多地冗余:遵循 3-2-1 备份原则(3 份副本,2 种介质,1 个异地)。
经验总结:
- 备份的成功不在于“写完”,而在于“能读”。
- 自动化验证是备份系统的核心组件,不可或缺。
- 灾难发生时,唯一能救你的是上周刚演练过的备份。
事故 93:灾备演练引发真实故障事故#
事故现象:
- 计划内进行“主备切换演练”
- 切换过程中,主库被意外清空,备库数据同步覆盖主库(空数据)
- 双向同步导致所有生产数据被擦除
- 演练变成真实灾难
事故原因:
- 演练方案缺陷:未隔离生产流量,未停止双向同步
- 操作失误:在备库执行了
DROP DATABASE,触发了同步机制 - 权限过大:演练账号拥有生产库
DROP 权限 - 缺乏“熔断”机制:发现异常未及时停止同步
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| -- 1. 检查数据状态
SHOW TABLES;
-- 结果为空
-- 2. 查看 Binlog
mysqlbinlog mysql-bin.000XXX | grep -i "DROP"
-- 发现来自备库 IP 的 DROP 语句
-- 3. 检查同步状态
SHOW SLAVE STATUS\G
-- 发现同步仍在进行,且正在 replay 删除操作
-- 4. 追溯操作记录
audit_log | grep "DROP"
-- 定位到演练人员执行的命令
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 紧急切断同步
STOP SLAVE;
RESET SLAVE ALL;
# 物理断开网络连接
# 2. 停止所有写入
# 开启全局只读
SET GLOBAL read_only = ON;
# 3. 数据恢复
# 从异地冷备份恢复(参考事故 92)
# 耗时 12 小时
# 4. 复盘与问责
# 暂停所有自动同步任务
# 审查所有高权限账号
|
预防措施:
- 演练隔离:演练必须在完全隔离的环境或使用影子流量进行。
- 权限最小化:演练账号严禁拥有
DROP、TRUNCATE 等高危权限。 - 双人复核:高危操作必须双人确认(Four-eyes principle)。
- 同步单向化:生产环境严禁随意开启双向同步,除非有严格冲突检测。
- 预案审批:演练方案必须经过架构师和安全团队严格评审。
经验总结:
- 演练是把双刃剑,准备不足的演练比不演练更危险。
- 生产环境的任何变更(包括演练)都要有“一键回滚”和“紧急熔断”。
- 敬畏生产环境,永远不要高估人的可靠性。
事故 94:第三方依赖服务故障(供应链风险)#
事故现象:
- 核心下单功能不可用
- 内部服务正常,但调用“短信验证码”接口超时
- 由于代码中未做降级,整个下单流程被阻塞
- 用户无法注册、无法登录、无法下单
事故原因:
- 强依赖第三方服务(短信商),无备用方案
- 代码中同步调用第三方接口,且无超时限制(默认无限等待)
- 第三方服务商发生区域性故障
- 未配置熔断器,拖垮自身线程池
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| # 1. 链路追踪
# SkyWalking 显示 span 卡在 "SendSMS" 环节,耗时 > 60s
# 2. 检查线程池
jstack <PID> | grep -c "WAITING"
# 发现 200 个线程全部阻塞在 HTTP 请求上
# 3. 测试第三方接口
curl -v --connect-timeout 5 https://sms-provider.com/api
# 连接超时
# 4. 查看代码
grep -A 10 "sendSms" OrderService.java
# 发现无 timeout 配置,无 try-catch 降级逻辑
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
| // 1. 紧急热修复(Hotfix)
// 通过配置中心动态开启“降级开关”
if (config.isSmsFallbackEnabled()) {
// 跳过短信验证,直接返回成功(仅限测试环境或白名单)
// 或者切换到备用短信商
return sendSmsViaProviderB(phone, code);
} else {
return sendSmsViaProviderA(phone, code);
}
// 2. 代码重构(根本解决)
// 添加超时
RequestConfig config = RequestConfig.custom()
.setConnectTimeout(3000)
.setSocketTimeout(3000)
.build();
// 添加熔断
@CircuitBreaker(name = "smsService", fallbackMethod = "smsFallback")
public void sendSms(...) { ... }
public void smsFallback(...) {
// 记录日志,发送异步消息稍后重试
// 或者提示用户“稍后重试”
}
// 3. 多活供应商
// 接入 2-3 家短信服务商,自动故障切换
|
预防措施:
- 依赖治理:梳理所有外部依赖,区分核心与非核心。
- 超时与熔断:所有外部调用必须设置超时,必须配备熔断降级。
- 多供应商策略:关键依赖(短信、支付、地图)必须有 Plan B。
- 异步解耦:非实时依赖(如发短信、发邮件)改为异步消息队列处理。
经验总结:
- 你的系统强度取决于最弱的那个第三方依赖。
- 永远不要信任外部服务,假设它们随时会挂。
- 降级不是失败,而是为了保住核心业务的生存。
事故 95:API 版本兼容导致客户端大面积崩溃#
事故现象:
- APP 新版本发布后,旧版本用户(占 60%)无法打开首页
- 后端接口返回字段结构变更,旧客户端解析崩溃(Crash)
- 应用商店评分骤降至 1 星
事故原因:
- 后端接口修改未保持向后兼容(Breaking Change)
- 移除旧字段未通知前端
- 缺乏 API 版本管理策略
- 灰度发布范围过小,未能覆盖旧版本用户
排查过程:
1
2
3
4
5
6
7
8
9
| # 1. 查看崩溃日志
# Firebase/Crashlytics 显示:JsonParseException: Unrecognized field "new_field"
# 2. 对比接口文档
# v1.0 接口返回 { "name": "abc" }
# v1.1 接口返回 { "userName": "abc", "version": 2 } (字段名变了)
# 3. 检查流量分布
# 发现 60% 请求来自旧版本 APP,全部报错
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| # 1. 紧急回滚接口
# 恢复旧版接口逻辑,兼容新旧字段
if (clientVersion < 2.0) {
response.setName(response.getUserName());
}
# 2. 实施 API 版本控制
# URL 路径版本化:/api/v1/users, /api/v2/users
# Header 版本化:Accept-Version: 1.0
# 3. 强制升级策略
# 对于严重不兼容版本,通过接口返回强制升级标志
{ "code": 400, "msg": "Please update app", "forceUpdate": true }
# 4. 灰度发布
# 先对 5% 旧版本用户开放新接口,观察崩溃率
|
预防措施:
- 兼容性原则:接口只能新增字段,严禁修改/删除旧字段。
- 版本管理:严格执行 API 版本控制,废弃接口需提前公告并保留过渡期。
- 契约测试:引入 Pact 等工具,确保前后端契约一致。
- 监控崩溃率:实时监控客户端崩溃率,异常升高自动回滚。
经验总结:
- 后端接口的微小变动,可能导致百万级客户端崩溃。
- 兼容性是 API 设计的生命线。
- 永远不要低估旧版本用户的比例。
事故 96:数据库大版本升级失败事故#
事故现象:
- 计划内 MySQL 5.7 升级 8.0
- 升级后启动失败,报错字符集不兼容
- 回滚时发现数据目录已被修改,无法降回 5.7
- 业务中断 12 小时
事故原因:
- 未在测试环境充分验证升级路径
- 忽略字符集、排序规则(Collation)变更
- 升级脚本未备份原数据目录
- 回滚方案未经过实操验证
排查过程:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 查看启动错误
tail -f /var/log/mysql/error.log
# [ERROR] Table 'db.table' uses an unknown collation 'utf8mb4_0900_ai_ci'
# 2. 检查数据文件
ls -la /var/lib/mysql/
# 发现 ibdata1 已被 8.0 格式修改
# 3. 尝试回滚
yum install mysql-5.7
systemctl start mysqld
# 启动失败:InnoDB 版本不匹配
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
| # 1. 紧急恢复
# 从升级前的冷备份恢复数据
# 重新部署 5.7 版本
# 2. 重新规划升级
# 在测试环境完整演练升级 + 回滚流程
# 修复所有兼容性警告(字符集、SQL 语法、保留字)
# 3. 采用蓝绿部署
# 搭建一套新的 8.0 集群
# 通过 DTS/CDC 同步数据
# 验证无误后切换流量
# 保留旧集群作为回滚底牌
|
预防措施:
- 充分测试:升级前必须在仿真环境进行全量回归测试。
- 备份先行:升级前必须进行全量备份,并验证可恢复。
- 蓝绿策略:核心数据库升级严禁原地升级,采用双集群切换。
- 回滚演练:必须验证“降级”路径是否可行。
经验总结:
- 数据库升级是高风险操作,能不动就不动。
- 原地升级是运维的大忌,蓝绿切换才是正道。
- 回滚方案必须像升级方案一样被重视。
事故 97:网络分区(Split-Brain)导致数据不一致#
事故现象:
- 机房 A 与机房 B 之间光纤中断
- 两边集群都认为自己是 Master,继续接受写入
- 网络恢复后,数据严重冲突,部分数据丢失
事故原因:
- 集群仲裁机制配置错误(允许少数派成为 Master)
- 未配置脑裂保护(Fencing)
- 应用层未处理写冲突
排查过程:
1
2
3
4
5
6
7
| # 1. 检查集群状态
# 机房 A: Role=Master, Quorum=Yes (误判)
# 机房 B: Role=Master, Quorum=Yes (误判)
# 2. 比对数据
# 发现同一 ID 的记录在两边有不同的值
# 时间戳显示在网络中断期间双方都有写入
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
| # 1. 紧急停写
# 手动将其中一个机房设为只读
SET GLOBAL read_only = ON;
# 2. 数据合并
# 以时间戳最新为准(可能丢失部分数据)
# 或人工介入逐条核对
# 3. 修复配置
# 配置法定人数(Quorum):N/2 + 1
# 启用 STONITH (Shoot The Other Node In The Head) 机制
# 当检测到分区时,强制 fencing 掉另一方(断电/断网)
|
预防措施:
- 仲裁机制:严格配置 Quorum,确保只有多数派能当选 Master。
- Fencing 机制:硬件或软件层面的强制隔离,防止双写。
- 应用层冲突检测:使用版本号或向量时钟(Vector Clock)检测冲突。
- 避免双活写:除非必要,否则采用主从模式,禁止双 Master 写入。
经验总结:
- 网络分区是分布式系统的常态,必须按“必然发生”来设计。
- CAP 定理中,涉及金钱数据通常选择 CP(一致性),牺牲可用性。
- 脑裂的代价远高于短暂的服务不可用。
事故 98:资源配额(Quota)耗尽导致新业务无法上线#
事故现象:
- 新微服务部署失败,Pod 一直 Pending
- 报错:
Failed to create pod: exceeded quota - 现有业务不受影响,但无法扩容
- 紧急会议发现 Namespace 配额已满
事故原因:
- K8s Namespace 配额(CPU/Memory/Pod 数)设置过小
- 历史遗留僵尸 Pod 占用配额
- 缺乏配额监控和预警
- 资源申请未规划,随意创建
排查过程:
1
2
3
4
5
6
7
8
9
10
11
| # 1. 查看配额状态
kubectl describe quota -n production
# 输出:Used: 99%, Hard: 100%
# 2. 查找占用资源大户
kubectl top pods -n production --sort-by=cpu
# 发现几个测试 Pod 长期运行,占用大量资源
# 3. 检查僵尸资源
kubectl get pods -n production | grep "Completed\|Error"
# 发现 50 个已完成但未删除的 Pod
|
解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| # 1. 清理僵尸资源
kubectl delete pods --field-selector=status.phase==Succeeded -n production
kubectl delete jobs --field-selector=status.successful=1 -n production
# 2. 临时扩容配额
kubectl patch quota compute-resources -n production -p '{"spec":{"hard":{"cpu":"200", "memory":"400Gi"}}}'
# 3. 优化资源申请
# 调整 Requests/Limits,避免过度预留
resources:
requests:
cpu: "100m"
memory: "128Mi"
limits:
cpu: "500m"
memory: "512Mi"
# 4. 建立配额管理流程
# 新业务上线需评估配额
# 定期审计资源使用率
|
预防措施:
- 配额监控:监控配额使用率,>80% 即告警。
- 定期清理:自动化脚本定期清理 Completed/Error 状态的 Pod。
- 资源规划:根据实际负载动态调整 Requests/Limits。
- 多 Namespace 隔离:按业务线划分 Namespace,避免相互影响。
经验总结:
- 配额是保护集群的盾牌,但也可能成为业务的枷锁。
- 僵尸资源是配额的隐形杀手。
- 资源管理需要精细化运营。
事故 99:级联故障(雪崩)终极复盘#
事故现象:
- 某边缘服务(如“头像上传”)响应变慢
- 导致调用它的“用户中心”线程阻塞
- 进而导致“订单服务”获取用户信息超时
- 最终导致“网关”所有连接耗尽,全站不可用
- 历时 4 小时才恢复
原因链条:
- 诱因:对象存储(OSS)抖动,头像上传慢。
- 传播:用户中心未设置超时,线程池满。
- 放大:订单服务重试机制,流量翻倍。
- 爆发:网关连接池耗尽,拒绝所有请求。
- 失效:熔断器未配置或阈值过高,未起作用。
深度解决方案:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| # 1. 全链路超时设置
# 每一层调用都必须有超时,且上层 < 下层
Gateway Timeout: 3s
Order Service Timeout: 2s
User Service Timeout: 1s
OSS Timeout: 0.5s
# 2. 舱壁隔离
# 为每个依赖分配独立线程池
# 头像上传失败不影响下单核心流程
# 3. 智能熔断
# 错误率 > 50% 或 响应时间 > 1s 自动熔断
# 熔断后直接返回默认值(如默认头像)
# 4. 限流降级
# 网关层针对非核心接口限流
# 保护核心交易链路
# 5. 异步化
# 头像上传改为异步,不阻塞主流程
|
经验总结:
- 雪崩始于微末,终于全局。
- 系统的韧性取决于最弱一环的防护能力。
- 超时、熔断、限流、降级是分布式系统的“四大金刚”,缺一不可。
- 定期进行混沌工程(Chaos Engineering)演练,主动注入故障。
事故 100:黑天鹅事件(未知故障)#
事故现象:
- 无任何预兆,系统突然全面瘫痪
- 监控全绿(因为监控也挂了)或全是乱码
- 日志无法写入,SSH 无法连接
- 所有已知排查手段失效
可能原因:
- 底层虚拟化平台崩溃(云厂商底层故障)
- 机房整体断电/火灾/水灾
- 遭受国家级网络攻击(APT)
- 宇宙射线导致内存位翻转(极罕见但存在)
应对策略(非技术层面):
- 启动最高级别应急响应(War Room)
- 召集所有核心技术骨干
- 建立专用沟通频道(避开故障系统)
- 指定唯一指挥官(Commander)
- 信息收集与通报
- 联系云厂商/IDC 运营商确认基础设施状态
- 每 15 分钟向管理层和用户通报进度(即使暂无进展)
- 尝试带外管理(Out-of-Band)
- 通过 IPMI/iDRAC 物理重启服务器
- 切换至备用数据中心/云厂商
- 业务连续性计划(BCP)
- 启用手工操作流程(如线下记账)
- 挂出“维护中”公告,引导用户预期
- 事后深度复盘
- 不追究个人责任,聚焦流程改进
- 更新应急预案,填补盲区
经验总结:
- 面对黑天鹅,技术往往无能为力,依靠的是流程、组织和预案。
- 透明的沟通比快速修复更能赢得信任。
- 永远保持谦卑,系统越复杂,未知风险越大。
- 韧性架构的目标不是永不故障,而是故障后能快速恢复。