跳到主要内容

故障排查手册

一、渲染失败问题

1. FFmpeg渲染错误

错误1: "No such file or directory"

# 错误信息
[error] input.mp4: No such file or directory

# 原因
1. 文件路径错误
2. 文件不存在
3. 权限不足

# 解决方案
# 检查文件是否存在
ls -l input.mp4

# 检查权限
chmod 644 input.mp4

# 使用绝对路径
ffmpeg -i /absolute/path/input.mp4 output.mp4

错误2: "Invalid data found when processing input"

# 错误信息
[error] Invalid data found when processing input

# 原因
1. 文件损坏
2. 格式不支持
3. 编码异常

# 解决方案
# 检查文件完整性
ffprobe input.mp4

# 重新编码
ffmpeg -i input.mp4 -c:v libx264 -c:a aac output.mp4

# 使用更宽容的解码器
ffmpeg -err_detect ignore_err -i input.mp4 output.mp4

错误3: "Encoder not found"

# 错误信息
Encoder 'h264_nvenc' not found

# 原因
FFmpeg编译时未包含该编码器

# 解决方案
# 检查可用编码器
ffmpeg -encoders | grep h264

# 使用CPU编码器
ffmpeg -i input.mp4 -c:v libx264 output.mp4

# 重新编译FFmpeg (包含NVENC支持)
./configure --enable-nvenc
make && make install

2. 内存不足

错误信息:

MemoryError: Unable to allocate array

Killed (进程被系统终止)

诊断:

# 检查内存使用
free -h

# 检查渲染进程内存
ps aux | grep ffmpeg
# 或
ps aux | grep python

# 查看OOM日志
dmesg | grep -i "out of memory"

解决方案:

# 方案1: 分段处理
def render_long_video(input_file, output_file):
"""将长视频分段渲染,避免内存溢出"""
duration = get_video_duration(input_file)
segment_duration = 300 # 5分钟一段

segments = []
for start in range(0, duration, segment_duration):
segment_file = f"segment_{start}.mp4"

# 渲染片段
ffmpeg_cmd = f"""
ffmpeg -ss {start} -t {segment_duration} -i {input_file} \
-c copy {segment_file}
"""
subprocess.run(ffmpeg_cmd, shell=True)
segments.append(segment_file)

# 合并片段
concat_segments(segments, output_file)

# 方案2: 降低分辨率/帧率
ffmpeg -i input.mp4 -s 720x1280 -r 24 output.mp4

# 方案3: 增加swap空间
sudo fallocate -l 8G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile

3. 渲染超时

现象: 渲染时间超过预期,或任务卡死

诊断:

# 查看渲染进程
ps aux | grep ffmpeg

# 查看进程状态 (D=不可中断睡眠,R=运行,S=睡眠)
ps -eo pid,stat,command | grep ffmpeg

# 查看磁盘I/O
iostat -x 1

# 查看CPU使用
top -p <pid\>

常见原因与解决:

原因1: 磁盘I/O瓶颈
- 症状: 进程处于D状态,iowait高
- 解决: 使用SSD,减少并发任务

原因2: 编码参数不当
- 症状: CPU 100%,渲染极慢
- 解决: 调整preset (slow → medium/fast)

原因3: 源文件问题
- 症状: 处理特定文件时卡死
- 解决: 检查源文件,尝试重新编码

原因4: 死锁
- 症状: 进程挂起,无CPU占用
- 解决: kill后重启,检查代码逻辑

超时处理代码:

import subprocess
import signal

def render_with_timeout(cmd, timeout=1800):
"""带超时的渲染"""
try:
process = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
preexec_fn=os.setsid
)

stdout, stderr = process.communicate(timeout=timeout)

if process.returncode != 0:
raise Exception(f"Render failed: {stderr.decode()}")

return True

except subprocess.TimeoutExpired:
# 超时,终止进程组
os.killpg(os.getpgid(process.pid), signal.SIGTERM)
raise Exception(f"Render timeout after {timeout}s")

