部署与运维
让应用在互联网上稳定运行
🎯 学习目标
学完本章,你将能够:
- 将应用部署到 Vercel、Railway 等 PaaS 平台
- 管理多环境配置和启动时环境变量校验
- 使用 Pino 实现结构化日志记录
- 配置 PM2 进程管理和 Nginx 反向代理
- 集成 Sentry 错误追踪和健康检查端点
你有没有想过
写完代码、跑通测试之后,怎么让全世界都能访问你的网站?服务器挂了怎么办?数据库数据丢了怎么办?
部署不是简单地把文件传到服务器。生产环境需要:进程管理、反向代理、SSL 证书、数据库备份、监控告警。
这一章覆盖从 PaaS 快速部署到传统 VPS 运维的完整知识链。
PaaS 平台对比
三个最流行的部署平台
| 特性 | Vercel | Railway | Render |
|---|---|---|---|
| 最适合 | Next.js / 前端 | 全栈 + 数据库 | Web 服务 + Worker |
| 数据库 | 需外部服务 | 内置 PG/Redis | 内置 PG/Redis |
| 自动部署 | Git push 自动 | Git push 自动 | Git push 自动 |
| 预览部署 | 每个 PR 自动 | 支持 | 支持 |
| Serverless | 核心特性 | 不支持 | 部分支持 |
| WebSocket | 有限 | 支持 | 支持 |
Part 1: PaaS 部署
推代码即部署,适合快速上线。
部署到 Vercel
两条命令上线你的 Next.js 应用
$ npm install -g vercel
$ vercel login
$ vercel # 首次部署(preview)
$ vercel --prod # 生产部署
// vercel.json(可选配置)
{
"framework": "nextjs",
"env": {
"DATABASE_URL": "@database_url",
"JWT_SECRET": "@jwt_secret"
}
}
$ vercel env add DATABASE_URL production
$ vercel env add JWT_SECRET production
部署到 Railway
内置数据库,全栈一站搞定
$ npm install -g @railway/cli
$ railway login
$ railway init
$ railway up # 部署
$ railway run npm run migrate # 在 Railway 环境运行迁移
# railway.toml(可选配置)
[build]
buildCommand = "npm run build"
[deploy]
startCommand = "node dist/index.js"
healthcheckPath = "/health"
restartPolicyType = "on_failure"
Part 2: 环境管理
"怎么跑"与"在哪儿跑"分开 —— 同一份代码,不同环境不同配置。
环境变量管理
development / staging / production 各司其职
| 环境 | 用途 | 数据来源 |
|---|---|---|
| development | 本地开发 | .env.local(不入库) |
| staging | 预发测试 | 平台 UI / Vault |
| production | 线上对外 | 平台 UI / Secret(加密) |
代码与环境解耦:同一份构建产物在不同环境注入不同的环境变量即可运行。本地用 .env 文件;线上优先使用平台提供的 Secret 管理(Vercel Env、Railway Variables、Vault 等),千万不要把 .env 文件上传到服务器。
# 本地开发:创建 .env(确保被 .gitignore 排除)
DATABASE_URL=postgresql://user:pass@localhost:5432/devdb
JWT_SECRET=dev-only-secret-change-me
REDIS_URL=redis://localhost:6379
LOG_LEVEL=debug
# .env.example 用作模板提交到仓库,真实值留空
DATABASE_URL=
JWT_SECRET=
启动时校验环境变量
dotenv + zod —— 少一个变量立刻崩,比线上 500 强
运行时才发现的配置错误代价高昂。用 TypeScript + Zod 在入口文件最前面做一次 schema 校验;缺字段、类型错、端口越界都立刻报错退出。
// src/config/env.ts
import { z } from 'zod';
import dotenv from 'dotenv';
// 开发环境读取 .env;线上由平台注入环境变量,不读文件
if (process.env.NODE_ENV === 'development') {
dotenv.config({ path: '.env.local' });
}
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'staging', 'production'])
.default('development'),
PORT: z.coerce.number().int().positive().default(3000),
DATABASE_URL: z.string().url(),
JWT_SECRET: z.string().min(32, 'secret too short'),
REDIS_URL: z.string().url().optional(),
LOG_LEVEL: z.enum(['trace','debug','info','warn','error'])
.default('info'),
});
const parsed = envSchema.safeParse(process.env);
if (!parsed.success) {
// 启动时直接失败——比线上 500 好得多
console.error(
'Invalid env:\n',
JSON.stringify(parsed.error.flatten().fieldErrors, null, 2)
);
process.exit(1);
}
export const env = parsed.data;
数据库连接池配置
连接数不是越多越好 —— 按 CPU 核数和实例数反推
每个 Node.js 实例与 PostgreSQL 之间维持一个连接池。连接数过少会排队等待;过多则浪费数据库资源甚至被打满。经验公式:pool_size = (CPU核数 * 2) + 有效磁盘数,再除以实例数。
// Prisma schema (prisma/schema.prisma)
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
// 连接池大小通过 URL 参数传递
}
// .env
// 单实例 4 核:pool_size=10
DATABASE_URL="postgresql://user:pass@host:5432/mydb?connection_limit=10&pool_timeout=30"
// 多实例(PM2 cluster 4 workers):每个 worker 5 个连接
DATABASE_URL="postgresql://user:pass@host:5432/mydb?connection_limit=5&pool_timeout=30"
# 查看当前 PG 连接数
$ psql -c "SELECT count(*) FROM pg_stat_activity WHERE datname='mydb';"
# 查看最大连接数(默认 100)
$ psql -c "SHOW max_connections;"
日志管理:别再用 console.log
结构化日志 + 分级输出,定位问题快十倍
console.log 输出的是纯文本字符串,grep 不到字段、过滤不了级别、stdout 缓冲还可能丢日志。生产环境推荐 JSON 结构化日志,能被 Loki / ELK / Datadog 直接采集,按 serviceName、level、traceId 聚合查询。
| 级别 | 使用场景 |
|---|---|
| trace | 进出函数、极其详细 |
| debug | 本地排查,线上关闭 |
| info | 启动完成、关键业务节点 |
| warn | 降级、重试、可恢复异常 |
| error | 不可恢复错误,需要介入 |
# .env(示例)
LOG_LEVEL=info # 生产
LOG_LEVEL=debug # 本地
Pino:最快的 Node.js 日志库
低开销 + 自带 Express 中间件 pino-http
Pino 把序列化推迟到 Worker 线程,单线程每秒可写数十万条日志。pino-http 自动给每个请求打一条 info 日志(method、url、status、响应时间),并提供绑定当前请求上下文的子 logger。
$ npm install pino pino-http
$ npm install -D pino-pretty # 开发时用,生产不装
// src/logger.ts
import pino from 'pino';
import { env } from './config/env';
export const logger = pino({
level: env.LOG_LEVEL,
// 开发环境:彩色、可读输出;生产环境:JSON 到 stdout
transport: env.NODE_ENV === 'development'
? { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:SS' } }
: undefined,
base: {
service: 'my-app',
version: process.env.npm_package_version ?? '0.0.0',
},
});
// src/index.ts —— 接入 Express 请求日志
import express from 'express';
import pinoHttp from 'pino-http';
import { logger } from './logger';
const app = express();
// 每个请求自动打 info 日志:method / url / status / 耗时
app.use(pinoHttp({ logger }));
app.get('/api/users/:id', async (req, res) => {
// req.log 是绑定当前请求上下文的子 logger
req.log.info({ userId: req.params.id }, 'fetching user');
const user = await getUser(req.params.id);
res.json(user);
});
Part 5: 传统 VPS
PM2 + Nginx + SSL,完全掌控你的服务器。
VPS 部署架构
用户请求从互联网到 Node.js 的完整链路

图:传统 VPS 部署的完整架构图
【AI 生图提示词】请生成一张中文技术示意图,风格简洁、配色柔和、适合教学: - 画面主体:展示一个典型 VPS 服务器的内部架构,从用户浏览器到 Node.js 应用的完整请求链路 - 从左到右的层次结构: 1. 左侧:互联网云朵图标,标注"用户浏览器",发出 HTTPS 请求(箭头) 2. 防火墙层:一堵半透明的墙,标注"iptables / UFW 防火墙",只放行 80/443 端口 3. Nginx 层:一个蓝色方块,标注"Nginx 反向代理",内部注明职责:SSL 终止、静态文件服务、负载均衡、安全头注入。箭头分两路:一路标注"静态文件 / → /var/www/static",另一路标注"API /api/* → proxy_pass" 4. PM2 层:一个橙色方块,标注"PM2 进程管理器",内部展示 2-3 个并排的小方块,每个标注"Node.js Worker (cluster 模式)",旁边标注"自动重启、零停机重载、内存监控" 5. 数据层:底部两个圆柱体,左侧标注"PostgreSQL (5432)",右侧标注"Redis (6379)",用虚线箭头连接到 Node.js Workers - 右上角单独画一个监控区域:标注"Sentry 错误追踪 + UptimeRobot 可用性监控",用虚线连接到 Node.js Workers - 各层之间用实线箭头连接,箭头上标注通信协议(HTTP、FastCGI、TCP) - 用虚线框把整个 VPS 内部组件框起来,框外标注"VPS 服务器 (Ubuntu 22.04)" - 配色建议:浅灰背景,Nginx 用浅蓝色,PM2/Node 用浅橙色,数据库用浅绿色,防火墙用浅红色,监控用浅紫色 - 画面比例:16:9 - 避免:过度装饰、3D 效果、文字过小 - 标注文字用中文,字号适中,关键数字(端口号)用等宽字体
PM2 进程管理
Node.js 的守护神
$ npm install -g pm2
$ pm2 start dist/index.js --name "my-app"
// ecosystem.config.js
module.exports = {
apps: [{
name: 'my-app',
script: 'dist/index.js',
instances: 'max', // 按 CPU 核心数启动
exec_mode: 'cluster', // 集群模式
max_memory_restart: '512M', // 内存超限自动重启
env_production: { NODE_ENV: 'production', PORT: 3000 },
}],
};
$ pm2 start ecosystem.config.js --env production
$ pm2 list # 查看进程
$ pm2 logs my-app # 查看日志
$ pm2 monit # 实时监控
$ pm2 reload my-app # 零停机重载
$ pm2 startup # 开机自启
$ pm2 save # 保存进程列表
Nginx 反向代理
HTTPS、安全头、静态文件一站搞定
# /etc/nginx/sites-available/my-app
upstream nodejs_app {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name myapp.com;
ssl_certificate /etc/letsencrypt/live/myapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.com/privkey.pem;
# 安全头
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
# API 反向代理
location /api/ {
proxy_pass http://nodejs_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location / {
proxy_pass http://nodejs_app;
}
}
SSL 证书
Let's Encrypt 免费 HTTPS
# 安装 Certbot
$ sudo apt-get install -y certbot python3-certbot-nginx
# 获取证书(自动配置 Nginx)
$ sudo certbot --nginx -d myapp.com -d www.myapp.com
# 自动续期(Certbot 已自动添加定时任务)
$ sudo certbot renew --dry-run
/.env、/node_modules/、/.git/ 的外部访问。
Part 4: 监控与日志
知道出问题了,才能解决问题。
健康检查端点
让监控系统知道你的应用还好不好
// src/routes/health.ts
app.get('/health', async (req, res) => {
const checks = {
status: 'ok',
uptime: process.uptime(),
checks: {
database: await checkDatabase(),
redis: await checkRedis(),
memory: checkMemory(),
},
};
const isHealthy = Object.values(checks.checks)
.every(c => c.status === 'ok');
res.status(isHealthy ? 200 : 503).json(checks);
});
async function checkDatabase() {
try {
await prisma.$queryRaw`SELECT 1`;
return { status: 'ok' };
} catch (error) {
return { status: 'error', message: error.message };
}
}
监控工具选型
从小型项目到企业级,按需选择
| 层级 | 工具 | 监控内容 |
|---|---|---|
| 可用性 | UptimeRobot / Uptime Kuma | 服务是否在线 |
| 指标 | Prometheus + Grafana | CPU/内存/请求量 |
| 错误 | Sentry | 异常堆栈、用户影响 |
| 日志 | Pino + ELK / Loki | 结构化日志 |
小型项目推荐免费组合:UptimeRobot + Sentry + PM2。规模增长后再引入 Prometheus + Grafana。
Sentry 错误追踪
线上报错了,第一时间知道
// npm install @sentry/node
import * as Sentry from '@sentry/node';
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 0.1,
});
// Express 中间件(放在路由前后)
app.use(Sentry.Handlers.requestHandler());
// ... 你的路由 ...
app.use(Sentry.Handlers.errorHandler());
// 手动捕获错误
try {
await riskyOperation();
} catch (error) {
Sentry.captureException(error, {
extra: { userId: currentUser.id },
});
}
Part 3: 进阶运维
零停机、全球加速、可灰度 —— 让部署不再是"心惊胆战"的时刻。
部署策略对比
滚动更新 / 蓝绿部署 / 金丝雀发布
不同业务场景需要不同的部署策略。小型项目用 PM2 reload 就够了;中大型系统需要更精细的流量控制。
| 策略 | 原理 | 优点 | 缺点 |
|---|---|---|---|
| 滚动替换 | 逐台重启 worker | 简单、资源少 | 版本短暂共存 |
| 蓝绿部署 | 两套环境切流量 | 秒级回滚 | 资源翻倍 |
| 金丝雀发布 | 先放 5% 流量 | 风险可控 | 需要网关支持 |
# 蓝绿部署示意(Nginx 切换 upstream)
# 当前:green 在跑
$ ln -sfn /var/www/green /var/www/current
$ nginx -s reload
# 出问题:秒级切回 blue
$ ln -sfn /var/www/blue /var/www/current
$ nginx -s reload
# 金丝雀:Nginx split_clients 按比例分流
split_clients "${remote_addr}${http_user_agent}" $app_variant {
5% canary;
* stable;
}
零停机部署
pm2 reload 热重载 vs pm2 restart 冷重启
restart 先杀后起,期间所有请求失败;reload 会先启动新 worker,等其 ready 后再优雅关掉旧 worker —— 整个过程服务不中断。前提是应用必须支持优雅关闭(Graceful Shutdown)。
| 命令 | 行为 | 影响 |
|---|---|---|
pm2 restart | 杀进程再启 | 所有在途请求失败 |
pm2 reload | 滚动替换 worker | 对新请求零影响 |
pm2 gracefulReload | 同 reload,走 IPC | 同上 |
# ecosystem.config.js —— 关键:cluster 模式 + wait_ready
module.exports = {
apps: [{
name: 'my-app',
script: 'dist/index.js',
instances: 'max', // 按 CPU 核数起多个 worker
exec_mode: 'cluster',
wait_ready: true, // 等 process.send('ready') 再视为启动完成
listen_timeout: 8000, // 8 秒未 ready 视为失败
kill_timeout: 5000, // 给旧 worker 5 秒优雅关闭
max_memory_restart: '500M',
}],
};
优雅关闭(Graceful Shutdown)
收到 SIGTERM 后:停止接收新请求 → 处理完在途请求 → 关闭连接 → 退出
零停机部署的前提是应用能正确处理 SIGTERM。PM2 reload 发出 SIGTERM 后等待 kill_timeout,超时则强杀。应用必须自己关闭 HTTP server、数据库连接、消息队列消费者。
// src/graceful-shutdown.ts
import { Server } from 'http';
import { prisma } from './db';
import { logger } from './logger';
export function setupGracefulShutdown(server: Server) {
let isShuttingDown = false;
const shutdown = async (signal: string) => {
if (isShuttingDown) return;
isShuttingDown = true;
logger.info({ signal }, 'shutdown initiated');
// 1. 停止接收新请求
server.close((err) => {
if (err) logger.error({ err }, 'error closing server');
else logger.info('http server closed');
});
// 2. 关闭数据库连接
await prisma.$disconnect();
logger.info('database disconnected');
// 3. 通知 PM2 已就绪退出(配合 wait_ready)
process.exit(0);
};
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));
}
// src/index.ts
import { setupGracefulShutdown } from './graceful-shutdown';
const server = app.listen(env.PORT, () => {
logger.info(`listening on :${env.PORT}`);
process.send?.('ready'); // 通知 PM2:我已就绪
});
setupGracefulShutdown(server);
#!/usr/bin/env bash
# deploy.sh —— 零停机一键部署
set -euo pipefail
echo '⬇️ pulling code...'
git pull origin main
echo '📦 installing deps...'
npm ci --omit=dev
echo '🔨 building...'
npm run build
echo '🗃️ running migrations...'
npm run migrate
echo '🔄 reloading PM2 (zero-downtime)...'
pm2 reload ecosystem.config.js --update-env
echo '✅ deploy done'
Nginx upstream 滚动更新
多实例场景下,逐台摘流量、更新、加回
当应用跑在多台服务器或同一台多个端口上,Nginx 的 upstream 配合健康检查可以实现逐台滚动更新,比单机 PM2 reload 更可控。
# /etc/nginx/conf.d/upstream.conf
upstream node_backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
}
server {
listen 80;
location / {
proxy_pass http://node_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_next_upstream error timeout http_502;
}
}
# 滚动更新单台:先标记 down,更新完再恢复
curl -X POST 'http://nginx-api/servers/3001/down'
pm2 reload my-app --only 3001
sleep 5
curl -X POST 'http://nginx-api/servers/3001/up'
CDN 与静态资源加速
把"离用户最近"这件事交给专业服务商
CDN(Content Delivery Network)在全球各地部署边缘节点,把静态资源缓存到离用户最近的位置。用户请求不再跨洋回源,延迟从 200ms 降到 20ms。

