跳到主要内容

性能与容量评估

本文档基于实际配置和代码,评估平台在 2C4G 应用服务器 + 2C2G MySQL(阿里云 RDS) 下的承载能力,并给出压测方法与内部部署的推算。

工程推算,非实测

以下数字是基于配置和代码的工程推算,不是实测。每个环境的真实瓶颈取决于业务复杂度、SQL 质量、数据量、并发模式,强烈建议用本文「压测方法」一节的方法实测。推算的逻辑链全部列出,便于你按自己的场景修正。

部署拓扑(单机跑全套)

生产现状是单台 2C4G 应用服务器同时跑全部前端服务 + 后端 + Redis,数据库用独立的 2C2G RDS:

┌─────────────────────────────────────────────────────────┐
│ 2C4G 应用服务器 │
│ │
│ nginx ──┬── admin 域名 → /opt/qzt-admin/(静态) │
│ ├── m 域名 → /opt/qzt-mobile/(静态) │
│ └── 主域名 → 127.0.0.1:3000(CMS SSR) │
│ pm2 qzt-cms (Next.js SSR) ── 127.0.0.1:3000 │
│ qzt-server (Go API) ── 127.0.0.1:9000 │
│ redis-server ── 127.0.0.1:6379 │
└─────────────────────────────────────────────────────────┘

▼ TCP 3306
┌─────────────────────────────────────────────────────────┐
│ 2C2G 阿里云 RDS MySQL 8.0 │
└─────────────────────────────────────────────────────────┘

关键约束:4G 内存要同时跑 Go API + Redis + Next.js SSR + nginx + 系统,留给后端的内存预算非常紧张。这是单机拓扑最现实的上限因素。

内存预算分析(4G 怎么分)

单机跑全套时,内存是第一个被吃光的资源,远早于 CPU。

进程预估常驻内存说明
系统 + sshd + 监控 agent~300 MB不可压
nginx~50-100 MB静态 serve + 反代
Redis~150-300 MBJWT 黑名单、权限缓存、限流计数
qzt-cms(Next.js SSR)~600-1000 MBNode 进程 + SSR 渲染,内存大头
qzt-server(Go API)~200-400 MBGo 运行时 + 连接池 + gin
Go 上传缓冲峰值+最高 20MB×N每个在途上传最多吃 20MB 内存
安全余量~300 MB防 OOM 的 buffer
合计~1.8-2.4 GB峰值(高并发上传时上浮)
省内存的关键

4G 单机跑全套,常态占用 2-2.4G,峰值可能逼近 3G。Next.js CMS 是最大的内存消耗者,如果关掉 CMS(或改静态导出),后端能多出 ~800MB 预算,承载力显著提升。

CPU 预算:2 核被 nginx/Go/Node/Redis 共享,Go API 实际能稳定占用约 1.2-1.5 核。

后端单机承载推算

连接数瓶颈(硬约束)

资源配置值来源
MySQL max_open_conns(Go 侧)20config.yaml mysql.max_open_conns
MySQL max_idle_conns4同上
Redis pool_size10config.yaml redis.pool_size
nginx upstream keepalive32deploy/nginx/qzt.conf

关键瓶颈是 max_open_conns=20。绝大多数请求都要查库,20 个 DB 连接 = 同一时刻最多 20 个请求在执行 SQL,超出的请求排队等连接。

备注

阿里云 RDS 2C2G 的默认 max_connections 约 200-400,Go 侧只占 20,RDS 侧不是瓶颈。

单请求耗时模型(典型 CRM 列表请求)

nginx 反代 ~1 ms
JWT 鉴权 + Redis 查黑名单 ~2-3 ms(Redis 往返)
Casbin 权限检查(Redis 缓存)~1-2 ms
Handler 参数绑定 ~1 ms
Service → Repository → GORM 查询 ~10-40 ms(取决于数据量和索引)
JSON 序列化 + 响应 ~2-5 ms
────────────────────────────────
合计 ~20-50 ms / 请求

取 30ms 作为典型值(带索引、数据量中等的乐观估计)。无索引或全表扫描的慢查询会到 200ms+,把吞吐拉到零头。

吞吐量(QPS)推算

综合连接池约束(理论上限 ~660 QPS)和 2 核 CPU 约束:

负载类型估算 QPS说明
纯读轻接口(字典、配置)500-800Redis 缓存命中,几乎不碰 DB
典型业务读(列表/详情)150-250DB 连接池是瓶颈
写接口(创建/更新)80-150多一次事务 + 审计日志
复杂导出(Excel 全量)5-15单请求耗时长,占连接久
文件上传(20MB)并发受内存限制每个在途上传吃 20MB,并发上限约 10-15 个

稳妥可持续 QPS:~150-200(混合读多写少的日常办公负载)。

