Postman并发测试踩坑实录:我的likes接口是怎么被100个虚拟用户‘点爆’的
Postman并发测试踩坑实录我的likes接口是怎么被100个虚拟用户‘点爆’的那天下午当我盯着Postman Runner里飙升的错误率曲线时后背突然一阵发凉——原本设计承载100并发请求的点赞接口在真实压力测试中竟像纸糊的一样瞬间崩溃。数据库里凭空多出300条重复点赞记录服务器日志疯狂报警连接池耗尽而监控大屏上的响应时间曲线活像过山车。这场事故最终让我们团队付出了两小时紧急回滚的代价也让我明白并发测试不是配置几个参数就能通关的游戏。1. 从信心满满到灾难现场1.1 天真的测试配置我按照标准流程创建了包含三个接口的Postman集合{ item: [ { name: get_books, request: { method: GET, url: api/v1/books } }, { name: like_book, request: { method: POST, url: api/v1/likes, body: { book_id: 123 } } } ] }测试脚本看起来毫无破绽pm.test(Like success, function() { pm.expect(pm.response.code).to.be.oneOf([200, 201]); pm.expect(pm.response.json().message).to.include(success); });1.2 灾难性结果当Runner以每秒20个请求的速率发起100次并发调用后数据异常单个用户ID竟给同一本书点赞47次性能崩溃第38秒时平均响应时间从200ms飙升至12秒系统雪崩MySQL出现Too many connections错误关键教训没有隔离的测试环境就像在雷区里跳踢踏舞2. 解剖五个致命陷阱2.1 自增ID的诅咒数据库检查暴露了第一个问题SELECT user_id, book_id, COUNT(*) FROM likes GROUP BY user_id, book_id HAVING COUNT(*) 1;返回结果中出现了大量重复条目——接口竟然没有做幂等校验。解决方案对比方案类型实现方式优缺点数据库唯一索引ALTER TABLE likes ADD UNIQUE(user_id, book_id)简单但错误信息不友好应用层校验if (existsLike(userId, bookId)) return error;灵活但增加查询开销分布式锁Redis SETNX 过期时间可靠但架构复杂度高2.2 连接池饥饿现象监控显示连接池配置存在严重缺陷[监控指标] [阈值] [实际值] 最大连接数 100 100 活跃连接数 80 100爆满 等待获取连接超时 5s 平均8.2s调整策略# 应用配置修改 spring: datasource: hikari: maximum-pool-size: 150 connection-timeout: 3000 leak-detection-threshold: 50002.3 虚假的并发模拟初始测试犯了个低级错误——所有并发请求使用完全相同的参数。真实场景应该// 在Pre-request Script中动态生成数据 pm.variables.set(randomBookId, _.random(1, 1000)); pm.variables.set(userId, _.random(10000, 99999));2.4 缺失的断言维度原有测试只检查了HTTP状态码补充这些关键断言后立即发现了新问题pm.test(Response time acceptable, () { pm.expect(pm.response.responseTime).to.be.below(500); }); pm.test(No duplicate likes, async () { const likeCount await database.query( SELECT COUNT(*) FROM likes WHERE user_id${pm.variables.get(userId)} ); pm.expect(likeCount).to.equal(1); });2.5 事务的边界错误原代码像这样处理点赞public void likeBook(Long userId, Long bookId) { // 非原子操作 updateLikeCount(bookId); // 更新书籍总点赞数 insertLikeRecord(userId, bookId); // 插入点赞记录 }改为事务性操作后Transactional public void likeBook(Long userId, Long bookId) { if (likeRepository.existsByUserIdAndBookId(userId, bookId)) { throw new DuplicateLikeException(); } bookRepository.incrementLikeCount(bookId); likeRepository.save(new Like(userId, bookId)); }3. 重建可靠的测试方案3.1 环境隔离策略建立专属测试环境的三层防护数据隔离每个测试会话使用独立数据库schemaCREATE SCHEMA test_${timestamp};网络隔离通过Docker compose构建完整微服务栈services: mock-payment-service: image: mockserver/mockserver ports: - 1080:1080资源限制精确控制测试资源docker run --cpus2 --memory4g my-api-image3.2 智能参数化技巧在Collection级别设置动态变量// Collection Pre-request Script const faker require(faker); pm.collectionVariables.set(fakeEmail, faker.internet.email()); pm.collectionVariables.set(fakeName, faker.name.findName());3.3 渐进式压力测试采用阶梯式压力增长策略阶段并发数持续时间检查点预热102min响应时间稳定性爬坡10→1005min错误率变化曲线峰值1503min系统资源水位回落100→02min恢复能力4. 终极防御 checklist每次执行并发测试前必查[ ] 所有写操作都有事务保护[ ] 测试数据具有足够随机性[ ] 监控仪表盘已就绪包括数据库连接池、线程池、CPU/MEM[ ] 断言覆盖了业务一致性而不仅是HTTP状态[ ] 有自动化的环境清理机制# 环境清理示例 #!/bin/bash mysql -e DROP DATABASE test_${TEST_ID}; redis-cli FLUSHALL当我把这套改进方案应用到新的阅读历史接口测试时面对200并发的冲击波系统像瑞士钟表一样平稳运行——错误率始终保持在0.2%以下响应时间标准差不超过15ms。这大概就是工程学的浪漫用严谨对抗混沌拿数据证明可靠。