SiameseUniNLU部署教程:Docker Compose编排+NLU服务+Redis缓存+MySQL日志持久化
1. 为什么需要更完整的部署方案
SiameseUniNLU是个很实用的中文NLU模型,它用一个模型就能搞定命名实体识别、关系抽取、情感分析、文本分类等八九种任务。但官方提供的快速启动方式——直接运行app.py或简单Docker打包——在实际项目中会遇到几个明显问题:服务一断就全停、多次请求重复加载模型拖慢响应、日志只能看文件不好查、没有历史记录难追溯问题。
你可能已经试过nohup python3 app.py > server.log 2>&1 &,也跑通了docker run -p 7860:7860,但当团队开始接入、接口被高频调用、需要回溯某次异常结果时,就会发现这些方式不够稳、不够快、也不够可维护。
这篇教程不讲怎么训练模型,也不深挖指针网络原理,而是聚焦一个工程落地的核心问题:如何把SiameseUniNLU变成一个真正能放进生产环境的服务?我们会用Docker Compose统一编排,加Redis做推理缓存降低重复计算,用MySQL持久化每一次调用日志,让整个服务具备高可用性、低延迟和可审计能力。所有操作都在Linux服务器上完成,不需要GPU也能跑起来。
2. 整体架构与组件分工
2.1 四层服务架构设计
我们不堆砌复杂组件,只保留真正必要的四个角色,每一块都解决一个具体痛点:
NLU应用服务(App):核心业务层,承载
nlp_structbert_siamese-uninlu_chinese-base模型推理逻辑,接收HTTP请求,返回结构化结果。它不再自己管日志和缓存,专注做好一件事:准确、稳定地执行Prompt+Text联合推理。Redis缓存服务:专门负责“结果复用”。当相同文本+相同Schema的请求再次到来时,直接从内存读取上次结果,跳过耗时的模型前向传播。实测对重复查询类场景(如客服知识库问答、固定模板提取),响应时间从平均1.8秒降到45毫秒以内。
MySQL日志服务:不是存模型参数,而是记录每一次API调用的完整上下文:时间戳、原始输入text、schema定义、返回结果、耗时、是否命中缓存、客户端IP。这些数据能帮你回答“上周三下午三点哪类请求失败最多”“哪个schema配置最容易出错”这类运维问题。
Nginx反向代理(可选但推荐):加一层轻量网关,统一处理HTTPS、请求限流、跨域头、健康检查路径。哪怕暂时不用HTTPS,它也能让你把
http://localhost:7860换成更干净的http://nlu-api.example.com。
这四块通过Docker Compose定义在一个docker-compose.yml里,启动只需一条命令,停止也只要一条命令,彻底告别ps aux | grep app.py和手动pkill。
2.2 数据流向图解
一次典型请求的完整链路是这样的:
客户端 → Nginx(可选) → NLU App ↓ Redis(查缓存) ↓(未命中则继续) 模型推理 → 结果 → 写入MySQL日志 ↓ 返回客户端关键点在于:缓存检查在最前端,日志写入在最后端,两者完全解耦。NLU App本身代码几乎不用改,只需要在预测函数前后加几行Redis操作和MySQL插入逻辑——我们后面会给出具体补丁。
3. 环境准备与基础镜像构建
3.1 服务器基础要求
推荐使用一台内存≥4GB、磁盘≥20GB的Linux服务器(Ubuntu 22.04或CentOS 7.9均可)。不需要GPU,CPU有4核就足够支撑中小规模调用量。确认以下工具已安装:
# 检查Docker和Docker Compose版本 docker --version # 需 ≥ 20.10 docker-compose --version # 需 ≥ 2.10(注意:不是旧版docker-compose v1) # 若未安装,一键安装Docker(Ubuntu示例) curl -fsSL https://get.docker.com | sh sudo usermod -aG docker $USER newgrp docker # 刷新用户组,避免后续sudo3.2 构建增强版NLU应用镜像
官方Docker方式只打包了Python运行时和模型,我们在此基础上增加三样东西:Redis客户端、PyMySQL驱动、以及一个轻量日志中间件。新建一个空目录,放入以下文件:
siamese-uninlu-pro/ ├── Dockerfile ├── requirements.txt ├── app-enhanced.py # 基于原app.py修改的增强版 └── config/ └── database.ini # MySQL连接配置requirements.txt内容如下(比原版多两行):
transformers==4.35.2 torch==2.1.0 fastapi==0.104.1 uvicorn==0.23.2 redis==4.6.0 pymysql==1.1.0 python-dotenv==1.0.0Dockerfile采用多阶段构建,兼顾镜像大小与启动速度:
# 构建阶段:安装依赖并复制模型 FROM python:3.9-slim WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 复制模型(假设你已将模型下载到宿主机/root/ai-models/iic/下) COPY /root/ai-models/iic/nlp_structbert_siamese-uninlu_chinese-base ./model/ # 运行阶段:极简基础镜像,只保留必要运行时 FROM python:3.9-slim # 安装系统级依赖(如mysql-client用于调试) RUN apt-get update && apt-get install -y mysql-client && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY --from=0 /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages COPY --from=0 /usr/local/bin/uvicorn /usr/local/bin/uvicorn COPY . . # 暴露端口,设置启动命令 EXPOSE 7860 CMD ["uvicorn", "app-enhanced:app", "--host", "0.0.0.0:7860", "--port", "7860", "--workers", "2"]构建命令:
cd siamese-uninlu-pro docker build -t siamese-uninlu-pro .镜像大小控制在1.2GB左右,比纯Python镜像略大,但换来的是开箱即用的缓存与日志能力。
4. Docker Compose编排与服务协同
4.1 编写docker-compose.yml
在项目根目录创建docker-compose.yml,定义四个服务及其网络、卷、依赖关系:
version: '3.8' services: # NLU核心服务 uninlu-app: image: siamese-uninlu-pro restart: unless-stopped ports: - "7860:7860" environment: - REDIS_URL=redis://redis:6379/0 - MYSQL_URL=mysql+pymysql://nlu_user:nlu_pass@mysql:3306/nlu_log - MODEL_PATH=/app/model depends_on: - redis - mysql networks: - nlu-net # Redis缓存 redis: image: redis:7-alpine restart: unless-stopped command: redis-server --save 60 1 --loglevel warning volumes: - redis-data:/data networks: - nlu-net # MySQL日志库 mysql: image: mysql:8.0 restart: unless-stopped environment: MYSQL_ROOT_PASSWORD: root_pass MYSQL_DATABASE: nlu_log MYSQL_USER: nlu_user MYSQL_PASSWORD: nlu_pass volumes: - mysql-data:/var/lib/mysql - ./init.sql:/docker-entrypoint-initdb.d/init.sql networks: - nlu-net # Nginx反向代理(可选) nginx: image: nginx:alpine restart: unless-stopped ports: - "80:80" - "443:443" volumes: - ./nginx.conf:/etc/nginx/nginx.conf:ro - ./ssl:/etc/nginx/ssl:ro depends_on: - uninlu-app networks: - nlu-net volumes: redis-data: mysql-data: networks: nlu-net: driver: bridge4.2 初始化MySQL表结构
创建init.sql,让MySQL容器启动时自动建好日志表:
CREATE TABLE IF NOT EXISTS prediction_log ( id BIGINT AUTO_INCREMENT PRIMARY KEY, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, text TEXT NOT NULL, schema_json JSON NOT NULL, result_json JSON, elapsed_ms INT NOT NULL, is_cached TINYINT(1) DEFAULT 0, client_ip VARCHAR(45), status_code INT DEFAULT 200 ); -- 添加索引提升查询效率 CREATE INDEX idx_created_at ON prediction_log(created_at); CREATE INDEX idx_is_cached ON prediction_log(is_cached);4.3 启动与验证
执行启动命令:
docker-compose up -d等待约30秒,检查服务状态:
# 查看所有服务是否healthy docker-compose ps # 查看NLU服务日志(应看到"Uvicorn running on...") docker-compose logs -f uninlu-app # 测试Redis连通性 docker-compose exec redis redis-cli ping # 应返回PONG # 测试MySQL连通性 docker-compose exec mysql mysql -unlu_user -pnlu_pass -Dnlu_log -e "SHOW TABLES;"此时访问http://YOUR_SERVER_IP:7860,应该能正常打开Web界面。注意:Nginx服务默认未启用,如需启用,请先配置好nginx.conf再启动。
5. 增强版app-enhanced.py核心改造
5.1 缓存逻辑:用Redis键值对存结果
原app.py中predict()函数只做模型推理。我们在它前面加一层缓存检查,后面加一层缓存写入。关键代码片段:
import redis import json import hashlib from fastapi import Request # 初始化Redis连接(从环境变量读取) redis_client = redis.from_url(os.getenv("REDIS_URL", "redis://localhost:6379/0")) def get_cache_key(text: str, schema: str) -> str: """生成唯一缓存key:text+schema的SHA256""" key_str = f"{text}|{schema}" return hashlib.sha256(key_str.encode()).hexdigest()[:16] @app.post("/api/predict") async def predict(request: Request, text: str = Form(...), schema: str = Form(...)): cache_key = get_cache_key(text, schema) # 1. 先查缓存 cached = redis_client.get(cache_key) if cached: result = json.loads(cached) return {"result": result, "cached": True, "elapsed_ms": 0} # 2. 缓存未命中,执行模型推理(此处调用原predict_logic) start_time = time.time() result = predict_logic(text, schema) # 原有模型调用 elapsed = int((time.time() - start_time) * 1000) # 3. 写入缓存(过期时间设为1小时) redis_client.setex(cache_key, 3600, json.dumps(result)) return {"result": result, "cached": False, "elapsed_ms": elapsed}这个改动让相同请求永远只算一次,后续全是毫秒级响应。
5.2 日志持久化:异步写入MySQL
为避免日志写入拖慢主请求,我们用threading.Thread异步提交。在predict()函数末尾添加:
import threading import pymysql def async_log_to_mysql(text, schema, result, elapsed, is_cached, client_ip, status_code): try: conn = pymysql.connect( host=os.getenv("MYSQL_HOST", "mysql"), port=3306, user=os.getenv("MYSQL_USER", "nlu_user"), password=os.getenv("MYSQL_PASSWORD", "nlu_pass"), database=os.getenv("MYSQL_DB", "nlu_log"), charset='utf8mb4' ) cursor = conn.cursor() sql = """ INSERT INTO prediction_log (text, schema_json, result_json, elapsed_ms, is_cached, client_ip, status_code) VALUES (%s, %s, %s, %s, %s, %s, %s) """ cursor.execute(sql, (text, schema, json.dumps(result), elapsed, is_cached, client_ip, status_code)) conn.commit() except Exception as e: print(f"[LOG ERROR] {e}") finally: if 'conn' in locals(): conn.close() # 在predict函数return前调用 threading.Thread( target=async_log_to_mysql, args=(text, schema, result, elapsed, is_cached, client_ip, 200) ).start()这样主线程不受影响,日志由后台线程默默处理。
6. 实用技巧与常见问题应对
6.1 快速验证缓存与日志是否生效
启动后立即发两次相同请求:
# 第一次(应为未缓存) curl -X POST "http://localhost:7860/api/predict" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "text=张三在北京工作" \ -d "schema={\"人物\":null,\"地理位置\":null}" # 第二次(应为已缓存) curl -X POST "http://localhost:7860/api/predict" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "text=张三在北京工作" \ -d "schema={\"人物\":null,\"地理位置\":null}"观察返回JSON中的"cached"字段,第一次为false,第二次为true。再登录MySQL检查日志表:
docker-compose exec mysql mysql -unlu_user -pnlu_pass nlu_log -e "SELECT * FROM prediction_log ORDER BY id DESC LIMIT 2\G"应看到两条记录,is_cached值分别为0和1。
6.2 模型热更新不重启服务
当你要换新模型时,无需docker-compose down。只需:
- 将新模型文件夹(如
nlp_structbert_siamese-uninlu_chinese-large)复制到宿主机/root/ai-models/iic/下 - 修改
docker-compose.yml中uninlu-app服务的environment,添加:environment: - MODEL_PATH=/app/model-large # 指向新路径 - 重新构建并重启应用:
docker-compose build uninlu-app docker-compose up -d uninlu-app
由于模型加载逻辑在app-enhanced.py中是运行时读取MODEL_PATH环境变量,服务重启后自动加载新模型,其他组件(Redis、MySQL)完全不受影响。
6.3 故障排查清单
| 现象 | 快速定位命令 | 根本原因与修复 |
|---|---|---|
docker-compose up卡住,提示redis:6379 connection refused | docker-compose logs redis | Redis容器启动失败,检查docker-compose.yml中command语法,删除--save参数重试 |
Web界面打不开,NLU日志报ModuleNotFoundError: No module named 'transformers' | docker-compose exec uninlu-app pip list | grep transformers | 镜像构建时requirements.txt未正确COPY,检查Dockerfile路径 |
| MySQL日志表为空,但API调用成功 | docker-compose logs uninlu-app | grep "LOG ERROR" | 异步线程抛异常,检查database.ini中密码是否与docker-compose.yml一致 |
| 缓存命中率始终为0 | docker-compose exec redis redis-cli keys "*" | get_cache_key生成逻辑有误,确认text和schema传入值无空格/换行 |
获取更多AI镜像
想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。