二、API错误

1. 认证错误

401 Unauthorized

{
"error": {
"code": "unauthorized",
"message": "Invalid API key"
}
}

排查步骤:

# 检查API Key是否正确
curl -H "Authorization: Bearer YOUR_API_KEY" \
https://api.videomatic.com/v1/videos

# 常见错误
1. API Key复制时多了空格
2. 使用了已删除/过期的Key
3. Header拼写错误 (Authorization vs Authorisation)
4. Bearer关键字缺失

# 解决
1. 重新生成API Key
2. 检查环境变量
echo $API_KEY
3. 查看日志确认收到的Header

2. 限流错误

429 Too Many Requests

{
"error": {
"code": "rate_limit_exceeded",
"message": "API调用次数超限",
"retry_after": 3600
}
}

处理方案:

import time
import requests

def api_call_with_retry(url, max_retries=3):
"""带重试的API调用"""
for attempt in range(max_retries):
response = requests.get(url)

if response.status_code == 200:
return response.json()

elif response.status_code == 429:
# 限流,等待后重试
retry_after = int(response.headers.get('Retry-After', 60))
print(f"Rate limited, waiting {retry_after}s...")
time.sleep(retry_after)

else:
raise Exception(f"API error: {response.status_code}")

raise Exception("Max retries exceeded")

3. 参数错误

400 Bad Request

{
"error": {
"code": "invalid_parameter",
"message": "template_id is required",
"field": "template_id"
}
}

常见错误:

# ❌ 错误1: 缺少必需参数
data = {
"data": {"title": "Test"}
# 缺少 template_id
}

# ✅ 正确
data = {
"template_id": "template_abc123",
"data": {"title": "Test"}
}

# ❌ 错误2: 参数类型错误
data = {
"template_id": 123, # 应该是字符串
"data": {"price": "99"} # 应该是数字
}

# ✅ 正确
data = {
"template_id": "template_abc123",
"data": {"price": 99}
}

# ❌ 错误3: JSON格式错误
# 多了逗号,缺少引号等

调试技巧:

import json

# 发送前验证JSON
try:
json_str = json.dumps(data)
print(json_str) # 检查输出
except TypeError as e:
print(f"JSON序列化失败: {e}")

# 使用JSON schema验证
from jsonschema import validate

schema = {
"type": "object",
"required": ["template_id", "data"],
"properties": {
"template_id": {"type": "string"},
"data": {"type": "object"}
}
}

validate(instance=data, schema=schema)

三、性能问题

1. 渲染速度慢

问题: 单个视频渲染超过10分钟

诊断流程:

┌─────────────────┐
│ 测量渲染时间 │
└────────┬────────┘

\>10分钟?

┌────┴────┐
是 否
│ │
┌───┴───┐ 正常结束
│检查CPU │
│使用率 │
└───┬───┘

\\<50%?

┌───┴───┐
│检查I/O │
│是否慢 │
└───┬───┘

iowait高?

┌───┴────┐
│使用SSD │
│或减少并发│
└────────┘

优化检查清单:

# 1. 检查硬件
# CPU
lscpu | grep "Model name"
# GPU
nvidia-smi

# 2. 检查FFmpeg参数
# 使用GPU?
ffmpeg -hwaccel cuda ...
# preset合理?
-preset medium # 不要用slow

# 3. 检查并发数
# 并发太多会抢占资源
ps aux | grep ffmpeg | wc -l

# 4. 检查磁盘
# 读写速度
hdparm -Tt /dev/sda
# 或
dd if=/dev/zero of=testfile bs=1G count=1 oflag=direct

2. 队列积压

现象: 任务队列长度持续增长,处理不过来

诊断:

# Celery队列长度
celery -A app inspect active_queues

# Redis队列长度
redis-cli llen celery:queue_name

# 查看worker状态
celery -A app inspect active
celery -A app inspect stats

原因分析:

1. Worker数量不足
- 解决: 增加worker

