startup-plan/技术设计/api-server/服务器与数据库部署方案.MD

797 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 知习数据库与服务器部署方案(最终版)
> 定版日期2026-05-18 | 状态:已拍板,按此执行
---
## 一、存储架构总览
知识库不需要 MongoDB。最终存储分成四类
```text
MySQL :权威业务数据(用户、知识库、学习记录、额度、审计)
Qdrant 向量索引chunk embedding、语义检索
COS :对象存储(原始文件、解析结果、备份快照)
Redis :队列 / 缓存 / 限流 / 锁
```
不引入的组件:
```text
MongoDB → 不需要(没有文档型存储场景)
Elasticsearch → 第一阶段不需要Qdrant + rerank 够用)
ClickHouse → 第一阶段不需要OLAP 后续再说)
```
---
## 二、MySQL唯一权威业务数据库
### 存放内容
```text
# 用户与认证
users
auth_accounts
refresh_tokens
memberships
membership_plans
# 知识库核心
files
knowledge_bases
knowledge_sources
document_imports
knowledge_chunks
import_candidates
knowledge_items
# 学习引擎
active_recall_answers
ai_analysis_results
focus_items
review_cards
learning_activities
chat_sessions
chat_messages
# 运营与审计
ai_usage_logs
quota_usage
admin_audit_logs
backup_jobs
feedback
notifications
```
### ID 类型(重要)
当前 Prisma schema 主键是 `BigInt @default(autoincrement())`JavaScript/JSON 对 64 位整数有精度问题。
**现在数据库空,必须立即改:**
```prisma
id String @id @default(cuid())
```
推荐 `cuid/cuid2`:适合业务 ID前端、后端、日志、JWT、API 都好处理。以后数据多了再改会很痛苦。
### 生产配置
文件:`/etc/mysql/conf.d/zhixi.cnf`
```ini
[mysqld]
# InnoDB
innodb_buffer_pool_size = 8G
innodb_buffer_pool_instances = 4
innodb_log_file_size = 1G
innodb_flush_log_at_trx_commit = 1
innodb_file_per_table = 1
# Connections
max_connections = 100
wait_timeout = 300
interactive_timeout = 300
# Charset
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
# Slow query
slow_query_log = 1
long_query_time = 1
log_queries_not_using_indexes = 0
# Binlog无主从复制时可关
skip-log-bin
```
### 32G 内存分配
```text
MySQL buffer pool 8G
Qdrant 8G12G动态
Redis 1G 内
RAG Worker / 解析 4G8G
系统 + Docker + 缓存 4G+
预留安全空间 4G+
```
后续调整规则:
```text
MySQL 慢查询多 + 内存充足 → buffer pool 调到 10G12G
Qdrant / Worker 内存吃紧 → 不再加 MySQL 内存
Too many connections → max_connections 调到 150
---
## 三、Qdrant向量数据库
### 存放内容
```text
Collectionzhixi_chunks
├─ chunk embedding 向量1024dCosine
├─ userId / knowledgeBaseId / sourceId / chunkIdpayload 索引)
├─ pageNumber / sectionTitle元数据
└─ deletedbool软删除标记
```
### 负责的能力
```text
语义检索ANN Top-50 → rerank Top-5~8
知识库问答召回
相似知识点查找
学习内容推荐
```
### 不负责
```text
用户体系
会员额度
知识点管理
学习记录
订单支付
```
Qdrant 是 MySQL 的补充,不是替代。
### 安全策略(已拍板)
```text
第一阶段:不公网暴露 Qdrant
优先方案:不映射端口,只在 Docker internal network 用容器名访问
调试方案:仅绑定 127.0.0.1:6333
暂不启用 API Key但预留 QDRANT_API_KEY 环境变量
未来跨服务器访问时 → 必须启用 API Key
```
### Docker Compose 推荐写法(不映射端口)
```yaml
services:
qdrant:
image: qdrant/qdrant:latest
restart: unless-stopped
volumes:
- /data/qdrant:/qdrant/storage
networks:
- zhixi_internal
# 不写 ports公网访问不到
backend:
image: zhixi-backend:latest
restart: unless-stopped
environment:
QDRANT_URL: http://qdrant:6333
networks:
- zhixi_internal
- zhixi_public
networks:
zhixi_internal:
internal: true # 不对外暴露
zhixi_public:
```
### 如需宿主机调试(仅绑定 localhost
```yaml
services:
qdrant:
ports:
- "127.0.0.1:6333:6333"
- "127.0.0.1:6334:6334"
```
### 不要这样写
```yaml
ports:
- "6333:6333" # 暴露公网!没有 API Key 时非常危险
```
---
## 四、COS对象存储
### 存放内容
```text
zhixi-prod/
├─ users/{userId}/knowledge-bases/... ← 原始文件 + 解析结果
├─ users/{userId}/profile/avatar/... ← 头像
├─ users/{userId}/feedback/... ← 反馈截图
├─ system/knowledge-bases/... ← 系统内置知识库
├─ system/backups/qdrant/{date}/... ← Qdrant 快照
├─ system/backups/mysql/{date}/... ← MySQL 备份
├─ exports/users/{userId}/... ← 用户导出
└─ public/app-assets/... ← 公共资源
```
### 原则
```text
数据库是主关系COS 是文件仓库
权限、归属、状态永远以数据库为准
处理文件时从 COS 临时拉取,处理完删除本地临时文件
```
---
## 五、Redis队列与缓存
### 负责
```text
BullMQ 任务队列文档导入、AI 分析、通知发送)
API 限流
分布式锁(防止重复导入、重复扣额度)
用户 session备选也可用 MySQL
临时状态缓存
```
### 不负责
```text
长期业务数据
向量索引
文件存储
```
---
## 六、服务器分工(已拍板)
### 总原则
现在数据库基本空,**不要再把新表建到旧服务器上**。唯一权威 MySQL 放 8核32G。
### 8核32G CVM正式生产主服务器
**部署清单:**
```text
/opt/zhixi/backend ← NestJS 主后端
/opt/zhixi/rag-worker ← Python RAG Worker
/opt/zhixi/deploy ← docker-compose.yml + 脚本
/data/docker ← Docker data-root
/data/mysql ← MySQL 数据文件
/data/redis ← Redis 数据文件
/data/qdrant ← Qdrant 存储
/data/tmp/imports ← 临时下载/解析工作区
/data/logs ← 统一日志
/data/worker-cache ← llama-index 缓存
/data/backups/mysql ← MySQL 每日备份
/data/backups/qdrant ← Qdrant 每日快照
```
**运行服务:**
```text
Nginx可选
NestJS APINode
MySQL 8.0
Redis 7
QdrantDocker
RAG WorkerPython
AI Gateway WorkerNode
Backup WorkerCron
Cron Jobs定时清理、stale job recovery
```
**负责的业务:**
```text
全部用户数据、认证、会员
全部知识库数据(表 + 向量)
全部学习引擎主动回忆、AI 诊断、复习)
全部 AI 调用与成本记录
全部后台管理与审计
```
### 4核4G 轻量云:工具服务器 / 辅助服务器
**定位:** 继续当工具服务器,不承载生产核心数据。
**已运行服务(保留):**
```text
Web 产品页 / landing page
Gitea
```
**适合新增的服务:**
```text
n8n ← 营销自动化、流程编排、Webhook
轻量 agent runner ← CI/CD 辅助、定时调研、通知
Uptime Kuma ← 服务监控
跳板机 ← SSH 入口
测试 / staging 环境 ← 预发布验证
Gitea Webhook 自动化 ← CI/CD 触发
营销自动化脚本 ← 定时任务
轻量后台工具 ← 不操作生产库的管理工具
```
**不建议放 4核4G 的服务:**
```text
正式 MySQL / Redis / Qdrant ← 生产核心数据
RAG Worker / 文档解析 ← 重型 CPU/内存任务
Dify 生产环境 ← 最低 4G 但跑不稳(后面细说)
本地大模型 / 多模态模型 ← GPU/内存不够
重型爬虫 / 大量自动化任务 ← 吃满 CPU
```
---
## 七、4核4G 上各服务决策
### 1. n8n可以放
n8n 适合放 4核4G用来做
```text
营销自动化
内容生产流程编排
Gitea Webhook 触发
定时任务
数据同步
通知提醒
AI API 调用编排
运营脚本
```
部署方式:
```text
Docker Compose
nginx/caddy 反代 → https://n8n.yourdomain.com
```
安全要求:
```text
强密码
HTTPS
禁止公开注册
后台路径不裸奔
定期备份 n8n 数据目录
```
**核心规则n8n 不直接操作生产数据库。**
正确方式:
```text
n8n / agent
→ 调用 8核32G 的受控内部 API
→ 8核32G 做权限、额度、日志、落库
```
不要让 n8n 或 agent 变成"野生后台"。
### 2. Dify第一阶段不部署
Dify 最低要求 CPU ≥ 2 Core、RAM ≥ 4 GiB官方推荐 8 GiB 更稳。4核4G 已有 Web 产品页 + Gitea + n8n + agent再放 Dify 容易"能启动但卡、能用但不稳、一跑 workflow 就吃满"。
而且 Dify 和你自己的 AI Gateway / RAG Worker 有功能重叠,知习核心知识库才是优先事项。
**决策:**
```text
第一阶段不部署 Dify。
后续如需实验,放 8核32G sandbox profile不连生产库
```
```bash
# 以后如需实验:
docker compose --profile dify up -d # 用完关掉
```
### 3. 轻量 agent可以放
适合放 4核4G 的 agent 类型:
```text
营销内容生成 agent
定时调研 agent
Gitea issue 分析 agent
自动发布提醒 agent
数据采集辅助 agent
n8n workflow agent
小型 webhook agent
```
规则和 n8n 一样:**agent 可以调用知习 API但不可直接连生产 MySQL、操作 Qdrant、拿 COS 主密钥。**
---
## 八、最终部署架构图
```text
iOS / Web
┌───────────────┴───────────────┐
│ │
┌───────┴──────────┐ ┌───────┴──────────┐
│ 8核32G CVM │ │ 4核4G 轻量云 │
│ (生产核心服务器) │ │ (工具/辅助服务器) │
│ │ │ │
│ ┌──────────────┐ │ │ ┌───────────────┐ │
│ │ gitea-runner │ │ CI/CD │ │ gitea-runner │ │
│ │ -prod │ │ │ │ -web │ │
│ └──────────────┘ │ │ └───────────────┘ │
│ ┌──────────────┐ │ │ ┌───────────────┐ │
│ │ NestJS API │ │ │ │ Web 产品页 │ │
│ └──────┬───────┘ │ │ │ (landing) │ │
│ │ │ │ └───────────────┘ │
│ ┌──────┴───────┐ │ │ ┌───────────────┐ │
│ │ MySQL │ │ 权威业务库 │ │ Gitea │ │
│ └──────────────┘ │ │ │ (主服务) │ │
│ ┌──────────────┐ │ │ └───────────────┘ │
│ │ Redis │ │ 队列/缓存 │ ┌───────────────┐ │
│ └──────────────┘ │ │ │ n8n │ │
│ ┌──────────────┐ │ │ └───────────────┘ │
│ │ Qdrant │ │ 向量索引 │ ┌───────────────┐ │
│ └──────────────┘ │ │ │ agent runner │ │
│ ┌──────────────┐ │ │ └───────────────┘ │
│ │ RAG Worker │ │ 解析/切片 │ ┌───────────────┐ │
│ └──────────────┘ │ │ │ Uptime Kuma │ │
│ ┌──────────────┐ │ │ └───────────────┘ │
│ │ AI Gateway │ │ AI 调度 │ │
│ └──────────────┘ │ │ (不承载正式 │
│ ┌──────────────┐ │ │ 业务数据) │
│ │ Backup+Cron │ │ 备份/清理 └───────────────────┘
│ └──────────────┘ │
│ │
│ /data (70G) │
└────────┬─────────┘
┌───────┴───────┐
│ 腾讯云 COS │
│ │
│ 原始文件 │
│ parsed.md │
│ OCR/Vision │
│ 备份快照 │
│ 用户头像 │
│ 导出文件 │
└───────────────┘
```
---
## 九、CI/CD 部署流水线
### 1. 总原则
```text
Gitea 本体只放 4核4GRunner 两台都装
服务跑在哪台服务器Runner 就装在哪台服务器
每台 Runner 本地构建、本地部署,不跨服务器传输构建产物
```
不要搞成"4核 Runner 构建后端 → 传到 8核部署",多一层 SSH + 传输 + 1Mbps 带宽瓶颈。
### 2. Runner 分工
**4核4Ggitea-runner-web**
```text
标签web, tools, staging, docker
负责:
Web 产品页部署
Gitea Webhook 自动化
n8n / 工具服务部署
轻量 agent 更新
```
**8核32Ggitea-runner-prod**
```text
标签prod, backend, rag, docker
负责:
NestJS 后端部署
Prisma migration
MySQL / Redis / Qdrant docker compose 更新
RAG Worker 部署
AI Gateway 部署
备份脚本部署
```
### 3. Runner 安装
两台服务器都创建 `deploy` 用户(不要用 root 跑 runner
```bash
sudo useradd -m -s /bin/bash deploy
sudo usermod -aG docker deploy
```
8核上注册 runner 到 4核 Gitea
```bash
# 在 8核32G 上执行
./gitea-actions-runner register \
--name zhixi-prod-runner \
--labels prod,backend,rag,docker \
--instance https://gitea.yourdomain.com \
--token <从 Gitea Admin 获取>
```
### 4. Workflow 示例
**后端部署(跑在 8核 runner**
```yaml
# .gitea/workflows/deploy-backend.yml
name: deploy-backend
on:
push:
branches: [main]
paths:
- "backend/**"
- "rag-worker/**"
- "deploy/**"
jobs:
deploy:
runs-on: [prod, backend, docker]
steps:
- uses: actions/checkout@v4
- name: Build & Deploy
run: |
cd backend
pnpm install --frozen-lockfile
pnpm build
npx prisma migrate deploy
docker compose -f ../deploy/docker-compose.prod.yml up -d --build
```
**Web 部署(跑在 4核 runner**
```yaml
# .gitea/workflows/deploy-web.yml
name: deploy-web
on:
push:
branches: [main]
paths:
- "web/**"
jobs:
deploy:
runs-on: [web, docker]
steps:
- uses: actions/checkout@v4
- name: Build & Deploy
run: |
cd web
pnpm install --frozen-lockfile
pnpm build
docker compose -f docker-compose.web.yml up -d --build
```
### 5. 密钥管理
生产密钥不写入仓库,放在 8核服务器本地或 Gitea Secrets
```text
/opt/zhixi/env/.env.production
必需密钥:
DATABASE_URL
REDIS_URL
QDRANT_URL
COS_SECRET_ID / COS_SECRET_KEY
DEEPSEEK_API_KEY
SILICONFLOW_API_KEY
BAIDU_OCR_KEY
JWT_SECRET
RAG_WORKER_SECRET内部 API 认证)
```
### 6. Gitea 挂了不影响生产
```text
Gitea 只影响代码仓库和部署流水线
8核上的后端、MySQL、Redis、Qdrant 继续运行
最多暂时不能自动部署新版本
```
---
## 十、迁移执行计划
因为现在数据库基本空,迁移很简单,按此顺序推进:
### 第一步8核32G 基础环境
```text
1. 挂载 /data 数据盘
2. 安装 Docker / Docker Compose
3. 配置 Docker data-root → /data/docker
4. 创建所有 /data 子目录
5. 创建 deploy 用户(用于 Runner 和部署)
```
### 第二步:部署核心服务
```text
6. 部署 MySQL → /data/mysql创建数据库 zhixi_prod
7. 部署 Redis → /data/redis
8. 部署 Qdrant → /data/qdrant创建 collection zhixi_chunks
9. 配置数据库连接DATABASE_URL / REDIS_URL / QDRANT_URL
```
### 第三步:数据库迁移
```text
9. 修改 Prisma schemaBigInt → String(cuid)
10. 初始化空库 migration
11. 4核4G mysqldump → 导入 8核32G仅登录相关 2~3 张表)
12. 配置备份脚本MySQL + Qdrant + 同步 COS
```
### 第四步CI/CD 搭建
```text
14. 8核32G 安装 gitea-runner-prod标签 prod, backend, rag, docker
15. 4核4G 安装 gitea-runner-web标签 web, tools, docker
16. 配置 Gitea Secrets密钥不入仓库
17. 编写 deploy-backend.yml / deploy-web.yml
18. 测试 push → 自动构建 → 自动部署流水线
```
### 第五步DNS 与后端切换
域名规划:
```text
zhixi.app / api.zhixi.app → 8核32GNestJS 后端 API
www.zhixi.app / zhixi.app → 4核4GWeb 产品页)
n8n.zhixi.app → 4核4Gn8n
gitea.zhixi.app → 4核4GGitea
```
```text
19. 确认 8核32G 上 NestJS 正常运行(先用 IP:3000 测试)
20. DNS 主域名api.zhixi.app / 主站)从 4核4G 切换到 8核32G
21. Caddy/Nginx 配置 SSL 证书Let's Encrypt 自动申请)
22. 修改 DATABASE_URL 指向新 MySQL
23. 测试 iOS 登录功能(通过域名 + HTTPS
24. 4核4G 旧后端保留 7~14 天作为回滚备用
25. n8n / Gitea 的子域名保持指向 4核4G不动
```
> **当前状态DNS 尚在 4核4G。切换完成后告知更新本文档状态。**
### 第六步:知识库开发
```text
23. 在 8核32G 上按知识库设计文档建表
24. 实现文件上传 → COS
25. 部署 RAG Worker
26. 打通索引流程 → 学习流程
```
---
## 十一、最终拍板清单
```text
# 存储架构
1. 唯一权威 MySQL 放 8核32G4核4G 不再建新表
2. ID 类型立即从 BigInt 改为 String(cuid),趁库空
3. Redis 放 8核32G队列 / 缓存 / 限流 / 锁)
4. Qdrant 放 8核32G向量索引
5. COS 继续作为文件 + 备份存储
6. 不引入 MongoDB / Elasticsearch / ClickHouse
# 8核32G生产核心服务器
7. 全量承载 NestJS + MySQL + Redis + Qdrant + RAG Worker + AI Gateway
8. 负责全部用户数据、知识库、学习引擎、成本记录、后台审计
9. 安装 gitea-runner-prod负责后端/知识库/RAG 部署)
# 4核4G工具服务器 / 辅助服务器
10. 保留 Gitea 主服务 + Web 产品页
11. 安装 gitea-runner-web负责 Web/工具/n8n 部署)
12. 可以加 n8n营销自动化、Webhook、流程编排
13. 可以加轻量 agent runner不直接操作生产库
14. 可以加 Uptime Kuma 监控
15. Dify 第一阶段不部署(后续如需实验放 8核32G sandbox
16. 不承载正式 MySQL / Qdrant / Redis / RAG Worker
# CI/CD
17. Gitea 本体只放 4核4GRunner 两台都装
18. 服务跑在哪台服务器Runner 就装在哪台服务器
19. 生产密钥放 Gitea Secrets 或服务器本地 .env不写入仓库
20. Runner 使用 deploy 用户,不用 root
# 安全边界
21. n8n / agent 调用知习受控 API不直连生产库
22. 迁移完成后 4核4G 旧后端保留 7~14 天作为回滚备用
```
后续不会再有一次大的数据库迁移。
---
## 十二、仍存缺陷与待决策项
以下是当前方案中尚未覆盖或需要你拍板的点,按风险等级排序。
### 🔴 高风险(上线前必须定)
| # | 问题 | 现状 | 建议 |
|---|------|------|------|
| 1 | **3Mbps 公网带宽** | 8核32G 3Mbps | 日常增量文件(单文件 ≤20MB影响可控。大文件下载 COS + 备份上传走后台队列异步处理,不阻塞 API。首次部署大量依赖包安装可能稍慢耐心等待即可。 |
| 2 | **Qdrant 无内置认证** | 已定 | 第一阶段不公网暴露。优先不映射端口Docker internal network需调试时仅绑定 `127.0.0.1:6333`。暂不启用 API Key预留 `QDRANT_API_KEY` 配置。跨服务器访问时必须启用。 |
| 3 | **HTTPS 证书** | 已定免费证书Let's Encrypt | 域名解析当前指向 4核4G待切换到 8核32G。切换后域名 → 8核32G 公网 IP → Caddy/Nginx 反代 → 本地 NestJS:3000。Caddy 自动申请续期。 |
| 4 | **MySQL 配置调优** | 已定 | `innodb_buffer_pool_size=8G``max_connections=100`,完整配置见下方 MySQL 配置小节。后续观察慢查询/内存压力再调。 |
### 🟡 中风险(迁移前应定)
| # | 问题 | 现状 | 建议 |
|---|------|------|------|
| 5 | **Redis 持久化** | 未配置 | 推荐 AOF everysec + RDB 双开。Redis 主要做队列BullMQ任务丢失影响可控但要尽量保留。 |
| 6 | **Docker 容器网络规划** | 未定义 | 建议一个 `zhixi` 自定义 bridge 网络MySQL/Redis/Qdrant 只监听 localhost 或容器内网,不绑定 0.0.0.0。 |
| 7 | **日志轮转与保留** | /data/logs 目录已规划但无策略 | 建议 logrotate每天轮转保留 30 天压缩。Worker 日志量可能较大,需要单独评估。 |
| 8 | **健康检查端点** | 未定义 | NestJS 需 `/health` 端点(返回 DB/Redis/Qdrant 连通性。Uptime Kuma 可用此端点监控。 |
| 9 | **两服务器间通信** | 内网还是公网?| 看腾讯云是否支持同地域内网互通北京。如果可以Gitea → Runner 通信走内网更快。如果不能Runner 通过公网拉取 Gitea 任务。 |
| 10 | **COS 带宽成本** | 备份上传/下载走公网 | COS 同地域内网访问免费。确认 8核32G 和 COS 都在"北京"地域,使用内网 Endpoint`cos.ap-beijing.myqcloud.com` 的内网域名)。 |
### 🟢 低风险(后续迭代时再定)
| # | 问题 | 现状 | 建议 |
|---|------|------|------|
| 11 | **API 版本管理** | 未定义 | 建议 `/api/v1/...` 前缀,为后续 Breaking Change 留空间。 |
| 12 | **灰度/蓝绿部署** | 未涉及 | 第一阶段不需要。先单实例跑稳,后续再考虑 `docker compose` 滚动更新。 |
| 13 | **数据库读写分离** | 未涉及 | 第一阶段不需要。MySQL 单实例完全够。 |
| 14 | **异地备份** | COS 在北京 | COS 本身支持跨地域复制。如果预算允许,可以复制到另一个地域的 bucket。目前本地 + COS 同地域已较安全。 |
| 15 | **4核4G 最终去留** | 迁移后保留 7~14 天 | 后续如果 4核4G 作为长期工具服务器,建议保留。如果成本敏感且 8核32G 完全够用,可以到期前评估是否下线。 |
| 16 | **Docker Compose 文件结构** | 未定义 | 建议 `deploy/docker-compose.prod.yml` 统一管理 8核32G 上的所有服务,用一个 compose 文件管理容器间的启动顺序和网络。 |
---
## 十三、文档关联索引
本文档应与其他设计文档配合使用:
```text
知识库设计.MD → 数据模型、API 设计、RAG 流程、额度系统
服务器与数据库部署方案.MD → 本文档服务器分工、存储架构、CI/CD
后续Prisma Schema → 具体表结构实现
后续docker-compose.yml → 容器编排配置
```