raise exception aiormq.exceptions.ConnectionNotAllowed: NOT_ALLOWED -
number of channels opened (2047) has reached the negotiated
channel_max (2047)
这个错误表明你遇到了 RabbitMQ 的 channel_max
限制。RabbitMQ 服务器与客户端在建立连接时会"协商"一个最大通道数(channel_max
),默认通常是 2047 。当你的应用创建的通道数量达到这个上限时,就会抛出 NOT_ALLOWED - number of channels opened (2047) has reached the negotiated channel_max (2047)
错误。
问题分析:
这个问题是另一个层面的问题:通道(Channel)的生命周期管理不当。
存在一个关键误解:
通道(Channel)不应该被复用或"池化"。
每次使用完通道后,它应该被 关闭 ,而不是放回连接池。如果你反复从同一个连接创建新通道但不关闭它们,通道计数就会不断累积,最终达到 channel_max
限制。
✅ 正确的做法
- 每个操作使用一个新通道。
- 操作结束后,通道必须被关闭。
- 连接可以复用,但通道不能。
🔧 修复方案:修改 get_channel()
上下文管理器
你需要确保每次 get_channel()
使用完毕后,通道被关闭,而连接被放回池中。
python
@asynccontextmanager
async def get_channel(self) -> AsyncGenerator[AbstractRobustChannel, None]:
"""
获取通道的上下文管理器
每次调用都会从连接池获取一个连接,创建一个新通道
使用完毕后,通道会被关闭,连接会被归还到池中
"""
if not self._is_initialized:
await self.initialize()
async with self._lock:
while True:
if self._connection_pool:
connection = self._connection_pool.pop()
if not connection.is_closed:
break
else:
logger.warning("Found a closed connection in pool, discarding it.")
continue
else:
logger.warning("No available connections in pool, creating new one")
connection = await self._create_connection()
break
try:
# 创建新通道
channel = await connection.channel()
logger.debug("Created new channel for operation")
yield channel # 使用通道
finally:
# 关键:使用完后关闭通道
if 'channel' in locals() and not channel.is_closed:
await channel.close()
logger.debug("Channel closed after use")
# 将连接(不是通道)归还到池中
async with self._lock:
if not connection.is_closed:
self._connection_pool.append(connection)
logger.debug("Connection returned to pool")
else:
logger.warning("Connection was closed, not returning to pool")
🚫 为什么不能复用通道?
- 线程/协程不安全:AMQP 通道不是线程安全的,多个协程同时使用同一个通道可能导致协议错误。
- 状态污染:通道可能处于某种状态(如事务、QoS 设置等),复用会导致不可预测的行为。
- 最佳实践:官方推荐是"每个操作一个通道"或"每个线程/协程一个通道"。
🛠 其他建议
- 检查
channel_max
配置 (可选):
你可以尝试在连接时设置更低的channel_max
来避免达到默认的 2047,但这只是治标不治本。
python
connection = await aio_pika.connect_robust(
self.url,
heartbeat=self.heartbeat,
timeout=self.connection_timeout,
client_properties={"channel_max": 100}, # 限制客户端通道数
)
-
监控连接和通道状态 :
添加日志,监控连接和通道的创建、关闭情况。
-
避免长时间持有通道 :
通道应该用于短时间的操作(如发布、消费一次消息),不要长期持有。
✅ 总结
你遇到的错误是因为通道没有被正确关闭 ,导致连接上的通道数不断累积,最终达到 channel_max
限制。
解决方案:
- 修改
get_channel()
,确保每次使用后 关闭通道。 - 将 连接 归还到连接池,而不是通道。
- 遵循"每个操作一个通道"的最佳实践。
这样就能避免 channel_max
被耗尽,解决 ConnectionNotAllowed
错误。