使用Mobility House OCPP库时self.call函数延迟30秒问题排查
问题原因分析
核心问题在于同步阻塞的Redis队列操作卡住了asyncio事件循环:
你的handle_actions()函数在asyncio事件循环线程中调用了CleanChargeRedis.redis_queue_pop,这个方法大概率基于Redis的BLPOP(阻塞式弹出)实现,且超时时间设置为30秒。在asyncio的事件循环中,任何同步阻塞的IO操作会占据整个线程,导致事件循环无法处理其他异步任务——包括OCPP库接收充电桩响应的网络IO。
虽然充电桩已即时返回RemoteStartTransaction的响应,但OCPP库的回调协程被阻塞的事件循环卡住,无法被调度执行,直到Redis的阻塞操作超时(30秒)或拿到新消息,这才造成了await self.call()的延迟。
解决方法
有两种可行修复方案,核心都是避免同步阻塞操作占用asyncio事件循环:
方案1:使用异步Redis客户端替换同步操作
改用异步Redis库(如aioredis),将Redis队列读取改为异步非阻塞操作,让事件循环可同时处理OCPP网络IO和Redis任务:
import aioredis import json async def handle_actions(): # 初始化异步Redis连接 redis_conn = await aioredis.from_url(settings.REDIS_CONFIG['url']) queue_name = settings.REDIS_CONFIG['queue_name'] while True: # 异步阻塞弹出队列元素,不会卡住事件循环 _, msg_json = await redis_conn.blpop(queue_name) msg = json.loads(msg_json.decode('utf-8')) print(f"Received from Redis queue: {msg_json.decode('utf-8')}") if msg['action'] == 'remote_start': await handle_remote_start(msg) elif msg['action'] == 'reserve_now': await handle_reserve_now(msg)
方案2:将同步Redis操作移到独立线程执行
如果无法替换同步Redis客户端,使用asyncio.to_thread(Python 3.9+)或线程池,把同步阻塞的Redis操作放到单独线程中执行,避免阻塞事件循环:
import asyncio import json async def handle_actions(): db = CleanChargeRedis.redis_db(settings.REDIS_CONFIG) queue_name = settings.REDIS_CONFIG['queue_name'] while True: # 把同步阻塞的pop操作放到线程中执行 msg_json = await asyncio.to_thread(CleanChargeRedis.redis_queue_pop, db, queue_name) if not msg_json: continue print(f"Received from Redis queue: {msg_json}") msg = json.loads(msg_json) if msg['action'] == 'remote_start': await handle_remote_start(msg) elif msg['action'] == 'reserve_now': await handle_reserve_now(msg)
验证点
先确认CleanChargeRedis.redis_queue_pop的实现:如果它调用了Redis的BLPOP命令且超时参数为30秒,完全符合你遇到的延迟现象,验证后应用上述方案即可解决问题。
内容的提问来源于stack exchange,提问作者Tan Wei Kang
相关产品推荐
相关产品推荐

