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.
Files changed (64) hide show
  1. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/PKG-INFO +1 -1
  2. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/pyproject.toml +1 -1
  3. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/traceid.py +1 -1
  4. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_client.py +89 -8
  5. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_pool.py +77 -23
  6. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/rabbitmq/rabbitmq_service.py +197 -81
  7. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
  8. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/README.md +0 -0
  9. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/setup.cfg +0 -0
  10. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/command/cli.py +0 -0
  11. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/__init__.py +0 -0
  12. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/Config.py +0 -0
  13. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/DatabaseConfig.py +0 -0
  14. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/EmbeddingConfig.py +0 -0
  15. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/LLMConfig.py +0 -0
  16. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/MQConfig.py +0 -0
  17. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/RerankerConfig.py +0 -0
  18. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/config/__init__.py +0 -0
  19. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/base_db_service.py +0 -0
  20. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/database/database_service.py +0 -0
  21. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/__init__.py +0 -0
  22. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/health_check.py +0 -0
  23. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/metrics.py +0 -0
  24. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/health/ping.py +0 -0
  25. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/__init__.py +0 -0
  26. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/kafka_log.py +0 -0
  27. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/logger_wrapper.py +0 -0
  28. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/logging/sql_logger.py +0 -0
  29. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/__init__.py +0 -0
  30. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/context.py +0 -0
  31. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/cors.py +0 -0
  32. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/docs.py +0 -0
  33. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/exception.py +0 -0
  34. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/middleware.py +0 -0
  35. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/monitor_memory.py +0 -0
  36. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/mq.py +0 -0
  37. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/middleware/timeout.py +0 -0
  38. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/__init__.py +0 -0
  39. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/base_http.py +0 -0
  40. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/log.py +0 -0
  41. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqlistener_config.py +0 -0
  42. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqmsg_model.py +0 -0
  43. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/mqsend_config.py +0 -0
  44. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/models/sso_user.py +0 -0
  45. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/services.py +0 -0
  46. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/__init__.py +0 -0
  47. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/event.py +0 -0
  48. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/sse/sse.py +0 -0
  49. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/__init__.py +0 -0
  50. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/example.py +0 -0
  51. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/example2.py +0 -0
  52. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/feign.py +0 -0
  53. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/feign_client.py +0 -0
  54. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/nacos_service.py +0 -0
  55. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/synacos/param.py +0 -0
  56. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/__init__.py +0 -0
  57. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/docs.py +0 -0
  58. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/snowflake.py +0 -0
  59. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon/tools/timing.py +0 -0
  60. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
  61. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
  62. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
  63. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
  64. {sycommon_python_lib-0.1.41 → sycommon_python_lib-0.1.43}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.41
3
+ Version: 0.1.43
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sycommon-python-lib"
3
- version = "0.1.41"
3
+ version = "0.1.43"
4
4
  description = "Add your description here"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -79,7 +79,7 @@ def setup_trace_id_handler(app):
79
79
 
80
80
  content_type = response.headers.get("Content-Type", "")
81
81
 
82
- # 处理 SSE 响应 - 关键修复点
82
+ # 处理 SSE 响应
83
83
  if "text/event-stream" in content_type:
84
84
  # 流式响应不能有Content-Length,移除它
85
85
  if "Content-Length" in response.headers:
@@ -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
- # 1. 从连接池获取通道+连接
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:
@@ -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 = 10,
22
+ channel_pool_size: int = 5,
23
23
  heartbeat: int = 30,
24
24
  app_name: str = "",
25
- reconnect_interval: int = 3,
26
- connection_timeout: int = 30
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 # 最大重试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
- # 指数退避重试(1s, 2s, 4s)
109
- await asyncio.sleep(self.reconnect_interval * (2 ** (retry_count - 1)))
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.error(
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
- # 直接向 close_callbacks 添加回调(AbstractConnection 原生支持)
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
- """后台监控连接状态(每30秒检查一次)"""
187
- while self._initialized:
224
+ """后台监控:固定15秒检查一次连接状态"""
225
+ while self._initialized and not self._is_shutdown:
188
226
  try:
189
- await asyncio.sleep(30)
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
- for _ in range(missing_conn_count):
209
- asyncio.create_task(self._recreate_connection())
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:
@@ -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', 5), # 连接池大小
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(3)
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
- # 新客户端connect无declare_queue参数,自动根据create_if_not_exists处理
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", ** kwargs
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: # 直接访问 _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
- client.create_if_not_exists = True
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, ** kwargs) -> None:
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
- ** kwargs
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, ** kwargs) -> None:
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]], ** kwargs
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
- """启动指定客户端的消费者(适配新客户端的消费API)"""
582
+ """启动指定客户端的消费者"""
474
583
  if cls._is_shutdown:
475
584
  logger.warning("服务已关闭,无法启动消费者")
476
585
  return
477
586
 
478
- if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
479
- logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
480
- return
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
- # 设置消息处理器(适配新客户端的async方法)
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秒,确保consumer_tag完全生成(不删消息)
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
- # 确保队列已就绪(直接访问 _queue 属性)
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, ** kwargs
782
+ queue_name: str, **kwargs
671
783
  ) -> None:
672
- """发送消息到指定队列(适配新客户端的publish API)"""
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
- # 发送消息(适配新客户端的publish方法)
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
- """优雅关闭所有资源(线程安全,适配新客户端的close API)"""
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=5.0)
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
- # 2. 关闭所有客户端
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
- # 3. 关闭连接池
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
- # 4. 清理状态
916
+ # 5. 清理状态
801
917
  cls._clients.clear()
802
918
  cls._message_handlers.clear()
803
919
  cls._consumer_tasks.clear()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sycommon-python-lib
3
- Version: 0.1.41
3
+ Version: 0.1.43
4
4
  Summary: Add your description here
5
5
  Requires-Python: >=3.10
6
6
  Description-Content-Type: text/markdown