sycommon-python-lib 0.1.32__py3-none-any.whl → 0.1.46__py3-none-any.whl

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.
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import json
2
3
  from typing import (
3
4
  Callable, Coroutine, Dict, List, Optional, Type, Union, Any, Set
4
5
  )
@@ -18,12 +19,14 @@ logger = SYLogger
18
19
  class RabbitMQService:
19
20
  """
20
21
  RabbitMQ服务封装,管理多个客户端实例,基于连接池实现资源复用
22
+ 适配细粒度锁设计的RabbitMQClient,确保线程安全+高可用
21
23
  """
22
24
 
23
25
  # 保存多个客户端实例
24
26
  _clients: Dict[str, RabbitMQClient] = {}
25
27
  # 保存消息处理器
26
- _message_handlers: Dict[str, Callable] = {}
28
+ _message_handlers: Dict[str, Callable[[
29
+ MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]]] = {}
27
30
  # 保存消费者任务
28
31
  _consumer_tasks: Dict[str, asyncio.Task] = {}
29
32
  # 保存配置信息
@@ -45,14 +48,26 @@ class RabbitMQService:
45
48
  CONSUMER_START_TIMEOUT = 30 # 30秒超时
46
49
  # 连接池实例
47
50
  _connection_pool: Optional[RabbitMQConnectionPool] = None
51
+ # 服务关闭标记
52
+ _is_shutdown: bool = False
53
+ # 服务关闭锁
54
+ _shutdown_lock = asyncio.Lock()
55
+ # 连接状态监控任务
56
+ _connection_monitor_task: Optional[asyncio.Task] = None
57
+ # 重连配置
58
+ RECONNECT_INTERVAL = 15 # 重连基础间隔(秒)
59
+ MAX_RECONNECT_ATTEMPTS = 10 # 最大连续重连次数
48
60
 
49
61
  @classmethod
50
62
  def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
51
- """
52
- 初始化RabbitMQ服务(支持集群配置),同时创建连接池
53
- """
63
+ """初始化RabbitMQ服务(支持集群配置),同时创建连接池"""
54
64
  from sycommon.synacos.nacos_service import NacosService
55
65
 
66
+ # 防止重复初始化
67
+ if cls._config:
68
+ logger.warning("RabbitMQService已初始化,无需重复调用")
69
+ return cls
70
+
56
71
  # 获取MQ配置
57
72
  cls._config = NacosService(config).share_configs.get(
58
73
  "mq.yml", {}).get('spring', {}).get('rabbitmq', {})
@@ -63,22 +78,28 @@ class RabbitMQService:
63
78
  f"RabbitMQ服务初始化 - 集群节点: {cls._config.get('host')}, "
64
79
  f"端口: {cls._config.get('port')}, "
65
80
  f"虚拟主机: {cls._config.get('virtual-host')}, "
66
- f"应用名: {cls._config.get('APP_NAME')}"
81
+ f"应用名: {cls._config.get('APP_NAME')}, "
82
+ f"心跳: {cls._config.get('heartbeat', 30)}s"
67
83
  )
68
84
 
69
85
  # 保存发送器和监听器存在状态
70
86
  cls._has_listeners = has_listeners
71
87
  cls._has_senders = has_senders
88
+ cls._is_shutdown = False
72
89
 
73
90
  # 初始化连接池(在单独的异步方法中启动)
74
91
  asyncio.create_task(cls._init_connection_pool())
75
92
 
93
+ # 启动连接监控任务(监听连接状态,自动重连)
94
+ cls._connection_monitor_task = asyncio.create_task(
95
+ cls._monitor_connections())
96
+
76
97
  return cls
77
98
 
78
99
  @classmethod
79
100
  async def _init_connection_pool(cls):
80
- """初始化连接池(异步操作)"""
81
- if cls._connection_pool or not cls._config:
101
+ """初始化连接池(异步操作,带重试)"""
102
+ if cls._connection_pool or not cls._config or cls._is_shutdown:
82
103
  return
83
104
 
84
105
  try:
@@ -89,6 +110,8 @@ class RabbitMQService:
89
110
  if not hosts_list:
90
111
  raise ValueError("RabbitMQ集群配置为空,请检查host参数")
91
112
 
113
+ global_prefetch_count = cls._config.get('prefetch_count', 2)
114
+
92
115
  # 创建连接池
