python-socketio异步多客户端连接实现及高并发CPU占用优化
Python-SocketIO 高并发连接CPU占用过高优化方案
问题描述
我参考python-socketio官方文档编写了异步通信代码,目前已实现客户端与服务端单连接搭建,可完成少量消息emit收发。现需要批量创建大量客户端连接Socket服务端,并行观测消息收发性能表现。
现有测试代码
client.py
import random import gevent import time import socketio import asyncio class ConnectionTest(): def __init__(self): self.sio = socketio.AsyncClient(handle_sigint=True, logger=True, engineio_logger=True) async def listen_to_redis(self): await self.sio.connect('http://dfs.confec.co.in/?socket_qa2=True&session_id='+str(random.randint(1,100000))) @self.sio.on('custom_event') async def on_custom_event(data): await self.sio.emit('message', '27 July 2015) was an Indian aerospace scientist') @self.sio.on('client') async def on_client(data): pass async def run(self): await self.listen_to_redis() async def main(): async def iterations(i): print("connection :",i) obj1 = ConnectionTest() await obj1.run() await obj1.sio.wait() x = [iterations(i) for i in range(2000)] await asyncio.gather(*x) if __name__ == '__main__': loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.run_until_complete(main())
注:原代码中main函数存在缩进错误,上述代码已做修正
Server.py
import uvicorn import asyncio import socketio import aiohttp url='redis://my_master:q2_gadse@10.1.7.4:6379/0' channel='public_chat' mgr = socketio.AsyncRedisManager(url=url, channel=channel, write_only=False) sio = socketio.AsyncServer(async_mode='aiohttp',client_manager=mgr, async_handlers=True, logger=True, engineio_logger=True) app = aiohttp.web.Application() sio.attach(app) @sio.on('connect') async def test_connect(sid, environ): print('connected', sid) @sio.event async def message(sid, message): await sio.emit('client', message) @sio.on('disconnect') async def test_disconnect(sid): print('Client disconnected') if __name__=='__main__': aiohttp.web.run_app(app, host='0.0.0.0', port=31451)
当前测试情况
目前已通过实例化多个AsyncClient对象完成多连接性能测试,上述代码已做对应更新,但无论采用何种部署策略,CPU占用始终持续达到100%。性能目标为单处理器至少承载5000个连接(最初目标为10000个)。
测试硬件配置为4GB内存、2核/4核CPU,客户端与服务端分别部署在不同Linux节点上。测试过程中客户端CPU占用快速攀升至100%-200%,服务端因使用Redis作为外部脚本emit消息的中间件,运行状态相对稳定。
查阅python-socketio官方文档后,未找到类似Node.js版Socket.IO提供的性能调优、负载测试、压力测试相关章节,压测统计截图已附,现求可实现单进程至少5000个连接、解决CPU占用过高问题的可行优化建议。
优化方案
按优先级顺序执行即可:
- 第一优先级:关闭调试日志
当前客户端和服务端初始化时都开启了logger=True, engineio_logger=True,两个参数会输出所有协议交互、心跳、事件收发的详细日志,高并发下日志IO和字符串拼接会消耗70%以上的CPU资源。压测和生产环境必须设置为False,仅在调试单连接问题时临时开启。 - 修正客户端事件注册逻辑
当前把@self.sio.on事件装饰器写在了listen_to_redis方法内部,每次新建连接都会重复注册事件handler,额外占用内存和事件分发的CPU开销。把事件注册移到__init__方法中,每个客户端实例只注册一次即可。 - 调整系统文件句柄限制
Linux默认单进程文件句柄上限通常为1024,连接数达到2000已经接近上限,会触发大量连接重试、超时逻辑占用CPU。压测前执行ulimit -n 65535临时修改,同时修改/etc/security/limits.conf永久生效,保证单进程可打开的句柄数大于目标连接数。 - 替换异步事件循环实现
Python默认asyncio事件循环在高并发网络IO下性能一般,客户端和服务端都可以使用uvloop替换默认事件循环,安装后仅需在代码入口加两行:
网络IO性能可提升30%-50%。import uvloop asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) - 控制连接建立的并发速率
当前用asyncio.gather一次性发起2000+连接,瞬间的TCP握手、HTTP升级、SocketIO握手请求会打满事件循环的调度队列,导致CPU尖峰。可以用信号量控制并发建连速率,比如每秒建立100-200个连接,避免瞬间流量冲击:sem = asyncio.Semaphore(200) async def iterations(i): async with sem: print("connection :",i) obj1 = ConnectionTest() await obj1.run() await asyncio.sleep(1) await obj1.sio.wait() - 关闭不必要的客户端配置
客户端初始化时handle_sigint=True仅需在单连接调试时开启,多实例压测时可以设为False,避免信号处理的额外开销;如果不需要自动重连、心跳检测的精细配置,可以调整ping_interval、ping_timeout参数为更宽松的值,减少心跳包的收发频率。 - 服务端配置优化
服务端当前用aiohttp.web.run_app启动默认是单进程,可以配合gunicorn+uvicorn worker启动多进程实例,绑定不同端口分摊连接压力;如果不需要异步handler的隔离,可以把async_handlers=True设为False,减少事件调度的开销。
按以上步骤调整后,单2核CPU节点承载5000-10000个空闲长连接的CPU占用通常可以降到10%以下,可达到预期性能目标。
内容的提问来源于stack exchange,提问作者keto ja
相关产品推荐
相关产品推荐

