故障排查手册
一、渲染失败问题
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: 初始版本,完成故障排查手册