93
116
  cls._connection_pool = RabbitMQConnectionPool(
94
117
  hosts=hosts_list,
@@ -96,38 +119,153 @@ class RabbitMQService:
96
119
  username=cls._config.get('username', ""),
97
120
  password=cls._config.get('password', ""),
98
121
  virtualhost=cls._config.get('virtual-host', "/"),
99
- connection_pool_size=cls._config.get(
100
- 'connection_pool_size', 5), # 连接池大小
101
- channel_pool_size=cls._config.get(
102
- 'channel_pool_size', 10), # 通道池大小
103
- heartbeat=cls._config.get('heartbeat', 30),
104
- app_name=cls._config.get("APP_NAME", "")
122
+ app_name=cls._config.get("APP_NAME", ""),
123
+ prefetch_count=global_prefetch_count,
105
124
  )
106
125
 
107
126
  # 初始化连接池
108
- await cls._connection_pool.init_pools()
127
+ await asyncio.wait_for(cls._connection_pool.init_pools(), timeout=30)
109
128
  logger.info("RabbitMQ连接池初始化成功")
110
129
 
111
130
  except Exception as e:
112
131
  logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
113
- # 连接池初始化失败时重试
114
- await asyncio.sleep(1)
115
- asyncio.create_task(cls._init_connection_pool())
132
+ # 连接池初始化失败时重试(未关闭状态下)
133
+ if not cls._is_shutdown:
134
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
135
+ asyncio.create_task(cls._init_connection_pool())
136
+
137
+ @classmethod
138
+ async def _monitor_connections(cls):
139
+ """连接监控任务:定期检查所有客户端连接状态,自动重连"""
140
+ logger.info("RabbitMQ连接监控任务启动")
141
+ while not cls._is_shutdown:
142
+ try:
143
+ await asyncio.sleep(cls.RECONNECT_INTERVAL) # 固定15秒间隔
144
+
145
+ # 跳过未初始化的连接池
146
+ if not cls._connection_pool or not cls._connection_pool._initialized:
147
+ continue
148
+
149
+ # 检查所有客户端连接
150
+ # 使用list避免迭代中修改
151
+ for client_name, client in list(cls._clients.items()):
152
+ try:
153
+ # 双重校验连接状态(客户端内部校验 + 连接池连接校验)
154
+ client_connected = await client.is_connected
155
+ conn_connected = not (
156
+ client._channel_conn and client._channel_conn.is_closed)
157
+
158
+ if not client_connected or not conn_connected:
159
+ logger.warning(
160
+ f"客户端 '{client_name}' 连接异常 - 客户端状态: {client_connected}, "
161
+ f"连接状态: {conn_connected},触发自动重连"
162
+ )
163
+
164
+ # 重连前先清理无效资源
165
+ await cls._clean_client_resources(client)
166
+
167
+ # 执行重连(带重试)
168
+ reconnect_success = await cls._reconnect_client(client_name, client)
169
+ if reconnect_success:
170
+ logger.info(f"客户端 '{client_name}' 重连成功")
171
+ else:
172
+ logger.error(f"客户端 '{client_name}' 重连失败,将继续监控")
173
+
174
+ except Exception as e:
175
+ logger.error(
176
+ f"监控客户端 '{client_name}' 连接状态失败: {str(e)}", exc_info=True)
177
+
178
+ # 检查连接池状态(如果连接池已关闭,重新初始化)
179
+ if not await cls._connection_pool.is_alive:
180
+ logger.error("RabbitMQ连接池已关闭,尝试重新初始化")
181
+ asyncio.create_task(cls._init_connection_pool())
182
+
183
+ except Exception as e:
184
+ logger.error("RabbitMQ连接监控任务异常", exc_info=True)
185
+ await asyncio.sleep(cls.RECONNECT_INTERVAL) # 异常后延迟重启监控
186
+
187
+ logger.info("RabbitMQ连接监控任务停止")
188
+
189
+ @classmethod
190
+ async def _clean_client_resources(cls, client: RabbitMQClient):
191
+ """清理客户端无效资源(通道+连接)"""
192
+ try:
193
+ if client._channel and client._channel_conn:
194
+ # 先停止消费(避免消费中释放资源)
195
+ if client._consumer_tag:
196
+ await client.stop_consuming()
197
+ # 释放通道到连接池
198
+ await cls._connection_pool.release_channel(client._channel, client._channel_conn)
199
+ logger.debug("客户端无效资源释放成功")
200
+ except Exception as e:
201
+ logger.warning(f"释放客户端无效资源失败: {str(e)}")
202
+ finally:
203
+ # 强制重置客户端状态
204
+ client._channel = None
205
+ client._channel_conn = None
206
+ client._exchange = None
207
+ client._queue = None
208
+ client._consumer_tag = None
209
+
210
+ @classmethod
211
+ async def _reconnect_client(cls, client_name: str, client: RabbitMQClient) -> bool:
212
+ """客户端重连(带重试机制)"""
213
+ # 重连冷却,避免任务堆积
214
+ cooldown = cls.RECONNECT_INTERVAL * 2
215
+ await asyncio.sleep(cooldown)
216
+
217
+ for attempt in range(cls.MAX_RECONNECT_ATTEMPTS):
218
+ try:
219
+ # 执行重连
220
+ await client.connect()
221
+
222
+ # 验证重连结果(双重校验)
223
+ if await client.is_connected and client._queue:
224
+ # 如果是消费者,重新启动消费
225
+ if client_name in cls._message_handlers:
226
+ # 先停止旧的消费任务
227
+ if client_name in cls._consumer_tasks:
228
+ old_task = cls._consumer_tasks[client_name]
229
+ if not old_task.done():
230
+ old_task.cancel()
231
+ try:
232
+ await asyncio.wait_for(old_task, timeout=5)
233
+ except:
234
+ pass
235
+ # 启动新的消费任务
236
+ await cls.start_consumer(client_name)
237
+ return True
238
+ else:
239
+ logger.warning(
240
+ f"客户端 '{client_name}' 重连尝试 {attempt+1} 失败:资源未完全初始化")
241
+ except Exception as e:
242
+ logger.error(
243
+ f"客户端 '{client_name}' 重连尝试 {attempt+1} 失败: {str(e)}", exc_info=True)
244
+
245
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
246
+
247
+ if not cls._is_shutdown:
248
+ asyncio.create_task(cls._reconnect_client(client_name, client))
249
+ return False
116
250
 