在线用户 → 并发请求换算

「在线用户」不等于「并发请求」。办公系统的典型行为:一个用户思考、看屏幕的时间占 95%+,假设平均每分钟发 2-4 个请求,100 个在线用户 ≈ 3-7 QPS。

指标公式结果
可支撑持续在线用户QPS 上限 / 人均 QPS200 / 0.05 ≈ 200-400 人在线
可支撑注册用户(按 10% 同时在线)在线数 / 0.12000-4000 注册用户
峰值(短时突发,如早高峰打卡)连接池排队容忍短时 2-3 倍,即 500-800 并发请求 可排队消化

单机承载结论

场景可承载用户数触发瓶颈
日常办公负载(CRM/审批/进销存日常操作)100-200 活跃在线 / 1000-2000 注册内存(CMS)+ DB 连接池
中等负载(关掉 CMS 或 CMS 静态化)200-400 活跃在线 / 2000-4000 注册DB 连接池 + CPU
峰值突发(早高峰、批量导入)短时可承受 2-3 倍,响应延迟上升排队,不崩
保守对外口径

单机 2C4G 跑全套 + 2C2G RDS,建议承载 ≤ 150 活跃在线用户 / ≤ 1500 注册用户。超过这个规模建议参考下文扩容路径。

压测能承载多少用户(直接回答)

压测类型可承载并发对应在线用户说明
接口压测(单接口纯打)200-500 并发请求取决于接口类型,字典类 500+,列表类 200,写类 100
混合场景压测(模拟真实操作)30-80 并发虚拟用户150-400 在线用户每个虚拟用户带思考时间(10-30s),更贴近真实
登录峰值(早高峰集中登录)50-80 并发登录受登录限流 + Redis 写黑名单影响
并发虚拟用户 vs 在线用户

压测工具的 50 个并发虚拟用户(VU)持续发请求,约等于真实场景 200-400 人在线(因为真人 95% 时间在思考)。

压测方法(怎么测出真实数字)

工具

  • wrk(推荐,单接口压测):brew install wrk
  • k6(混合场景,JavaScript 脚本):brew install k6
  • vegeta(恒定速率):brew install vegeta

单接口压测示例(wrk)

