软件项目开发全流程 · 第 23 课 · 性能与安全测试
1、功能都对,一上线就垮
团队内部测试一切正常,功能没问题。上线当天搞了个活动,流量一冲——页面转圈、接口超时、数据库连接池打满,全线雪崩。
另一个更隐蔽的故事:功能稳定跑了一年,某天被安全研究者发现用户列表接口能把别人的数据全拉出来(越权),数据泄露上了新闻。
功能测试只能证明"做对了",证明不了"扛得住"和"守得住"。 本课补上这两块:性能测试与安全测试。
2、性能测试:问四个问题
性能不只是"快不快",要分场景问:
| 类型 | 问题 | 目的 |
|---|---|---|
| 负载测试(Load) | 在预期负载下表现如何? | 验证能不能扛住日常/峰值 |
| 压力测试(Stress) | 超过极限会怎样? | 找崩溃点、看是否优雅降级 |
| 并发测试 | 同时操作会不会出错? | 查竞态、超卖、死锁 |
| 浸泡测试(Soak) | 长时间运行会退化吗? | 查内存泄漏、连接泄漏 |
核心指标:
QPS / RPS 每秒请求数(吞吐)
响应时间 P95 95% 的请求在这个时间内返回(比"平均值"更有意义)
错误率 请求失败的比例
资源占用 CPU / 内存 / 连接数 / 磁盘 IO为什么看 P95 而不是平均? 平均 50ms 听着很好,但可能 5% 的用户等了 5 秒——长尾体验才是用户真正感受到的。
3、怎么测性能
# 用 locust 写一个压测脚本
from locust import HttpUser, task
class TaskClient(HttpUser):
@task
def list_tasks(self):
self.client.get("/api/tasks")
@task(3) # 权重 3:更频繁地调用
def create_task(self):
self.client.post("/api/tasks", json={"title": "load-test"})工具箱:
- k6 / JMeter / Locust / wrk:压测工具。
- 压测要有基线:先测"当前版本",改动后再测,对比才有意义。
- 压测要找瓶颈:逐步加压,观察哪个资源先到顶(CPU?DB?连接池?),那才是瓶颈。
性能优化常见手段:加缓存、数据库加索引、连接池调优、异步化、水平扩容、限流降级。
但要记住:先测量,再优化。凭感觉优化是浪费——先看 Profiler 和指标,找到真正的热点。
4、安全测试:常见攻击面
| 漏洞 | 原理 | 防护 |
|---|---|---|
| SQL 注入 | 用户输入拼进 SQL,改变语义 | 参数化查询(占位符),永不拼字符串 |
| XSS | 注入恶意脚本,在别人浏览器执行 | 输出转义、CSP、HttpOnly Cookie |
| 越权(IDOR) | 改个 id 就能看别人数据 | 服务端鉴权:每次校验"这数据属于你吗" |
| CSRF | 借你的登录态发请求 | CSRF Token、SameSite Cookie |
| 敏感信息泄露 | 密码明文、日志带 token | 哈希存储、脱敏、最小暴露 |
| 依赖漏洞 | 用的库有已知 CVE | 依赖扫描、定期升级 |
SQL 注入对比:
# ❌ 危险:字符串拼接
sql = f"SELECT * FROM users WHERE name='{name}'"
# 输入 name = "' OR '1'='1" → 拉出所有用户
# ✅ 安全:参数化查询
cursor.execute("SELECT * FROM users WHERE name = %s", (name,))越权的经典案例:
# ❌ 只校验"登录了",没校验"是不是你的"
@app.get("/api/orders/{order_id}")
def get_order(order_id):
return db.query(Order, id=order_id)
# ✅ 服务端强制校验归属
@app.get("/api/orders/{order_id}")
def get_order(order_id, current_user=Depends(auth)):
order = db.query(Order, id=order_id, user_id=current_user.id)
if not order:
raise HTTPException(404) # 别用 403 泄露"它存在但不属于你"
return order核心心法:永远不信任客户端。前端校验只是体验,真正的鉴权与校验必须在服务端。
5、安全测试的方法
| 方法 | 说明 |
|---|---|
| 静态扫描(SAST) | 扫源码找危险模式(如拼接 SQL) |
| 依赖扫描(SCA) | 扫第三方库的已知漏洞(如 pip-audit、npm audit、Dependabot) |
| 动态扫描(DAST) | 对运行中的系统跑扫描器(如 OWASP ZAP) |
| 渗透测试 | 人主动模拟攻击,找逻辑漏洞 |
| 代码审查 | 安全清单式 Review(输入校验、鉴权、日志脱敏) |
OWASP Top 10:安全领域的"必知清单",列出最常见的十类 Web 漏洞(注入、鉴权失效、敏感数据泄露……),值得每人过一遍。
把安全左移:别等上线前才扫描。把依赖扫描和 SAST 塞进 CI(第 15 课),让漏洞在提交时就暴露。
6、非功能需求的验收
性能和安全的坑在于:它们通常不是"功能",容易被漏掉。解法是在需求阶段就写成可验收的非功能需求(第 7 课):
性能需求(可测):
- 首页 P95 响应 < 800ms(100 并发下)
- 订单接口 QPS ≥ 500
- 内存占用稳定(8 小时浸泡无增长)
安全需求(可测):
- 所有输入参数做类型与长度校验
- 所有资源接口做归属鉴权
- 依赖无 HIGH/CRITICAL 级别已知漏洞
- 密码用 bcrypt/argon2 加盐哈希存储能写成数字的需求,才能被自动验证。
7、常见误区
| 误区 | 问题 | 正解 |
|---|---|---|
| 上线后才做性能测试 | 架构问题来不及改 | 关键路径尽早压测(左移) |
| 只看平均值 | 掩盖长尾慢请求 | 看 P95 / P99 |
| 盲目优化 | 猜错瓶颈白费力 | 先测量(Profiler/指标)再动手 |
| 只靠前端校验 | 绕过前端即失守 | 服务端必须重新校验 |
| 一次扫完就完事 | 新漏洞/新依赖不断出现 | 扫描进 CI,持续进行 |
| 用 403 暴露资源存在 | 泄露信息给攻击者 | 用 404 统一处理 |
8、动手实验 🎯
任务:给贯穿项目「团队任务清单」做一次性能 + 安全体检。
- 写压测脚本(Locust 或 k6):压
/api/tasks列表接口,逐步加到 200 并发。 - 记录基线:测出 QPS、P95 响应时间、错误率,画一张"并发 vs 延迟"曲线。
- 找瓶颈:加压时观察哪个资源先到顶(CPU/DB/连接池),写下你的判断依据。
- 优化一处:针对瓶颈做一项优化(如加索引/缓存),再测一次对比,证明有效。
- 安全自查:在你的项目里找/修三类问题——SQL 注入、越权、敏感信息日志。
- 接扫描:在 CI 里加依赖扫描(
pip-audit或 Dependabot),制造一个"含已知漏洞的旧依赖",看它是否报红。 - 反思:性能和安全的测试,为什么越早做越便宜?
下一阶段进入交付与运维——从第 24 课「构建与打包」开始。
9、今天你带走的三句话
- 性能看 P95 而非平均;先测量、找到瓶颈,再优化。
- 永远不信任客户端:参数化查询防注入、服务端做归属鉴权。
- 性能与安全要写成可测的非功能需求,并左移进 CI。
下一阶段:交付与运维。