117
251
  @classmethod
118
252
  async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
119
- if not cls._connection_pool:
253
+ """创建客户端实例(适配新的RabbitMQClient API)"""
254
+ if cls._is_shutdown:
255
+ raise RuntimeError("RabbitMQService已关闭,无法创建客户端")
256
+
257
+ if not cls._connection_pool or not cls._connection_pool._initialized:
120
258
  # 等待连接池初始化
121
259
  start_time = asyncio.get_event_loop().time()
122
- while not cls._connection_pool:
260
+ while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
123
261
  if asyncio.get_event_loop().time() - start_time > 30:
124
262
  raise TimeoutError("等待连接池初始化超时")
125
263
  await asyncio.sleep(1)
264
+ if cls._is_shutdown:
265
+ raise RuntimeError("服务关闭中,取消创建客户端")
126
266
 
127
267
  app_name = kwargs.get('app_name', cls._config.get(
128
268
  "APP_NAME", "")) if cls._config else ""
129
-
130
- # 确定是否为发送器
131
269
  is_sender = not cls._has_listeners
132
270
 
133
271
  # 根据组件类型决定是否允许创建队列
@@ -147,7 +285,7 @@ class RabbitMQService:
147
285
  f"允许创建: {create_if_not_exists}"
148
286
  )
149
287
 