# 先登录拿 token
TOKEN=$(curl -s http://localhost:9000/system/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}' | jq -r '.data.access_token')

# 压客户列表(典型读接口)
wrk -t2 -c50 -d60s -H "Authorization: Bearer $TOKEN" \
"http://localhost:9000/crm/customers?page=1&page_size=20"
# -t2 线程数(等于 CPU 核数)-c50 并发连接 -d60s 持续 60 秒

关注三个指标:Requests/sec(QPS)、Latency P99(99 分位延迟)、错误率。P99 < 500ms 且错误率 < 0.1% 是健康线

混合场景压测示例(k6)

import http from 'k6/http';
import { check, sleep } from 'k6';

const TOKEN = __ENV.TOKEN;
export const options = {
stages: [
{ duration: '2m', target: 30 }, // 2 分钟爬升到 30 VU
{ duration: '5m', target: 30 }, // 稳定 5 分钟
{ duration: '2m', target: 50 }, // 加压到 50 VU
{ duration: '5m', target: 50 }, // 稳定 5 分钟
],
thresholds: { http_req_duration: ['p(99)<1000'], http_req_failed: ['<0.01'] },
};

export default function () {
const headers = { Authorization: `Bearer ${TOKEN}` };
// 模拟真实操作流:看列表 → 翻页 → 看详情 → 休息
http.get('http://localhost:9000/crm/customers?page=1&page_size=20', { headers });
sleep(2);
http.get('http://localhost:9000/crm/opportunities?stage=PROSPECTING', { headers });
sleep(3);
http.get('http://localhost:9000/dashboard/overview', { headers });
sleep(5); // 思考时间
}

跑法:TOKEN=xxx k6 run script.js。逐步加压,观察在哪一档 P99 开始 > 1s 或出现错误,那一档就是真实承载线。

压测时的监控(必须同时看)

# 应用服务器
ssh root@server "vmstat 1" # CPU + 内存(r 列 > 2 表示 CPU 排队)
ssh root@server "free -h" # 内存(看 available)
ssh root@server "top -p $(pidof qzt-server)" # Go 进程 CPU/内存

# Go 内置 pprof(配置 enable_pprof: true)
go tool pprof http://localhost:9000/debug/pprof/heap # 内存火焰图
go tool pprof http://localhost:9000/debug/pprof/profile?seconds=30 # CPU 火焰图

MySQL 监控看 RDS 控制台:活跃连接数(是否打满 20)、慢查询日志、CPU 使用率。

判断瓶颈在哪

现象瓶颈定位处理
CPU 满、内存有富余CPU 瓶颈Go 逻辑或 GC 压力大
DB 连接数打满 20、接口延迟陡增连接池瓶颈调大 max_open_conns
内存 available < 500MB 且持续下降内存瓶颈优先砍 CMS 或升级内存
RDS CPU > 80%数据库瓶颈看慢查询、加索引

内部部署的承载差异

「内部部署」指企业内网/局域网,用户规模已知且可控,网络条件好。与公网生产相比,承载特点不同。

单机内部部署(同样 2C4G + 2C2G)

差异点影响
内网带宽不是瓶颈公网带宽可能是隐性瓶颈,内网千兆/万兆基本不限
无 CDN,但本地访问延迟低图片走 OSS 公网反而慢,内网部署推荐 driver: local
无公网 DDoS/爬虫压力流量更「干净」,限流可以放宽
用户规模可控一般企业内部 50-300 人,不会突然爆发

内部单机承载结论:同样配置下,承载 200-400 内部活跃用户比公网更稳。对于 50-200 人的中小企业,这套配置绰绰有余,甚至可以降到 2C2G 应用 + 2C2G DB。

内部部署优化建议(同样硬件跑更多人)

优化项效果代价
关掉 CMS(内网不需要企业官网)省下 ~800MB 内存,并发翻倍
调大 max_open_conns 到 50-80并发上限提升 2-4 倍RDS 连接数压力,2C2G RDS 建议不超过 80
CMS 改静态导出next export省 SSR 内存需重新构建发布流程
确保关键查询有索引单请求 30ms → 5ms,QPS 翻几倍需 SQL 优化
热数据加 Redis 缓存减少压库代码改造
文件用 local 不用 OSS省公网带宽、省 OSS 费用需关注磁盘容量

内部部署优化后,单机 2C4G 可稳定支撑 400-800 活跃内部用户

不同内部规模的配置建议

内部用户规模推荐配置说明
≤ 50 人2C2G 应用 + 2C2G DB(本地 Redis)单机够用,关掉 CMS
50-200 人2C4G 应用 + 2C2G DB(本配置)舒适区,可开 CMS
200-500 人2C8G 应用 + 4C8G DB + 调大连接池DB 成为瓶颈,升 DB
500-2000 人4C8G×2 应用(LB)+ 8C16G DB + Redis 独立需要负载均衡
> 2000 人拆分:API 集群 + DB 主从 + Redis 集群 + OSS超出单机架构范围

已知性能敏感点

基于代码审查,以下场景在压测时容易成为瓶颈:

风险缓解
max_open_conns=20首要瓶颈,高并发排队内部部署调到 50-80
操作日志异步写库每个写操作 +1 次 INSERT,高写入压 DB已异步;可考虑批量写
Casbin 策略加载角色变更触发全量 LoadPolicy,瞬时卡顿已缓存到 Redis(10min TTL)
Excel 全量导出单请求占连接久、内存上浮限制单次导出行数(如 1 万行)
文件上传内存缓冲20MB×N 并发占内存大文件改前端直传 STS
列表数据权限过滤dept_id 递归 + 角色范围,SQL 变复杂确保 dept_id/owner_id 有索引

扩容路径(超过单机上限怎么办)

按瓶颈类型选择:

内存瓶颈(最先撞到)
  1. 关闭/静态化 CMS → 省 800MB
  2. 升级应用到 2C8G
  3. Redis 独立到专门实例
DB 连接瓶颈
  1. max_open_conns 调到 50-80(配合 RDS 升配)
  2. RDS 升到 4C8G
  3. 加读副本(enable_read_write_separation: true,代码已支持)
CPU 瓶颈
  1. 应用水平扩展:2 台 2C4G + nginx upstream 加一台
  2. Redis 必须独立(多实例共享缓存)
  3. 静态资源全上 CDN

当前架构是「模块化单体」,天然支持从单机演进到集群——JWT 无状态、Redis 已外置、存储可切 OSS。扩容主要是加机器,不需要改代码。

结论速查表

配置活跃在线用户注册用户持续 QPS峰值 QPS
2C4G 单机跑全套 + 2C2G RDS(本配置,公网生产)100-2001000-2000150-200400-600
2C4G 单机 + 2C2G RDS(内部部署,关 CMS)200-4002000-4000250-350600-900
2C4G 单机 + 2C2G RDS(内部,关 CMS + 调连接池)400-8004000-8000350-500900-1500
2C8G 应用 + 4C8G RDS500-10005000-10000500-8001500-2500

一句话结论:当前 2C4G 单机 + 2C2G RDS 的配置,公网生产环境保守承载 150 活跃在线用户 / 1500 注册用户内部部署优化后可到 400-800 活跃用户,足够绝大多数中小企业使用。超过这个规模,优先升 DB 配置和调大连接池,其次加应用节点。