2. Worker卡死
- 解决: 重启worker,检查日志

3. 任务执行时间过长
- 解决: 优化渲染参数,使用GPU

4. 突发流量
- 解决: 自动扩容,限流

扩容方案:

# 手动增加worker
celery -A app worker -Q default -c 8 & # 8个并发

# Docker自动扩容
docker-compose scale worker=10

# Kubernetes自动扩容
kubectl autoscale deployment video-worker \
--min=2 --max=20 --cpu-percent=70

3. 数据库慢查询

现象: API响应慢,数据库CPU高

诊断:

-- PostgreSQL慢查询
SELECT pid, now() - query_start as duration, query
FROM pg_stat_activity
WHERE state = 'active'
AND now() - query_start \> interval '5 seconds'
ORDER BY duration DESC;

-- 查看锁等待
SELECT * FROM pg_locks WHERE NOT granted;

-- MySQL慢查询
SHOW PROCESSLIST;
SELECT * FROM information_schema.processlist
WHERE time \> 5;

常见问题:

-- 问题1: 缺少索引
-- 症状: WHERE/JOIN字段无索引
EXPLAIN SELECT * FROM videos WHERE user_id = 123;
-- 看到 Seq Scan (全表扫描) 说明缺索引

-- 解决
CREATE INDEX idx_videos_user_id ON videos(user_id);

-- 问题2: 索引未使用
-- 原因: 函数包裹字段
SELECT * FROM videos WHERE LOWER(status) = 'completed'; -- 不走索引

-- 解决
SELECT * FROM videos WHERE status = 'completed'; -- 走索引

-- 问题3: N+1查询
-- 在代码层面解决 (使用select_related/prefetch_related)

四、服务故障

1. 服务无响应

现象: API请求超时或返回502/504

排查步骤:

# 1. 检查服务是否运行
systemctl status video-api
# 或
ps aux | grep gunicorn

# 2. 检查端口监听
netstat -tlnp | grep 8000

# 3. 检查进程健康
curl http://localhost:8000/health

# 4. 检查负载
uptime
top

# 5. 检查日志
tail -f /var/log/video-api/error.log
journalctl -u video-api -f

常见原因:

1. 进程崩溃
- 解决: 重启服务,查看崩溃日志

2. 内存耗尽,被OOM杀死
- 解决: 增加内存,优化代码

3. 端口被占用
- 解决: 杀死占用进程或换端口

4. 防火墙阻止
- 解决: 开放端口
sudo ufw allow 8000/tcp

5. 依赖服务故障 (数据库/Redis)
- 解决: 检查依赖服务状态

2. 数据库连接失败

错误信息:

OperationalError: could not connect to server: Connection refused

排查:

# 1. 数据库是否运行
systemctl status postgresql
# 或
docker ps | grep postgres

# 2. 能否连接
psql -h localhost -U dbuser -d dbname

# 3. 检查连接数
SELECT count(*) FROM pg_stat_activity;
SELECT max_connections FROM pg_settings WHERE name='max_connections';

# 4. 检查配置
# settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': 'videomatic',
'USER': 'dbuser',
'PASSWORD': '***',
'HOST': 'localhost', # 检查这里
'PORT': '5432',
}
}

解决方案:

# 连接数耗尽
# 增加max_connections
# postgresql.conf
max_connections = 200

# 或使用连接池
# settings.py
DATABASES['default']['CONN_MAX_AGE'] = 600 # 10分钟

# 使用pgBouncer连接池
sudo apt-get install pgbouncer

3. Redis连接失败

错误信息:

redis.exceptions.ConnectionError: Error connecting to Redis

排查:

# 1. Redis是否运行
systemctl status redis
redis-cli ping # 应返回 PONG

# 2. 检查配置
# settings.py
CELERY_BROKER_URL = 'redis://localhost:6379/0'
# 确认主机名、端口、数据库编号

# 3. 检查内存
redis-cli info memory

# 4. 检查连接数
redis-cli client list | wc -l
redis-cli config get maxclients