150
- # 创建客户端实例
288
+ # 创建客户端实例(适配新的RabbitMQClient参数)
151
289
  client = RabbitMQClient(
152
290
  connection_pool=cls._connection_pool,
153
291
  exchange_name=cls._config.get(
@@ -160,38 +298,41 @@ class RabbitMQService:
160
298
  auto_delete=kwargs.get('auto_delete', False),
161
299
  auto_parse_json=kwargs.get('auto_parse_json', True),
162
300
  create_if_not_exists=create_if_not_exists,
163
- connection_timeout=kwargs.get('connection_timeout', 10),
164
- rpc_timeout=kwargs.get('rpc_timeout', 10),
165
- reconnection_delay=kwargs.get('reconnection_delay', 1),
166
- max_reconnection_attempts=kwargs.get(
167
- 'max_reconnection_attempts', 5),
168
301
  prefetch_count=kwargs.get('prefetch_count', 2),
169
- consumption_stall_threshold=kwargs.get(
170
- 'consumption_stall_threshold', 10),
171
302
  )
172
303
 
173
- # 使用declare_queue控制是否声明队列(发送器不声明,监听器声明)
174
- await client.connect(declare_queue=not is_sender)
304
+ # 连接客户端
305
+ await client.connect()
306
+
307
+ # 监听器客户端连接后延迟1秒,确保消费状态就绪(仅首次启动)
308
+ if not is_sender and create_if_not_exists:
309
+ logger.info(
310
+ f"监听器客户端 '{processed_queue_name}' 连接成功,延迟1秒启动消费(解决启动时序问题)")
311
+ await asyncio.sleep(1)
312
+
175
313
  return client
176
314
 
177
315
  @classmethod
178
316
  async def get_client(
179
317
  cls,
180
- client_name: str = "default", ** kwargs
318
+ client_name: str = "default", **kwargs
181
319
  ) -> RabbitMQClient:
182
- """
183
- 获取或创建RabbitMQ客户端(基于连接池)
184
- """
320
+ """获取或创建RabbitMQ客户端(基于连接池,线程安全)"""
321
+ if cls._is_shutdown:
322
+ raise RuntimeError("RabbitMQService已关闭,无法获取客户端")
323
+
185
324
  if not cls._config:
186
325
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
187
326
 
188
327
  # 等待连接池就绪
189
- if not cls._connection_pool:
328
+ if not cls._connection_pool or not cls._connection_pool._initialized:
190
329
  start_time = asyncio.get_event_loop().time()
191
- while not cls._connection_pool:
330
+ while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
192
331
  if asyncio.get_event_loop().time() - start_time > 30:
193
332
  raise TimeoutError("等待连接池初始化超时")
194
333
  await asyncio.sleep(1)
334
+ if cls._is_shutdown:
335
+ raise RuntimeError("服务关闭中,取消获取客户端")
195
336
 
196
337
  # 确保锁存在
197
338
  if client_name not in cls._init_locks:
@@ -204,18 +345,18 @@ class RabbitMQService:
204
345
  is_sender = not cls._has_listeners or (
205
346
  not kwargs.get('create_if_not_exists', True))
206
347
 
207
- if client.is_connected:
348
+ if await client.is_connected:
208
349
  # 如果是监听器但队列未初始化,重新连接
209
- if not is_sender and not client.queue:
350
+ if not is_sender and not client._queue:
210
351
  logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
211
352
  client.create_if_not_exists = True
212
- await client.connect(force_reconnect=True, declare_queue=True)
353
+ await client.connect()
213
354
  return client
214
355
  else:
215
356
  logger.info(f"客户端 '{client_name}' 连接已关闭,重新连接")
216
357
  if not is_sender:
217
358
  client.create_if_not_exists = True
218
- await client.connect(declare_queue=not is_sender)
359
+ await client.connect()
219
360
  return client
220
361
 
221
362
  # 创建新客户端
@@ -231,7 +372,6 @@ class RabbitMQService:
231
372
  app_name=cls._config.get("APP_NAME", ""),
232
373
  **kwargs
233
374
  )
234
- await client.connect(declare_queue=False)
235
375
  cls._clients[client_name] = client
236
376
  return client
237
377
 
@@ -241,10 +381,7 @@ class RabbitMQService:
241
381
  # 检查队列是否已初始化
242
382
  if initial_queue_name in cls._initialized_queues:
243
383
  logger.info(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
244
- client = await cls._create_client(
245
- initial_queue_name, ** kwargs
246
- )
247
- await client.connect(declare_queue=True)
384
+ client = await cls._create_client(initial_queue_name, **kwargs)
248
385
  cls._clients[client_name] = client
249
386
  return client
250
387
 
@@ -255,15 +392,12 @@ class RabbitMQService:
255
392
  **kwargs
256
393
  )
257
394
 
258
- client.create_if_not_exists = True
259
- await client.connect(declare_queue=True)
260
-
261
395
  # 验证队列是否创建成功
262
- if not client.queue:
396
+ if not client._queue:
263
397
  logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
264
398
  client.create_if_not_exists = True
265
- await client.connect(force_reconnect=True, declare_queue=True)
266
- if not client.queue:
399
+ await client.connect()
400
+ if not client._queue:
267
401
  raise Exception(f"无法创建队列 '{initial_queue_name}'")
268
402
 
269
403
  # 记录已初始化的队列
@@ -274,10 +408,13 @@ class RabbitMQService:
274
408
  cls._clients[client_name] = client
275
409
  return client
276
410
 
277
- # 以下方法逻辑与原有保持一致(无需修改)
278
411
  @classmethod
279
- async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, ** kwargs) -> None:
280
- """设置消息发送器"""
412
+ async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **kwargs) -> None:
413
+ """设置消息发送器(适配新客户端)"""
414
+ if cls._is_shutdown:
415
+ logger.warning("服务已关闭,无法设置发送器")
416
+ return
417
+
281
418
  cls._has_senders = True
282
419
  cls._has_listeners = has_listeners
283
420
  logger.info(f"开始设置 {len(senders)} 个消息发送器")
@@ -306,8 +443,8 @@ class RabbitMQService:
306
443
  # 获取或创建客户端
307
444
  if normalized_name in cls._clients:
