我见过太多人把压力测试当成一种玄学。压力他们写了个爬虫,测试跑起来发现CPU飙到100%,压力然后就开始瞎猜——是测试不是代理池不够?是不是目标网站有反爬?其实大多数情况下,问题就出在你自己的压力代码上。
拿我去年做的测试一个电商网站采集项目来说。目标站点的压力商品接口响应速度大概200毫秒,我一开始用requests库写了个简单的测试循环,单线程跑,压力一秒钟能抓5个商品。测试听起来还行对吧?压力但业务方说要全量同步,大概80万条数据,测试算下来要44个小时。压力这显然不行。测试
于是压力我开始压测。第一版优化用了ThreadPoolExecutor,开了20个线程。结果一跑,CPU直接飙到80%,内存也涨得飞快,但吞吐量只提升了3倍——远低于理论上的20倍提升。为什么呢?我加了日志一看,原来大部分线程都在等待网络响应,真正的CPU计算时间少得可怜。这就像你雇了20个工人,但他们都站在传送带前等着货物送过来,货物没到,工人都闲着。
真正的瓶颈不在线程数,而在IO等待。我改用asyncio异步IO,配合aiohttp,把并发量提到100个。这下效果立竿见影——每秒能抓80个商品,而且CPU占用反而降到了30%左右。因为异步IO在等待响应的时候会切换到其他任务,不会傻等。
但压测还没完。当我跑到第10万条数据的时候,突然报了一堆超时错误。排查后发现,目标服务器的连接池被打满了。你以为是自己这边的问题,结果是对面扛不住了。这个教训很重要——压力测试不只是测自己的系统,还要考虑对目标系统的冲击。后来我加了限速逻辑,每秒钟最多发50个请求,并且用随机延迟来模拟真实用户行为。
还有一次更诡异。我的代码在本地测试一切正常,每秒稳定抓取60条。但部署到云服务器上,性能直接腰斩。一开始怀疑是网络带宽问题,但检查后发现出口带宽才用了不到1Mbps。最后用perf工具一查,发现是Python的GIL在多核CPU上导致的上下文切换开销。本地是2核,云服务器是8核,线程调度反而变慢了。
解决方式很简单——改用multiprocessing,每个进程独立跑一部分任务,用消息队列来分发URL。性能立刻恢复到正常水平,而且还能横向扩展。
说实话,压力测试最让人头疼的从来不是工具用不熟,而是你根本不知道瓶颈在哪。我见过有人用Locust跑了几万并发,结果发现瓶颈是自己的数据库连接池配错了。也见过有人压测时直接把自己服务器压崩了,因为忘了设置文件描述符上限。
我的建议是:先测单线程,搞清楚基线性能;再逐步增加并发,观察CPU、内存、网络IO的变化曲线;每次只改一个变量,用cProfile或py-spy来定位热点函数。别一上来就开1000个线程,那只会让你更难找到问题。
压测的本质是暴露系统的脆弱点,而不是为了跑出一个好看的数字。如果你压测完发现一切正常,那说明你测的还不够狠。