sycommon-python-lib 0.1.29__py3-none-any.whl → 0.1.40__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.
- sycommon/models/mqlistener_config.py +1 -0
- sycommon/rabbitmq/rabbitmq_client.py +205 -588
- sycommon/rabbitmq/rabbitmq_pool.py +141 -65
- sycommon/rabbitmq/rabbitmq_service.py +269 -145
- sycommon/services.py +64 -22
- sycommon/synacos/example.py +153 -0
- sycommon/synacos/example2.py +129 -0
- sycommon/synacos/feign.py +51 -436
- sycommon/synacos/feign_client.py +317 -0
- sycommon/synacos/nacos_service.py +1 -1
- sycommon/synacos/param.py +75 -0
- {sycommon_python_lib-0.1.29.dist-info → sycommon_python_lib-0.1.40.dist-info}/METADATA +1 -1
- {sycommon_python_lib-0.1.29.dist-info → sycommon_python_lib-0.1.40.dist-info}/RECORD +16 -12
- {sycommon_python_lib-0.1.29.dist-info → sycommon_python_lib-0.1.40.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.29.dist-info → sycommon_python_lib-0.1.40.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.29.dist-info → sycommon_python_lib-0.1.40.dist-info}/top_level.txt +0 -0
|
@@ -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,6 +19,7 @@ logger = SYLogger
|
|
|
18
19
|
class RabbitMQService:
|
|
19
20
|
"""
|
|
20
21
|
RabbitMQ服务封装,管理多个客户端实例,基于连接池实现资源复用
|
|
22
|
+
适配细粒度锁设计的RabbitMQClient,确保线程安全
|
|
21
23
|
"""
|
|
22
24
|
|
|
23
25
|
# 保存多个客户端实例
|
|
@@ -45,6 +47,10 @@ class RabbitMQService:
|
|
|
45
47
|
CONSUMER_START_TIMEOUT = 30 # 30秒超时
|
|
46
48
|
# 连接池实例
|
|
47
49
|
_connection_pool: Optional[RabbitMQConnectionPool] = None
|
|
50
|
+
# 服务关闭标记
|
|
51
|
+
_is_shutdown: bool = False
|
|
52
|
+
# 服务关闭锁
|
|
53
|
+
_shutdown_lock = asyncio.Lock()
|
|
48
54
|
|
|
49
55
|
@classmethod
|
|
50
56
|
def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
|
|
@@ -53,6 +59,11 @@ class RabbitMQService:
|
|
|
53
59
|
"""
|
|
54
60
|
from sycommon.synacos.nacos_service import NacosService
|
|
55
61
|
|
|
62
|
+
# 防止重复初始化
|
|
63
|
+
if cls._config:
|
|
64
|
+
logger.warning("RabbitMQService已初始化,无需重复调用")
|
|
65
|
+
return cls
|
|
66
|
+
|
|
56
67
|
# 获取MQ配置
|
|
57
68
|
cls._config = NacosService(config).share_configs.get(
|
|
58
69
|
"mq.yml", {}).get('spring', {}).get('rabbitmq', {})
|
|
@@ -69,6 +80,7 @@ class RabbitMQService:
|
|
|
69
80
|
# 保存发送器和监听器存在状态
|
|
70
81
|
cls._has_listeners = has_listeners
|
|
71
82
|
cls._has_senders = has_senders
|
|
83
|
+
cls._is_shutdown = False
|
|
72
84
|
|
|
73
85
|
# 初始化连接池(在单独的异步方法中启动)
|
|
74
86
|
asyncio.create_task(cls._init_connection_pool())
|
|
@@ -77,8 +89,8 @@ class RabbitMQService:
|
|
|
77
89
|
|
|
78
90
|
@classmethod
|
|
79
91
|
async def _init_connection_pool(cls):
|
|
80
|
-
"""
|
|
81
|
-
if cls._connection_pool or not cls._config:
|
|
92
|
+
"""初始化连接池(异步操作,带重试)"""
|
|
93
|
+
if cls._connection_pool or not cls._config or cls._is_shutdown:
|
|
82
94
|
return
|
|
83
95
|
|
|
84
96
|
try:
|
|
@@ -105,24 +117,31 @@ class RabbitMQService:
|
|
|
105
117
|
)
|
|
106
118
|
|
|
107
119
|
# 初始化连接池
|
|
108
|
-
await cls._connection_pool.init_pools()
|
|
120
|
+
await asyncio.wait_for(cls._connection_pool.init_pools(), timeout=30)
|
|
109
121
|
logger.info("RabbitMQ连接池初始化成功")
|
|
110
122
|
|
|
111
123
|
except Exception as e:
|
|
112
124
|
logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
|
|
113
|
-
#
|
|
114
|
-
|
|
115
|
-
|
|
125
|
+
# 连接池初始化失败时重试(未关闭状态下)
|
|
126
|
+
if not cls._is_shutdown:
|
|
127
|
+
await asyncio.sleep(3)
|
|
128
|
+
asyncio.create_task(cls._init_connection_pool())
|
|
116
129
|
|
|
117
130
|
@classmethod
|
|
118
131
|
async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
|
|
132
|
+
"""创建客户端实例(适配新的RabbitMQClient API)"""
|
|
133
|
+
if cls._is_shutdown:
|
|
134
|
+
raise RuntimeError("RabbitMQService已关闭,无法创建客户端")
|
|
135
|
+
|
|
119
136
|
if not cls._connection_pool:
|
|
120
137
|
# 等待连接池初始化
|
|
121
138
|
start_time = asyncio.get_event_loop().time()
|
|
122
|
-
while not cls._connection_pool:
|
|
139
|
+
while not cls._connection_pool and not cls._is_shutdown:
|
|
123
140
|
if asyncio.get_event_loop().time() - start_time > 30:
|
|
124
141
|
raise TimeoutError("等待连接池初始化超时")
|
|
125
142
|
await asyncio.sleep(1)
|
|
143
|
+
if cls._is_shutdown:
|
|
144
|
+
raise RuntimeError("服务关闭中,取消创建客户端")
|
|
126
145
|
|
|
127
146
|
app_name = kwargs.get('app_name', cls._config.get(
|
|
128
147
|
"APP_NAME", "")) if cls._config else ""
|
|
@@ -142,12 +161,12 @@ class RabbitMQService:
|
|
|
142
161
|
else:
|
|
143
162
|
logger.info(f"监听器队列已包含app-name: {processed_queue_name}")
|
|
144
163
|
|
|
145
|
-
logger.
|
|
164
|
+
logger.info(
|
|
146
165
|
f"创建客户端 - 队列: {processed_queue_name}, 发送器: {is_sender}, "
|
|
147
166
|
f"允许创建: {create_if_not_exists}"
|
|
148
167
|
)
|
|
149
168
|
|
|
150
|
-
#
|
|
169
|
+
# 创建客户端实例(适配新的RabbitMQClient参数)
|
|
151
170
|
client = RabbitMQClient(
|
|
152
171
|
connection_pool=cls._connection_pool,
|
|
153
172
|
exchange_name=cls._config.get(
|
|
@@ -160,18 +179,21 @@ class RabbitMQService:
|
|
|
160
179
|
auto_delete=kwargs.get('auto_delete', False),
|
|
161
180
|
auto_parse_json=kwargs.get('auto_parse_json', True),
|
|
162
181
|
create_if_not_exists=create_if_not_exists,
|
|
163
|
-
|
|
164
|
-
rpc_timeout=kwargs.get('rpc_timeout', 5),
|
|
165
|
-
reconnection_delay=kwargs.get('reconnection_delay', 1),
|
|
166
|
-
max_reconnection_attempts=kwargs.get(
|
|
167
|
-
'max_reconnection_attempts', 5),
|
|
182
|
+
rpc_timeout=kwargs.get('rpc_timeout', 10),
|
|
168
183
|
prefetch_count=kwargs.get('prefetch_count', 2),
|
|
169
184
|
consumption_stall_threshold=kwargs.get(
|
|
170
|
-
'consumption_stall_threshold',
|
|
185
|
+
'consumption_stall_threshold', 60), # 延长停滞阈值
|
|
171
186
|
)
|
|
172
187
|
|
|
173
|
-
#
|
|
174
|
-
await client.connect(
|
|
188
|
+
# 新客户端connect无declare_queue参数,自动根据create_if_not_exists处理
|
|
189
|
+
await client.connect()
|
|
190
|
+
|
|
191
|
+
# 监听器客户端连接后延迟1秒,确保消费状态就绪(仅首次启动)
|
|
192
|
+
if not is_sender and create_if_not_exists:
|
|
193
|
+
logger.info(
|
|
194
|
+
f"监听器客户端 '{processed_queue_name}' 连接成功,延迟1秒启动消费(解决启动时序问题)")
|
|
195
|
+
await asyncio.sleep(1)
|
|
196
|
+
|
|
175
197
|
return client
|
|
176
198
|
|
|
177
199
|
@classmethod
|
|
@@ -180,18 +202,24 @@ class RabbitMQService:
|
|
|
180
202
|
client_name: str = "default", ** kwargs
|
|
181
203
|
) -> RabbitMQClient:
|
|
182
204
|
"""
|
|
183
|
-
获取或创建RabbitMQ
|
|
205
|
+
获取或创建RabbitMQ客户端(基于连接池,线程安全)
|
|
206
|
+
适配新的RabbitMQClient异步状态检查
|
|
184
207
|
"""
|
|
208
|
+
if cls._is_shutdown:
|
|
209
|
+
raise RuntimeError("RabbitMQService已关闭,无法获取客户端")
|
|
210
|
+
|
|
185
211
|
if not cls._config:
|
|
186
212
|
raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
|
|
187
213
|
|
|
188
214
|
# 等待连接池就绪
|
|
189
215
|
if not cls._connection_pool:
|
|
190
216
|
start_time = asyncio.get_event_loop().time()
|
|
191
|
-
while not cls._connection_pool:
|
|
217
|
+
while not cls._connection_pool and not cls._is_shutdown:
|
|
192
218
|
if asyncio.get_event_loop().time() - start_time > 30:
|
|
193
219
|
raise TimeoutError("等待连接池初始化超时")
|
|
194
220
|
await asyncio.sleep(1)
|
|
221
|
+
if cls._is_shutdown:
|
|
222
|
+
raise RuntimeError("服务关闭中,取消获取客户端")
|
|
195
223
|
|
|
196
224
|
# 确保锁存在
|
|
197
225
|
if client_name not in cls._init_locks:
|
|
@@ -204,18 +232,22 @@ class RabbitMQService:
|
|
|
204
232
|
is_sender = not cls._has_listeners or (
|
|
205
233
|
not kwargs.get('create_if_not_exists', True))
|
|
206
234
|
|
|
207
|
-
|
|
235
|
+
# 异步检查连接状态(适配新客户端的async属性)
|
|
236
|
+
if await client.is_connected:
|
|
208
237
|
# 如果是监听器但队列未初始化,重新连接
|
|
209
|
-
if not is_sender
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
238
|
+
if not is_sender:
|
|
239
|
+
# 异步获取队列(适配新客户端的原子方法)
|
|
240
|
+
_, _, queue = await client._get_connection_resources()
|
|
241
|
+
if not queue:
|
|
242
|
+
logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
|
|
243
|
+
client.create_if_not_exists = True
|
|
244
|
+
await client.connect() # 移除force_reconnect和declare_queue
|
|
213
245
|
return client
|
|
214
246
|
else:
|
|
215
|
-
logger.
|
|
247
|
+
logger.info(f"客户端 '{client_name}' 连接已关闭,重新连接")
|
|
216
248
|
if not is_sender:
|
|
217
249
|
client.create_if_not_exists = True
|
|
218
|
-
await client.connect(declare_queue
|
|
250
|
+
await client.connect() # 移除force_reconnect和declare_queue
|
|
219
251
|
return client
|
|
220
252
|
|
|
221
253
|
# 创建新客户端
|
|
@@ -231,7 +263,7 @@ class RabbitMQService:
|
|
|
231
263
|
app_name=cls._config.get("APP_NAME", ""),
|
|
232
264
|
**kwargs
|
|
233
265
|
)
|
|
234
|
-
await client.connect(declare_queue=False
|
|
266
|
+
await client.connect() # 移除declare_queue=False
|
|
235
267
|
cls._clients[client_name] = client
|
|
236
268
|
return client
|
|
237
269
|
|
|
@@ -240,11 +272,11 @@ class RabbitMQService:
|
|
|
240
272
|
|
|
241
273
|
# 检查队列是否已初始化
|
|
242
274
|
if initial_queue_name in cls._initialized_queues:
|
|
243
|
-
logger.
|
|
275
|
+
logger.info(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
|
|
244
276
|
client = await cls._create_client(
|
|
245
277
|
initial_queue_name, ** kwargs
|
|
246
278
|
)
|
|
247
|
-
await client.connect(declare_queue=True
|
|
279
|
+
await client.connect() # 移除declare_queue=True
|
|
248
280
|
cls._clients[client_name] = client
|
|
249
281
|
return client
|
|
250
282
|
|
|
@@ -256,14 +288,16 @@ class RabbitMQService:
|
|
|
256
288
|
)
|
|
257
289
|
|
|
258
290
|
client.create_if_not_exists = True
|
|
259
|
-
await client.connect(declare_queue=True
|
|
291
|
+
await client.connect() # 移除declare_queue=True
|
|
260
292
|
|
|
261
|
-
#
|
|
262
|
-
|
|
293
|
+
# 验证队列是否创建成功(异步获取队列)
|
|
294
|
+
_, _, queue = await client._get_connection_resources()
|
|
295
|
+
if not queue:
|
|
263
296
|
logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
|
|
264
297
|
client.create_if_not_exists = True
|
|
265
|
-
await client.connect(
|
|
266
|
-
|
|
298
|
+
await client.connect()
|
|
299
|
+
_, _, queue = await client._get_connection_resources()
|
|
300
|
+
if not queue:
|
|
267
301
|
raise Exception(f"无法创建队列 '{initial_queue_name}'")
|
|
268
302
|
|
|
269
303
|
# 记录已初始化的队列
|
|
@@ -274,10 +308,13 @@ class RabbitMQService:
|
|
|
274
308
|
cls._clients[client_name] = client
|
|
275
309
|
return client
|
|
276
310
|
|
|
277
|
-
# 以下方法逻辑与原有保持一致(无需修改)
|
|
278
311
|
@classmethod
|
|
279
|
-
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False) -> None:
|
|
280
|
-
"""
|
|
312
|
+
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, ** kwargs) -> None:
|
|
313
|
+
"""设置消息发送器(适配新客户端)"""
|
|
314
|
+
if cls._is_shutdown:
|
|
315
|
+
logger.warning("服务已关闭,无法设置发送器")
|
|
316
|
+
return
|
|
317
|
+
|
|
281
318
|
cls._has_senders = True
|
|
282
319
|
cls._has_listeners = has_listeners
|
|
283
320
|
logger.info(f"开始设置 {len(senders)} 个消息发送器")
|
|
@@ -287,6 +324,7 @@ class RabbitMQService:
|
|
|
287
324
|
if not sender_config.queue_name:
|
|
288
325
|
raise ValueError(f"发送器配置第{idx+1}项缺少queue_name")
|
|
289
326
|
|
|
327
|
+
prefetch_count = sender_config.prefetch_count
|
|
290
328
|
queue_name = sender_config.queue_name
|
|
291
329
|
app_name = cls._config.get(
|
|
292
330
|
"APP_NAME", "") if cls._config else ""
|
|
@@ -295,18 +333,18 @@ class RabbitMQService:
|
|
|
295
333
|
normalized_name = queue_name
|
|
296
334
|
if app_name and normalized_name.endswith(f".{app_name}"):
|
|
297
335
|
normalized_name = normalized_name[:-len(f".{app_name}")]
|
|
298
|
-
logger.
|
|
336
|
+
logger.info(f"发送器队列名称移除app-name后缀: {normalized_name}")
|
|
299
337
|
|
|
300
338
|
# 检查是否已初始化
|
|
301
339
|
if normalized_name in cls._sender_client_names:
|
|
302
|
-
logger.
|
|
340
|
+
logger.info(f"发送客户端 '{normalized_name}' 已存在,跳过")
|
|
303
341
|
continue
|
|
304
342
|
|
|
305
343
|
# 获取或创建客户端
|
|
306
344
|
if normalized_name in cls._clients:
|
|
307
345
|
client = cls._clients[normalized_name]
|
|
308
|
-
if not client.is_connected:
|
|
309
|
-
await client.connect(declare_queue=False
|
|
346
|
+
if not await client.is_connected:
|
|
347
|
+
await client.connect() # 移除declare_queue=False
|
|
310
348
|
else:
|
|
311
349
|
client = await cls.get_client(
|
|
312
350
|
client_name=normalized_name,
|
|
@@ -315,7 +353,9 @@ class RabbitMQService:
|
|
|
315
353
|
auto_delete=sender_config.auto_delete,
|
|
316
354
|
auto_parse_json=sender_config.auto_parse_json,
|
|
317
355
|
queue_name=queue_name,
|
|
318
|
-
create_if_not_exists=False
|
|
356
|
+
create_if_not_exists=False,
|
|
357
|
+
prefetch_count=prefetch_count,
|
|
358
|
+
** kwargs
|
|
319
359
|
)
|
|
320
360
|
|
|
321
361
|
# 记录客户端
|
|
@@ -334,8 +374,12 @@ class RabbitMQService:
|
|
|
334
374
|
logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
|
|
335
375
|
|
|
336
376
|
@classmethod
|
|
337
|
-
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False) -> None:
|
|
338
|
-
"""
|
|
377
|
+
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, ** kwargs) -> None:
|
|
378
|
+
"""设置消息监听器(适配新客户端)"""
|
|
379
|
+
if cls._is_shutdown:
|
|
380
|
+
logger.warning("服务已关闭,无法设置监听器")
|
|
381
|
+
return
|
|
382
|
+
|
|
339
383
|
cls._has_listeners = True
|
|
340
384
|
cls._has_senders = has_senders
|
|
341
385
|
logger.info(f"开始设置 {len(listeners)} 个消息监听器")
|
|
@@ -366,13 +410,14 @@ class RabbitMQService:
|
|
|
366
410
|
|
|
367
411
|
@classmethod
|
|
368
412
|
async def _verify_consumers_started(cls, timeout: int = 30) -> None:
|
|
369
|
-
"""
|
|
413
|
+
"""验证消费者是否成功启动(适配新客户端的消费者标签管理)"""
|
|
370
414
|
start_time = asyncio.get_event_loop().time()
|
|
371
415
|
required_clients = list(cls._message_handlers.keys())
|
|
372
416
|
running_clients = []
|
|
373
417
|
|
|
374
418
|
while len(running_clients) < len(required_clients) and \
|
|
375
|
-
(asyncio.get_event_loop().time() - start_time) < timeout
|
|
419
|
+
(asyncio.get_event_loop().time() - start_time) < timeout and \
|
|
420
|
+
not cls._is_shutdown:
|
|
376
421
|
|
|
377
422
|
running_clients = [
|
|
378
423
|
name for name, task in cls._consumer_tasks.items()
|
|
@@ -384,7 +429,7 @@ class RabbitMQService:
|
|
|
384
429
|
await asyncio.sleep(1)
|
|
385
430
|
|
|
386
431
|
failed_clients = [
|
|
387
|
-
name for name in required_clients if name not in running_clients]
|
|
432
|
+
name for name in required_clients if name not in running_clients and not cls._is_shutdown]
|
|
388
433
|
if failed_clients:
|
|
389
434
|
logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
|
|
390
435
|
for client_name in failed_clients:
|
|
@@ -397,12 +442,16 @@ class RabbitMQService:
|
|
|
397
442
|
queue_name: str,
|
|
398
443
|
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], ** kwargs
|
|
399
444
|
) -> None:
|
|
400
|
-
"""
|
|
445
|
+
"""添加消息监听器(线程安全)"""
|
|
446
|
+
if cls._is_shutdown:
|
|
447
|
+
logger.warning("服务已关闭,无法添加监听器")
|
|
448
|
+
return
|
|
449
|
+
|
|
401
450
|
if not cls._config:
|
|
402
451
|
raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
|
|
403
452
|
|
|
404
453
|
if queue_name in cls._message_handlers:
|
|
405
|
-
logger.
|
|
454
|
+
logger.info(f"监听器 '{queue_name}' 已存在,跳过重复添加")
|
|
406
455
|
return
|
|
407
456
|
|
|
408
457
|
# 创建并初始化客户端
|
|
@@ -418,15 +467,23 @@ class RabbitMQService:
|
|
|
418
467
|
|
|
419
468
|
@classmethod
|
|
420
469
|
async def start_all_consumers(cls) -> None:
|
|
421
|
-
"""
|
|
470
|
+
"""启动所有已注册的消费者(线程安全)"""
|
|
471
|
+
if cls._is_shutdown:
|
|
472
|
+
logger.warning("服务已关闭,无法启动消费者")
|
|
473
|
+
return
|
|
474
|
+
|
|
422
475
|
for client_name in cls._message_handlers:
|
|
423
476
|
await cls.start_consumer(client_name)
|
|
424
477
|
|
|
425
478
|
@classmethod
|
|
426
479
|
async def start_consumer(cls, client_name: str) -> None:
|
|
427
|
-
"""
|
|
480
|
+
"""启动指定客户端的消费者(适配新客户端的消费API)"""
|
|
481
|
+
if cls._is_shutdown:
|
|
482
|
+
logger.warning("服务已关闭,无法启动消费者")
|
|
483
|
+
return
|
|
484
|
+
|
|
428
485
|
if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
|
|
429
|
-
logger.
|
|
486
|
+
logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
|
|
430
487
|
return
|
|
431
488
|
|
|
432
489
|
if client_name not in cls._clients:
|
|
@@ -439,16 +496,24 @@ class RabbitMQService:
|
|
|
439
496
|
logger.warning(f"未找到客户端 '{client_name}' 的处理器,使用默认处理器")
|
|
440
497
|
handler = cls.default_message_handler
|
|
441
498
|
|
|
442
|
-
#
|
|
443
|
-
client.set_message_handler(handler)
|
|
499
|
+
# 设置消息处理器(适配新客户端的async方法)
|
|
500
|
+
await client.set_message_handler(handler)
|
|
444
501
|
|
|
445
|
-
#
|
|
502
|
+
# 确保客户端已连接(异步检查连接状态)
|
|
446
503
|
start_time = asyncio.get_event_loop().time()
|
|
447
|
-
while not client.is_connected:
|
|
504
|
+
while not await client.is_connected and not cls._is_shutdown:
|
|
448
505
|
if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
|
|
449
506
|
raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
|
|
450
507
|
|
|
451
|
-
logger.
|
|
508
|
+
logger.info(f"等待客户端 '{client_name}' 连接就绪...")
|
|
509
|
+
await asyncio.sleep(1)
|
|
510
|
+
if cls._is_shutdown:
|
|
511
|
+
logger.info("服务关闭中,取消启动消费者")
|
|
512
|
+
return
|
|
513
|
+
|
|
514
|
+
# 监听器启动消费前额外延迟1秒,确保consumer_tag完全生成(不删消息)
|
|
515
|
+
if cls._has_listeners and not client_name.startswith("sender-"):
|
|
516
|
+
logger.info(f"消费者 '{client_name}' 准备启动,延迟1秒等待消费状态就绪(不删除积压消息)")
|
|
452
517
|
await asyncio.sleep(1)
|
|
453
518
|
|
|
454
519
|
# 创建停止事件
|
|
@@ -463,8 +528,22 @@ class RabbitMQService:
|
|
|
463
528
|
attempt = 0
|
|
464
529
|
consumer_tag = None
|
|
465
530
|
|
|
466
|
-
while attempt < max_attempts and not stop_event.is_set():
|
|
531
|
+
while attempt < max_attempts and not stop_event.is_set() and not cls._is_shutdown:
|
|
467
532
|
try:
|
|
533
|
+
# 启动消费前再次校验连接和队列状态
|
|
534
|
+
if not await client.is_connected: # 确保这里没有括号
|
|
535
|
+
logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
|
|
536
|
+
await client.connect()
|
|
537
|
+
|
|
538
|
+
# 确保队列已就绪
|
|
539
|
+
_, _, queue = await client._get_connection_resources()
|
|
540
|
+
if not queue:
|
|
541
|
+
raise Exception("队列未初始化完成")
|
|
542
|
+
|
|
543
|
+
# 确保消息处理器已设置
|
|
544
|
+
if not hasattr(client, '_message_handler') or not client._message_handler:
|
|
545
|
+
raise Exception("消息处理器未设置")
|
|
546
|
+
|
|
468
547
|
consumer_tag = await client.start_consuming()
|
|
469
548
|
if consumer_tag:
|
|
470
549
|
break
|
|
@@ -473,14 +552,19 @@ class RabbitMQService:
|
|
|
473
552
|
logger.warning(
|
|
474
553
|
f"启动消费者尝试 {attempt}/{max_attempts} 失败: {str(e)}")
|
|
475
554
|
if attempt < max_attempts:
|
|
476
|
-
await asyncio.sleep(
|
|
555
|
+
await asyncio.sleep(2) # 延长重试间隔
|
|
556
|
+
|
|
557
|
+
if cls._is_shutdown:
|
|
558
|
+
logger.info("服务关闭中,消费者启动中止")
|
|
559
|
+
return
|
|
477
560
|
|
|
478
561
|
if not consumer_tag:
|
|
479
562
|
raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
|
|
480
563
|
|
|
481
564
|
# 记录消费者标签
|
|
482
565
|
cls._consumer_tags[client_name] = consumer_tag
|
|
483
|
-
logger.info(
|
|
566
|
+
logger.info(
|
|
567
|
+
f"消费者 '{client_name}' 开始消费,tag: {consumer_tag}(将处理队列中所有积压消息)")
|
|
484
568
|
|
|
485
569
|
# 等待停止事件
|
|
486
570
|
await stop_event.wait()
|
|
@@ -492,8 +576,10 @@ class RabbitMQService:
|
|
|
492
576
|
logger.error(
|
|
493
577
|
f"消费者 '{client_name}' 错误: {str(e)}", exc_info=True)
|
|
494
578
|
# 非主动停止时尝试重启
|
|
495
|
-
if not stop_event.is_set():
|
|
579
|
+
if not stop_event.is_set() and not cls._is_shutdown:
|
|
496
580
|
logger.info(f"尝试重启消费者 '{client_name}'")
|
|
581
|
+
# 延迟重启,避免频繁重试
|
|
582
|
+
await asyncio.sleep(3)
|
|
497
583
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
498
584
|
finally:
|
|
499
585
|
# 清理资源
|
|
@@ -502,7 +588,7 @@ class RabbitMQService:
|
|
|
502
588
|
except Exception as e:
|
|
503
589
|
logger.error(f"停止消费者 '{client_name}' 时出错: {str(e)}")
|
|
504
590
|
|
|
505
|
-
#
|
|
591
|
+
# 移除状态记录(线程安全)
|
|
506
592
|
if client_name in cls._consumer_tags:
|
|
507
593
|
del cls._consumer_tags[client_name]
|
|
508
594
|
if client_name in cls._consumer_events:
|
|
@@ -523,7 +609,7 @@ class RabbitMQService:
|
|
|
523
609
|
except Exception as e:
|
|
524
610
|
logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
|
|
525
611
|
# 任务异常时自动重启(如果服务未关闭)
|
|
526
|
-
if client_name in cls._message_handlers:
|
|
612
|
+
if client_name in cls._message_handlers and not cls._is_shutdown:
|
|
527
613
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
528
614
|
|
|
529
615
|
task.add_done_callback(task_done_callback)
|
|
@@ -539,22 +625,50 @@ class RabbitMQService:
|
|
|
539
625
|
logger.info("===================\n")
|
|
540
626
|
|
|
541
627
|
@classmethod
|
|
542
|
-
def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
543
|
-
"""
|
|
628
|
+
async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
629
|
+
"""获取发送客户端(异步检查连接状态)"""
|
|
630
|
+
if cls._is_shutdown:
|
|
631
|
+
logger.warning("服务已关闭,无法获取发送器")
|
|
632
|
+
return None
|
|
633
|
+
|
|
544
634
|
if not queue_name:
|
|
545
635
|
logger.warning("发送器名称不能为空")
|
|
546
636
|
return None
|
|
547
637
|
|
|
548
638
|
# 检查是否在已注册的发送器中
|
|
549
639
|
if queue_name in cls._sender_client_names and queue_name in cls._clients:
|
|
550
|
-
|
|
640
|
+
client = cls._clients[queue_name]
|
|
641
|
+
# 异步检查连接状态
|
|
642
|
+
if await client.is_connected:
|
|
643
|
+
return client
|
|
644
|
+
else:
|
|
645
|
+
logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
|
|
646
|
+
try:
|
|
647
|
+
await client.connect() # 移除declare_queue=False
|
|
648
|
+
if await client.is_connected:
|
|
649
|
+
return client
|
|
650
|
+
except Exception as e:
|
|
651
|
+
logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
|
|
652
|
+
return None
|
|
551
653
|
|
|
552
654
|
# 检查是否带有app-name后缀
|
|
553
655
|
app_name = cls._config.get("APP_NAME", "") if cls._config else ""
|
|
554
|
-
if app_name
|
|
555
|
-
|
|
656
|
+
if app_name:
|
|
657
|
+
suffixed_name = f"{queue_name}.{app_name}"
|
|
658
|
+
if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
|
|
659
|
+
client = cls._clients[suffixed_name]
|
|
660
|
+
if await client.is_connected:
|
|
661
|
+
return client
|
|
662
|
+
else:
|
|
663
|
+
logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
|
|
664
|
+
try:
|
|
665
|
+
await client.connect() # 移除declare_queue=False
|
|
666
|
+
if await client.is_connected:
|
|
667
|
+
return client
|
|
668
|
+
except Exception as e:
|
|
669
|
+
logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
|
|
556
670
|
|
|
557
|
-
logger.
|
|
671
|
+
logger.info(f"未找到可用的发送器 '{queue_name}'")
|
|
558
672
|
return None
|
|
559
673
|
|
|
560
674
|
@classmethod
|
|
@@ -563,28 +677,32 @@ class RabbitMQService:
|
|
|
563
677
|
data: Union[BaseModel, str, Dict[str, Any], None],
|
|
564
678
|
queue_name: str, ** kwargs
|
|
565
679
|
) -> None:
|
|
566
|
-
"""
|
|
567
|
-
|
|
568
|
-
|
|
680
|
+
"""发送消息到指定队列(适配新客户端的publish API)"""
|
|
681
|
+
if cls._is_shutdown:
|
|
682
|
+
raise RuntimeError("RabbitMQService已关闭,无法发送消息")
|
|
683
|
+
|
|
684
|
+
# 获取发送客户端(异步方法)
|
|
685
|
+
sender = await cls.get_sender(queue_name)
|
|
569
686
|
if not sender:
|
|
570
687
|
error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
|
|
571
688
|
logger.error(error_msg)
|
|
572
689
|
raise ValueError(error_msg)
|
|
573
690
|
|
|
574
691
|
# 确保连接有效
|
|
575
|
-
if not sender.is_connected:
|
|
692
|
+
if not await sender.is_connected:
|
|
576
693
|
logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
|
|
577
694
|
max_retry = 3 # 最大重试次数
|
|
578
695
|
retry_count = 0
|
|
579
696
|
last_exception = None
|
|
580
697
|
|
|
581
|
-
while retry_count < max_retry:
|
|
698
|
+
while retry_count < max_retry and not cls._is_shutdown:
|
|
582
699
|
try:
|
|
583
700
|
# 尝试重连,每次重试间隔1秒
|
|
584
|
-
await sender.connect(force_reconnect
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
701
|
+
await sender.connect() # 移除force_reconnect和declare_queue
|
|
702
|
+
if await sender.is_connected:
|
|
703
|
+
logger.info(
|
|
704
|
+
f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
|
|
705
|
+
break # 重连成功则退出循环
|
|
588
706
|
except Exception as e:
|
|
589
707
|
last_exception = e
|
|
590
708
|
retry_count += 1
|
|
@@ -595,7 +713,7 @@ class RabbitMQService:
|
|
|
595
713
|
await asyncio.sleep(1) # 重试前等待1秒
|
|
596
714
|
|
|
597
715
|
# 所有重试都失败则抛出异常
|
|
598
|
-
if retry_count >= max_retry and not sender.is_connected:
|
|
716
|
+
if retry_count >= max_retry and not await sender.is_connected:
|
|
599
717
|
error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
|
|
600
718
|
logger.error(f"{error_msg}: {str(last_exception)}")
|
|
601
719
|
raise Exception(error_msg) from last_exception
|
|
@@ -608,8 +726,7 @@ class RabbitMQService:
|
|
|
608
726
|
elif isinstance(data, BaseModel):
|
|
609
727
|
msg_content = data.model_dump_json()
|
|
610
728
|
elif isinstance(data, dict):
|
|
611
|
-
|
|
612
|
-
msg_content = json.dumps(data)
|
|
729
|
+
msg_content = json.dumps(data, ensure_ascii=False)
|
|
613
730
|
|
|
614
731
|
# 创建标准消息模型
|
|
615
732
|
mq_message = MQMsgModel(
|
|
@@ -636,7 +753,7 @@ class RabbitMQService:
|
|
|
636
753
|
).model_dump_json()
|
|
637
754
|
}
|
|
638
755
|
|
|
639
|
-
#
|
|
756
|
+
# 发送消息(适配新客户端的publish方法)
|
|
640
757
|
await sender.publish(
|
|
641
758
|
message_body=mq_message.model_dump_json(),
|
|
642
759
|
headers=mq_header,
|
|
@@ -648,73 +765,80 @@ class RabbitMQService:
|
|
|
648
765
|
raise
|
|
649
766
|
|
|
650
767
|
@classmethod
|
|
651
|
-
async def shutdown(cls, timeout: float =
|
|
652
|
-
"""
|
|
653
|
-
|
|
654
|
-
|
|
768
|
+
async def shutdown(cls, timeout: float = 15.0) -> None:
|
|
769
|
+
"""优雅关闭所有资源(线程安全,适配新客户端的close API)"""
|
|
770
|
+
async with cls._shutdown_lock:
|
|
771
|
+
if cls._is_shutdown:
|
|
772
|
+
logger.info("RabbitMQService已关闭,无需重复操作")
|
|
773
|
+
return
|
|
774
|
+
|
|
775
|
+
cls._is_shutdown = True
|
|
776
|
+
logger.info("开始关闭RabbitMQ服务...")
|
|
777
|
+
|
|
778
|
+
# 发送停止信号给所有消费者
|
|
779
|
+
for client_name, event in cls._consumer_events.items():
|
|
780
|
+
event.set()
|
|
781
|
+
logger.info(f"已向消费者 '{client_name}' 发送退出信号")
|
|
782
|
+
|
|
783
|
+
# 等待消费者任务完成
|
|
784
|
+
remaining_time = max(
|
|
785
|
+
0.0, timeout - (asyncio.get_event_loop().time() - asyncio.get_event_loop().time()))
|
|
786
|
+
if remaining_time > 0 and cls._consumer_tasks:
|
|
787
|
+
try:
|
|
788
|
+
done, pending = await asyncio.wait(
|
|
789
|
+
list(cls._consumer_tasks.values()),
|
|
790
|
+
timeout=remaining_time,
|
|
791
|
+
return_when=asyncio.ALL_COMPLETED
|
|
792
|
+
)
|
|
655
793
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
794
|
+
# 处理超时的任务
|
|
795
|
+
for task in pending:
|
|
796
|
+
task_name = task.get_name()
|
|
797
|
+
logger.warning(f"任务 '{task_name}' 关闭超时,强制取消")
|
|
798
|
+
task.cancel()
|
|
799
|
+
try:
|
|
800
|
+
await task
|
|
801
|
+
except (asyncio.CancelledError, RuntimeError):
|
|
802
|
+
pass
|
|
660
803
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
0.0, timeout - (asyncio.get_event_loop().time() - start_time))
|
|
664
|
-
if remaining_time > 0 and cls._consumer_tasks:
|
|
665
|
-
try:
|
|
666
|
-
done, pending = await asyncio.wait(
|
|
667
|
-
list(cls._consumer_tasks.values()),
|
|
668
|
-
timeout=remaining_time,
|
|
669
|
-
return_when=asyncio.ALL_COMPLETED
|
|
670
|
-
)
|
|
804
|
+
except Exception as e:
|
|
805
|
+
logger.error(f"等待消费者任务完成时出错: {str(e)}")
|
|
671
806
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
await task
|
|
679
|
-
except (asyncio.CancelledError, RuntimeError):
|
|
680
|
-
pass
|
|
807
|
+
# 关闭所有客户端连接(适配新客户端的async close方法)
|
|
808
|
+
remaining_time = max(
|
|
809
|
+
0.0, timeout - (asyncio.get_event_loop().time() - asyncio.get_event_loop().time()))
|
|
810
|
+
if remaining_time > 0 and cls._clients:
|
|
811
|
+
client_count = len(cls._clients)
|
|
812
|
+
client_timeout = remaining_time / client_count # 平均分配剩余时间
|
|
681
813
|
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
client_count = len(cls._clients)
|
|
690
|
-
client_timeout = remaining_time / client_count # 平均分配剩余时间
|
|
814
|
+
for name, client in cls._clients.items():
|
|
815
|
+
try:
|
|
816
|
+
if await client.is_connected:
|
|
817
|
+
await asyncio.wait_for(client.close(), timeout=client_timeout)
|
|
818
|
+
except Exception as e:
|
|
819
|
+
logger.warning(f"关闭客户端 '{name}' 时出错: {str(e)}")
|
|
820
|
+
logger.info(f"客户端 '{name}' 已关闭")
|
|
691
821
|
|
|
692
|
-
|
|
822
|
+
# 关闭连接池
|
|
823
|
+
if cls._connection_pool:
|
|
693
824
|
try:
|
|
694
|
-
await
|
|
825
|
+
await cls._connection_pool.close()
|
|
826
|
+
logger.info("RabbitMQ连接池已关闭")
|
|
695
827
|
except Exception as e:
|
|
696
|
-
logger.warning(f"
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
cls._consumer_tags.clear()
|
|
714
|
-
cls._initialized_queues.clear()
|
|
715
|
-
cls._init_locks.clear()
|
|
716
|
-
cls._has_listeners = False
|
|
717
|
-
cls._has_senders = False
|
|
718
|
-
cls._connection_pool = None
|
|
719
|
-
|
|
720
|
-
logger.info("RabbitMQ服务已完全关闭")
|
|
828
|
+
logger.warning(f"关闭连接池时出错: {str(e)}")
|
|
829
|
+
|
|
830
|
+
# 清理所有状态
|
|
831
|
+
cls._clients.clear()
|
|
832
|
+
cls._consumer_tasks.clear()
|
|
833
|
+
cls._message_handlers.clear()
|
|
834
|
+
cls._sender_client_names.clear()
|
|
835
|
+
cls._consumer_events.clear()
|
|
836
|
+
cls._consumer_tags.clear()
|
|
837
|
+
cls._initialized_queues.clear()
|
|
838
|
+
cls._init_locks.clear()
|
|
839
|
+
cls._has_listeners = False
|
|
840
|
+
cls._has_senders = False
|
|
841
|
+
cls._connection_pool = None
|
|
842
|
+
cls._config = None
|
|
843
|
+
|
|
844
|
+
logger.info("RabbitMQ服务已完全关闭")
|