我干了十年后端开发,一直用Python。同事换了Go,说性能好。我不服气,想看看Python的协程到底能不能扛高并发。我写了个压测脚本,模拟一万个连接同时发请求。
机器是8核16G的云服务器。Python版本3.11。框架用了aiohttp和uvicorn。第一轮测普通同步代码,每秒处理300个请求,CPU跑到80%。内存占用2.1G。一万个请求还没跑完,连接池先挂了。
我改成asyncio协程版本,代码差不多,加了async def和await。同样一万个连接,每秒能处理3800个请求,CPU只用了35%。内存稳定在1.5G。连接没断,响应时间平均80毫秒。同步版本平均响应是2.3秒。差距很明显。
但问题也来了。协程版本里有个数据库查询操作,我用了同步的pymysql。结果服务器直接卡死。一万个连接全部阻塞在数据库调用上。所有请求等了30秒才超时断开。我换了异步的aiomysql,问题解决。这说明协程里绝对不能混用同步阻塞操作。
我又试了文件读写。用内置的open函数读取日志,同样阻塞。改成aiofiles异步库,处理速度从每秒200条日志提升到6000条。线程版本虽然也能处理,但线程切换开销太大,内存涨到4G才搞定。协程版本内存始终在2G以下。
复杂的业务逻辑里,协程也有效。我模拟了三个外部API调用,每个平均延迟200毫秒。同步版本串行执行,总耗时600毫秒。协程版本用asyncio.gather并发调用,总耗时还是200毫秒。结果不变。但前提是这些API互相独立没依赖。
遇到过坑。协程里用requests库发HTTP请求,代码直接报错。必须全部换成aiohttp。还有try except捕获异常,协程的异常不会自动传播,要用asyncio.ensure_future或gather的return_exceptions参数。这些细节不处理,线上会出大问题。
真实场景下,协程最适合IO密集型任务。网络请求,数据库查询,文件读写,消息队列消费,这些场景收益明显。CPU密集型计算,比如图像处理,加密解密,协程反而拖慢速度。因为协程切换也不能让CPU计算变快,不如用进程池。
我拿这段代码去团队分享。老同事们看完都沉默了。有个同事说Python协程就是玩具。我说你试试这个压测结果。他第二天改了自己的项目,把同步的Flask换成aiohttp。数据库连接池从10个扩大到50个。首屏加载时间从3秒降到0.4秒。他后来主动找我聊天,说真香。
但别把所有请求都塞进一个事件循环里。我试过用单事件循环处理所有请求,超过2万并发时事件循环开始丢事件。用asyncio的Windows事件循环在高负载下容易漏事件。Linux上用uvloop替换标准事件循环,性能提升明显,但要注意版本兼容。最后我用了多事件循环,每个CPU核心跑一个。实测10万并发,CPU利用率75%,内存5.6G,无连接丢失。
总结一下我的真实感受。协程有用,但不是万能药。高并发场景里,协程能把Python的IO能力拉高一个档次。但前提是代码要干净,所有阻塞操作都得换成异步库。遇到CPU计算就得让路,让给多进程处理。团队里有人问我到底该不该用协程,我说你先看看你的瓶颈在哪。带宽用满了吗,数据库响应时间正常吗。这些没优化前,协程帮不了多大忙。
协程的调试比同步代码麻烦。堆栈信息不直观,断点经常停在奇怪的地方。我用loguru替代标准logging,加上了asyncio的contextvars传递请求ID。排查问题效率反而比同步版本高。因为协程代码比多线程代码更可控,没有竞态条件和死锁。这块经验让我对协程更有信心。
讲个真事。我有个项目在上线后第三天崩溃了。查日志发现某个第三方REST API调用超时,导致整个事件循环被卡住。原因是用了sync_to_async,把同步函数转成异步,底层还是线程。去掉这个包装,直接上异步客户端,系统再没出过问题。结论是能用原生异步就用原生,别搞杂交。
现在我用Python写新项目,默认选异步框架。FastAPI加SQLAlchemy异步模式,配合Redis异步客户端。部署时用gunicorn加uvicorn workers,每个worker都有自己的事件循环。日常压测稳定在5万QPS。对比之前同步项目提升了10倍。这个数据是真实的,不是理论值。
协程改变了Python在服务端的命运。以前大家说Python只能写脚本和CRUD,现在高并发场景下也能站住脚。虽然比Go和Node.js还有差距,但对我们这些Python老用户来说,够用了。你如果还在纠结要不要学协程,直接用压测工具跑一下就明白。数据比任何争论都有说服力。