308
445
  client = cls._clients[normalized_name]
309
- if not client.is_connected:
310
- await client.connect(declare_queue=False)
446
+ if not await client.is_connected:
447
+ await client.connect()
311
448
  else:
312
449
  client = await cls.get_client(
313
450
  client_name=normalized_name,
@@ -318,7 +455,7 @@ class RabbitMQService:
318
455
  queue_name=queue_name,
319
456
  create_if_not_exists=False,
320
457
  prefetch_count=prefetch_count,
321
- ** kwargs
458
+ **kwargs
322
459
  )
323
460
 
324
461
  # 记录客户端
@@ -337,8 +474,12 @@ class RabbitMQService:
337
474
  logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
338
475
 
339
476
  @classmethod
340
- async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, ** kwargs) -> None:
341
- """设置消息监听器"""
477
+ async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **kwargs) -> None:
478
+ """设置消息监听器(适配新客户端)"""
479
+ if cls._is_shutdown:
480
+ logger.warning("服务已关闭,无法设置监听器")
481
+ return
482
+
342
483
  cls._has_listeners = True
343
484
  cls._has_senders = has_senders
344
485
  logger.info(f"开始设置 {len(listeners)} 个消息监听器")
@@ -348,9 +489,11 @@ class RabbitMQService:
348
489
  # 转换配置并强制设置create_if_not_exists为True
349
490
  listener_dict = listener_config.model_dump()
350
491
  listener_dict['create_if_not_exists'] = True
492
+ listener_dict['prefetch_count'] = listener_config.prefetch_count
351
493
  queue_name = listener_dict['queue_name']
352
494
 
353
- logger.info(f"设置监听器 {idx+1}/{len(listeners)}: {queue_name}")
495
+ logger.info(
496
+ f"设置监听器 {idx+1}/{len(listeners)}: {queue_name} (prefetch_count: {listener_config.prefetch_count})")
354
497
 
355
498
  # 添加监听器
356
499
  await cls.add_listener(**listener_dict)
@@ -375,7 +518,8 @@ class RabbitMQService:
375
518
  running_clients = []
376
519
 
377
520
  while len(running_clients) < len(required_clients) and \
378
- (asyncio.get_event_loop().time() - start_time) < timeout:
521
+ (asyncio.get_event_loop().time() - start_time) < timeout and \
522
+ not cls._is_shutdown:
379
523
 
380
524
  running_clients = [
381
525
  name for name, task in cls._consumer_tasks.items()
@@ -387,7 +531,7 @@ class RabbitMQService:
387
531
  await asyncio.sleep(1)
388
532
 
389
533
  failed_clients = [
390
- name for name in required_clients if name not in running_clients]
534
+ name for name in required_clients if name not in running_clients and not cls._is_shutdown]
391
535
  if failed_clients:
392
536
  logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
393
537
  for client_name in failed_clients:
@@ -398,9 +542,13 @@ class RabbitMQService:
398
542
  async def add_listener(
399
543
  cls,
400
544
  queue_name: str,
401
- handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], ** kwargs
545
+ handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **kwargs
402
546
  ) -> None:
403
- """添加消息监听器"""
547
+ """添加消息监听器(线程安全)"""
548
+ if cls._is_shutdown:
549
+ logger.warning("服务已关闭,无法添加监听器")
550
+ return
551
+
404
552
  if not cls._config:
405
553
  raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
406
554
 
@@ -421,17 +569,35 @@ class RabbitMQService:
421
569
 
422
570
  @classmethod
423
571
  async def start_all_consumers(cls) -> None:
424
- """启动所有已注册的消费者"""
572
+ """启动所有已注册的消费者(线程安全)"""
573
+ if cls._is_shutdown:
574
+ logger.warning("服务已关闭,无法启动消费者")
575
+ return
576
+
425
577
  for client_name in cls._message_handlers:
426
578
  await cls.start_consumer(client_name)
427
579
 
428
580
  @classmethod
429
581
  async def start_consumer(cls, client_name: str) -> None:
430
582
  """启动指定客户端的消费者"""
431
- if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
432
- logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
583
+ if cls._is_shutdown:
584
+ logger.warning("服务已关闭,无法启动消费者")
433
585
  return
434
586
 
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}' 任务已完成,重新启动")
600
+
435
601
  if client_name not in cls._clients:
436
602
  raise ValueError(f"RabbitMQ客户端 '{client_name}' 未初始化")
437
603
 
@@ -443,16 +609,23 @@ class RabbitMQService:
443
609
  handler = cls.default_message_handler
444
610
 
