第9讲:部署与运维 📅 发布时间:2026/8/21 8:03:10 👁 浏览次数: 前八讲我们构建了一个高性能的协同编辑系统完成了从文档引擎到性能优化的全部核心功能。现在是时候把这个系统部署到生产环境让它真正服务用户了。这一讲我们来实现完整的部署和运维体系。一、系统架构总览1.1 生产环境架构┌──────────────┐ │ CDN │ │ (静态资源) │ └──────┬───────┘ │ ┌──────────┐ ┌────────┴────────┐ ┌──────────┐ │ Browser │◀────────▶│ Load Balancer │◀────────▶│ Browser │ │ (Client) │ HTTPS │ (Nginx/HA) │ HTTPS │ (Client) │ └──────────┘ └────────┬────────┘ └──────────┘ │ ┌────────────────┼────────────────┐ ▼ ▼ ▼ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ WebSocket│ │ WebSocket│ │ WebSocket│ │ Server 1 │ │ Server 2 │ │ Server 3 │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │ │ └───────────────┼───────────────┘ │ ┌───────▼───────┐ │ Redis │ │ (Pub/Sub │ │ Session) │ └───────┬───────┘ │ ┌───────▼───────┐ │ PostgreSQL │ │ (持久化存储) │ └───────────────┘ 监控体系 ┌──────────┐ ┌──────────┐ ┌──────────┐ │ Prometheus│ │ Grafana │ │ ELK │ │ (指标) │ │ (可视化) │ │ (日志) │ └──────────┘ └──────────┘ └──────────┘1.2 技术栈选型组件技术选型用途Web 服务器Nginx反向代理、SSL终止、负载均衡应用服务器Python UvicornWebSocket 服务消息队列Redis Pub/Sub跨进程广播持久化PostgreSQL文档存储缓存RedisSession、临时数据容器化Docker Docker Compose部署编排Kubernetes生产集群监控Prometheus Grafana指标采集与可视化日志ELK Stack日志聚合二、Docker 容器化2.1 Dockerfile# Dockerfile FROM python:3.11-slim AS builder WORKDIR /app # 安装系统依赖 RUN apt-get update apt-get install -y --no-install-recommends \ gcc \ libpq-dev \ rm -rf /var/lib/apt/lists/* # 安装 Python 依赖 COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 第二阶段运行镜像 FROM python:3.11-slim WORKDIR /app # 复制依赖 COPY --frombuilder /usr/local/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages COPY --frombuilder /usr/local/bin /usr/local/bin # 复制应用代码 COPY . . # 创建非 root 用户 RUN useradd -m -u 1000 appuser chown -R appuser:appuser /app USER appuser # 健康检查 HEALTHCHECK --interval30s --timeout3s --start-period5s --retries3 \ CMD python -c import urllib.request; urllib.request.urlopen(http://localhost:9000/health) EXPOSE 9000 CMD [uvicorn, main:app, --host, 0.0.0.0, --port, 9000, --workers, 4]2.2 docker-compose.yml# docker-compose.yml version: 3.8 services: # WebSocket 服务器 ws-server: build: . image: collab-editor:latest ports: - 9000:9000 environment: - REDIS_URLredis://redis:6379/0 - DATABASE_URLpostgresql://collab:passwordpostgres:5432/collab_editor - LOG_LEVELinfo depends_on: redis: condition: service_healthy postgres: condition: service_healthy restart: unless-stopped deploy: replicas: 3 resources: limits: cpus: 1 memory: 512M reservations: cpus: 0.5 memory: 256M # Nginx 反向代理 nginx: image: nginx:alpine ports: - 80:80 - 443:443 volumes: - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro - ./nginx/ssl:/etc/nginx/ssl:ro - ./static:/usr/share/nginx/html:ro depends_on: - ws-server restart: unless-stopped # Redis redis: image: redis:7-alpine ports: - 6379:6379 volumes: - redis-data:/data healthcheck: test: [CMD, redis-cli, ping] interval: 5s timeout: 3s retries: 5 restart: unless-stopped # PostgreSQL postgres: image: postgres:15-alpine ports: - 5432:5432 environment: POSTGRES_DB: collab_editor POSTGRES_USER: collab POSTGRES_PASSWORD: password volumes: - postgres-data:/var/lib/postgresql/data - ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql healthcheck: test: [CMD-SHELL, pg_isready -U collab] interval: 5s timeout: 3s retries: 5 restart: unless-stopped # Prometheus 监控 prometheus: image: prom/prometheus:latest ports: - 9090:9090 volumes: - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml - prometheus-data:/prometheus command: - --config.file/etc/prometheus/prometheus.yml - --storage.tsdb.path/prometheus restart: unless-stopped # Grafana 可视化 grafana: image: grafana/grafana:latest ports: - 3000:3000 environment: - GF_SECURITY_ADMIN_PASSWORDadmin volumes: - grafana-data:/var/lib/grafana - ./monitoring/grafana/dashboards:/etc/grafana/provisioning/dashboards - ./monitoring/grafana/datasources:/etc/grafana/provisioning/datasources depends_on: - prometheus restart: unless-stopped volumes: redis-data: postgres-data: prometheus-data: grafana-data:三、Nginx 配置3.1 反向代理配置# nginx/nginx.conf events { worker_connections 1024; multi_accept on; use epoll; } http { upstream ws_backend { # 最少连接负载均衡 least_conn; server ws-server:9000 weight3; server ws-server:9001 weight2; server ws-server:9002 weight1; # 健康检查 keepalive 32; } # HTTP - HTTPS 重定向 server { listen 80; server_name collab.example.com; return 301 https://$server_name$request_uri; } # HTTPS 服务器 server { listen 443 ssl http2; server_name collab.example.com; # SSL 配置 ssl_certificate /etc/nginx/ssl/cert.pem; ssl_certificate_key /etc/nginx/ssl/key.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # 安全头 add_header Strict-Transport-Security max-age63072000 always; add_header X-Frame-Options DENY; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection 1; modeblock; # 静态资源 location /static/ { root /usr/share/nginx/html; expires 30d; add_header Cache-Control public, immutable; } # API 路由 location /api/ { proxy_pass http://ws_backend; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } # WebSocket 升级 location /ws/ { proxy_pass http://ws_backend; proxy_http_version 1.1; # WebSocket 必要头 proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; # 超时设置 proxy_read_timeout 86400s; proxy_send_timeout 86400s; # 缓冲关闭 proxy_buffering off; proxy_request_buffering off; } # 健康检查 location /health { access_log off; return 200 OK\n; add_header Content-Type text/plain; } # 指标端点 location /metrics { access_log off; proxy_pass http://ws_backend/metrics; } } }四、数据库初始化4.1 SQL 脚本-- sql/init.sql -- 协同编辑器数据库初始化 -- 文档表 CREATE TABLE IF NOT EXISTS documents ( id VARCHAR(36) PRIMARY KEY, title VARCHAR(255) NOT NULL DEFAULT Untitled, content TEXT NOT NULL DEFAULT , version INTEGER NOT NULL DEFAULT 0, owner_id VARCHAR(36), created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), is_deleted BOOLEAN DEFAULT FALSE ); -- 操作历史表 CREATE TABLE IF NOT EXISTS operation_history ( id BIGSERIAL PRIMARY KEY, document_id VARCHAR(36) NOT NULL REFERENCES documents(id), operation_json JSONB NOT NULL, site_id VARCHAR(36) NOT NULL, sequence INTEGER NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(document_id, site_id, sequence) ); -- 文档快照表 CREATE TABLE IF NOT EXISTS document_snapshots ( id BIGSERIAL PRIMARY KEY, document_id VARCHAR(36) NOT NULL REFERENCES documents(id), content TEXT NOT NULL, version INTEGER NOT NULL, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(document_id, version) ); -- 用户会话表 CREATE TABLE IF NOT EXISTS user_sessions ( session_id VARCHAR(64) PRIMARY KEY, user_id VARCHAR(36) NOT NULL, document_id VARCHAR(36) REFERENCES documents(id), connected_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), disconnected_at TIMESTAMP WITH TIME ZONE, is_active BOOLEAN DEFAULT TRUE ); -- 索引 CREATE INDEX IF NOT EXISTS idx_operation_history_doc_id ON operation_history(document_id); CREATE INDEX IF NOT EXISTS idx_operation_history_created ON operation_history(created_at DESC); CREATE INDEX IF NOT EXISTS idx_document_snapshots_doc_version ON document_snapshots(document_id, version DESC); CREATE INDEX IF NOT EXISTS idx_user_sessions_active ON user_sessions(is_active) WHERE is_active TRUE; -- 触发器自动更新 updated_at CREATE OR REPLACE FUNCTION update_updated_at_column() RETURNS TRIGGER AS $$ BEGIN NEW.updated_at NOW(); RETURN NEW; END; $$ language plpgsql; CREATE TRIGGER update_documents_updated_at BEFORE UPDATE ON documents FOR EACH ROW EXECUTE FUNCTION update_updated_at_column(); -- 插入测试文档 INSERT INTO documents (id, title, content, version, owner_id) VALUES (welcome-doc, 欢迎文档, 欢迎使用协同编辑器, 1, system), (demo-doc, 演示文档, 这是一个多人实时编辑的演示文档。, 1, system) ON CONFLICT (id) DO NOTHING;五、监控与告警5.1 Prometheus 配置# monitoring/prometheus.yml global: scrape_interval: 15s evaluation_interval: 15s alerting: alertmanagers: - static_configs: - targets: [alertmanager:9093] rule_files: - alerts.yml scrape_configs: # WebSocket 服务器 - job_name: ws-server static_configs: - targets: [ws-server:9000] metrics_path: /metrics # Redis - job_name: redis static_configs: - targets: [redis-exporter:9121] # PostgreSQL - job_name: postgres static_configs: - targets: [postgres-exporter:9187] # Node 指标 - job_name: node static_configs: - targets: [node-exporter:9100]5.2 告警规则# monitoring/alerts.yml groups: - name: collab_editor_alerts rules: # 服务器宕机 - alert: ServerDown expr: up{jobws-server} 0 for: 1m labels: severity: critical annotations: summary: WebSocket 服务器 {{ $labels.instance }} 宕机 # 高延迟 - alert: HighLatency expr: histogram_quantile(0.95, rate(ws_message_duration_seconds_bucket[5m])) 0.5 for: 5m labels: severity: warning annotations: summary: P95 消息延迟超过 500ms # 连接数过高 - alert: HighConnectionCount expr: ws_active_connections 1000 for: 5m labels: severity: warning annotations: summary: 活跃连接数超过 1000 # 错误率过高 - alert: HighErrorRate expr: rate(ws_errors_total[5m]) / rate(ws_messages_total[5m]) 0.05 for: 5m labels: severity: critical annotations: summary: 错误率超过 5% # Redis 不可用 - alert: RedisDown expr: redis_up 0 for: 1m labels: severity: critical annotations: summary: Redis 不可用 # 磁盘空间不足 - alert: DiskSpaceLow expr: (node_filesystem_avail_bytes{mountpoint/} / node_filesystem_size_bytes{mountpoint/}) * 100 10 for: 5m labels: severity: critical annotations: summary: 磁盘剩余空间不足 10%5.3 Grafana Dashboard{ dashboard: { title: 协同编辑器监控, panels: [ { title: 活跃连接数, type: graph, targets: [ { expr: sum(ws_active_connections), legendFormat: 活跃连接 } ] }, { title: 消息吞吐量, type: graph, targets: [ { expr: rate(ws_messages_total[1m]), legendFormat: 消息/秒 } ] }, { title: 操作延迟 (P50/P95/P99), type: graph, targets: [ { expr: histogram_quantile(0.50, rate(ws_op_duration_seconds_bucket[5m])), legendFormat: P50 }, { expr: histogram_quantile(0.95, rate(ws_op_duration_seconds_bucket[5m])), legendFormat: P95 }, { expr: histogram_quantile(0.99, rate(ws_op_duration_seconds_bucket[5m])), legendFormat: P99 } ] }, { title: 错误率, type: graph, targets: [ { expr: rate(ws_errors_total[5m]) / rate(ws_messages_total[5m]), legendFormat: 错误率 } ] }, { title: 内存使用, type: graph, targets: [ { expr: process_resident_memory_bytes, legendFormat: {{ instance }} } ] }, { title: 文档操作统计, type: stat, targets: [ { expr: sum(ws_operations_total), legendFormat: 总操作数 } ] } ] } }六、部署脚本6.1 一键部署#!/bin/bash # scripts/deploy.sh # 协同编辑器部署脚本 set -e echo echo 协同编辑器部署脚本 echo # 配置 PROJECT_NAMEcollab-editor DEPLOY_DIR/opt/${PROJECT_NAME} BACKUP_DIR${DEPLOY_DIR}/backups LOG_DIR${DEPLOY_DIR}/logs # 颜色 RED\033[0;31m GREEN\033[0;32m YELLOW\033[1;33m NC\033[0m log() { echo -e ${GREEN}[$(date %Y-%m-%d %H:%M:%S)]${NC} $1 } error() { echo -e ${RED}[ERROR]${NC} $1 exit 1 } # 1. 环境检查 log 检查环境... command -v docker /dev/null 21 || error Docker 未安装 command -v docker-compose /dev/null 21 || error Docker Compose 未安装 # 2. 创建目录 log 创建目录... mkdir -p ${DEPLOY_DIR} ${BACKUP_DIR} ${LOG_DIR} # 3. 备份当前版本 if [ -d ${DEPLOY_DIR}/current ]; then log 备份当前版本... BACKUP_NAMEbackup-$(date %Y%m%d_%H%M%S) cp -r ${DEPLOY_DIR}/current ${BACKUP_DIR}/${BACKUP_NAME} # 保留最近7天的备份 find ${BACKUP_DIR} -name backup-* -mtime 7 -exec rm -rf {} \; fi # 4. 拉取最新代码 log 拉取最新代码... cd ${DEPLOY_DIR} git pull origin main || git clone https://github.com/your-repo/collab-editor.git . # 5. 构建镜像 log 构建 Docker 镜像... docker-compose build --no-cache ws-server # 6. 数据库迁移 log 运行数据库迁移... docker-compose run --rm ws-server python scripts/migrate.py # 7. 启动服务 log 启动服务... docker-compose up -d # 8. 健康检查 log 健康检查... sleep 10 for i in {1..30}; do if curl -sf http://localhost:9000/health /dev/null 21; then log 服务健康检查通过! break fi if [ $i -eq 30 ]; then error 服务启动失败请检查日志 fi sleep 2 done # 9. 清理旧镜像 log 清理旧镜像... docker image prune -f log log ✅ 部署完成 log echo echo 访问地址: https://collab.example.com echo 监控面板: https://collab.example.com:3000 echo 日志目录: ${LOG_DIR} echo 6.2 CI/CD 配置# .github/workflows/deploy.yml name: Deploy on: push: branches: [ main ] pull_request: branches: [ main ] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Setup Python uses: actions/setup-pythonv4 with: python-version: 3.11 - name: Install dependencies run: | pip install -r requirements.txt pip install pytest pytest-asyncio - name: Run tests run: pytest tests/ -v --cov./ --cov-reportxml - name: Upload coverage uses: codecov/codecov-actionv3 build-and-deploy: needs: test runs-on: ubuntu-latest if: github.ref refs/heads/main steps: - uses: actions/checkoutv3 - name: Build Docker image run: docker build -t collab-editor:${{ github.sha }} . - name: Push to registry run: | docker tag collab-editor:${{ github.sha }} registry.example.com/collab-editor:latest docker push registry.example.com/collab-editor:latest - name: Deploy to production uses: appleboy/ssh-actionmaster with: host: ${{ secrets.DEPLOY_HOST }} username: ${{ secrets.DEPLOY_USER }} key: ${{ secrets.DEPLOY_KEY }} script: | cd /opt/collab-editor docker-compose pull docker-compose up -d --force-recreate七、运维命令7.1 日常运维# scripts/ops.sh # 日常运维命令集 # 查看服务状态 alias collab-statusdocker-compose ps alias collab-logsdocker-compose logs -f --tail100 alias collab-logs-wsdocker-compose logs -f ws-server # 重启服务 alias collab-restartdocker-compose restart alias collab-restart-wsdocker-compose restart ws-server # 扩容/缩容 alias collab-scale-updocker-compose up -d --scale ws-server5 alias collab-scale-downdocker-compose up -d --scale ws-server2 # 备份数据库 collab-backup() { local backup_filecollab_backup_$(date %Y%m%d_%H%M%S).sql docker-compose exec -T postgres pg_dump -U collab collab_editor backups/$backup_file echo Backup saved: backups/$backup_file } # 恢复数据库 collab-restore() { if [ -z $1 ]; then echo Usage: collab-restore backup_file return 1 fi cat $1 | docker-compose exec -T postgres psql -U collab collab_editor echo Database restored from: $1 } # 清理旧数据 collab-cleanup() { echo Cleaning up old data... # 清理30天前的操作历史 docker-compose exec -T postgres psql -U collab collab_editor -c DELETE FROM operation_history WHERE created_at NOW() - INTERVAL 30 days; # 清理过期会话 docker-compose exec -T postgres psql -U collab collab_editor -c DELETE FROM user_sessions WHERE is_active false AND disconnected_at NOW() - INTERVAL 7 days; echo Cleanup completed! } # 查看性能指标 collab-metrics() { echo 活跃连接 curl -s http://localhost:9000/metrics | grep ws_active_connections echo 消息速率 curl -s http://localhost:9000/metrics | grep ws_messages_total echo 内存使用 docker-compose stats --no-stream ws-server }八、总结8.1 本讲成果组件文件功能DockerfileDockerfile容器化构建docker-compose.ymldocker-compose.yml多服务编排Nginx 配置nginx/nginx.conf反向代理与SSLSQL 初始化sql/init.sql数据库建表Prometheus 配置monitoring/prometheus.yml指标采集告警规则monitoring/alerts.yml异常告警部署脚本scripts/deploy.sh一键部署运维命令scripts/ops.sh日常运维8.2 运维要点高可用多副本部署 负载均衡可观测Prometheus Grafana ELK自动化CI/CD 一键部署安全HTTPS 非root用户 定期备份伸缩水平扩展 资源限制8.3 下一讲预告第10讲项目总结与展望最后一讲我们将回顾整个项目的架构设计总结经验教训并探讨未来的发展方向架构演进路线性能优化方向功能扩展计划开源社区建设让我们一起完成这场精彩的旅程开发之余的小工具推荐处理 Base64、JWT 解析、JSON 格式化、Crontab 计算、PDF 合并压缩这些碎片需求我常用一个纯前端本地工具箱zz365.top子页 PDF 大师PDF 大师 - zz365工具箱。所有计算在浏览器完成文件不上传服务器关页即清。免费、无登录、无广告适合开发者当常驻标签页。