Files
AIclinicalresearch/docs/05-部署文档/04-Python微服务-SAE容器部署指南.md
HaHafeng 19f9c5ea93 docs(deployment): Fix 8 critical deployment issues and enhance documentation
Summary of fixes:
- Fix service discovery address (change .sae domain to internal IP)
- Unify timezone configuration (Asia/Shanghai for all services)
- Enhance ECS security group configuration (Redis/Weaviate port binding)
- Add image pull strategy best practices
- Add Python service memory management guidelines
- Update Dify API Key deployment strategy (avoid deadlock)
- Add SSH tunnel for RDS database access
- Add NAT gateway cost optimization explanation

Modified files (7 docs):
- 00-部署架构总览.md (enhanced with 7 sections)
- 03-Dify-ECS部署完全指南.md (security hardening)
- 04-Python微服务-SAE容器部署指南.md (timezone + service discovery)
- 05-Node.js后端-SAE容器部署指南.md (timezone configuration)
- PostgreSQL部署策略-摸底报告.md (timezone best practice)
- 07-关键配置补充说明.md (3 new sections)
- 08-部署检查清单.md (service address fix)

New files:
- 文档修正报告-20251214.md (comprehensive fix report)
- Review documents from technical team

Impact:
- Fixed 3 P0/P1 critical issues (100% connection failure risk)
- Fixed 3 P2 important issues (stability and maintainability)
- Added 2 P3 best practices (developer convenience)

Status: All deployment documents reviewed and corrected, ready for production deployment
2025-12-14 13:25:28 +08:00

38 KiB
Raw Blame History

Python 微服务 SAE 容器部署完全指南

文档版本: v1.1 (修复内网地址和临时文件问题)
创建时间: 2025-12-13
最后修订: 2025-12-13
适用范围: AIclinicalresearch 平台 - Python 微服务extraction_service
目标读者: 运维工程师、后端开发工程师

v1.1 更新日志:

  • 修复:内网地址使用 SAE 控制台显示的真实 IP不猜测域名
  • 优化Dockerfile 系统依赖说明libmupdf-dev 可选)
  • 新增:确保 /tmp 目录可写(大文件临时存储)
  • 完善:功能验证流程和监控指南

📋 文档目录

  1. 为什么选择 SAE 容器部署
  2. 部署架构图
  3. 前置准备清单
  4. Python 服务分析
  5. 依赖优化策略
  6. 构建 Docker 镜像
  7. 部署到 SAE
  8. 测试与验证
  9. 监控与维护
  10. 故障排查
  11. 注意事项与禁忌

为什么选择 SAE 容器部署

SAE 容器部署 vs. SAE Python 运行时