445
611
  # 设置消息处理器
446
- client.set_message_handler(handler)
612
+ await client.set_message_handler(handler)
447
613
 
448
614
  # 确保客户端已连接
449
615
  start_time = asyncio.get_event_loop().time()
450
- while not client.is_connected:
616
+ while not await client.is_connected and not cls._is_shutdown:
451
617
  if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
452
618
  raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
453
619
 
454
620
  logger.info(f"等待客户端 '{client_name}' 连接就绪...")
455
621
  await asyncio.sleep(1)
622
+ if cls._is_shutdown:
623
+ return
624
+
625
+ # 监听器启动消费前额外延迟1秒
626
+ if cls._has_listeners and not client_name.startswith("sender-"):
627
+ logger.info(f"消费者 '{client_name}' 准备启动,延迟1秒等待消费状态就绪")
628
+ await asyncio.sleep(1)
456
629
 
457
630
  # 创建停止事件
458
631
  stop_event = asyncio.Event()
@@ -466,8 +639,19 @@ class RabbitMQService:
466
639
  attempt = 0
467
640
  consumer_tag = None
468
641
 
469
- while attempt < max_attempts and not stop_event.is_set():
642
+ while attempt < max_attempts and not stop_event.is_set() and not cls._is_shutdown:
470
643
  try:
644
+ # 启动消费前再次校验连接和队列状态
645
+ if not await client.is_connected:
646
+ logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
647
+ await client.connect()
648
+
649
+ # 确保队列和处理器已就绪
650
+ if not client._queue:
651
+ raise Exception("队列未初始化完成")
652
+ if not client._message_handler:
653
+ raise Exception("消息处理器未设置")
654
+
471
655
  consumer_tag = await client.start_consuming()
472
656
  if consumer_tag:
473
657
  break
