sycommon-python-lib 0.1.56b4__tar.gz → 0.1.56b5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/PKG-INFO +1 -1
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/pyproject.toml +1 -1
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/rabbitmq/rabbitmq_client.py +26 -19
- sycommon_python_lib-0.1.56b5/src/sycommon/rabbitmq/rabbitmq_pool.py +320 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- sycommon_python_lib-0.1.56b4/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -338
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/README.md +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/setup.cfg +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/database/async_base_db_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/database/async_database_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/embedding.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/get_llm.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/llm_logger.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/llm_tokens.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/async_sql_logger.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/logger_levels.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/notice/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/notice/uvicorn_monitor.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/rabbitmq/rabbitmq_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/merge_headers.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
|
@@ -145,10 +145,15 @@ class RabbitMQClient:
|
|
|
145
145
|
raise RuntimeError("客户端已关闭,无法重新连接")
|
|
146
146
|
|
|
147
147
|
async with self._connect_lock:
|
|
148
|
-
#
|
|
149
|
-
if self.
|
|
150
|
-
|
|
151
|
-
self.
|
|
148
|
+
# 1. 清理旧连接回调(防止内存泄漏)
|
|
149
|
+
if self._channel_conn and self._conn_close_callback:
|
|
150
|
+
try:
|
|
151
|
+
self._channel_conn.close_callbacks.discard(
|
|
152
|
+
self._conn_close_callback)
|
|
153
|
+
except Exception:
|
|
154
|
+
pass
|
|
155
|
+
|
|
156
|
+
# 2. 清理状态
|
|
152
157
|
self._channel = None
|
|
153
158
|
self._channel_conn = None
|
|
154
159
|
self._exchange = None
|
|
@@ -156,18 +161,14 @@ class RabbitMQClient:
|
|
|
156
161
|
self._conn_close_callback = None
|
|
157
162
|
|
|
158
163
|
try:
|
|
159
|
-
#
|
|
164
|
+
# 3. 获取新通道
|
|
160
165
|
self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
|
|
161
166
|
|
|
167
|
+
# 4. 设置新连接回调(使用 weakref)
|
|
162
168
|
def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
self._reconnect_fail_count += 1
|
|
167
|
-
# 超过阈值告警
|
|
168
|
-
if self._reconnect_fail_count >= self._reconnect_alert_threshold:
|
|
169
|
-
logger.error(
|
|
170
|
-
f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
|
|
169
|
+
# 注意:这里需要访问外部的 self,使用闭包或 weakref
|
|
170
|
+
# 简单起见,这里用闭包,但务必在 self.close 或 self.connect 时清理回调
|
|
171
|
+
logger.warning(f"检测到连接关闭: {exc}")
|
|
171
172
|
if not self._closed:
|
|
172
173
|
asyncio.create_task(self._safe_reconnect())
|
|
173
174
|
|
|
@@ -176,20 +177,26 @@ class RabbitMQClient:
|
|
|
176
177
|
self._channel_conn.close_callbacks.add(
|
|
177
178
|
self._conn_close_callback)
|
|
178
179
|
|
|
179
|
-
#
|
|
180
|
+
# 5. 重建资源
|
|
180
181
|
await self._rebuild_resources()
|
|
181
182
|
|
|
182
|
-
#
|
|
183
|
+
# 重置计数
|
|
183
184
|
self._reconnect_fail_count = 0
|
|
184
185
|
logger.info("客户端连接初始化完成")
|
|
185
186
|
except Exception as e:
|
|
186
187
|
logger.error(f"客户端连接失败: {str(e)}", exc_info=True)
|
|
187
|
-
#
|
|
188
|
-
if self.
|
|
189
|
-
|
|
190
|
-
self.
|
|
188
|
+
# 失败时也要清理可能产生的残留引用
|
|
189
|
+
if self._channel_conn and self._conn_close_callback:
|
|
190
|
+
try:
|
|
191
|
+
self._channel_conn.close_callbacks.discard(
|
|
192
|
+
self._conn_close_callback)
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
195
|
+
# 清空状态
|
|
191
196
|
self._channel = None
|
|
192
197
|
self._channel_conn = None
|
|
198
|
+
self._conn_close_callback = None
|
|
199
|
+
|
|
193
200
|
# 触发重连
|
|
194
201
|
if not self._closed:
|
|
195
202
|
asyncio.create_task(self._safe_reconnect())
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import random
|
|
3
|
+
from typing import Optional, List, Dict, Callable, Tuple
|
|
4
|
+
from aio_pika import connect_robust, RobustChannel, Message
|
|
5
|
+
from aio_pika.abc import (
|
|
6
|
+
AbstractRobustConnection, AbstractQueue, AbstractExchange, AbstractMessage
|
|
7
|
+
)
|
|
8
|
+
from sycommon.logging.kafka_log import SYLogger
|
|
9
|
+
|
|
10
|
+
logger = SYLogger
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AsyncProperty:
|
|
14
|
+
"""实现 await obj.attr 的支持"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, method):
|
|
17
|
+
self.method = method
|
|
18
|
+
|
|
19
|
+
def __get__(self, obj, objtype=None):
|
|
20
|
+
if obj is None:
|
|
21
|
+
return self
|
|
22
|
+
# 关键:当访问 obj.attr 时,直接返回协程对象,而不是方法本身
|
|
23
|
+
return self.method(obj)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class RabbitMQConnectionPool:
|
|
27
|
+
"""单连接单通道RabbitMQ客户端 (增强版日志)"""
|
|
28
|
+
|
|
29
|
+
def __init__(
|
|
30
|
+
self,
|
|
31
|
+
hosts: List[str],
|
|
32
|
+
port: int,
|
|
33
|
+
username: str,
|
|
34
|
+
password: str,
|
|
35
|
+
virtualhost: str = "/",
|
|
36
|
+
heartbeat: int = 30,
|
|
37
|
+
app_name: str = "",
|
|
38
|
+
connection_timeout: int = 30,
|
|
39
|
+
reconnect_interval: int = 5,
|
|
40
|
+
prefetch_count: int = 2,
|
|
41
|
+
):
|
|
42
|
+
self.hosts = [host.strip() for host in hosts if host.strip()]
|
|
43
|
+
if not self.hosts:
|
|
44
|
+
raise ValueError("至少需要提供一个RabbitMQ主机地址")
|
|
45
|
+
|
|
46
|
+
self.port = port
|
|
47
|
+
self.username = username
|
|
48
|
+
self.password = password
|
|
49
|
+
self.virtualhost = virtualhost
|
|
50
|
+
self.app_name = app_name or "rabbitmq-client"
|
|
51
|
+
self.heartbeat = heartbeat
|
|
52
|
+
self.connection_timeout = connection_timeout
|
|
53
|
+
self.reconnect_interval = reconnect_interval
|
|
54
|
+
self.prefetch_count = prefetch_count
|
|
55
|
+
|
|
56
|
+
self._current_host: str = random.choice(self.hosts)
|
|
57
|
+
logger.info(f"[INIT] 随机选择RabbitMQ主机: {self._current_host}")
|
|
58
|
+
|
|
59
|
+
# 核心资源
|
|
60
|
+
self._connection: Optional[AbstractRobustConnection] = None
|
|
61
|
+
self._channel: Optional[RobustChannel] = None
|
|
62
|
+
self._consumer_channels: Dict[str, RobustChannel] = {}
|
|
63
|
+
|
|
64
|
+
# 状态控制
|
|
65
|
+
self._lock = asyncio.Lock()
|
|
66
|
+
self._initialized = False
|
|
67
|
+
self._is_shutdown = False
|
|
68
|
+
|
|
69
|
+
@AsyncProperty
|
|
70
|
+
async def is_alive(self) -> bool:
|
|
71
|
+
"""对外暴露的连接存活状态(原子化判断)"""
|
|
72
|
+
async with self._lock:
|
|
73
|
+
if self._is_shutdown:
|
|
74
|
+
return False
|
|
75
|
+
|
|
76
|
+
if not self._initialized:
|
|
77
|
+
return False
|
|
78
|
+
|
|
79
|
+
if self._connection is None or self._connection.is_closed:
|
|
80
|
+
return False
|
|
81
|
+
|
|
82
|
+
# 可选:检查主通道是否存活
|
|
83
|
+
if self._channel is None or self._channel.is_closed:
|
|
84
|
+
# 如果你认为通道断了连接也算死,就保留这行;否则删除
|
|
85
|
+
return False
|
|
86
|
+
|
|
87
|
+
return True
|
|
88
|
+
|
|
89
|
+
async def _create_connection_impl(self) -> AbstractRobustConnection:
|
|
90
|
+
"""
|
|
91
|
+
连接创建入口
|
|
92
|
+
"""
|
|
93
|
+
conn_url = (
|
|
94
|
+
f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/"
|
|
95
|
+
f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
|
|
96
|
+
f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
|
|
97
|
+
)
|
|
98
|
+
logger.info(
|
|
99
|
+
f"🔌 [CONNECT_START] 尝试创建连接 -> {self._current_host}:{self.port}")
|
|
100
|
+
try:
|
|
101
|
+
conn = await connect_robust(conn_url, timeout=self.connection_timeout)
|
|
102
|
+
# 注意:connect_robust 返回时,底层 TCP 可能还在握手,但对象已创建
|
|
103
|
+
logger.info(f"✅ [CONNECT_OK] 连接对象创建成功: {id(conn)}")
|
|
104
|
+
return conn
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"❌ [CONNECT_FAIL] 连接创建失败: {str(e)}")
|
|
107
|
+
raise ConnectionError(f"无法连接RabbitMQ {self._current_host}") from e
|
|
108
|
+
|
|
109
|
+
async def _create_channel_impl(self, connection: AbstractRobustConnection) -> RobustChannel:
|
|
110
|
+
"""创建通道"""
|
|
111
|
+
try:
|
|
112
|
+
channel = await connection.channel()
|
|
113
|
+
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
114
|
+
logger.debug(f"✅ [CHANNEL_OK] 通道创建成功: {id(channel)}")
|
|
115
|
+
return channel
|
|
116
|
+
except Exception as e:
|
|
117
|
+
logger.error(f"❌ [CHANNEL_FAIL] 通道创建失败: {str(e)}")
|
|
118
|
+
raise
|
|
119
|
+
|
|
120
|
+
async def _ensure_main_channel(self) -> RobustChannel:
|
|
121
|
+
"""确保主通道有效 (原子操作)"""
|
|
122
|
+
async with self._lock:
|
|
123
|
+
if self._is_shutdown:
|
|
124
|
+
raise RuntimeError("客户端已关闭")
|
|
125
|
+
|
|
126
|
+
# 1. 确保底层连接存在
|
|
127
|
+
conn = self._connection
|
|
128
|
+
if conn is None or conn.is_closed:
|
|
129
|
+
logger.info("⚠️ [RECONNECT] 检测到连接不存在或已关闭,开始重建...")
|
|
130
|
+
conn = await self._create_connection_impl()
|
|
131
|
+
self._connection = conn
|
|
132
|
+
logger.info(f"🔗 [UPDATE] 连接对象已更新: {id(conn)}")
|
|
133
|
+
|
|
134
|
+
# 2. 确保主通道存在
|
|
135
|
+
if self._channel is None or self._channel.is_closed:
|
|
136
|
+
logger.info("⚠️ [RECOVER_CHANNEL] 检测到主通道不存在或已关闭,开始恢复...")
|
|
137
|
+
self._channel = await self._create_channel_impl(conn)
|
|
138
|
+
|
|
139
|
+
return self._channel
|
|
140
|
+
|
|
141
|
+
async def init_pools(self):
|
|
142
|
+
"""
|
|
143
|
+
初始化入口与异常处理 (修复泄漏的关键)
|
|
144
|
+
"""
|
|
145
|
+
async with self._lock:
|
|
146
|
+
if self._is_shutdown:
|
|
147
|
+
raise RuntimeError("客户端已关闭")
|
|
148
|
+
if self._initialized:
|
|
149
|
+
return
|
|
150
|
+
|
|
151
|
+
conn_created_in_this_try = None
|
|
152
|
+
try:
|
|
153
|
+
# 步骤 A: 创建连接 (在锁外进行,避免阻塞其他操作)
|
|
154
|
+
conn = await self._create_connection_impl()
|
|
155
|
+
conn_created_in_this_try = conn # 记录本次创建的对象,用于失败回滚
|
|
156
|
+
|
|
157
|
+
# 步骤 B: 更新状态和初始化通道 (在锁内进行,保证原子性)
|
|
158
|
+
async with self._lock:
|
|
159
|
+
if self._is_shutdown:
|
|
160
|
+
# 如果在创建连接期间,外部调用了 close,则必须立即清理刚创建的连接
|
|
161
|
+
logger.warning("⚠️ [ABORT] 检测到关闭信号,放弃初始化并清理资源")
|
|
162
|
+
raise RuntimeError("客户端已关闭")
|
|
163
|
+
|
|
164
|
+
self._connection = conn
|
|
165
|
+
self._channel = await self._create_channel_impl(conn)
|
|
166
|
+
self._initialized = True
|
|
167
|
+
logger.info(
|
|
168
|
+
f"🚀 [INIT_SUCCESS] 客户端初始化完成. ConnID: {id(self._connection)}")
|
|
169
|
+
|
|
170
|
+
except Exception as e:
|
|
171
|
+
logger.error(f"💥 [INIT_ERROR] 初始化流程异常: {str(e)}", exc_info=True)
|
|
172
|
+
# 如果步骤A成功但步骤B失败(例如通道创建失败),或者步骤B中出错,
|
|
173
|
+
# 必须显式关闭在步骤A中创建的连接,否则它会变成“游离连接”。
|
|
174
|
+
if conn_created_in_this_try:
|
|
175
|
+
logger.warning(
|
|
176
|
+
f"🧹 [LEAK_PREVENTION] 检测到初始化失败,正在显式关闭刚创建的连接: {id(conn_created_in_this_try)}")
|
|
177
|
+
try:
|
|
178
|
+
await conn_created_in_this_try.close()
|
|
179
|
+
logger.info(
|
|
180
|
+
f"✅ [CLOSE_OK] 泄漏连接已关闭: {id(conn_created_in_this_try)}")
|
|
181
|
+
except Exception as close_err:
|
|
182
|
+
logger.error(f"❌ [CLOSE_ERR] 关闭泄漏连接时出错: {str(close_err)}")
|
|
183
|
+
|
|
184
|
+
# 如果是因为中途关闭导致的错误,不需要再次调用全局 close,否则调用
|
|
185
|
+
if not self._is_shutdown:
|
|
186
|
+
await self.close()
|
|
187
|
+
raise
|
|
188
|
+
|
|
189
|
+
async def acquire_channel(self) -> Tuple[RobustChannel, AbstractRobustConnection]:
|
|
190
|
+
"""获取主通道"""
|
|
191
|
+
if not self._initialized and not self._is_shutdown:
|
|
192
|
+
await self.init_pools()
|
|
193
|
+
return await self._ensure_main_channel(), self._connection
|
|
194
|
+
|
|
195
|
+
async def publish_message(self, routing_key: str, message_body: bytes, exchange_name: str = "", **kwargs):
|
|
196
|
+
"""发布消息"""
|
|
197
|
+
channel, _ = await self.acquire_channel()
|
|
198
|
+
try:
|
|
199
|
+
exchange = channel.default_exchange if not exchange_name else await channel.get_exchange(exchange_name)
|
|
200
|
+
message = Message(body=message_body, **kwargs)
|
|
201
|
+
await exchange.publish(message, routing_key=routing_key)
|
|
202
|
+
logger.debug(f"📤 [PUBLISH] 消息发布成功 - RK: {routing_key}")
|
|
203
|
+
except Exception as e:
|
|
204
|
+
logger.error(f"❌ [PUBLISH_FAIL] 发布失败: {str(e)}")
|
|
205
|
+
raise
|
|
206
|
+
|
|
207
|
+
async def consume_queue(self, queue_name: str, callback: Callable[[AbstractMessage], asyncio.Future], auto_ack: bool = False, **kwargs):
|
|
208
|
+
"""消费队列"""
|
|
209
|
+
if not self._initialized:
|
|
210
|
+
await self.init_pools()
|
|
211
|
+
|
|
212
|
+
async with self._lock:
|
|
213
|
+
if self._is_shutdown:
|
|
214
|
+
raise RuntimeError("客户端已关闭")
|
|
215
|
+
if queue_name in self._consumer_channels:
|
|
216
|
+
logger.warning(f"⚠️ [CONSUMER_EXISTS] 队列 {queue_name} 已在消费中")
|
|
217
|
+
return
|
|
218
|
+
if not self._connection or self._connection.is_closed:
|
|
219
|
+
raise RuntimeError("连接不可用,无法启动消费")
|
|
220
|
+
|
|
221
|
+
await self.declare_queue(queue_name, **kwargs)
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
# 获取原始连接对象创建新通道
|
|
225
|
+
conn = self._connection
|
|
226
|
+
consumer_channel = await conn.channel()
|
|
227
|
+
await consumer_channel.set_qos(prefetch_count=self.prefetch_count)
|
|
228
|
+
logger.info(
|
|
229
|
+
f"✅ [CONSUMER_CHANNEL_OK] 消费者通道创建: {id(consumer_channel)}")
|
|
230
|
+
|
|
231
|
+
async with self._lock:
|
|
232
|
+
if self._is_shutdown:
|
|
233
|
+
await consumer_channel.close()
|
|
234
|
+
return
|
|
235
|
+
self._consumer_channels[queue_name] = consumer_channel
|
|
236
|
+
|
|
237
|
+
async def consume_callback_wrapper(message: AbstractMessage):
|
|
238
|
+
try:
|
|
239
|
+
await callback(message)
|
|
240
|
+
if not auto_ack:
|
|
241
|
+
await message.ack()
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(
|
|
244
|
+
f"❌ [CALLBACK_ERR] 消费回调异常 {queue_name}: {str(e)}")
|
|
245
|
+
if not auto_ack:
|
|
246
|
+
await message.nack(requeue=True)
|
|
247
|
+
|
|
248
|
+
await consumer_channel.basic_consume(
|
|
249
|
+
queue_name, consumer_callback=consume_callback_wrapper, auto_ack=auto_ack, **kwargs
|
|
250
|
+
)
|
|
251
|
+
logger.info(f"🎧 [CONSUME_START] 开始消费队列: {queue_name}")
|
|
252
|
+
|
|
253
|
+
except Exception as e:
|
|
254
|
+
logger.error(f"💥 [CONSUME_ERR] 启动消费失败 {queue_name}: {str(e)}")
|
|
255
|
+
async with self._lock:
|
|
256
|
+
if queue_name in self._consumer_channels:
|
|
257
|
+
del self._consumer_channels[queue_name]
|
|
258
|
+
raise
|
|
259
|
+
|
|
260
|
+
async def close(self):
|
|
261
|
+
"""
|
|
262
|
+
资源销毁入口
|
|
263
|
+
"""
|
|
264
|
+
async with self._lock:
|
|
265
|
+
if self._is_shutdown:
|
|
266
|
+
return
|
|
267
|
+
self._is_shutdown = True
|
|
268
|
+
self._initialized = False
|
|
269
|
+
# 记录即将关闭的连接ID
|
|
270
|
+
conn_to_close_id = id(
|
|
271
|
+
self._connection) if self._connection else None
|
|
272
|
+
|
|
273
|
+
logger.info(f"🛑 [CLOSE_START] 开始关闭客户端... (准备关闭连接: {conn_to_close_id})")
|
|
274
|
+
|
|
275
|
+
# 1. 关闭消费者通道
|
|
276
|
+
channels_to_close = []
|
|
277
|
+
async with self._lock:
|
|
278
|
+
channels_to_close = list(self._consumer_channels.values())
|
|
279
|
+
self._consumer_channels.clear()
|
|
280
|
+
|
|
281
|
+
for ch in channels_to_close:
|
|
282
|
+
try:
|
|
283
|
+
if not ch.is_closed:
|
|
284
|
+
await ch.close()
|
|
285
|
+
logger.debug(f"✅ [CLOSE_CHANNEL] 消费者通道已关闭")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.warning(f"❌ [CLOSE_CHANNEL_ERR] 关闭消费者通道失败: {str(e)}")
|
|
288
|
+
|
|
289
|
+
# 2. 关闭主通道
|
|
290
|
+
if self._channel:
|
|
291
|
+
try:
|
|
292
|
+
if not self._channel.is_closed:
|
|
293
|
+
await self._channel.close()
|
|
294
|
+
logger.info(f"✅ [CLOSE_CHANNEL] 主通道已关闭")
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
self._channel = None
|
|
298
|
+
|
|
299
|
+
# 3. 关闭连接
|
|
300
|
+
# 确保在 finally 或显式 close 中调用 connection.close()
|
|
301
|
+
if self._connection:
|
|
302
|
+
try:
|
|
303
|
+
# 打印关闭操作
|
|
304
|
+
logger.info(f"🔌 [CLOSE_CONN] 正在关闭连接: {id(self._connection)}")
|
|
305
|
+
await self._connection.close()
|
|
306
|
+
logger.info(f"✅ [CLOSE_OK] 连接已成功关闭: {id(self._connection)}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.warning(f"❌ [CLOSE_ERR] 关闭连接失败: {str(e)}")
|
|
309
|
+
self._connection = None
|
|
310
|
+
|
|
311
|
+
logger.info("🏁 [CLOSE_DONE] RabbitMQ客户端已完全关闭")
|
|
312
|
+
|
|
313
|
+
# --- 辅助方法省略 (declare_queue 等) ---
|
|
314
|
+
async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
|
|
315
|
+
channel, _ = await self.acquire_channel()
|
|
316
|
+
return await channel.declare_queue(queue_name, **kwargs)
|
|
317
|
+
|
|
318
|
+
async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
|
|
319
|
+
channel, _ = await self.acquire_channel()
|
|
320
|
+
return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)
|
|
@@ -1,338 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
import random
|
|
3
|
-
from typing import Optional, List, Dict, Callable, Tuple
|
|
4
|
-
from aio_pika import connect_robust, RobustChannel, Message
|
|
5
|
-
from aio_pika.abc import (
|
|
6
|
-
AbstractRobustConnection, AbstractQueue, AbstractExchange, AbstractMessage
|
|
7
|
-
)
|
|
8
|
-
from aio_pika.exceptions import ChannelClosed
|
|
9
|
-
import aiormq.exceptions
|
|
10
|
-
|
|
11
|
-
from sycommon.logging.kafka_log import SYLogger
|
|
12
|
-
|
|
13
|
-
logger = SYLogger
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class RabbitMQConnectionPool:
|
|
17
|
-
"""单连接单通道RabbitMQ客户端(核心特性:依赖connect_robust原生自动重连/恢复 + 仅关闭时释放资源)"""
|
|
18
|
-
|
|
19
|
-
def __init__(
|
|
20
|
-
self,
|
|
21
|
-
hosts: List[str],
|
|
22
|
-
port: int,
|
|
23
|
-
username: str,
|
|
24
|
-
password: str,
|
|
25
|
-
virtualhost: str = "/",
|
|
26
|
-
heartbeat: int = 30,
|
|
27
|
-
app_name: str = "",
|
|
28
|
-
connection_timeout: int = 30,
|
|
29
|
-
reconnect_interval: int = 5,
|
|
30
|
-
prefetch_count: int = 2,
|
|
31
|
-
):
|
|
32
|
-
# 基础配置校验与初始化
|
|
33
|
-
self.hosts = [host.strip() for host in hosts if host.strip()]
|
|
34
|
-
if not self.hosts:
|
|
35
|
-
raise ValueError("至少需要提供一个RabbitMQ主机地址")
|
|
36
|
-
|
|
37
|
-
self.port = port
|
|
38
|
-
self.username = username
|
|
39
|
-
self.password = password
|
|
40
|
-
self.virtualhost = virtualhost
|
|
41
|
-
self.app_name = app_name or "rabbitmq-client"
|
|
42
|
-
self.heartbeat = heartbeat
|
|
43
|
-
self.connection_timeout = connection_timeout
|
|
44
|
-
self.reconnect_interval = reconnect_interval
|
|
45
|
-
self.prefetch_count = prefetch_count
|
|
46
|
-
|
|
47
|
-
# 初始化时随机选择一个主机地址(固定使用,依赖原生重连)
|
|
48
|
-
self._current_host: str = random.choice(self.hosts)
|
|
49
|
-
logger.info(
|
|
50
|
-
f"随机选择RabbitMQ主机: {self._current_host}(依赖connect_robust原生自动重连/恢复)")
|
|
51
|
-
|
|
52
|
-
# 核心资源(单连接+单通道,基于原生自动重连)
|
|
53
|
-
self._connection: Optional[AbstractRobustConnection] = None # 原生自动重连连接
|
|
54
|
-
self._channel: Optional[RobustChannel] = None # 单通道(原生自动恢复)
|
|
55
|
-
# 消费者通道跟踪(独立于主通道)
|
|
56
|
-
self._consumer_channels: Dict[str,
|
|
57
|
-
Tuple[RobustChannel, Callable, bool, dict]] = {}
|
|
58
|
-
|
|
59
|
-
# 状态控制(并发安全+生命周期管理)
|
|
60
|
-
self._lock = asyncio.Lock()
|
|
61
|
-
self._initialized = False
|
|
62
|
-
self._is_shutdown = False
|
|
63
|
-
|
|
64
|
-
async def _is_connection_valid(self) -> bool:
|
|
65
|
-
"""原子化检查连接有效性(所有状态判断均加锁,确保原子性)"""
|
|
66
|
-
async with self._lock:
|
|
67
|
-
# 优先级:先判断是否关闭,再判断是否初始化,最后判断连接状态
|
|
68
|
-
return not self._is_shutdown and self._initialized and self._connection is not None and not self._connection.is_closed
|
|
69
|
-
|
|
70
|
-
@property
|
|
71
|
-
async def is_alive(self) -> bool:
|
|
72
|
-
"""对外暴露的连接存活状态(原子化判断)"""
|
|
73
|
-
async with self._lock:
|
|
74
|
-
if self._is_shutdown:
|
|
75
|
-
return False
|
|
76
|
-
# 存活条件:未关闭 + 已初始化 + 连接有效 + 主通道有效
|
|
77
|
-
return self._initialized and self._connection is not None and not self._connection.is_closed and self._channel is not None and not self._channel.is_closed
|
|
78
|
-
|
|
79
|
-
async def _create_connection(self) -> AbstractRobustConnection:
|
|
80
|
-
"""创建原生自动重连连接(仅创建一次,内部自动重试)"""
|
|
81
|
-
async with self._lock:
|
|
82
|
-
if self._is_shutdown:
|
|
83
|
-
raise RuntimeError("客户端已关闭,无法创建连接")
|
|
84
|
-
|
|
85
|
-
conn_url = f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}&reconnect_interval={self.reconnect_interval}&fail_fast=1"
|
|
86
|
-
logger.info(f"尝试创建原生自动重连连接: {self._current_host}:{self.port}")
|
|
87
|
-
|
|
88
|
-
try:
|
|
89
|
-
conn = await connect_robust(
|
|
90
|
-
conn_url,
|
|
91
|
-
timeout=self.connection_timeout,
|
|
92
|
-
)
|
|
93
|
-
logger.info(f"连接创建成功: {self._current_host}:{self.port}(原生自动重连已启用)")
|
|
94
|
-
return conn
|
|
95
|
-
except Exception as e:
|
|
96
|
-
logger.error(f"连接创建失败: {str(e)}", exc_info=True)
|
|
97
|
-
raise ConnectionError(
|
|
98
|
-
f"无法连接RabbitMQ主机 {self._current_host}:{self.port}") from e
|
|
99
|
-
|
|
100
|
-
async def _init_single_channel(self):
|
|
101
|
-
"""初始化单通道(通道自带原生自动恢复)"""
|
|
102
|
-
async with self._lock:
|
|
103
|
-
# 先判断是否关闭(优先级最高)
|
|
104
|
-
if self._is_shutdown:
|
|
105
|
-
raise RuntimeError("客户端已关闭,无法初始化通道")
|
|
106
|
-
# 再判断连接是否有效
|
|
107
|
-
if not self._connection or self._connection.is_closed:
|
|
108
|
-
raise RuntimeError("无有效连接,无法初始化通道")
|
|
109
|
-
|
|
110
|
-
# 清理旧通道(如果存在)
|
|
111
|
-
if self._channel and not self._channel.is_closed:
|
|
112
|
-
await self._channel.close()
|
|
113
|
-
|
|
114
|
-
# 创建单通道并设置QOS
|
|
115
|
-
try:
|
|
116
|
-
self._channel = await self._connection.channel()
|
|
117
|
-
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
118
|
-
logger.info(f"单通道初始化完成(带原生自动恢复)")
|
|
119
|
-
except Exception as e:
|
|
120
|
-
logger.error(f"创建单通道失败: {str(e)}", exc_info=True)
|
|
121
|
-
raise
|
|
122
|
-
|
|
123
|
-
async def _check_and_recover_channel(self) -> RobustChannel:
|
|
124
|
-
"""检查并恢复通道(确保通道有效,所有状态判断加锁)"""
|
|
125
|
-
async with self._lock:
|
|
126
|
-
# 1. 先判断是否关闭(优先级最高)
|
|
127
|
-
if self._is_shutdown:
|
|
128
|
-
raise RuntimeError("客户端已关闭,无法获取通道")
|
|
129
|
-
# 2. 检查连接状态
|
|
130
|
-
if not self._connection or self._connection.is_closed:
|
|
131
|
-
raise RuntimeError("连接已关闭(等待原生重连)")
|
|
132
|
-
# 3. 通道失效时重新创建
|
|
133
|
-
if not self._channel or self._channel.is_closed:
|
|
134
|
-
logger.warning("通道失效,重新创建(依赖原生自动恢复)")
|
|
135
|
-
await self._init_single_channel()
|
|
136
|
-
|
|
137
|
-
return self._channel
|
|
138
|
-
|
|
139
|
-
async def init_pools(self):
|
|
140
|
-
"""初始化客户端(仅执行一次)"""
|
|
141
|
-
async with self._lock:
|
|
142
|
-
# 原子化判断:是否已关闭/已初始化
|
|
143
|
-
if self._is_shutdown:
|
|
144
|
-
raise RuntimeError("客户端已关闭,无法初始化")
|
|
145
|
-
if self._initialized:
|
|
146
|
-
logger.warning("客户端已初始化,无需重复调用")
|
|
147
|
-
return
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
# 1. 创建原生自动重连连接
|
|
151
|
-
self._connection = await self._create_connection()
|
|
152
|
-
|
|
153
|
-
# 2. 初始化单通道
|
|
154
|
-
await self._init_single_channel()
|
|
155
|
-
|
|
156
|
-
# 3. 标记为已初始化(加锁保护)
|
|
157
|
-
async with self._lock:
|
|
158
|
-
self._initialized = True
|
|
159
|
-
|
|
160
|
-
logger.info("RabbitMQ单通道客户端初始化完成(原生自动重连/恢复已启用)")
|
|
161
|
-
except Exception as e:
|
|
162
|
-
logger.error(f"初始化失败: {str(e)}", exc_info=True)
|
|
163
|
-
await self.close() # 初始化失败直接关闭
|
|
164
|
-
raise
|
|
165
|
-
|
|
166
|
-
async def acquire_channel(self) -> Tuple[RobustChannel, AbstractRobustConnection]:
|
|
167
|
-
"""获取单通道(返回 (通道, 连接) 元组,保持API兼容)"""
|
|
168
|
-
async with self._lock:
|
|
169
|
-
# 原子化状态校验
|
|
170
|
-
if self._is_shutdown:
|
|
171
|
-
raise RuntimeError("客户端已关闭,无法获取通道")
|
|
172
|
-
if not self._initialized:
|
|
173
|
-
raise RuntimeError("客户端未初始化,请先调用init_pools()")
|
|
174
|
-
|
|
175
|
-
# 检查并恢复通道
|
|
176
|
-
channel = await self._check_and_recover_channel()
|
|
177
|
-
return channel, self._connection # 单通道无需管理"使用中/空闲"状态
|
|
178
|
-
|
|
179
|
-
async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
|
|
180
|
-
"""声明队列(使用单通道)"""
|
|
181
|
-
channel, _ = await self.acquire_channel()
|
|
182
|
-
return await channel.declare_queue(queue_name, **kwargs)
|
|
183
|
-
|
|
184
|
-
async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
|
|
185
|
-
"""声明交换机(使用单通道)"""
|
|
186
|
-
channel, _ = await self.acquire_channel()
|
|
187
|
-
return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)
|
|
188
|
-
|
|
189
|
-
async def publish_message(self, routing_key: str, message_body: bytes, exchange_name: str = "", **kwargs):
|
|
190
|
-
"""发布消息(依赖原生自动重连/恢复)"""
|
|
191
|
-
channel, _ = await self.acquire_channel()
|
|
192
|
-
try:
|
|
193
|
-
exchange = channel.default_exchange if not exchange_name else await channel.get_exchange(exchange_name)
|
|
194
|
-
message = Message(body=message_body, **kwargs)
|
|
195
|
-
await exchange.publish(message, routing_key=routing_key)
|
|
196
|
-
logger.debug(
|
|
197
|
-
f"消息发布成功 - 交换机: {exchange.name}, 路由键: {routing_key}"
|
|
198
|
-
)
|
|
199
|
-
except Exception as e:
|
|
200
|
-
logger.error(f"发布消息失败: {str(e)}", exc_info=True)
|
|
201
|
-
raise # 原生会自动重连,无需手动处理
|
|
202
|
-
|
|
203
|
-
async def consume_queue(self, queue_name: str, callback: Callable[[AbstractMessage], asyncio.Future], auto_ack: bool = False, **kwargs):
|
|
204
|
-
"""消费队列(独立通道,带原生自动恢复)"""
|
|
205
|
-
async with self._lock:
|
|
206
|
-
# 原子化状态校验
|
|
207
|
-
if self._is_shutdown:
|
|
208
|
-
raise RuntimeError("客户端已关闭,无法启动消费")
|
|
209
|
-
if not self._initialized:
|
|
210
|
-
raise RuntimeError("客户端未初始化,请先调用init_pools()")
|
|
211
|
-
if queue_name in self._consumer_channels:
|
|
212
|
-
logger.warning(f"队列 {queue_name} 已在消费中,无需重复启动")
|
|
213
|
-
return
|
|
214
|
-
|
|
215
|
-
# 先声明队列(确保队列存在)
|
|
216
|
-
await self.declare_queue(queue_name, **kwargs)
|
|
217
|
-
|
|
218
|
-
# 创建独立的消费者通道(不使用主单通道,避免消费阻塞发布)
|
|
219
|
-
async with self._lock:
|
|
220
|
-
if self._is_shutdown: # 二次校验:防止创建通道前客户端被关闭
|
|
221
|
-
raise RuntimeError("客户端已关闭,无法创建消费者通道")
|
|
222
|
-
if not self._connection or self._connection.is_closed:
|
|
223
|
-
raise RuntimeError("无有效连接,无法创建消费者通道")
|
|
224
|
-
channel = await self._connection.channel()
|
|
225
|
-
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
226
|
-
|
|
227
|
-
# 注册消费者通道
|
|
228
|
-
self._consumer_channels[queue_name] = (
|
|
229
|
-
channel, callback, auto_ack, kwargs)
|
|
230
|
-
|
|
231
|
-
async def consume_callback_wrapper(message: AbstractMessage):
|
|
232
|
-
"""消费回调包装(处理通道失效,依赖原生恢复)"""
|
|
233
|
-
try:
|
|
234
|
-
async with self._lock:
|
|
235
|
-
# 原子化校验状态:客户端是否关闭 + 通道是否有效 + 连接是否有效
|
|
236
|
-
if self._is_shutdown:
|
|
237
|
-
logger.warning(f"客户端已关闭,拒绝处理消息(队列: {queue_name})")
|
|
238
|
-
if not auto_ack:
|
|
239
|
-
await message.nack(requeue=True)
|
|
240
|
-
return
|
|
241
|
-
channel_valid = not channel.is_closed
|
|
242
|
-
conn_valid = self._connection and not self._connection.is_closed
|
|
243
|
-
|
|
244
|
-
if not channel_valid or not conn_valid:
|
|
245
|
-
logger.warning(f"消费者通道 {queue_name} 失效(等待原生自动恢复)")
|
|
246
|
-
if not auto_ack:
|
|
247
|
-
await message.nack(requeue=True)
|
|
248
|
-
return
|
|
249
|
-
|
|
250
|
-
# 执行业务回调
|
|
251
|
-
await callback(message)
|
|
252
|
-
if not auto_ack:
|
|
253
|
-
await message.ack()
|
|
254
|
-
except ChannelClosed as e:
|
|
255
|
-
logger.error(f"消费者通道 {queue_name} 关闭: {str(e)}", exc_info=True)
|
|
256
|
-
if not auto_ack:
|
|
257
|
-
await message.nack(requeue=True)
|
|
258
|
-
except aiormq.exceptions.ChannelInvalidStateError as e:
|
|
259
|
-
logger.error(
|
|
260
|
-
f"消费者通道 {queue_name} 状态异常: {str(e)}", exc_info=True)
|
|
261
|
-
if not auto_ack:
|
|
262
|
-
await message.nack(requeue=True)
|
|
263
|
-
except Exception as e:
|
|
264
|
-
logger.error(
|
|
265
|
-
f"消费消息失败(队列: {queue_name}): {str(e)}", exc_info=True)
|
|
266
|
-
if not auto_ack:
|
|
267
|
-
await message.nack(requeue=True)
|
|
268
|
-
|
|
269
|
-
logger.info(f"开始消费队列: {queue_name}(通道带原生自动恢复)")
|
|
270
|
-
|
|
271
|
-
try:
|
|
272
|
-
await channel.basic_consume(
|
|
273
|
-
queue_name,
|
|
274
|
-
consumer_callback=consume_callback_wrapper,
|
|
275
|
-
auto_ack=auto_ack,
|
|
276
|
-
**kwargs
|
|
277
|
-
)
|
|
278
|
-
except Exception as e:
|
|
279
|
-
logger.error(f"启动消费失败(队列: {queue_name}): {str(e)}", exc_info=True)
|
|
280
|
-
# 清理异常资源
|
|
281
|
-
try:
|
|
282
|
-
async with self._lock:
|
|
283
|
-
if not channel.is_closed:
|
|
284
|
-
await channel.close()
|
|
285
|
-
# 移除无效的消费者通道注册
|
|
286
|
-
if queue_name in self._consumer_channels:
|
|
287
|
-
del self._consumer_channels[queue_name]
|
|
288
|
-
except Exception as close_e:
|
|
289
|
-
logger.warning(f"关闭消费者通道失败: {str(close_e)}")
|
|
290
|
-
raise
|
|
291
|
-
|
|
292
|
-
async def close(self):
|
|
293
|
-
"""关闭客户端(释放所有资源,原子化状态管理)"""
|
|
294
|
-
async with self._lock:
|
|
295
|
-
if self._is_shutdown:
|
|
296
|
-
logger.warning("客户端已关闭,无需重复操作")
|
|
297
|
-
return
|
|
298
|
-
# 先标记为关闭,阻止后续所有操作(原子化修改)
|
|
299
|
-
self._is_shutdown = True
|
|
300
|
-
self._initialized = False
|
|
301
|
-
|
|
302
|
-
logger.info("开始关闭RabbitMQ单通道客户端(释放所有资源)...")
|
|
303
|
-
|
|
304
|
-
# 1. 关闭所有消费者通道
|
|
305
|
-
consumer_channels = []
|
|
306
|
-
async with self._lock:
|
|
307
|
-
consumer_channels = list(self._consumer_channels.values())
|
|
308
|
-
self._consumer_channels.clear()
|
|
309
|
-
for channel, _, _, _ in consumer_channels:
|
|
310
|
-
try:
|
|
311
|
-
if not channel.is_closed:
|
|
312
|
-
await channel.close()
|
|
313
|
-
except Exception as e:
|
|
314
|
-
logger.warning(f"关闭消费者通道失败: {str(e)}")
|
|
315
|
-
|
|
316
|
-
# 2. 关闭主单通道
|
|
317
|
-
if self._channel:
|
|
318
|
-
try:
|
|
319
|
-
async with self._lock:
|
|
320
|
-
if not self._channel.is_closed:
|
|
321
|
-
await self._channel.close()
|
|
322
|
-
except Exception as e:
|
|
323
|
-
logger.warning(f"关闭主通道失败: {str(e)}")
|
|
324
|
-
self._channel = None
|
|
325
|
-
|
|
326
|
-
# 3. 关闭连接(终止原生自动重连)
|
|
327
|
-
if self._connection:
|
|
328
|
-
try:
|
|
329
|
-
async with self._lock:
|
|
330
|
-
if not self._connection.is_closed:
|
|
331
|
-
await self._connection.close()
|
|
332
|
-
logger.info(
|
|
333
|
-
f"已关闭连接: {self._current_host}:{self.port}(终止原生自动重连)")
|
|
334
|
-
except Exception as e:
|
|
335
|
-
logger.warning(f"关闭连接失败: {str(e)}")
|
|
336
|
-
self._connection = None
|
|
337
|
-
|
|
338
|
-
logger.info("RabbitMQ单通道客户端已完全关闭")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/DatabaseConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/EmbeddingConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/LLMConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/MQConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/RerankerConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/config/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/health_check.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/health/metrics.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/llm_logger.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/llm/llm_tokens.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/kafka_log.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/logger_levels.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/logger_wrapper.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/logging/sql_logger.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/context.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/cors.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/docs.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/exception.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/middleware.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/timeout.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/middleware/traceid.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/base_http.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/mqmsg_model.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/mqsend_config.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/models/sso_user.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/notice/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/notice/uvicorn_monitor.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/example.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/example2.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/feign_client.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/synacos/nacos_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/merge_headers.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.56b4 → sycommon_python_lib-0.1.56b5}/src/sycommon/tools/snowflake.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|