解决:

# 内存耗尽
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru

# 连接数过多
# redis.conf
maxclients 10000

# 持久化导致阻塞
# 关闭RDB快照 (如不需要持久化)
save ""

五、快速诊断

问题决策树

用户反馈问题


能复现吗?
/ \
能 不能
│ │
▼ ▼
检查日志 引导用户提供:
│ - 详细步骤
│ - 截图/录屏
│ - 浏览器/设备信息

发现错误?
/ \
有 无
│ │
▼ ▼
错误类型? 加详细日志
│ 再次尝试复现
├─ 渲染失败 → 检查FFmpeg日志
├─ API错误 → 检查请求/响应
├─ 超时 → 检查性能指标
└─ 其他 → 查看stack trace

找到根因


修复 → 验证 → 部署 → 通知用户

日志分析技巧

结构化日志查询:

# 查找特定错误
grep "ERROR" app.log | tail -20

# 查找特定用户的请求
grep "user_id=123" app.log

# 统计错误类型
grep "ERROR" app.log | awk '{print $5}' | sort | uniq -c | sort -rn

# 查找慢请求 (\>5秒)
awk '$NF \> 5000 {print}' access.log

# 实时监控错误
tail -f app.log | grep --color=auto "ERROR"

使用ELK分析:

Kibana查询示例:

# 错误率趋势
level: ERROR
# 按时间聚合

# 最慢的API
response_time \> 5000
# 按endpoint分组

# 特定用户问题
user_id: "123"
# 查看完整请求链路

六、应急预案

1. 服务完全宕机

立即行动:

# 1. 回滚到上一个版本 (如因部署导致)
git checkout <last-stable-commit\>
./deploy.sh

# 2. 重启所有服务
systemctl restart video-api
systemctl restart celery-worker
systemctl restart nginx

# 3. 切换到备用服务器 (如有)
# 修改DNS或负载均衡配置

# 4. 发布公告
# 更新状态页: status.videomatic.com
# 发推特/公众号: "我们正在处理技术问题,预计30分钟内恢复"

根因分析 (事后):

1. 收集证据
- 日志快照
- 监控截图
- 用户反馈

2. 时间线
- 18:00 部署新版本
- 18:05 开始收到错误告警
- 18:10 服务完全不可用
- 18:15 回滚
- 18:20 服务恢复

3. 根因
- 代码bug
- 配置错误
- 资源耗尽

4. 改进措施
- 增加测试覆盖
- 灰度发布
- 增强监控

2. 数据丢失

紧急恢复:

# 1. 停止写入 (防止进一步损坏)
# 关闭API,停止接受新请求

# 2. 评估损失
# 查看最近备份时间
# 计算丢失数据量

# 3. 从备份恢复
# 数据库
pg_restore -d videomatic backup_20250109.dump

# 文件
aws s3 sync s3://backup-bucket/videos /var/lib/videos

# 4. 验证数据完整性
# 运行一致性检查脚本

# 5. 恢复服务
# 逐步开放,监控异常

预防措施:

# 自动备份
# 数据库 (每6小时)
0 */6 * * * pg_dump videomatic \> /backup/db_$(date +\%Y\%m\%d_\%H\%M).sql

# 文件 (每日)
0 2 * * * aws s3 sync /var/lib/videos s3://backup-bucket/videos

# 异地备份
# 使用AWS S3跨区域复制

3. 安全事件

SQL注入攻击:

# ❌ 危险代码
query = f"SELECT * FROM users WHERE id = {user_input}"
cursor.execute(query)

# ✅ 安全代码
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_input,))

# Django ORM (自动防注入)
User.objects.filter(id=user_input)

DDoS攻击:

# 1. 启用Cloudflare/CDN DDoS防护
# 2. 限流
# nginx.conf
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20;

# 3. 封禁恶意IP
# 临时
iptables -A INPUT -s 1.2.3.4 -j DROP