@@ -476,14 +660,18 @@ class RabbitMQService:
476
660
  logger.warning(
477
661
  f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
478
662
  if attempt < max_attempts:
479
- await asyncio.sleep(1)
663
+ await asyncio.sleep(2)
664
+
665
+ if cls._is_shutdown:
666
+ return
480
667
 
481
668
  if not consumer_tag:
482
669
  raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
483
670
 
484
671
  # 记录消费者标签
485
672
  cls._consumer_tags[client_name] = consumer_tag
486
- logger.info(f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
673
+ logger.info(
674
+ f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}")
487
675
 
488
676
  # 等待停止事件
489
677
  await stop_event.wait()
@@ -495,8 +683,9 @@ class RabbitMQService:
495
683
  logger.error(
496
684
  f"消费者 '{client_name}' 错误: {str(e)}", exc_info=True)
497
685
  # 非主动停止时尝试重启
498
- if not stop_event.is_set():
686
+ if not stop_event.is_set() and not cls._is_shutdown:
499
687
  logger.info(f"尝试重启消费者 '{client_name}'")
688
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
500
689
  asyncio.create_task(cls.start_consumer(client_name))
501
690
  finally:
502
691
  # 清理资源
@@ -525,8 +714,7 @@ class RabbitMQService:
525
714
  t.result()
526
715
  except Exception as e:
527
716
  logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
528
- # 任务异常时自动重启(如果服务未关闭)
529
- if client_name in cls._message_handlers: # 检查处理器是否仍存在
717
+ if client_name in cls._message_handlers and not cls._is_shutdown:
530
718
  asyncio.create_task(cls.start_consumer(client_name))
531
719
 
532
720
  task.add_done_callback(task_done_callback)
@@ -542,63 +730,90 @@ class RabbitMQService:
542
730
  logger.info("===================\n")
543
731
 
544
732
  @classmethod
545
- def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
733
+ async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
546
734
  """获取发送客户端"""
735
+ if cls._is_shutdown:
736
+ logger.warning("服务已关闭,无法获取发送器")
737
+ return None
738
+
547
739
  if not queue_name:
548
740
  logger.warning("发送器名称不能为空")
549
741
  return None
550
742
 
551
743
  # 检查是否在已注册的发送器中
552
744
  if queue_name in cls._sender_client_names and queue_name in cls._clients:
553
- return cls._clients[queue_name]
745
+ client = cls._clients[queue_name]
746
+ if await client.is_connected:
747
+ return client
748
+ else:
749
+ logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
750
+ try:
751
+ await client.connect()
752
+ if await client.is_connected:
753
+ return client
754
+ except Exception as e:
755
+ logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
756
+ return None
554
757
 
555
758
  # 检查是否带有app-name后缀
556
759
  app_name = cls._config.get("APP_NAME", "") if cls._config else ""
557
- if app_name and f"{queue_name}.{app_name}" in cls._sender_client_names:
558
- return cls._clients.get(f"{queue_name}.{app_name}")
760
+ if app_name:
761
+ suffixed_name = f"{queue_name}.{app_name}"
762
+ if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
763
+ client = cls._clients[suffixed_name]
764
+ if await client.is_connected:
765
+ return client
766
+ else:
767
+ logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
768
+ try:
769
+ await client.connect()
770
+ if await client.is_connected:
771
+ return client
772
+ except Exception as e:
773
+ logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
559
774
 
560
- logger.info(f"未找到发送器 '{queue_name}'")
775
+ logger.info(f"未找到可用的发送器 '{queue_name}'")
561
776
  return None
562
777
 
563
778
  @classmethod
564
779
  async def send_message(
565
780
  cls,
566
781
  data: Union[BaseModel, str, Dict[str, Any], None],
567
- queue_name: str, ** kwargs
782
+ queue_name: str, **kwargs
568
783
  ) -> None:
569
784
  """发送消息到指定队列"""
785
+ if cls._is_shutdown:
786
+ raise RuntimeError("RabbitMQService已关闭,无法发送消息")
787
+
570
788
  # 获取发送客户端
571
- sender = cls.get_sender(queue_name)
789
+ sender = await cls.get_sender(queue_name)
572
790
  if not sender:
573
791
  error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
574
792
  logger.error(error_msg)
575
793
  raise ValueError(error_msg)
576
794
 
577
795
  # 确保连接有效
578
- if not sender.is_connected:
796
+ if not await sender.is_connected:
579
797
  logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
580
- max_retry = 3 # 最大重试次数
798
+ max_retry = 3
581
799
  retry_count = 0
582
800
  last_exception = None
583
801
 
584
- while retry_count < max_retry:
802
+ while retry_count < max_retry and not cls._is_shutdown:
585
803
  try:
586
- # 尝试重连,每次重试间隔1秒
587
- await sender.connect(force_reconnect=True, declare_queue=False)
588
- logger.info(
589
- f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
590
- break # 重连成功则退出循环
804
+ await sender.connect()
805
+ if await sender.is_connected:
806
+ logger.info(
807
+ f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
808
+ break
591
809
  except Exception as e:
592
810
  last_exception = e
593
811
  retry_count += 1
594
812
  logger.warning(
595
- f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}"
596
- )
597
- if retry_count < max_retry:
598
- await asyncio.sleep(1) # 重试前等待1秒
813
+ f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
814
+ await asyncio.sleep(cls.RECONNECT_INTERVAL)
599
815
 
600
- # 所有重试都失败则抛出异常
601
- if retry_count >= max_retry and not sender.is_connected:
816
+ if retry_count >= max_retry and not await sender.is_connected:
602
817
  error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
603
818
  logger.error(f"{error_msg}: {str(last_exception)}")
604
819
  raise Exception(error_msg) from last_exception
@@ -611,19 +826,18 @@ class RabbitMQService:
611
826
  elif isinstance(data, BaseModel):
612
827
  msg_content = data.model_dump_json()
613
828
  elif isinstance(data, dict):
614
- import json
615
- msg_content = json.dumps(data)
829
+ msg_content = json.dumps(data, ensure_ascii=False)
616
830
 
617
831
  # 创建标准消息模型
618
832
  mq_message = MQMsgModel(
619
833
  topicCode=queue_name.split('.')[0] if queue_name else "",
620
834
  msg=msg_content,
621
835
  correlationDataId=kwargs.get(
622
- 'correlationDataId', SYLogger.get_trace_id()),
836
+ 'correlationDataId', logger.get_trace_id()),
623
837
  groupId=kwargs.get('groupId', ''),
624
838
  dataKey=kwargs.get('dataKey', ""),
625
839
  manualFlag=kwargs.get('manualFlag', False),
626
- traceId=SYLogger.get_trace_id()
840
+ traceId=logger.get_trace_id()
627
841
  )
628
842
 
629
843
  # 构建消息头
@@ -635,7 +849,7 @@ class RabbitMQService:
635
849
  user_name="SYSTEM",
636
850
  request_path="",
637
851
  req_type="SYSTEM",
638
- trace_id=SYLogger.get_trace_id(),
852
+ trace_id=logger.get_trace_id(),
639
853
  ).model_dump_json()
640
854
  }
641
855
 
@@ -652,72 +866,62 @@ class RabbitMQService:
652
866
 
653
867
  @classmethod
654
868
  async def shutdown(cls, timeout: float = 15.0) -> None:
