sycommon-python-lib 0.1.41__tar.gz → 0.1.43__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.41 → sycommon_python_lib-0.1.43}/PKG-INFO +1 -1
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/pyproject.toml +1 -1
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/traceid.py +1 -1
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_client.py +89 -8
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_pool.py +77 -23
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_service.py +197 -81
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/README.md +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/setup.cfg +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/middleware.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/services.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/feign.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/feign_client.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_client.py
RENAMED
|
@@ -85,6 +85,22 @@ class RabbitMQClient:
|
|
|
85
85
|
# 线程安全锁
|
|
86
86
|
self._consume_lock = asyncio.Lock()
|
|
87
87
|
self._connect_lock = asyncio.Lock()
|
|
88
|
+
# 跟踪连接关闭回调(用于后续移除)
|
|
89
|
+
self._conn_close_callback: Optional[Callable] = None
|
|
90
|
+
# 控制重连频率的信号量(避免短时间内大量重连任务)
|
|
91
|
+
self._reconnect_semaphore = asyncio.Semaphore(1)
|
|
92
|
+
# 重连冷却时间(秒)
|
|
93
|
+
self._reconnect_cooldown = 3
|
|
94
|
+
# 固定重连间隔15秒(全局统一)
|
|
95
|
+
self._RECONNECT_INTERVAL = 15
|
|
96
|
+
# 重连任务锁(确保同一时间只有一个重连任务)
|
|
97
|
+
self._reconnect_task_lock = asyncio.Lock()
|
|
98
|
+
# 跟踪当前重连任务(避免重复创建)
|
|
99
|
+
self._current_reconnect_task: Optional[asyncio.Task] = None
|
|
100
|
+
# 连接失败计数器(用于告警)
|
|
101
|
+
self._reconnect_fail_count = 0
|
|
102
|
+
# 连接失败告警阈值
|
|
103
|
+
self._reconnect_alert_threshold = 5
|
|
88
104
|
|
|
89
105
|
@property
|
|
90
106
|
async def is_connected(self) -> bool:
|
|
@@ -104,14 +120,16 @@ class RabbitMQClient:
|
|
|
104
120
|
return False
|
|
105
121
|
|
|
106
122
|
async def connect(self) -> None:
|
|
107
|
-
"""建立连接并初始化交换机/队列(支持重连)"""
|
|
108
123
|
if self._closed:
|
|
109
124
|
raise RuntimeError("客户端已关闭,无法重新连接")
|
|
110
125
|
|
|
111
126
|
async with self._connect_lock:
|
|
112
|
-
#
|
|
127
|
+
# 释放旧资源(保留原有回调清理逻辑)
|
|
113
128
|
if self._channel and self._channel_conn:
|
|
114
129
|
try:
|
|
130
|
+
if self._conn_close_callback and self._channel_conn:
|
|
131
|
+
self._channel_conn.close_callbacks.discard(
|
|
132
|
+
self._conn_close_callback)
|
|
115
133
|
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
116
134
|
except Exception as e:
|
|
117
135
|
SYLogger.warning(f"释放旧通道失败: {str(e)}")
|
|
@@ -119,11 +137,29 @@ class RabbitMQClient:
|
|
|
119
137
|
self._channel_conn = None
|
|
120
138
|
self._exchange = None
|
|
121
139
|
self._queue = None
|
|
140
|
+
self._conn_close_callback = None
|
|
122
141
|
|
|
123
142
|
try:
|
|
124
|
-
#
|
|
143
|
+
# 从连接池获取通道+连接(连接池已控制连接数)
|
|
125
144
|
self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
|
|
126
145
|
|
|
146
|
+
def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
|
|
147
|
+
"""连接关闭回调:触发固定间隔重连"""
|
|
148
|
+
SYLogger.warning(
|
|
149
|
+
f"客户端连接关闭: {conn!r},原因: {exc}", exc_info=exc)
|
|
150
|
+
self._reconnect_fail_count += 1
|
|
151
|
+
# 超过阈值告警
|
|
152
|
+
if self._reconnect_fail_count >= self._reconnect_alert_threshold:
|
|
153
|
+
SYLogger.error(
|
|
154
|
+
f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
|
|
155
|
+
if not self._closed:
|
|
156
|
+
asyncio.create_task(self._safe_reconnect())
|
|
157
|
+
|
|
158
|
+
self._conn_close_callback = on_conn_closed
|
|
159
|
+
if self._channel_conn:
|
|
160
|
+
self._channel_conn.close_callbacks.add(
|
|
161
|
+
self._conn_close_callback)
|
|
162
|
+
|
|
127
163
|
# 2. 设置预取计数(限流)
|
|
128
164
|
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
129
165
|
SYLogger.debug(f"设置预取计数: {self.prefetch_count}")
|
|
@@ -157,10 +193,15 @@ class RabbitMQClient:
|
|
|
157
193
|
f"(绑定交换机: {self.exchange_name}, routing_key: {self.routing_key})"
|
|
158
194
|
)
|
|
159
195
|
|
|
196
|
+
# 重连成功,重置失败计数器
|
|
197
|
+
self._reconnect_fail_count = 0
|
|
160
198
|
SYLogger.info("客户端连接初始化完成")
|
|
161
199
|
except Exception as e:
|
|
162
200
|
SYLogger.error(f"客户端连接失败: {str(e)}", exc_info=True)
|
|
163
201
|
# 清理异常状态
|
|
202
|
+
if self._conn_close_callback and self._channel_conn:
|
|
203
|
+
self._channel_conn.close_callbacks.discard(
|
|
204
|
+
self._conn_close_callback)
|
|
164
205
|
if self._channel and self._channel_conn:
|
|
165
206
|
try:
|
|
166
207
|
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
@@ -168,8 +209,40 @@ class RabbitMQClient:
|
|
|
168
209
|
pass
|
|
169
210
|
self._channel = None
|
|
170
211
|
self._channel_conn = None
|
|
212
|
+
# 触发重连(固定间隔)
|
|
213
|
+
if not self._closed:
|
|
214
|
+
asyncio.create_task(self._safe_reconnect())
|
|
171
215
|
raise
|
|
172
216
|
|
|
217
|
+
async def _safe_reconnect(self):
|
|
218
|
+
"""安全重连:固定15秒间隔,避免重复任务"""
|
|
219
|
+
# 检查是否已有重连任务在运行
|
|
220
|
+
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
221
|
+
SYLogger.debug("已有重连任务在运行,跳过重复触发")
|
|
222
|
+
return
|
|
223
|
+
|
|
224
|
+
async with self._reconnect_task_lock:
|
|
225
|
+
if self._closed or await self.is_connected:
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
# 固定15秒重连间隔
|
|
229
|
+
SYLogger.info(f"将在15秒后尝试重连...")
|
|
230
|
+
await asyncio.sleep(self._RECONNECT_INTERVAL)
|
|
231
|
+
|
|
232
|
+
if self._closed or await self.is_connected:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
self._current_reconnect_task = asyncio.create_task(
|
|
237
|
+
self.connect())
|
|
238
|
+
await self._current_reconnect_task
|
|
239
|
+
except Exception as e:
|
|
240
|
+
SYLogger.warning(f"重连失败: {str(e)}")
|
|
241
|
+
# 重连失败后,继续触发下一次重连(仍保持15秒间隔)
|
|
242
|
+
asyncio.create_task(self._safe_reconnect())
|
|
243
|
+
finally:
|
|
244
|
+
self._current_reconnect_task = None
|
|
245
|
+
|
|
173
246
|
async def set_message_handler(
|
|
174
247
|
self,
|
|
175
248
|
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]],
|
|
@@ -340,14 +413,18 @@ class RabbitMQClient:
|
|
|
340
413
|
)
|
|
341
414
|
|
|
342
415
|
async def close(self) -> None:
|
|
343
|
-
"""
|
|
344
|
-
if self._closed:
|
|
345
|
-
SYLogger.warning("客户端已关闭,无需重复操作")
|
|
346
|
-
return
|
|
347
|
-
|
|
416
|
+
"""关闭客户端(移除回调)"""
|
|
348
417
|
self._closed = True
|
|
349
418
|
SYLogger.info("开始关闭RabbitMQ客户端...")
|
|
350
419
|
|
|
420
|
+
# 停止重连任务
|
|
421
|
+
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
422
|
+
self._current_reconnect_task.cancel()
|
|
423
|
+
try:
|
|
424
|
+
await self._current_reconnect_task
|
|
425
|
+
except asyncio.CancelledError:
|
|
426
|
+
SYLogger.debug("重连任务已取消")
|
|
427
|
+
|
|
351
428
|
# 1. 停止消费
|
|
352
429
|
await self.stop_consuming()
|
|
353
430
|
|
|
@@ -355,6 +432,10 @@ class RabbitMQClient:
|
|
|
355
432
|
async with self._connect_lock:
|
|
356
433
|
if self._channel and self._channel_conn:
|
|
357
434
|
try:
|
|
435
|
+
# 移除连接关闭回调
|
|
436
|
+
if self._conn_close_callback:
|
|
437
|
+
self._channel_conn.close_callbacks.discard(
|
|
438
|
+
self._conn_close_callback)
|
|
358
439
|
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
359
440
|
SYLogger.info("通道释放成功")
|
|
360
441
|
except Exception as e:
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_pool.py
RENAMED
|
@@ -19,11 +19,11 @@ class RabbitMQConnectionPool:
|
|
|
19
19
|
password: str,
|
|
20
20
|
virtualhost: str = "/",
|
|
21
21
|
connection_pool_size: int = 2,
|
|
22
|
-
channel_pool_size: int =
|
|
22
|
+
channel_pool_size: int = 5,
|
|
23
23
|
heartbeat: int = 30,
|
|
24
24
|
app_name: str = "",
|
|
25
|
-
reconnect_interval: int =
|
|
26
|
-
connection_timeout: int =
|
|
25
|
+
reconnect_interval: int = 15,
|
|
26
|
+
connection_timeout: int = 10,
|
|
27
27
|
):
|
|
28
28
|
self.hosts = [host.strip() for host in hosts if host.strip()]
|
|
29
29
|
if not self.hosts:
|
|
@@ -55,6 +55,22 @@ class RabbitMQConnectionPool:
|
|
|
55
55
|
# 连接状态
|
|
56
56
|
self._initialized = False
|
|
57
57
|
self._reconnect_task: Optional[asyncio.Task] = None
|
|
58
|
+
self._is_shutdown = False
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def is_alive(self) -> bool:
|
|
62
|
+
if not self._initialized:
|
|
63
|
+
return False
|
|
64
|
+
# 异步清理失效连接(不阻塞当前调用)
|
|
65
|
+
asyncio.create_task(self._check_connections())
|
|
66
|
+
# 同步校验存活连接(即使清理未完成,也能反映当前状态)
|
|
67
|
+
return any(not conn.is_closed for conn, _ in self._connections)
|
|
68
|
+
|
|
69
|
+
async def _check_connections(self):
|
|
70
|
+
"""异步检查连接有效性(清理已关闭的连接)"""
|
|
71
|
+
async with self._conn_lock:
|
|
72
|
+
self._connections = [
|
|
73
|
+
(conn, ts) for conn, ts in self._connections if not conn.is_closed]
|
|
58
74
|
|
|
59
75
|
async def init_pools(self):
|
|
60
76
|
"""初始化连接池+启动连接监控任务"""
|
|
@@ -96,17 +112,21 @@ class RabbitMQConnectionPool:
|
|
|
96
112
|
continue
|
|
97
113
|
|
|
98
114
|
async def _create_single_connection(self) -> AbstractRobustConnection:
|
|
99
|
-
"""创建单个RabbitMQ连接(支持集群轮询+失败重试)"""
|
|
100
115
|
hosts = self.hosts.copy()
|
|
101
116
|
retry_count = 0
|
|
102
|
-
max_retries = 3 #
|
|
117
|
+
max_retries = 3 # 每个节点最多重试3次
|
|
103
118
|
|
|
104
|
-
while retry_count < max_retries:
|
|
119
|
+
while retry_count < max_retries and not self._is_shutdown:
|
|
105
120
|
if not hosts:
|
|
106
|
-
hosts = self.hosts.copy()
|
|
121
|
+
hosts = self.hosts.copy()
|
|
107
122
|
retry_count += 1
|
|
108
|
-
|
|
109
|
-
|
|
123
|
+
if retry_count >= max_retries:
|
|
124
|
+
logger.error(
|
|
125
|
+
f"所有RabbitMQ节点({self.hosts})均连接失败,已重试{max_retries}次,将在15秒后再次尝试"
|
|
126
|
+
)
|
|
127
|
+
# 固定15秒间隔后退出,由监控任务触发下一次重试
|
|
128
|
+
await asyncio.sleep(self.reconnect_interval)
|
|
129
|
+
break
|
|
110
130
|
|
|
111
131
|
host = hosts.pop(0)
|
|
112
132
|
conn_url = (
|
|
@@ -120,18 +140,17 @@ class RabbitMQConnectionPool:
|
|
|
120
140
|
client_properties={
|
|
121
141
|
"connection_name": f"{self.app_name}@{host}"
|
|
122
142
|
},
|
|
123
|
-
reconnect_interval=self.reconnect_interval,
|
|
143
|
+
reconnect_interval=self.reconnect_interval, # 客户端内置重连间隔也设为15秒
|
|
124
144
|
)
|
|
125
145
|
|
|
126
|
-
#
|
|
146
|
+
# 连接关闭回调(固定间隔重连)
|
|
127
147
|
def on_connection_closed(conn_instance: AbstractConnection, exc: Optional[BaseException]):
|
|
128
|
-
logger.
|
|
148
|
+
logger.warning(
|
|
129
149
|
f"RabbitMQ连接关闭: {conn_instance!r},原因: {exc}", exc_info=exc)
|
|
130
|
-
# 异步触发连接重建(需用 asyncio.create_task 包装)
|
|
131
150
|
asyncio.create_task(
|
|
132
151
|
self._remove_invalid_connection(cast(AbstractRobustConnection, conn_instance)))
|
|
133
152
|
|
|
134
|
-
|
|
153
|
+
setattr(conn, '_pool_close_callback', on_connection_closed)
|
|
135
154
|
conn.close_callbacks.add(on_connection_closed)
|
|
136
155
|
|
|
137
156
|
logger.info(f"成功连接到RabbitMQ节点: {host}:{self.port}")
|
|
@@ -140,6 +159,8 @@ class RabbitMQConnectionPool:
|
|
|
140
159
|
logger.warning(
|
|
141
160
|
f"连接节点 {host}:{self.port} 失败(重试{retry_count}/{max_retries}): {str(e)}"
|
|
142
161
|
)
|
|
162
|
+
# 每个节点连接失败后,固定等待15秒再尝试下一个节点
|
|
163
|
+
await asyncio.sleep(self.reconnect_interval)
|
|
143
164
|
|
|
144
165
|
raise RuntimeError(
|
|
145
166
|
f"所有RabbitMQ节点连接失败(已重试{max_retries}次),节点列表: {self.hosts}"
|
|
@@ -147,6 +168,14 @@ class RabbitMQConnectionPool:
|
|
|
147
168
|
|
|
148
169
|
async def _remove_invalid_connection(self, invalid_conn: AbstractRobustConnection) -> None:
|
|
149
170
|
"""移除失效连接及关联通道"""
|
|
171
|
+
try:
|
|
172
|
+
# 关键修复:移除连接关闭回调
|
|
173
|
+
callback = getattr(invalid_conn, '_pool_close_callback', None)
|
|
174
|
+
if callback:
|
|
175
|
+
invalid_conn.close_callbacks.discard(callback)
|
|
176
|
+
delattr(invalid_conn, '_pool_close_callback', None)
|
|
177
|
+
except Exception as e:
|
|
178
|
+
logger.warning(f"移除连接回调失败: {str(e)}")
|
|
150
179
|
# 1. 移除失效连接
|
|
151
180
|
async with self._conn_lock:
|
|
152
181
|
self._connections = [
|
|
@@ -164,8 +193,14 @@ class RabbitMQConnectionPool:
|
|
|
164
193
|
asyncio.create_task(self._recreate_connection())
|
|
165
194
|
|
|
166
195
|
async def _recreate_connection(self):
|
|
167
|
-
"""
|
|
196
|
+
"""重建连接:固定间隔重试"""
|
|
168
197
|
try:
|
|
198
|
+
# 重建前检查是否已达到连接池上限
|
|
199
|
+
async with self._conn_lock:
|
|
200
|
+
if len(self._connections) >= self.connection_pool_size:
|
|
201
|
+
logger.debug("连接池已达最大限制,跳过重建连接")
|
|
202
|
+
return
|
|
203
|
+
|
|
169
204
|
conn = await self._create_single_connection()
|
|
170
205
|
async with self._conn_lock:
|
|
171
206
|
self._connections.append(
|
|
@@ -181,20 +216,29 @@ class RabbitMQConnectionPool:
|
|
|
181
216
|
logger.warning(f"为新连接创建通道失败: {str(e)}")
|
|
182
217
|
except Exception as e:
|
|
183
218
|
logger.error(f"重建连接失败: {str(e)}", exc_info=True)
|
|
219
|
+
# 重建失败后,15秒后再次尝试
|
|
220
|
+
if not self._is_shutdown:
|
|
221
|
+
asyncio.create_task(self._recreate_connection())
|
|
184
222
|
|
|
185
223
|
async def _monitor_connections(self):
|
|
186
|
-
"""
|
|
187
|
-
while self._initialized:
|
|
224
|
+
"""后台监控:固定15秒检查一次连接状态"""
|
|
225
|
+
while self._initialized and not self._is_shutdown:
|
|
188
226
|
try:
|
|
189
|
-
await asyncio.sleep(
|
|
227
|
+
await asyncio.sleep(self.reconnect_interval) # 固定15秒间隔检查
|
|
190
228
|
current_time = asyncio.get_event_loop().time()
|
|
229
|
+
|
|
230
|
+
# 清理失效/超时连接
|
|
191
231
|
async with self._conn_lock:
|
|
192
|
-
# 检查连接有效性+清理超时连接(10分钟无活动)
|
|
193
232
|
valid_connections = []
|
|
194
233
|
for conn, last_active in self._connections:
|
|
195
|
-
if conn.is_closed or (current_time - last_active) > 600:
|
|
234
|
+
if conn.is_closed or (current_time - last_active) > 600: # 10分钟无活动清理
|
|
196
235
|
logger.warning(f"清理失效/超时连接: {conn}")
|
|
197
236
|
try:
|
|
237
|
+
# 移除回调+关闭连接
|
|
238
|
+
callback = getattr(
|
|
239
|
+
conn, '_pool_close_callback', None)
|
|
240
|
+
if callback:
|
|
241
|
+
conn.close_callbacks.discard(callback)
|
|
198
242
|
await conn.close()
|
|
199
243
|
except:
|
|
200
244
|
pass
|
|
@@ -202,13 +246,18 @@ class RabbitMQConnectionPool:
|
|
|
202
246
|
valid_connections.append((conn, last_active))
|
|
203
247
|
self._connections = valid_connections
|
|
204
248
|
|
|
205
|
-
#
|
|
249
|
+
# 补充缺失的连接(不超过连接池最大限制)
|
|
206
250
|
missing_conn_count = self.connection_pool_size - \
|
|
207
251
|
len(self._connections)
|
|
208
|
-
|
|
209
|
-
|
|
252
|
+
if missing_conn_count > 0:
|
|
253
|
+
logger.info(f"连接池缺少{missing_conn_count}个连接,尝试补充")
|
|
254
|
+
# 逐个补充,避免同时创建大量连接
|
|
255
|
+
for _ in range(missing_conn_count):
|
|
256
|
+
asyncio.create_task(self._recreate_connection())
|
|
210
257
|
except Exception as e:
|
|
211
258
|
logger.error(f"连接监控任务异常: {str(e)}", exc_info=True)
|
|
259
|
+
# 异常后仍保持15秒间隔
|
|
260
|
+
await asyncio.sleep(self.reconnect_interval)
|
|
212
261
|
|
|
213
262
|
async def acquire_channel(self) -> Tuple[Channel, AbstractRobustConnection]:
|
|
214
263
|
"""获取通道(返回通道+所属连接,便于释放时校验)"""
|
|
@@ -303,6 +352,11 @@ class RabbitMQConnectionPool:
|
|
|
303
352
|
async with self._conn_lock:
|
|
304
353
|
for conn, _ in self._connections:
|
|
305
354
|
try:
|
|
355
|
+
# 移除所有连接的回调
|
|
356
|
+
callback = getattr(conn, '_pool_close_callback', None)
|
|
357
|
+
if callback:
|
|
358
|
+
conn.close_callbacks.discard(callback)
|
|
359
|
+
delattr(conn, '_pool_close_callback', None)
|
|
306
360
|
if not conn.is_closed:
|
|
307
361
|
await conn.close()
|
|
308
362
|
except Exception as e:
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_service.py
RENAMED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
from typing import (
|
|
4
|
-
Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set
|
|
4
|
+
Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set, Tuple, cast
|
|
5
5
|
)
|
|
6
6
|
from pydantic import BaseModel
|
|
7
|
-
from aio_pika.abc import AbstractIncomingMessage, ConsumerTag
|
|
7
|
+
from aio_pika.abc import AbstractIncomingMessage, ConsumerTag, AbstractRobustConnection
|
|
8
|
+
from aio_pika import Channel, exceptions as aio_pika_exceptions
|
|
8
9
|
|
|
9
10
|
from sycommon.models.mqmsg_model import MQMsgModel
|
|
10
11
|
from sycommon.models.mqlistener_config import RabbitMQListenerConfig
|
|
@@ -19,7 +20,7 @@ logger = SYLogger
|
|
|
19
20
|
class RabbitMQService:
|
|
20
21
|
"""
|
|
21
22
|
RabbitMQ服务封装,管理多个客户端实例,基于连接池实现资源复用
|
|
22
|
-
适配细粒度锁设计的RabbitMQClient
|
|
23
|
+
适配细粒度锁设计的RabbitMQClient,确保线程安全+高可用
|
|
23
24
|
"""
|
|
24
25
|
|
|
25
26
|
# 保存多个客户端实例
|
|
@@ -52,12 +53,15 @@ class RabbitMQService:
|
|
|
52
53
|
_is_shutdown: bool = False
|
|
53
54
|
# 服务关闭锁
|
|
54
55
|
_shutdown_lock = asyncio.Lock()
|
|
56
|
+
# 连接状态监控任务
|
|
57
|
+
_connection_monitor_task: Optional[asyncio.Task] = None
|
|
58
|
+
# 重连配置
|
|
59
|
+
RECONNECT_INTERVAL = 15 # 重连基础间隔(秒)
|
|
60
|
+
MAX_RECONNECT_ATTEMPTS = 10 # 最大连续重连次数
|
|
55
61
|
|
|
56
62
|
@classmethod
|
|
57
63
|
def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
|
|
58
|
-
"""
|
|
59
|
-
初始化RabbitMQ服务(支持集群配置),同时创建连接池
|
|
60
|
-
"""
|
|
64
|
+
"""初始化RabbitMQ服务(支持集群配置),同时创建连接池"""
|
|
61
65
|
from sycommon.synacos.nacos_service import NacosService
|
|
62
66
|
|
|
63
67
|
# 防止重复初始化
|
|
@@ -75,7 +79,8 @@ class RabbitMQService:
|
|
|
75
79
|
f"RabbitMQ服务初始化 - 集群节点: {cls._config.get('host')}, "
|
|
76
80
|
f"端口: {cls._config.get('port')}, "
|
|
77
81
|
f"虚拟主机: {cls._config.get('virtual-host')}, "
|
|
78
|
-
f"应用名: {cls._config.get('APP_NAME')}"
|
|
82
|
+
f"应用名: {cls._config.get('APP_NAME')}, "
|
|
83
|
+
f"心跳: {cls._config.get('heartbeat', 30)}s"
|
|
79
84
|
)
|
|
80
85
|
|
|
81
86
|
# 保存发送器和监听器存在状态
|
|
@@ -86,6 +91,10 @@ class RabbitMQService:
|
|
|
86
91
|
# 初始化连接池(在单独的异步方法中启动)
|
|
87
92
|
asyncio.create_task(cls._init_connection_pool())
|
|
88
93
|
|
|
94
|
+
# 启动连接监控任务(监听连接状态,自动重连)
|
|
95
|
+
cls._connection_monitor_task = asyncio.create_task(
|
|
96
|
+
cls._monitor_connections())
|
|
97
|
+
|
|
89
98
|
return cls
|
|
90
99
|
|
|
91
100
|
@classmethod
|
|
@@ -110,9 +119,8 @@ class RabbitMQService:
|
|
|
110
119
|
password=cls._config.get('password', ""),
|
|
111
120
|
virtualhost=cls._config.get('virtual-host', "/"),
|
|
112
121
|
connection_pool_size=cls._config.get(
|
|
113
|
-
'connection_pool_size',
|
|
114
|
-
channel_pool_size=cls._config.get(
|
|
115
|
-
'channel_pool_size', 10), # 通道池大小
|
|
122
|
+
'connection_pool_size', 2),
|
|
123
|
+
channel_pool_size=cls._config.get('channel_pool_size', 5),
|
|
116
124
|
heartbeat=cls._config.get('heartbeat', 30),
|
|
117
125
|
app_name=cls._config.get("APP_NAME", "")
|
|
118
126
|
)
|
|
@@ -125,19 +133,133 @@ class RabbitMQService:
|
|
|
125
133
|
logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
|
|
126
134
|
# 连接池初始化失败时重试(未关闭状态下)
|
|
127
135
|
if not cls._is_shutdown:
|
|
128
|
-
await asyncio.sleep(
|
|
136
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
129
137
|
asyncio.create_task(cls._init_connection_pool())
|
|
130
138
|
|
|
139
|
+
@classmethod
|
|
140
|
+
async def _monitor_connections(cls):
|
|
141
|
+
"""连接监控任务:定期检查所有客户端连接状态,自动重连"""
|
|
142
|
+
logger.info("RabbitMQ连接监控任务启动")
|
|
143
|
+
while not cls._is_shutdown:
|
|
144
|
+
try:
|
|
145
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL) # 固定15秒间隔
|
|
146
|
+
|
|
147
|
+
# 跳过未初始化的连接池
|
|
148
|
+
if not cls._connection_pool or not cls._connection_pool._initialized:
|
|
149
|
+
continue
|
|
150
|
+
|
|
151
|
+
# 检查所有客户端连接
|
|
152
|
+
# 使用list避免迭代中修改
|
|
153
|
+
for client_name, client in list(cls._clients.items()):
|
|
154
|
+
try:
|
|
155
|
+
# 双重校验连接状态(客户端内部校验 + 连接池连接校验)
|
|
156
|
+
client_connected = await client.is_connected
|
|
157
|
+
conn_connected = not (
|
|
158
|
+
client._channel_conn and client._channel_conn.is_closed)
|
|
159
|
+
|
|
160
|
+
if not client_connected or not conn_connected:
|
|
161
|
+
logger.warning(
|
|
162
|
+
f"客户端 '{client_name}' 连接异常 - 客户端状态: {client_connected}, "
|
|
163
|
+
f"连接状态: {conn_connected},触发自动重连"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# 重连前先清理无效资源
|
|
167
|
+
await cls._clean_client_resources(client)
|
|
168
|
+
|
|
169
|
+
# 执行重连(带重试)
|
|
170
|
+
reconnect_success = await cls._reconnect_client(client_name, client)
|
|
171
|
+
if reconnect_success:
|
|
172
|
+
logger.info(f"客户端 '{client_name}' 重连成功")
|
|
173
|
+
else:
|
|
174
|
+
logger.error(f"客户端 '{client_name}' 重连失败,将继续监控")
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
logger.error(
|
|
178
|
+
f"监控客户端 '{client_name}' 连接状态失败: {str(e)}", exc_info=True)
|
|
179
|
+
|
|
180
|
+
# 检查连接池状态(如果连接池已关闭,重新初始化)
|
|
181
|
+
if not cls._connection_pool.is_alive:
|
|
182
|
+
logger.error("RabbitMQ连接池已关闭,尝试重新初始化")
|
|
183
|
+
asyncio.create_task(cls._init_connection_pool())
|
|
184
|
+
|
|
185
|
+
except Exception as e:
|
|
186
|
+
logger.error("RabbitMQ连接监控任务异常", exc_info=True)
|
|
187
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL) # 异常后延迟重启监控
|
|
188
|
+
|
|
189
|
+
logger.info("RabbitMQ连接监控任务停止")
|
|
190
|
+
|
|
191
|
+
@classmethod
|
|
192
|
+
async def _clean_client_resources(cls, client: RabbitMQClient):
|
|
193
|
+
"""清理客户端无效资源(通道+连接)"""
|
|
194
|
+
try:
|
|
195
|
+
if client._channel and client._channel_conn:
|
|
196
|
+
# 先停止消费(避免消费中释放资源)
|
|
197
|
+
if client._consumer_tag:
|
|
198
|
+
await client.stop_consuming()
|
|
199
|
+
# 释放通道到连接池
|
|
200
|
+
await cls._connection_pool.release_channel(client._channel, client._channel_conn)
|
|
201
|
+
logger.debug("客户端无效资源释放成功")
|
|
202
|
+
except Exception as e:
|
|
203
|
+
logger.warning(f"释放客户端无效资源失败: {str(e)}")
|
|
204
|
+
finally:
|
|
205
|
+
# 强制重置客户端状态
|
|
206
|
+
client._channel = None
|
|
207
|
+
client._channel_conn = None
|
|
208
|
+
client._exchange = None
|
|
209
|
+
client._queue = None
|
|
210
|
+
client._consumer_tag = None
|
|
211
|
+
|
|
212
|
+
@classmethod
|
|
213
|
+
async def _reconnect_client(cls, client_name: str, client: RabbitMQClient) -> bool:
|
|
214
|
+
"""客户端重连(带重试机制)"""
|
|
215
|
+
# 重连冷却,避免任务堆积
|
|
216
|
+
cooldown = cls.RECONNECT_INTERVAL * 2
|
|
217
|
+
await asyncio.sleep(cooldown)
|
|
218
|
+
|
|
219
|
+
for attempt in range(cls.MAX_RECONNECT_ATTEMPTS):
|
|
220
|
+
try:
|
|
221
|
+
# 执行重连
|
|
222
|
+
await client.connect()
|
|
223
|
+
|
|
224
|
+
# 验证重连结果(双重校验)
|
|
225
|
+
if await client.is_connected and client._queue:
|
|
226
|
+
# 如果是消费者,重新启动消费
|
|
227
|
+
if client_name in cls._message_handlers:
|
|
228
|
+
# 先停止旧的消费任务
|
|
229
|
+
if client_name in cls._consumer_tasks:
|
|
230
|
+
old_task = cls._consumer_tasks[client_name]
|
|
231
|
+
if not old_task.done():
|
|
232
|
+
old_task.cancel()
|
|
233
|
+
try:
|
|
234
|
+
await asyncio.wait_for(old_task, timeout=5)
|
|
235
|
+
except:
|
|
236
|
+
pass
|
|
237
|
+
# 启动新的消费任务
|
|
238
|
+
await cls.start_consumer(client_name)
|
|
239
|
+
return True
|
|
240
|
+
else:
|
|
241
|
+
logger.warning(
|
|
242
|
+
f"客户端 '{client_name}' 重连尝试 {attempt+1} 失败:资源未完全初始化")
|
|
243
|
+
except Exception as e:
|
|
244
|
+
logger.error(
|
|
245
|
+
f"客户端 '{client_name}' 重连尝试 {attempt+1} 失败: {str(e)}", exc_info=True)
|
|
246
|
+
|
|
247
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
248
|
+
|
|
249
|
+
if not cls._is_shutdown:
|
|
250
|
+
asyncio.create_task(cls._reconnect_client(client_name, client))
|
|
251
|
+
return False
|
|
252
|
+
|
|
131
253
|
@classmethod
|
|
132
254
|
async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
|
|
133
255
|
"""创建客户端实例(适配新的RabbitMQClient API)"""
|
|
134
256
|
if cls._is_shutdown:
|
|
135
257
|
raise RuntimeError("RabbitMQService已关闭,无法创建客户端")
|
|
136
258
|
|
|
137
|
-
if not cls._connection_pool:
|
|
259
|
+
if not cls._connection_pool or not cls._connection_pool._initialized:
|
|
138
260
|
# 等待连接池初始化
|
|
139
261
|
start_time = asyncio.get_event_loop().time()
|
|
140
|
-
while not cls._connection_pool and not cls._is_shutdown:
|
|
262
|
+
while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
|
|
141
263
|
if asyncio.get_event_loop().time() - start_time > 30:
|
|
142
264
|
raise TimeoutError("等待连接池初始化超时")
|
|
143
265
|
await asyncio.sleep(1)
|
|
@@ -146,8 +268,6 @@ class RabbitMQService:
|
|
|
146
268
|
|
|
147
269
|
app_name = kwargs.get('app_name', cls._config.get(
|
|
148
270
|
"APP_NAME", "")) if cls._config else ""
|
|
149
|
-
|
|
150
|
-
# 确定是否为发送器
|
|
151
271
|
is_sender = not cls._has_listeners
|
|
152
272
|
|
|
153
273
|
# 根据组件类型决定是否允许创建队列
|
|
@@ -183,7 +303,7 @@ class RabbitMQService:
|
|
|
183
303
|
prefetch_count=kwargs.get('prefetch_count', 2),
|
|
184
304
|
)
|
|
185
305
|
|
|
186
|
-
#
|
|
306
|
+
# 连接客户端
|
|
187
307
|
await client.connect()
|
|
188
308
|
|
|
189
309
|
# 监听器客户端连接后延迟1秒,确保消费状态就绪(仅首次启动)
|
|
@@ -197,12 +317,9 @@ class RabbitMQService:
|
|
|
197
317
|
@classmethod
|
|
198
318
|
async def get_client(
|
|
199
319
|
cls,
|
|
200
|
-
client_name: str = "default", **
|
|
320
|
+
client_name: str = "default", **kwargs
|
|
201
321
|
) -> RabbitMQClient:
|
|
202
|
-
"""
|
|
203
|
-
获取或创建RabbitMQ客户端(基于连接池,线程安全)
|
|
204
|
-
适配新的RabbitMQClient异步状态检查
|
|
205
|
-
"""
|
|
322
|
+
"""获取或创建RabbitMQ客户端(基于连接池,线程安全)"""
|
|
206
323
|
if cls._is_shutdown:
|
|
207
324
|
raise RuntimeError("RabbitMQService已关闭,无法获取客户端")
|
|
208
325
|
|
|
@@ -210,9 +327,9 @@ class RabbitMQService:
|
|
|
210
327
|
raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
|
|
211
328
|
|
|
212
329
|
# 等待连接池就绪
|
|
213
|
-
if not cls._connection_pool:
|
|
330
|
+
if not cls._connection_pool or not cls._connection_pool._initialized:
|
|
214
331
|
start_time = asyncio.get_event_loop().time()
|
|
215
|
-
while not cls._connection_pool and not cls._is_shutdown:
|
|
332
|
+
while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
|
|
216
333
|
if asyncio.get_event_loop().time() - start_time > 30:
|
|
217
334
|
raise TimeoutError("等待连接池初始化超时")
|
|
218
335
|
await asyncio.sleep(1)
|
|
@@ -230,10 +347,9 @@ class RabbitMQService:
|
|
|
230
347
|
is_sender = not cls._has_listeners or (
|
|
231
348
|
not kwargs.get('create_if_not_exists', True))
|
|
232
349
|
|
|
233
|
-
# 异步检查连接状态(适配新客户端的async属性)
|
|
234
350
|
if await client.is_connected:
|
|
235
351
|
# 如果是监听器但队列未初始化,重新连接
|
|
236
|
-
if not is_sender and not client._queue:
|
|
352
|
+
if not is_sender and not client._queue:
|
|
237
353
|
logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
|
|
238
354
|
client.create_if_not_exists = True
|
|
239
355
|
await client.connect()
|
|
@@ -258,7 +374,6 @@ class RabbitMQService:
|
|
|
258
374
|
app_name=cls._config.get("APP_NAME", ""),
|
|
259
375
|
**kwargs
|
|
260
376
|
)
|
|
261
|
-
await client.connect()
|
|
262
377
|
cls._clients[client_name] = client
|
|
263
378
|
return client
|
|
264
379
|
|
|
@@ -268,10 +383,7 @@ class RabbitMQService:
|
|
|
268
383
|
# 检查队列是否已初始化
|
|
269
384
|
if initial_queue_name in cls._initialized_queues:
|
|
270
385
|
logger.info(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
|
|
271
|
-
client = await cls._create_client(
|
|
272
|
-
initial_queue_name, ** kwargs
|
|
273
|
-
)
|
|
274
|
-
await client.connect()
|
|
386
|
+
client = await cls._create_client(initial_queue_name, **kwargs)
|
|
275
387
|
cls._clients[client_name] = client
|
|
276
388
|
return client
|
|
277
389
|
|
|
@@ -282,10 +394,7 @@ class RabbitMQService:
|
|
|
282
394
|
**kwargs
|
|
283
395
|
)
|
|
284
396
|
|
|
285
|
-
|
|
286
|
-
await client.connect()
|
|
287
|
-
|
|
288
|
-
# 验证队列是否创建成功(直接访问 _queue 属性)
|
|
397
|
+
# 验证队列是否创建成功
|
|
289
398
|
if not client._queue:
|
|
290
399
|
logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
|
|
291
400
|
client.create_if_not_exists = True
|
|
@@ -302,7 +411,7 @@ class RabbitMQService:
|
|
|
302
411
|
return client
|
|
303
412
|
|
|
304
413
|
@classmethod
|
|
305
|
-
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **
|
|
414
|
+
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **kwargs) -> None:
|
|
306
415
|
"""设置消息发送器(适配新客户端)"""
|
|
307
416
|
if cls._is_shutdown:
|
|
308
417
|
logger.warning("服务已关闭,无法设置发送器")
|
|
@@ -348,7 +457,7 @@ class RabbitMQService:
|
|
|
348
457
|
queue_name=queue_name,
|
|
349
458
|
create_if_not_exists=False,
|
|
350
459
|
prefetch_count=prefetch_count,
|
|
351
|
-
**
|
|
460
|
+
**kwargs
|
|
352
461
|
)
|
|
353
462
|
|
|
354
463
|
# 记录客户端
|
|
@@ -367,7 +476,7 @@ class RabbitMQService:
|
|
|
367
476
|
logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
|
|
368
477
|
|
|
369
478
|
@classmethod
|
|
370
|
-
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **
|
|
479
|
+
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **kwargs) -> None:
|
|
371
480
|
"""设置消息监听器(适配新客户端)"""
|
|
372
481
|
if cls._is_shutdown:
|
|
373
482
|
logger.warning("服务已关闭,无法设置监听器")
|
|
@@ -403,7 +512,7 @@ class RabbitMQService:
|
|
|
403
512
|
|
|
404
513
|
@classmethod
|
|
405
514
|
async def _verify_consumers_started(cls, timeout: int = 30) -> None:
|
|
406
|
-
"""
|
|
515
|
+
"""验证消费者是否成功启动"""
|
|
407
516
|
start_time = asyncio.get_event_loop().time()
|
|
408
517
|
required_clients = list(cls._message_handlers.keys())
|
|
409
518
|
running_clients = []
|
|
@@ -433,7 +542,7 @@ class RabbitMQService:
|
|
|
433
542
|
async def add_listener(
|
|
434
543
|
cls,
|
|
435
544
|
queue_name: str,
|
|
436
|
-
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **
|
|
545
|
+
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **kwargs
|
|
437
546
|
) -> None:
|
|
438
547
|
"""添加消息监听器(线程安全)"""
|
|
439
548
|
if cls._is_shutdown:
|
|
@@ -470,14 +579,24 @@ class RabbitMQService:
|
|
|
470
579
|
|
|
471
580
|
@classmethod
|
|
472
581
|
async def start_consumer(cls, client_name: str) -> None:
|
|
473
|
-
"""
|
|
582
|
+
"""启动指定客户端的消费者"""
|
|
474
583
|
if cls._is_shutdown:
|
|
475
584
|
logger.warning("服务已关闭,无法启动消费者")
|
|
476
585
|
return
|
|
477
586
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
587
|
+
# 检查任务状态,避免重复创建
|
|
588
|
+
if client_name in cls._consumer_tasks:
|
|
589
|
+
existing_task = cls._consumer_tasks[client_name]
|
|
590
|
+
if not existing_task.done():
|
|
591
|
+
# 检查任务是否处于异常状态,仅在异常时重启
|
|
592
|
+
if existing_task.exception() is not None:
|
|
593
|
+
logger.info(f"消费者 '{client_name}' 任务异常,重启")
|
|
594
|
+
existing_task.cancel()
|
|
595
|
+
else:
|
|
596
|
+
logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
|
|
597
|
+
return
|
|
598
|
+
else:
|
|
599
|
+
logger.info(f"消费者 '{client_name}' 任务已完成,重新启动")
|
|
481
600
|
|
|
482
601
|
if client_name not in cls._clients:
|
|
483
602
|
raise ValueError(f"RabbitMQ客户端 '{client_name}' 未初始化")
|
|
@@ -489,10 +608,10 @@ class RabbitMQService:
|
|
|
489
608
|
logger.warning(f"未找到客户端 '{client_name}' 的处理器,使用默认处理器")
|
|
490
609
|
handler = cls.default_message_handler
|
|
491
610
|
|
|
492
|
-
#
|
|
611
|
+
# 设置消息处理器
|
|
493
612
|
await client.set_message_handler(handler)
|
|
494
613
|
|
|
495
|
-
#
|
|
614
|
+
# 确保客户端已连接
|
|
496
615
|
start_time = asyncio.get_event_loop().time()
|
|
497
616
|
while not await client.is_connected and not cls._is_shutdown:
|
|
498
617
|
if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
|
|
@@ -501,12 +620,11 @@ class RabbitMQService:
|
|
|
501
620
|
logger.info(f"等待客户端 '{client_name}' 连接就绪...")
|
|
502
621
|
await asyncio.sleep(1)
|
|
503
622
|
if cls._is_shutdown:
|
|
504
|
-
logger.info("服务关闭中,取消启动消费者")
|
|
505
623
|
return
|
|
506
624
|
|
|
507
|
-
# 监听器启动消费前额外延迟1
|
|
625
|
+
# 监听器启动消费前额外延迟1秒
|
|
508
626
|
if cls._has_listeners and not client_name.startswith("sender-"):
|
|
509
|
-
logger.info(f"消费者 '{client_name}' 准备启动,延迟1
|
|
627
|
+
logger.info(f"消费者 '{client_name}' 准备启动,延迟1秒等待消费状态就绪")
|
|
510
628
|
await asyncio.sleep(1)
|
|
511
629
|
|
|
512
630
|
# 创建停止事件
|
|
@@ -528,11 +646,9 @@ class RabbitMQService:
|
|
|
528
646
|
logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
|
|
529
647
|
await client.connect()
|
|
530
648
|
|
|
531
|
-
#
|
|
649
|
+
# 确保队列和处理器已就绪
|
|
532
650
|
if not client._queue:
|
|
533
651
|
raise Exception("队列未初始化完成")
|
|
534
|
-
|
|
535
|
-
# 确保消息处理器已设置
|
|
536
652
|
if not client._message_handler:
|
|
537
653
|
raise Exception("消息处理器未设置")
|
|
538
654
|
|
|
@@ -544,10 +660,9 @@ class RabbitMQService:
|
|
|
544
660
|
logger.warning(
|
|
545
661
|
f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
|
|
546
662
|
if attempt < max_attempts:
|
|
547
|
-
await asyncio.sleep(2)
|
|
663
|
+
await asyncio.sleep(2)
|
|
548
664
|
|
|
549
665
|
if cls._is_shutdown:
|
|
550
|
-
logger.info("服务关闭中,消费者启动中止")
|
|
551
666
|
return
|
|
552
667
|
|
|
553
668
|
if not consumer_tag:
|
|
@@ -556,7 +671,7 @@ class RabbitMQService:
|
|
|
556
671
|
# 记录消费者标签
|
|
557
672
|
cls._consumer_tags[client_name] = consumer_tag
|
|
558
673
|
logger.info(
|
|
559
|
-
f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}
|
|
674
|
+
f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
|
|
560
675
|
|
|
561
676
|
# 等待停止事件
|
|
562
677
|
await stop_event.wait()
|
|
@@ -570,8 +685,7 @@ class RabbitMQService:
|
|
|
570
685
|
# 非主动停止时尝试重启
|
|
571
686
|
if not stop_event.is_set() and not cls._is_shutdown:
|
|
572
687
|
logger.info(f"尝试重启消费者 '{client_name}'")
|
|
573
|
-
|
|
574
|
-
await asyncio.sleep(3)
|
|
688
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
575
689
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
576
690
|
finally:
|
|
577
691
|
# 清理资源
|
|
@@ -580,7 +694,7 @@ class RabbitMQService:
|
|
|
580
694
|
except Exception as e:
|
|
581
695
|
logger.error(f"停止消费者 '{client_name}' 时出错: {str(e)}")
|
|
582
696
|
|
|
583
|
-
#
|
|
697
|
+
# 移除状态记录
|
|
584
698
|
if client_name in cls._consumer_tags:
|
|
585
699
|
del cls._consumer_tags[client_name]
|
|
586
700
|
if client_name in cls._consumer_events:
|
|
@@ -600,7 +714,6 @@ class RabbitMQService:
|
|
|
600
714
|
t.result()
|
|
601
715
|
except Exception as e:
|
|
602
716
|
logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
|
|
603
|
-
# 任务异常时自动重启(如果服务未关闭)
|
|
604
717
|
if client_name in cls._message_handlers and not cls._is_shutdown:
|
|
605
718
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
606
719
|
|
|
@@ -618,7 +731,7 @@ class RabbitMQService:
|
|
|
618
731
|
|
|
619
732
|
@classmethod
|
|
620
733
|
async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
621
|
-
"""
|
|
734
|
+
"""获取发送客户端"""
|
|
622
735
|
if cls._is_shutdown:
|
|
623
736
|
logger.warning("服务已关闭,无法获取发送器")
|
|
624
737
|
return None
|
|
@@ -630,7 +743,6 @@ class RabbitMQService:
|
|
|
630
743
|
# 检查是否在已注册的发送器中
|
|
631
744
|
if queue_name in cls._sender_client_names and queue_name in cls._clients:
|
|
632
745
|
client = cls._clients[queue_name]
|
|
633
|
-
# 异步检查连接状态
|
|
634
746
|
if await client.is_connected:
|
|
635
747
|
return client
|
|
636
748
|
else:
|
|
@@ -667,13 +779,13 @@ class RabbitMQService:
|
|
|
667
779
|
async def send_message(
|
|
668
780
|
cls,
|
|
669
781
|
data: Union[BaseModel, str, Dict[str, Any], None],
|
|
670
|
-
queue_name: str, **
|
|
782
|
+
queue_name: str, **kwargs
|
|
671
783
|
) -> None:
|
|
672
|
-
"""
|
|
784
|
+
"""发送消息到指定队列"""
|
|
673
785
|
if cls._is_shutdown:
|
|
674
786
|
raise RuntimeError("RabbitMQService已关闭,无法发送消息")
|
|
675
787
|
|
|
676
|
-
#
|
|
788
|
+
# 获取发送客户端
|
|
677
789
|
sender = await cls.get_sender(queue_name)
|
|
678
790
|
if not sender:
|
|
679
791
|
error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
|
|
@@ -683,28 +795,24 @@ class RabbitMQService:
|
|
|
683
795
|
# 确保连接有效
|
|
684
796
|
if not await sender.is_connected:
|
|
685
797
|
logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
|
|
686
|
-
max_retry = 3
|
|
798
|
+
max_retry = 3
|
|
687
799
|
retry_count = 0
|
|
688
800
|
last_exception = None
|
|
689
801
|
|
|
690
802
|
while retry_count < max_retry and not cls._is_shutdown:
|
|
691
803
|
try:
|
|
692
|
-
# 尝试重连,每次重试间隔1秒
|
|
693
804
|
await sender.connect()
|
|
694
805
|
if await sender.is_connected:
|
|
695
806
|
logger.info(
|
|
696
807
|
f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
|
|
697
|
-
break
|
|
808
|
+
break
|
|
698
809
|
except Exception as e:
|
|
699
810
|
last_exception = e
|
|
700
811
|
retry_count += 1
|
|
701
812
|
logger.warning(
|
|
702
|
-
f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}"
|
|
703
|
-
)
|
|
704
|
-
if retry_count < max_retry:
|
|
705
|
-
await asyncio.sleep(1) # 重试前等待1秒
|
|
813
|
+
f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
|
|
814
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
706
815
|
|
|
707
|
-
# 所有重试都失败则抛出异常
|
|
708
816
|
if retry_count >= max_retry and not await sender.is_connected:
|
|
709
817
|
error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
|
|
710
818
|
logger.error(f"{error_msg}: {str(last_exception)}")
|
|
@@ -745,7 +853,7 @@ class RabbitMQService:
|
|
|
745
853
|
).model_dump_json()
|
|
746
854
|
}
|
|
747
855
|
|
|
748
|
-
#
|
|
856
|
+
# 发送消息
|
|
749
857
|
await sender.publish(
|
|
750
858
|
message_body=mq_message.model_dump_json(),
|
|
751
859
|
headers=mq_header,
|
|
@@ -758,7 +866,7 @@ class RabbitMQService:
|
|
|
758
866
|
|
|
759
867
|
@classmethod
|
|
760
868
|
async def shutdown(cls, timeout: float = 15.0) -> None:
|
|
761
|
-
"""
|
|
869
|
+
"""优雅关闭所有资源(线程安全)"""
|
|
762
870
|
async with cls._shutdown_lock:
|
|
763
871
|
if cls._is_shutdown:
|
|
764
872
|
logger.info("RabbitMQService已关闭,无需重复操作")
|
|
@@ -767,7 +875,17 @@ class RabbitMQService:
|
|
|
767
875
|
cls._is_shutdown = True
|
|
768
876
|
logger.info("开始关闭RabbitMQ服务...")
|
|
769
877
|
|
|
770
|
-
# 1.
|
|
878
|
+
# 1. 停止连接监控任务
|
|
879
|
+
if cls._connection_monitor_task and not cls._connection_monitor_task.done():
|
|
880
|
+
cls._connection_monitor_task.cancel()
|
|
881
|
+
try:
|
|
882
|
+
await asyncio.wait_for(cls._connection_monitor_task, timeout=timeout)
|
|
883
|
+
except asyncio.TimeoutError:
|
|
884
|
+
logger.warning("连接监控任务关闭超时")
|
|
885
|
+
except Exception as e:
|
|
886
|
+
logger.error(f"关闭连接监控任务失败: {str(e)}")
|
|
887
|
+
|
|
888
|
+
# 2. 停止所有消费者任务
|
|
771
889
|
for client_name, task in cls._consumer_tasks.items():
|
|
772
890
|
if not task.done():
|
|
773
891
|
# 触发停止事件
|
|
@@ -776,28 +894,26 @@ class RabbitMQService:
|
|
|
776
894
|
# 取消任务
|
|
777
895
|
task.cancel()
|
|
778
896
|
try:
|
|
779
|
-
await asyncio.wait_for(task, timeout=
|
|
780
|
-
except asyncio.TimeoutError:
|
|
781
|
-
logger.warning(f"消费者 '{client_name}' 关闭超时")
|
|
897
|
+
await asyncio.wait_for(task, timeout=timeout)
|
|
782
898
|
except Exception as e:
|
|
783
899
|
logger.error(f"关闭消费者 '{client_name}' 失败: {str(e)}")
|
|
784
900
|
|
|
785
|
-
#
|
|
901
|
+
# 3. 关闭所有客户端
|
|
786
902
|
for client in cls._clients.values():
|
|
787
903
|
try:
|
|
788
904
|
await client.close()
|
|
789
905
|
except Exception as e:
|
|
790
906
|
logger.error(f"关闭客户端失败: {str(e)}")
|
|
791
907
|
|
|
792
|
-
#
|
|
793
|
-
if cls._connection_pool:
|
|
908
|
+
# 4. 关闭连接池
|
|
909
|
+
if cls._connection_pool and cls._connection_pool._initialized:
|
|
794
910
|
try:
|
|
795
911
|
await cls._connection_pool.close()
|
|
796
912
|
logger.info("RabbitMQ连接池已关闭")
|
|
797
913
|
except Exception as e:
|
|
798
914
|
logger.error(f"关闭连接池失败: {str(e)}")
|
|
799
915
|
|
|
800
|
-
#
|
|
916
|
+
# 5. 清理状态
|
|
801
917
|
cls._clients.clear()
|
|
802
918
|
cls._message_handlers.clear()
|
|
803
919
|
cls._consumer_tasks.clear()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/DatabaseConfig.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/EmbeddingConfig.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/RerankerConfig.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/base_db_service.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/database_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/health_check.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/logger_wrapper.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/sql_logger.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/__init__.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/context.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/exception.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/middleware.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/monitor_memory.py
RENAMED
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/timeout.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqlistener_config.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqmsg_model.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqsend_config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/feign_client.py
RENAMED
|
File without changes
|
{sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/nacos_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|