# 永久 (fail2ban)
# /etc/fail2ban/jail.local
[video-api]
enabled = true
filter = video-api
logpath = /var/log/video-api/access.log
maxretry = 100
findtime = 60
bantime = 3600

七、监控告警

关键告警规则

# Prometheus alerting rules
groups:
- name: critical
rules:
# 服务不可用
- alert: ServiceDown
expr: up{job="video-api"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "服务宕机"

# 错误率高
- alert: HighErrorRate
expr: rate(http_requests_total{status=~"5.."}[5m]) \> 0.05
for: 5m
labels:
severity: warning
annotations:
summary: "错误率 \> 5%"

# 队列积压严重
- alert: QueueBacklog
expr: celery_queue_length \> 1000
for: 10m
labels:
severity: warning
annotations:
summary: "队列积压 \> 1000"

# 渲染成功率低
- alert: LowRenderSuccessRate
expr: render_success_rate < 0.90
for: 15m
labels:
severity: warning
annotations:
summary: "渲染成功率 \< 90%"

八、常见问题FAQ

Q: 视频渲染一直卡在99%?

A: 通常是在合成音频或写入元数据
- 检查音频文件是否有问题
- 增加超时时间
- 查看FFmpeg详细日志

Q: 批量任务显示完成,但部分视频缺失?

A: 检查:
1. 任务状态表 (是否有failed)
2. 错误日志
3. 存储空间是否已满
4. 文件是否被清理脚本误删

Q: GPU渲染比CPU还慢?

A: 可能原因:
1. 视频分辨率太低 (GPU适合高分辨率)
2. 传输开销大 (CPU→GPU→CPU)
3. GPU驱动版本过旧
解决: 批量处理或提高分辨率

Q: Redis内存持续增长?

A: 检查:
1. 是否设置了过期时间
2. 淘汰策略配置 (maxmemory-policy)
3. 是否有内存泄漏 (Celery result未清理)
解决: 设置合理的TTL和淘汰策略

九、工具箱

诊断脚本

#!/bin/bash
# system_check.sh - 系统健康检查

echo "=== 系统健康检查 ==="

# 1. 服务状态
echo "## 服务状态"
systemctl is-active video-api || echo "❌ API服务异常"
systemctl is-active postgresql || echo "❌ 数据库异常"
systemctl is-active redis || echo "❌ Redis异常"

# 2. 资源使用
echo "## 资源使用"
echo "CPU: $(top -bn1 | grep "Cpu(s)" | awk '{print $2}')"
echo "内存: $(free -h | awk 'NR==2{print $3 "/" $2}')"
echo "磁盘: $(df -h / | awk 'NR==2{print $5}')"

# 3. 队列状态
echo "## 队列状态"
QUEUE_LEN=$(redis-cli llen celery)
echo "队列长度: $QUEUE_LEN"
[ $QUEUE_LEN -gt 1000 ] && echo "⚠️ 队列积压严重"

# 4. 最近错误
echo "## 最近错误 (5分钟内)"
journalctl -u video-api --since "5 minutes ago" | grep ERROR

echo "=== 检查完成 ==="

性能测试脚本

#!/usr/bin/env python3
# bench_render.py - 渲染性能测试

import time
import subprocess
import statistics

def benchmark_render(input_file, iterations=10):
"""测试渲染性能"""
times = []

for i in range(iterations):
start = time.time()

cmd = f"ffmpeg -y -i {input_file} -c:v libx264 -preset medium output_{i}.mp4"
subprocess.run(cmd, shell=True, capture_output=True)

elapsed = time.time() - start
times.append(elapsed)
print(f"第{i+1}次: {elapsed:.2f}秒")

print(f"\n平均: {statistics.mean(times):.2f}秒")
print(f"中位数: {statistics.median(times):.2f}秒")
print(f"标准差: {statistics.stdev(times):.2f}秒")

if __name__ == "__main__":
benchmark_render("sample.mp4", iterations=5)

更新记录

  • 2025-01-09: 初始版本,完成故障排查手册