图:CDN 工作原理——全球边缘节点加速
【AI 生图提示词】请生成一张中文技术示意图,风格简洁、配色柔和、适合教学: - 画面主体:展示 CDN 全球分发网络,包含用户、边缘节点和源站 - 顶部是三个用户:纽约用户(🇺🇸)、上海用户(🇨🇳)、柏林用户(🇩🇪) - 用户通过"边缘节点"箭头指向中间的 CDN Edge 大框 - CDN Edge 向下通过"缓存未命中时回源"虚线箭头指向底部的"源站 Nginx / S3" - 边缘节点旁边标注"边缘节点",源站旁边标注"源站" - 整体配色:橙色系表示 CDN,蓝色表示用户,灰色表示源站
| CDN 服务 | 免费额度 | 特点 |
|---|---|---|
| Cloudflare | 无限流量(免费套餐) | 自带 DDoS 防护、Workers |
| AWS CloudFront | 1TB / 月(首年) | 与 S3 / EC2 深度集成 |
| Vercel Edge | 100GB 带宽 | Next.js 原生支持 |
| 阿里云 DCDN | 按量付费 | 国内节点多、ICP 支持 |
静态资源长期缓存
文件名带 hash,缓存一年也不怕
构建工具(Vite / Webpack / Next.js)会给静态资源生成内容 hash —— 文件内容变了,文件名就变了。这样可以对静态资源设置 Cache-Control: max-age=31536000, immutable,大幅提升二次访问速度。
# 构建产物示例
assets/
index-a1b2c3d4.js # 内容变了 → hash 变了 → URL 变了
vendor-e5f6g7h8.js
style.i9j0k1l2.css
# Nginx 配置:静态资源长期缓存,HTML 不缓存
server {
location /assets/ {
expires 1y;
add_header Cache-Control "public, immutable";
}
location / {
expires -1; # HTML 每次都回源
proxy_pass http://node_backend;
}
}
故障恢复清单
出问题时按顺序排查,别慌
线上故障第一原则:先恢复,再追因。能回滚就回滚,能重启就重启,保留现场日志留到事后分析。
| 步骤 | 命令 / 操作 | 目的 |
|---|---|---|
| 1. 确认范围 | curl -I https://your.app/health | 全站 or 单接口 |
| 2. 看监控 | Sentry / Grafana / PM2 monit | 定位异常源 |
| 3. 看日志 | pm2 logs --nostream --lines 200 | 找 stack trace |
| 4. 回滚 | git revert HEAD && pm2 reload all | 先恢复 |
| 5. 复盘 | 写 postmortem,加告警 | 防止复发 |
# 一键回滚脚本
#!/usr/bin/env bash
set -euo pipefail
echo '⏪ reverting last commit...'
git revert --no-edit HEAD
npm run build
pm2 reload all
revert_exit=$?
if [ $revert_exit -ne 0 ]; then
echo '❌ reload failed, resetting git...'
git reset --hard HEAD@{1}
exit 1
fi
echo '✅ rollback done'
排错:服务挂了
原因:Node.js 进程崩溃(内存溢出、未捕获异常)。
修复:PM2 会自动重启。用
pm2 logs 查看崩溃日志,定位原因。常见问题:内存泄漏(重启频率高)、未处理的 Promise rejection。
$ pm2 logs my-app --lines 100 # 查看最近日志
$ pm2 show my-app # 查看重启次数
$ pm2 monit # 实时监控内存和 CPU
试一试
练习一
任务:在你的应用中添加 /health 端点,检查数据库连接和内存使用情况。
参考答案
app.get('/health', async (req, res) => {
try {
await prisma.$queryRaw`SELECT 1`;
const mem = process.memoryUsage();
res.json({ status: 'ok', heapUsed: mem.heapUsed });
} catch (e) {
res.status(503).json({ status: 'error' });
}
});
练习二
任务:将应用部署到 Vercel 或 Railway,配置环境变量并验证线上可访问。
# 提示:先在平台导入 GitHub 仓库
# 再配置 DATABASE_URL 等环境变量
# 最后访问平台分配的域名验证
练习三
任务:编写一个 bash 脚本 deploy.sh,实现零停机部署:拉代码 → 安装依赖 → 构建 → 迁移 → pm2 reload all。
参考答案
#!/usr/bin/env bash
set -euo pipefail
echo '⬇️ pulling code...'
git pull origin main
echo '📦 installing deps...'
npm ci --omit=dev
echo '🔨 building...'
npm run build
echo '🗃️ running migrations...'
npm run migrate
echo '🔄 reloading all PM2 apps (zero-downtime)...'
pm2 reload all
echo '✅ deploy done'
练习四
任务:配置 Pino,让开发环境输出彩色可读日志,生产环境输出 JSON 到 stdout。
参考答案
import pino from 'pino';
const isDev = process.env.NODE_ENV === 'development';
export const logger = pino({
level: process.env.LOG_LEVEL ?? (isDev ? 'debug' : 'info'),
transport: isDev
? { target: 'pino-pretty', options: { colorize: true, translateTime: 'HH:SS' } }
: undefined,
base: { service: 'my-app' },
});
上线前安全加固清单
别让部署日变成被黑日
部署完不等于结束。下面这些是生产环境最容易忽略的安全项,每条都可能成为攻击入口。
| 项目 | 做法 |
|---|---|
| Helmet 头 | app.use(helmet()) —— X-Frame-Options / CSP / HSTS |
| CORS 白名单 | 只允许业务域名,不要 * |
| Rate Limiting | express-rate-limit —— 单 IP 限流 |
| .env 不入库 | .gitignore + 平台 Secret 管理 |
| 依赖漏洞 | npm audit / GitHub Dependabot |
| 最小权限 | 数据库账号只给必要权限,不要用 superuser |
// 最小权限示例:应用账号只读 + 写特定表
// migration 用高权限账号,运行时用受限账号
// DATABASE_URL=migrator:pass@host/db ← 仅迁移时用
// DATABASE_URL=app_user:pass@host/db ← 运行时用
// app_user 权限: SELECT/INSERT/UPDATE/DELETE ON app_schema.*
📝 要点总结
本章核心知识点
PaaS 部署
Vercel/Railway 等平台让部署变得简单,专注代码而非基础设施
日志管理
结构化日志 + 级别控制 + 集中收集,快速定位生产问题
进程管理
PM2 提供自动重启、负载均衡和日志管理,生产环境必备
监控告警
健康检查 + Sentry 错误追踪 + 性能指标 = 可观测的生产系统
下一章
应用跑起来了,但用户抱怨慢?下一章是最后一章——性能优化与安全,让应用又快又安全。