655
- """优雅关闭所有资源(新增连接池关闭逻辑)"""
656
- start_time = asyncio.get_event_loop().time()
657
- logger.info("开始关闭RabbitMQ服务...")
658
-
659
- # 发送停止信号给所有消费者
660
- for client_name, event in cls._consumer_events.items():
661
- event.set()
662
- logger.info(f"已向消费者 '{client_name}' 发送退出信号")
663
-
664
- # 等待消费者任务完成
665
- remaining_time = max(
666
- 0.0, timeout - (asyncio.get_event_loop().time() - start_time))
667
- if remaining_time > 0 and cls._consumer_tasks:
668
- try:
669
- done, pending = await asyncio.wait(
670
- list(cls._consumer_tasks.values()),
671
- timeout=remaining_time,
672
- return_when=asyncio.ALL_COMPLETED
673
- )
674
-
675
- # 处理超时的任务
676
- for task in pending:
677
- task_name = task.get_name()
678
- logger.warning(f"任务 '{task_name}' 关闭超时,强制取消")
869
+ """优雅关闭所有资源(线程安全)"""
870
+ async with cls._shutdown_lock:
871
+ if cls._is_shutdown:
872
+ logger.info("RabbitMQService已关闭,无需重复操作")
873
+ return
874
+
875
+ cls._is_shutdown = True
876
+ logger.info("开始关闭RabbitMQ服务...")
877
+
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. 停止所有消费者任务
889
+ for client_name, task in cls._consumer_tasks.items():
890
+ if not task.done():
891
+ # 触发停止事件
892
+ if client_name in cls._consumer_events:
893
+ cls._consumer_events[client_name].set()
894
+ # 取消任务
679
895
  task.cancel()
680
896
  try:
681
- await task
682
- except (asyncio.CancelledError, RuntimeError):
683
- pass
684
-
685
- except Exception as e:
686
- logger.error(f"等待消费者任务完成时出错: {str(e)}")
687
-
688
- # 关闭所有客户端连接
689
- remaining_time = max(
690
- 0.0, timeout - (asyncio.get_event_loop().time() - start_time))
691
- if remaining_time > 0 and cls._clients:
692
- client_count = len(cls._clients)
693
- client_timeout = remaining_time / client_count # 平均分配剩余时间
897
+ await asyncio.wait_for(task, timeout=timeout)
898
+ except Exception as e:
899
+ logger.error(f"关闭消费者 '{client_name}' 失败: {str(e)}")
694
900
 
695
- for name, client in cls._clients.items():
901
+ # 3. 关闭所有客户端
902
+ for client in cls._clients.values():
696
903
  try:
697
- await asyncio.wait_for(client.close(), timeout=client_timeout)
904
+ await client.close()
698
905
  except Exception as e:
699
- logger.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
700
- logger.info(f"客户端 '{name}' 已关闭")
906
+ logger.error(f"关闭客户端失败: {str(e)}")
701
907
 
702
- # 关闭连接池
703
- if cls._connection_pool:
704
- try:
705
- await cls._connection_pool.close()
706
- logger.info("RabbitMQ连接池已关闭")
707
- except Exception as e:
708
- logger.warning(f"关闭连接池时出错: {str(e)}")
709
-
710
- # 清理所有状态
711
- cls._clients.clear()
712
- cls._consumer_tasks.clear()
713
- cls._message_handlers.clear()
714
- cls._sender_client_names.clear()
715
- cls._consumer_events.clear()
716
- cls._consumer_tags.clear()
717
- cls._initialized_queues.clear()
718
- cls._init_locks.clear()
719
- cls._has_listeners = False
720
- cls._has_senders = False
721
- cls._connection_pool = None
722
-
723
- logger.info("RabbitMQ服务已完全关闭")
908
+ # 4. 关闭连接池
909
+ if cls._connection_pool and cls._connection_pool._initialized:
910
+ try:
911
+ await cls._connection_pool.close()
912
+ logger.info("RabbitMQ连接池已关闭")
913
+ except Exception as e:
914
+ logger.error(f"关闭连接池失败: {str(e)}")
915
+
916
+ # 5. 清理状态
917
+ cls._clients.clear()
918
+ cls._message_handlers.clear()
919
+ cls._consumer_tasks.clear()
920
+ cls._consumer_events.clear()
921
+ cls._consumer_tags.clear()
922
+ cls._initialized_queues.clear()
923
+ cls._sender_client_names.clear()
924
+ cls._init_locks.clear()
925
+ cls._config = None
926
+
927
+ logger.info("RabbitMQService已完全关闭")