对比维度 SAE Python 运行时 SAE 容器部署 (推荐)
系统依赖 无法安装系统库 完全可控
复杂依赖 PyMuPDF/OpenCV 报错 完美支持
环境一致性 ⚠️ 云上和本地可能不同 本地跑通 = 云上跑通
Nougat (Torch) 版本冲突风险高 轻松支持
部署方式 上传 ZIP 包 推送 Docker 镜像
启动速度 快(< 5秒 较快10-20秒
运维复杂度
推荐度 不推荐 强烈推荐

🎯 核心原因

1. 系统级依赖缺失(致命问题)

# 您的代码使用了这些库:
import fitz  # PyMuPDF → 依赖 libmupdf.so, libfreetype.so
import cv2   # OpenCV → 依赖 libGL.so.1, libgthread-2.0.so
import polars  # Polars → 依赖 libgomp.so

SAE Python 运行时

❌ 只提供标准 Python 环境
❌ 无法执行 apt-get install
❌ 运行时报错ImportError: libGL.so.1: cannot open shared object file

SAE 容器部署

✅ Dockerfile 中自由安装:
RUN apt-get update && apt-get install -y \
    libgl1-mesa-glx \
    libglib2.0-0 \
    libgomp1

2. 环境完全可控

本地开发环境 = Docker 镜像 = SAE 生产环境
  • 您在本地 Docker 中跑通了,推到 SAE 就一定能跑通
  • 没有"本地好用、云上报错"的问题

3. 扩展性强

未来需求:
  ├─ 添加 Nougat OCR (需要 PyTorch + GPU 支持)
  ├─ 添加图像预处理 (需要 OpenCV)
  ├─ 添加更多文档格式 (需要更多系统库)
  └─ 容器部署都能轻松支持

4. 运维统一

您的整体架构:
  ├─ 前端 Nginx → SAE 容器
  ├─ 后端 Node.js → SAE 容器
  └─ Python 服务 → SAE 容器 ✅ (统一管理)

部署架构图

┌─────────────────────────────────────────────────────────────┐
│                     阿里云架构                                │
│                                                               │
│  ┌─────────────┐         ┌─────────────────────────────┐   │
│  │  SAE (后端)  │ ←内网→  │   SAE (Python 微服务)        │   │
│  │             │         │                              │   │
│  │  Node.js    │         │  ┌─────────────────────┐    │   │
│  │  Backend    │         │  │  Docker 容器:         │    │   │
│  │             │         │  │  - FastAPI           │    │   │
│  │             │         │  │  - PyMuPDF           │    │   │
│  │             │         │  │  - Polars            │    │   │
│  └─────────────┘         │  │  - Mammoth           │    │   │
│         │                │  └─────────────────────┘    │   │
│         │                └─────────────────────────────┘   │
│         │                                                    │
│         ├──────────────→  RDS PostgreSQL 15                │
│         └──────────────→  OSS (文档存储)                    │
└─────────────────────────────────────────────────────────────┘

关键点

  • Python 微服务和 Node.js 后端都部署在 SAE 上(同一 VPC
  • 通过内网通信(延时 < 5ms
  • 共享 RDS 和 OSS 资源

前置准备清单

必需资源

资源类型 配置建议 预估费用 用途
SAE 应用 1核2G / 1实例 ~100元/月 运行 Python 服务
容器镜像仓库 阿里云 ACR 个人版 免费5GB 存储 Docker 镜像
OSS 存储 已有(共用) 0元增量 文档存储
RDS PostgreSQL 已有(共用) 0元 数据库

软件准备

# 本地开发机器需要安装
- Docker Desktop
- 阿里云 CLI可选

# 不需要在 SAE 上安装任何东西(容器已包含)

账号与权限

  • 阿里云账号(已有)
  • 容器镜像仓库访问权限
  • SAE 应用创建权限

Python 服务分析

📂 当前服务概览

服务 1: extraction_service文档提取

位置: AIclinicalresearch/extraction_service/

用途:

  • PKB 模块: 上传文档到 Dify 前,先提取文本
  • ASL 模块: 提取 PDF 全文用于深度阅读
  • DC 模块: 提取 Excel/CSV 数据

核心文件:

extraction_service/
├── main.py                 # FastAPI 入口
├── requirements.txt        # 依赖列表
├── services/
│   ├── pdf_extractor.py    # PDF 提取(调度器)
│   ├── pymupdf_extractor.py # PyMuPDF 实现
│   ├── nougat_extractor.py # Nougat OCR 实现
│   ├── docx_extractor.py   # Word 提取
│   └── txt_extractor.py    # 纯文本提取
└── operations/
    └── fillna_operations.py # 数据清洗Polars

关键端点:

POST /extract/pdf      # PDF 提取
POST /extract/docx     # Word 提取
POST /extract/txt      # 文本提取
POST /operations/fillna # 数据清洗

📊 依赖分析

当前 requirements.txt 内容:

fastapi==0.115.5
uvicorn[standard]==0.32.1
python-multipart==0.0.20
PyMuPDF==1.24.14
pdfplumber==0.11.4
nougat-ocr==0.1.17
torch==2.1.0
torchvision==0.16.0
mammoth==1.8.0
python-docx==1.1.2
langdetect==1.0.9
chardet==5.2.0
polars==1.17.1
numpy==1.26.4

依赖大小预估:

包名 大小 用途 是否必需
PyMuPDF ~50MB PDF 提取(核心) 必需
pdfplumber ~10MB PDF 表格提取 ⚠️ 可选(暂未使用)
nougat-ocr ~300MB 学术论文 OCR ⚠️ 阶段性(见下文)
torch ~800MB Nougat 依赖 ⚠️ 阶段性
torchvision ~100MB Nougat 依赖 ⚠️ 阶段性
mammoth ~5MB Word 提取 必需
python-docx ~3MB Word 提取 必需
polars ~50MB 数据清洗 必需
numpy ~20MB 数值计算 必需
fastapi ~10MB Web 框架 必需
uvicorn ~5MB ASGI 服务器 必需
其他 ~10MB 辅助库 必需
总计(含 Nougat ~1.4GB - -
总计(不含 Nougat ~163MB - -

依赖优化策略

🎯 阶段 1最小化部署推荐用于首次部署

目标: 快速上线,验证核心功能

策略:

  • 保留 PyMuPDF核心 PDF 提取)
  • 保留 Mammoth/python-docxWord 提取)
  • 保留 Polars数据清洗
  • 暂时移除 Nougat体积大使用频率低

优化后的 requirements.txt:

# Web 框架
fastapi==0.115.5
uvicorn[standard]==0.32.1
python-multipart==0.0.20

# 文档提取(核心)
PyMuPDF==1.24.14
mammoth==1.8.0
python-docx==1.1.2

# 数据处理
polars==1.17.1
numpy==1.26.4

# 辅助工具
langdetect==1.0.9
chardet==5.2.0

# 日志和监控
python-json-logger==2.0.7

镜像大小预估: ~500MB含 Python 基础镜像)

代码修改:

# services/pdf_extractor.py

# 注释掉 Nougat 相关代码
# from .nougat_extractor import extract_pdf_nougat, check_nougat_available

async def extract_pdf(pdf_path: str, filename: str):
    """PDF 提取阶段1仅 PyMuPDF"""
    
    # 检测语言和文档类型
    language = detect_language(pdf_path)
    is_academic = detect_academic_paper(pdf_path)
    
    # 阶段1直接使用 PyMuPDF
    text = extract_pdf_pymupdf(pdf_path)
    
    # 阶段2可以加回 Nougat 降级逻辑
    # if language == 'english' and is_academic:
    #     try:
    #         if check_nougat_available():
    #             text = extract_pdf_nougat(pdf_path)
    #     except:
    #         text = extract_pdf_pymupdf(pdf_path)  # 降级
    
    return {
        'text': text,
        'method': 'pymupdf',
        'language': language,
        'is_academic': is_academic
    }

🎯 阶段 2完整部署未来需要时

时机:

  • 当用户反馈英文学术论文提取质量不佳时
  • 有足够的 GPU 资源时

策略:

  • 加回 Nougat + Torch
  • 使用 GPU 实例SAE 目前不支持 GPU需迁移到 ECS

完整的 requirements.txt:

# 恢复全部依赖(包括 Nougat
fastapi==0.115.5
uvicorn[standard]==0.32.1
python-multipart==0.0.20
PyMuPDF==1.24.14
pdfplumber==0.11.4
nougat-ocr==0.1.17
torch==2.1.0
torchvision==0.16.0
mammoth==1.8.0
python-docx==1.1.2
langdetect==1.0.9
chardet==5.2.0
polars==1.17.1
numpy==1.26.4

镜像大小预估: ~2GB


构建 Docker 镜像

步骤 1创建优化的 Dockerfile

extraction_service/ 目录下创建 Dockerfile:

# ========================================
# 多阶段构建:减小镜像体积
# ========================================

# 阶段 1: 构建阶段(安装依赖)
FROM python:3.11-slim as builder

# 设置工作目录
WORKDIR /app

# 安装系统依赖(构建时需要)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    make \
    libffi-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装 Python 依赖到虚拟环境
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# ========================================
# 阶段 2: 运行阶段(最小化镜像)
# ========================================
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 安装运行时依赖(系统级库 + 时区数据)
RUN apt-get update && apt-get install -y --no-install-recommends \
    # PyMuPDF 依赖
    # 注libmupdf-dev 通常用于编译pip 安装的 PyMuPDF wheel 包已自带动态库
    # 保留它作为保险,如需瘦身可尝试移除后验证
    libmupdf-dev \
    libfreetype6 \
    libjpeg62-turbo \
    libopenjp2-7 \
    # Polars 依赖
    libgomp1 \
    # 其他工具
    curl \
    # 时区数据
    tzdata \
    && rm -rf /var/lib/apt/lists/*

# ⚠️ 统一时区Asia/Shanghai
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone

# 确保临时目录可写(大文件上传时需要)
RUN mkdir -p /tmp && chmod 1777 /tmp

# 从构建阶段复制虚拟环境
COPY --from=builder /opt/venv /opt/venv

# 复制应用代码
COPY . .

# 设置环境变量
ENV PATH="/opt/venv/bin:$PATH" \
    PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PORT=8000

# 暴露端口
EXPOSE 8000

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

# 启动命令
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "2"]

步骤 2创建 .dockerignore

# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
venv/
env/
ENV/

# IDE
.vscode/
.idea/
*.swp
*.swo

# 测试和文档
tests/
test_files/
*.md
README.md

# Git
.git/
.gitignore

# 日志
*.log

# 临时文件
tmp/
temp/

步骤 3本地构建镜像

# 进入 extraction_service 目录
cd d:\MyCursor\AIclinicalresearch\extraction_service

# 构建镜像(本地测试)
docker build -t extraction-service:latest .

# 查看镜像大小
docker images extraction-service

步骤 4本地测试镜像

# 启动容器(本地测试)
docker run -d \
  --name extraction-test \
  -p 8000:8000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/dbname" \
  extraction-service:latest

# 查看日志
docker logs -f extraction-test

# 测试健康检查
curl http://localhost:8000/health

# 测试 PDF 提取
curl -X POST \
  -F "file=@test.pdf" \
  http://localhost:8000/extract/pdf

# 停止并删除测试容器
docker stop extraction-test
docker rm extraction-test

步骤 5推送到阿里云容器镜像仓库

5.1 创建镜像仓库(首次部署)

  1. 登录阿里云控制台容器镜像服务 ACR

  2. 创建个人实例(免费版):

    实例名称: extraction-service
    地域: 华东1杭州
    
  3. 创建命名空间:

    命名空间: clinical-research
    
  4. 创建镜像仓库:

    仓库名称: extraction-service
    代码源: 本地仓库
    

5.2 推送镜像

# 1. 登录阿里云容器镜像服务
# 获取登录命令:阿里云控制台 → 容器镜像服务 → 访问凭证 → 设置Registry登录密码
docker login --username=<your-username> registry.cn-hangzhou.aliyuncs.com

# 2. 给镜像打标签
docker tag extraction-service:latest \
  registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:v1.0

# 3. 推送到阿里云
docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:v1.0

# 4. 推送 latest 标签(便于后续更新)
docker tag extraction-service:latest \
  registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:latest
docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:latest

部署到 SAE

步骤 1创建 SAE 应用

  1. 登录阿里云控制台Serverless 应用引擎 SAE

  2. 创建应用:

    应用名称: extraction-service
    命名空间: 选择后端所在的命名空间(同 VPC
    部署方式: 镜像
    
  3. 镜像配置:

    镜像地址: registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:latest
    镜像版本: latest
    镜像拉取策略: Always每次部署都拉取最新镜像
    
  4. 规格配置:

    CPU: 1核
    内存: 2GB
    实例数: 1初始
    弹性扩缩容: 
      - 最小实例数: 1
      - 最大实例数: 3
      - CPU 触发阈值: 70%
    
  5. 网络配置:

    专有网络 VPC: 选择后端所在的 VPC
    vSwitch: 选择后端所在的交换机
    安全组: 允许 VPC 内访问
    

步骤 2配置环境变量

在 SAE 应用配置中添加以下环境变量:

# ========= 数据库配置 =========
DATABASE_URL=postgresql://user:password@rm-xxxx.pg.rds.aliyuncs.com:5432/clinical_research

# ========= 存储配置 =========
OSS_ENDPOINT=oss-cn-hangzhou-internal.aliyuncs.com
OSS_BUCKET=your-bucket-name
OSS_ACCESS_KEY_ID=<your-id>
OSS_ACCESS_KEY_SECRET=<your-secret>

# ========= 服务配置 =========
SERVICE_NAME=extraction-service
SERVICE_VERSION=v1.0
LOG_LEVEL=INFO

# ========= 性能配置 =========
WORKERS=2
TIMEOUT=300
MAX_FILE_SIZE=52428800

# ========= 时区 =========
TZ=Asia/Shanghai

步骤 3配置健康检查

健康检查路径: /health
健康检查端口: 8000
健康检查协议: HTTP
初始延迟: 30秒
检查间隔: 10秒
超时时间: 5秒
健康阈值: 2次
不健康阈值: 3次

步骤 4配置日志

日志目录: /app/logs
日志文件: extraction-service.log
日志级别: INFO
日志保留天数: 7天

步骤 5配置 SLB可选如果需要公网访问

# 通常 Python 微服务只需要内网访问(被后端调用)
# 如果需要公网访问(如:调试、第三方集成):

负载均衡类型: 公网
监听端口: 80
后端端口: 8000
健康检查: 启用

步骤 6部署应用

  1. 点击"部署应用"

  2. 等待部署完成(约 2-3 分钟)

  3. 查看部署日志:

    [INFO] Pulling image...
    [INFO] Image pulled successfully
    [INFO] Starting container...
    [INFO] Container started successfully
    [INFO] Health check passed
    [INFO] Application is running
    

测试与验证

步骤 1获取内网地址关键步骤

⚠️ 重要SAE 实例间是跨主机的,必须使用 SAE 提供的内网地址

获取真实内网地址的正确方法:

  1. 登录 SAE 控制台应用列表点击 extraction-service 应用

  2. 在应用详情页,找到"应用访问配置"或"VPC 内网访问"部分

  3. 查看并复制"内网访问地址",通常是以下格式之一:

    # 格式 1: 内网 IP + 端口(⭐⭐⭐⭐⭐ 强烈推荐,最稳定)
    172.16.0.10:8000
    
    # 格式 2: SAE 内网 Service 域名(需要额外配置服务发现,不推荐)
    extraction-service-xxxxx.cn-hangzhou.sae.aliyuncs.com:8000
    
    # 格式 3: K8s Service 域名需要配置K8s服务发现复杂不推荐
    extraction-service.namespace.svc.cluster.local:8000
    
  4. 错误做法(会导致连接失败)

    # ❌ 不要猜测或假设域名100%失败)
    http://extraction-service.sae:8000           # .sae 域名不存在
    http://extraction-service.internal:8000      # .internal 域名不存在
    http://extraction-service.cluster.local:8000 # 需要K8s服务发现配置
    
    # ❌ 不要使用 localhost
    http://localhost:8000  # SAE 实例间是跨主机的
    
    # ❌ 不要使用 Docker 服务名
    http://extraction-service:8000  # 这不是单机 Docker Compose
    
  5. 推荐做法(按优先级排序)

    # ⭐⭐⭐⭐⭐ 方案A直接使用内网IP强烈推荐
    EXTRACTION_SERVICE_URL=http://172.16.0.10:8000
    # 获取方式SAE控制台 > Python应用 > 实例列表 > 查看内网IP
    
    # ⭐⭐⭐ 方案B使用SAE服务发现需要额外配置不推荐初期使用
    # 需要在SAE控制台配置"微服务注册中心"
    EXTRACTION_SERVICE_URL=http://extraction-service-xxxxx.cn-hangzhou.sae.aliyuncs.com:8000
    

步骤 2配置后端环境变量

在 SAE 后端应用的环境变量中添加:

# ⚠️ 使用 SAE 控制台显示的真实内网地址
EXTRACTION_SERVICE_URL=http://172.16.0.10:8000

# 注意:
# 1. 不要使用猜测的域名
# 2. 必须从 SAE 控制台的"应用访问配置"中获取
# 3. 如果 IP 变化(如重新部署),需要同步更新这个环境变量

配置后重启后端应用

  • SAE 控制台 → 后端应用 → 重启

步骤 3从后端服务测试

在您的 Node.js 后端服务中添加测试端点:

// backend/src/tests/test-extraction-service.ts

import axios from 'axios';
import FormData from 'form-data';
import fs from 'fs';

const EXTRACTION_SERVICE_URL = process.env.EXTRACTION_SERVICE_URL || 'http://extraction-service.internal:8000';

export async function testExtractionService() {
  try {
    // 1. 健康检查
    console.log('Testing health endpoint...');
    const healthRes = await axios.get(`${EXTRACTION_SERVICE_URL}/health`);
    console.log('Health check:', healthRes.data);

    // 2. 测试 PDF 提取
    console.log('Testing PDF extraction...');
    const form = new FormData();
    form.append('file', fs.createReadStream('./test.pdf'));
    
    const pdfRes = await axios.post(
      `${EXTRACTION_SERVICE_URL}/extract/pdf`,
      form,
      { headers: form.getHeaders() }
    );
    console.log('PDF extraction result:', pdfRes.data);

    // 3. 测试 Word 提取
    console.log('Testing Word extraction...');
    const form2 = new FormData();
    form2.append('file', fs.createReadStream('./test.docx'));
    
    const docxRes = await axios.post(
      `${EXTRACTION_SERVICE_URL}/extract/docx`,
      form2,
      { headers: form2.getHeaders() }
    );
    console.log('Word extraction result:', docxRes.data);

    console.log('✅ All tests passed!');
  } catch (error) {
    console.error('❌ Test failed:', error.message);
    if (error.response) {
      console.error('Response:', error.response.data);
    }
  }
}

步骤 4验证端到端流程完整业务场景

测试以下业务流程:

场景 1: PKB 文档上传

业务流程

用户上传 PDF 
  → Node.js 后端接收 
  → HTTP POST 转发文件流到 Python 服务 (EXTRACTION_SERVICE_URL)
  → Python 服务解析 PDF返回 JSON 文本
  → Node.js 后端收到文本
  → 上传到 Dify
  → 返回前端

测试步骤

  1. 在前端上传一个 PDF 文档(建议 < 5MB 的简单文档)

  2. 查看 Node.js 后端日志SAE 控制台 → 后端应用 → 日志):

    [INFO] Calling extraction service: http://172.16.0.10:8000/extract/pdf
    [INFO] Extraction completed in 2.3s
    [INFO] Extracted text preview: "This is a test document..."
    
  3. 查看 Python 服务日志SAE 控制台 → extraction-service 应用 → 日志):

    INFO: Request: POST /extract/pdf
    INFO: File size: 1.2MB, filename: test.pdf
    INFO: Using PyMuPDF extraction
    INFO: Response: 200 (took 2.10s)
    
  4. 在 Dify Web UI 中确认文档已上传

如果失败,检查

  • 后端日志是否显示 "Connection refused" → 检查 EXTRACTION_SERVICE_URL 配置
  • Python 日志是否显示 "ImportError" → 检查 Dockerfile 系统依赖
  • 提取超时(> 300s→ 文件太大或需要增加超时配置

场景 2: ASL 深度阅读

用户点击"深度阅读" → 后端调用 Python 服务提取全文 → 返回 LLM 分析结果

测试步骤

  1. 在 ASL 模块点击"深度阅读"
  2. 查看后端日志(确认调用 Python 服务)
  3. 查看 Python 服务日志(确认提取成功)
  4. 前端显示分析结果

场景 3: DC 数据清洗

用户上传 Excel → 后端调用 Python 服务 fillna → 返回清洗后数据

测试步骤

  1. 在 DC 模块上传 Excel 文件
  2. 执行 fillna 操作
  3. 查看 Python 服务日志
  4. 验证清洗结果

监控与维护

📊 SAE 自带监控

1. 查看应用监控

SAE 控制台 → 应用详情 → 监控

关键指标

  • CPU 使用率< 70%PDF 提取是 CPU 密集型任务
  • 内存使用率< 80%):大文件处理时会占用较多内存
  • 请求 QPS(每秒查询数):了解负载情况
  • 平均响应时间< 1000ms小文件应 < 2s大文件 < 30s
  • 错误率< 1%):监控文件解析失败率

性能基准(参考)

小文件(< 1MB PDF响应时间 1-3s
中等文件1-10MB PDF响应时间 5-15s
大文件10-50MB PDF响应时间 20-60s
超大文件(> 50MB建议限制或拒绝

2. 实时日志查看

SAE 控制台 → 应用详情 → 日志 → 实时日志

日志类型

  • 应用日志stdout/stderruvicorn 启动信息、请求日志
  • 访问日志HTTP 请求):请求路径、响应时间、状态码
  • 错误日志异常堆栈Python 异常详情

关键日志示例

# ✅ 正常启动
INFO: Started server process [1]
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8000

# ✅ 正常请求
INFO: Request: POST /extract/pdf
INFO: File: test.pdf (1.2MB)
INFO: Response: 200 (took 2.10s)

# ❌ 错误日志(需关注)
ERROR: ImportError: libGL.so.1: cannot open shared object file
ERROR: Timeout: PDF extraction took > 300s
ERROR: Memory error: Cannot allocate memory

3. 弹性伸缩配置

SAE 控制台 → 应用详情 → 弹性伸缩

推荐配置

最小实例数: 1确保服务不中断
最大实例数: 3根据实际负载调整

触发条件:
  - CPU 使用率 > 70% 持续 3 分钟 → 扩容 1 个实例
  - CPU 使用率 < 30% 持续 5 分钟 → 缩容 1 个实例

注意事项

  • PDF 提取是 CPU 密集型,扩容主要看 CPU
  • 如果经常扩容考虑直接增加实例规格2核 → 4核
  • SAE 会自动负载均衡,无需手动配置

📊 应用内监控

添加健康检查端点

# main.py

from fastapi import FastAPI
import psutil
import os

app = FastAPI()

@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {
        "status": "healthy",
        "service": "extraction-service",
        "version": os.getenv("SERVICE_VERSION", "unknown")
    }

@app.get("/metrics")
async def metrics():
    """性能指标端点"""
    cpu_percent = psutil.cpu_percent(interval=1)
    memory = psutil.virtual_memory()
    disk = psutil.disk_usage('/app')
    
    return {
        "cpu": {
            "percent": cpu_percent,
            "count": psutil.cpu_count()
        },
        "memory": {
            "total": memory.total,
            "available": memory.available,
            "percent": memory.percent
        },
        "disk": {
            "total": disk.total,
            "used": disk.used,
            "free": disk.free,
            "percent": disk.percent
        }
    }

添加请求日志

# main.py

import logging
from fastapi import Request
import time

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('/app/logs/extraction-service.log'),
        logging.StreamHandler()
    ]
)
logger = logging.getLogger(__name__)

@app.middleware("http")
async def log_requests(request: Request, call_next):
    """请求日志中间件"""
    start_time = time.time()
    
    # 记录请求
    logger.info(f"Request: {request.method} {request.url}")
    
    # 执行请求
    response = await call_next(request)
    
    # 记录响应
    process_time = time.time() - start_time
    logger.info(
        f"Response: {response.status_code} "
        f"(took {process_time:.2f}s)"
    )
    
    return response

🔄 定期维护任务

每周任务

# 1. 检查日志大小
du -sh /app/logs

# 2. 查看错误日志
tail -n 100 /app/logs/extraction-service.log | grep ERROR

# 3. 重启应用(如果有内存泄漏)
# SAE 控制台 → 应用详情 → 重启

每月任务

# 1. 更新 Python 依赖
pip list --outdated

# 2. 重建镜像(包含安全更新)
docker build -t extraction-service:v1.1 .
docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:v1.1

# 3. 在 SAE 中更新镜像版本

故障排查

🔥 常见问题

问题 1容器启动失败

症状

SAE 显示:应用启动失败
日志显示ImportError: libXXX.so: cannot open shared object file

原因:缺少系统依赖

解决

# 在 Dockerfile 中添加缺失的库
RUN apt-get update && apt-get install -y \
    libgl1-mesa-glx \      # OpenCV
    libglib2.0-0 \         # OpenCV
    libgomp1 \             # Polars
    libmupdf-dev \         # PyMuPDF
    && rm -rf /var/lib/apt/lists/*

问题 2PDF 提取超时

症状

请求超时(> 300秒
日志显示Timeout error

排查步骤

# 1. 检查文件大小
# 如果文件 > 50MB考虑分块处理

# 2. 增加超时时间
# SAE 控制台 → 应用配置 → 环境变量
TIMEOUT=600

# 3. 优化提取逻辑
# 跳过图片页、压缩图片等

问题 3内存溢出OOM

症状

容器自动重启
日志显示Killed (signal 9)

解决

# 1. 增加内存配置
# SAE 控制台 → 应用配置 → 规格
内存: 2GB → 4GB

# 2. 优化代码(流式处理)
# 不要一次性加载整个文件到内存
with open(pdf_path, 'rb') as f:
    # 分块处理
    for chunk in read_in_chunks(f):
        process(chunk)

问题 4后端无法连接 Python 服务(高频错误)

症状

后端日志Connection refused
或
ECONNREFUSED: connect ECONNREFUSED 172.16.0.10:8000
或
Error: getaddrinfo ENOTFOUND extraction-service.internal

根本原因排查

原因 1内网地址配置错误最常见

# ❌ 错误配置(猜测的域名)
EXTRACTION_SERVICE_URL=http://extraction-service.internal:8000

# ✅ 正确配置SAE 控制台显示的真实地址)
EXTRACTION_SERVICE_URL=http://172.16.0.10:8000

解决方法

# 1. 获取真实内网地址
# SAE 控制台 → extraction-service 应用 → 应用详情 → 应用访问配置
# 复制显示的"VPC 内网访问地址"

# 2. 更新后端环境变量
# SAE 控制台 → 后端应用 → 应用配置 → 环境变量
EXTRACTION_SERVICE_URL=http://<真实内网IP>:8000

# 3. 重启后端应用
# SAE 控制台 → 后端应用 → 重启

原因 2Python 服务未启动

# 检查 Python 服务状态
# SAE 控制台 → extraction-service 应用 → 实例列表
# 确认实例状态为"运行中"

# 查看启动日志
# SAE 控制台 → extraction-service 应用 → 日志
# 应该看到:
# INFO: Application startup complete.
# INFO: Uvicorn running on http://0.0.0.0:8000

原因 3安全组规则限制

# SAE 默认同 VPC 内应用可互相访问
# 如果仍无法连接,检查:
# SAE 控制台 → extraction-service 应用 → 网络配置 → 安全组
# 确认入站规则允许 VPC 内访问 8000 端口

测试内网连通性

# 方法 1在 SAE 控制台的"Webshell"中测试(如果支持)
curl http://<Python服务内网IP>:8000/health

# 方法 2在后端应用的启动脚本中添加测试
echo "Testing extraction service connectivity..."
curl -f http://<Python服务内网IP>:8000/health || echo "❌ Cannot connect to extraction service"

# 方法 3使用 telnet 测试端口
telnet <Python服务内网IP> 8000

注意事项与禁忌

最佳实践

1. 镜像优化

# ✅ 使用多阶段构建
FROM python:3.11-slim as builder
# ... 构建 ...
FROM python:3.11-slim
COPY --from=builder /opt/venv /opt/venv

# ✅ 清理缓存
RUN apt-get update && apt-get install -y ... \
    && rm -rf /var/lib/apt/lists/*

# ✅ 使用 .dockerignore
# 避免将不必要的文件打包到镜像

2. 版本管理

# ✅ 使用语义化版本
v1.0.0  # 主版本.次版本.补丁版本

# ✅ 保留多个版本
docker tag ... extraction-service:v1.0.0
docker tag ... extraction-service:v1.0
docker tag ... extraction-service:latest

# ✅ 记录变更
# CHANGELOG.md
## v1.0.1 (2025-12-20)
- 修复: PDF 提取超时问题
- 优化: 减小镜像体积 30%

3. 安全加固

# ✅ 文件大小限制
MAX_FILE_SIZE = 50 * 1024 * 1024  # 50MB

@app.post("/extract/pdf")
async def extract_pdf(file: UploadFile):
    if file.size > MAX_FILE_SIZE:
        raise HTTPException(
            status_code=413,
            detail="File too large"
        )

# ✅ 文件类型验证
ALLOWED_TYPES = {'application/pdf', 'application/msword'}

if file.content_type not in ALLOWED_TYPES:
    raise HTTPException(
        status_code=415,
        detail="Unsupported file type"
    )

4. 性能优化

# ✅ 异步处理大文件
import asyncio

async def extract_large_pdf(pdf_path: str):
    # 使用异步 I/O
    async with aiofiles.open(pdf_path, 'rb') as f:
        content = await f.read()
    
    # 在线程池中执行 CPU 密集型任务
    loop = asyncio.get_event_loop()
    text = await loop.run_in_executor(None, pymupdf_extract, content)
    
    return text

# ✅ 连接池
from sqlalchemy.pool import NullPool

engine = create_engine(
    DATABASE_URL,
    poolclass=NullPool,  # SAE 环境推荐
    echo=False
)

绝对禁止

1. 禁止猜测或假设内网地址(致命错误)

# ❌ 错误做法(会导致连接失败)
EXTRACTION_SERVICE_URL=http://extraction-service.internal:8000
EXTRACTION_SERVICE_URL=http://localhost:8000
EXTRACTION_SERVICE_URL=http://extraction-service:8000

# ✅ 正确做法:从 SAE 控制台获取真实地址
# SAE 控制台 → extraction-service 应用 → 应用访问配置
# 复制显示的"VPC 内网访问地址"
EXTRACTION_SERVICE_URL=http://172.16.0.10:8000

原因

  • SAE 实例间是跨主机的,不能使用 Docker 服务名
  • SAE 的 K8s Service 域名格式因配置而异,不能假设
  • 最稳妥的是使用 SAE 控制台显示的 IP 地址

2. 禁止在镜像中硬编码敏感信息

# ❌ 错误示例
ENV DATABASE_PASSWORD=my-secret-password

# ✅ 正确做法:在 SAE 环境变量中配置

3. 禁止使用本地文件持久化存储

# ❌ 错误示例(容器重启后丢失)
output_path = '/app/output/result.txt'
with open(output_path, 'w') as f:
    f.write(result)

# ✅ 正确做法:使用 /tmp 存临时文件,结果上传到 OSS
import tempfile
with tempfile.NamedTemporaryFile(mode='w', delete=False) as f:
    f.write(result)
# 上传到 OSS使用 oss2 库)
# 最后删除临时文件

4. 禁止使用 :latest 标签在生产环境

# ❌ 错误做法(无法回滚)
image: extraction-service:latest

# ✅ 正确做法(语义化版本)
image: extraction-service:v1.0.0

5. 禁止在容器内修改代码

# ❌ 错误操作(容器重启后丢失)
# SAE Webshell → vi /app/main.py

# ✅ 正确流程:
# 1. 本地修改代码
# 2. 重建镜像
# 3. 推送到 ACR
# 4. SAE 中更新镜像版本

6. 禁止使用无限增长的全局变量

# ❌ 错误示例(内存泄漏)
CACHE = {}  # 全局缓存,无限增长

@app.post("/extract/pdf")
async def extract_pdf(file: UploadFile):
    key = file.filename
    if key not in CACHE:
        CACHE[key] = extract(file)  # 内存会持续增长!
    return CACHE[key]

# ✅ 正确做法:使用有限容量的缓存
from functools import lru_cache

@lru_cache(maxsize=100)  # 最多缓存 100 个结果
def extract_with_cache(file_hash: str):
    return extract(file_hash)

7. 禁止忽略 /tmp 目录的大小限制

# ⚠️ 注意SAE 容器的 /tmp 目录通常有大小限制(如 1-2GB
# 处理大文件后必须清理临时文件

import os
import tempfile

async def extract_large_pdf(file: UploadFile):
    # 保存到临时文件
    with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as tmp:
        content = await file.read()
        tmp.write(content)
        tmp_path = tmp.name
    
    try:
        # 处理文件
        result = extract_pdf_pymupdf(tmp_path)
        return result
    finally:
        # ✅ 关键:必须清理临时文件
        if os.path.exists(tmp_path):
            os.unlink(tmp_path)

📚 附录

A. 完整的 requirements.txt阶段1

# Web 框架
fastapi==0.115.5
uvicorn[standard]==0.32.1
python-multipart==0.0.20

# 文档提取
PyMuPDF==1.24.14
mammoth==1.8.0
python-docx==1.1.2

# 数据处理
polars==1.17.1
numpy==1.26.4

# 辅助工具
langdetect==1.0.9
chardet==5.2.0
aiofiles==23.2.1

# 数据库
sqlalchemy==2.0.25
asyncpg==0.29.0

# 阿里云 OSS
oss2==2.18.3

# 日志和监控
python-json-logger==2.0.7
psutil==5.9.8

B. Dockerfile 完整版

参见前文 构建 Docker 镜像 - 步骤 1

C. 本地测试脚本

#!/bin/bash
# test-local.sh

echo "Building Docker image..."
docker build -t extraction-service:test .

echo "Starting container..."
docker run -d \
  --name extraction-test \
  -p 8000:8000 \
  -e DATABASE_URL="postgresql://user:pass@host:5432/db" \
  extraction-service:test

echo "Waiting for service to start..."
sleep 10

echo "Testing health endpoint..."
curl http://localhost:8000/health

echo "Testing PDF extraction..."
curl -X POST \
  -F "file=@test.pdf" \
  http://localhost:8000/extract/pdf

echo "Cleaning up..."
docker stop extraction-test
docker rm extraction-test

echo "Done!"

D. 相关文档链接


🎯 快速参考

常用命令

# 构建镜像
docker build -t extraction-service:v1.0 .

# 推送镜像
docker push registry.cn-hangzhou.aliyuncs.com/clinical-research/extraction-service:v1.0

# 查看 SAE 日志
# SAE 控制台 → 应用详情 → 日志

# 重启 SAE 应用
# SAE 控制台 → 应用详情 → 重启

# 测试内网连通性
curl http://extraction-service.internal:8000/health

# 查看容器资源
docker stats extraction-service

关键配置

配置项 推荐值 说明
CPU 1核 初始配置
内存 2GB 不含 Nougat
实例数 1-3 自动弹性伸缩
超时时间 300秒 大文件处理
健康检查 30秒 初始延迟
Worker 数量 2 Uvicorn workers

文档维护

  • 如有问题或建议,请联系技术负责人
  • 最后更新2025-12-13
  • 下次审查2025-03-13