sycommon-python-lib 0.1.16__py3-none-any.whl → 0.1.56b1__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/config/Config.py +6 -2
- sycommon/config/RerankerConfig.py +1 -0
- sycommon/database/async_base_db_service.py +36 -0
- sycommon/database/async_database_service.py +96 -0
- sycommon/database/database_service.py +6 -1
- sycommon/health/metrics.py +13 -0
- sycommon/llm/__init__.py +0 -0
- sycommon/llm/embedding.py +149 -0
- sycommon/llm/get_llm.py +177 -0
- sycommon/llm/llm_logger.py +126 -0
- sycommon/logging/async_sql_logger.py +65 -0
- sycommon/logging/kafka_log.py +36 -14
- sycommon/logging/logger_levels.py +23 -0
- sycommon/logging/sql_logger.py +53 -0
- sycommon/middleware/context.py +2 -0
- sycommon/middleware/middleware.py +4 -0
- sycommon/middleware/traceid.py +155 -32
- sycommon/models/mqlistener_config.py +1 -0
- sycommon/rabbitmq/rabbitmq_client.py +377 -821
- sycommon/rabbitmq/rabbitmq_pool.py +338 -0
- sycommon/rabbitmq/rabbitmq_service.py +411 -229
- sycommon/services.py +116 -61
- sycommon/synacos/example.py +153 -0
- sycommon/synacos/example2.py +129 -0
- sycommon/synacos/feign.py +90 -413
- sycommon/synacos/feign_client.py +335 -0
- sycommon/synacos/nacos_service.py +159 -106
- sycommon/synacos/param.py +75 -0
- sycommon/tools/merge_headers.py +97 -0
- sycommon/tools/snowflake.py +296 -7
- {sycommon_python_lib-0.1.16.dist-info → sycommon_python_lib-0.1.56b1.dist-info}/METADATA +19 -13
- sycommon_python_lib-0.1.56b1.dist-info/RECORD +68 -0
- sycommon_python_lib-0.1.16.dist-info/RECORD +0 -52
- {sycommon_python_lib-0.1.16.dist-info → sycommon_python_lib-0.1.56b1.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.16.dist-info → sycommon_python_lib-0.1.56b1.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.16.dist-info → sycommon_python_lib-0.1.56b1.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
import
|
|
2
|
+
import json
|
|
3
3
|
from typing import (
|
|
4
|
-
Callable, Coroutine, Dict, List, Optional, Type, Union, Any
|
|
4
|
+
Callable, Coroutine, Dict, List, Optional, Type, Union, Any
|
|
5
5
|
)
|
|
6
6
|
from pydantic import BaseModel
|
|
7
7
|
from aio_pika.abc import AbstractIncomingMessage, ConsumerTag
|
|
@@ -11,21 +11,22 @@ from sycommon.models.mqlistener_config import RabbitMQListenerConfig
|
|
|
11
11
|
from sycommon.models.mqsend_config import RabbitMQSendConfig
|
|
12
12
|
from sycommon.models.sso_user import SsoUser
|
|
13
13
|
from sycommon.logging.kafka_log import SYLogger
|
|
14
|
-
from .rabbitmq_client import RabbitMQClient
|
|
14
|
+
from sycommon.rabbitmq.rabbitmq_client import RabbitMQClient, RabbitMQConnectionPool
|
|
15
15
|
|
|
16
16
|
logger = SYLogger
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class RabbitMQService:
|
|
20
20
|
"""
|
|
21
|
-
RabbitMQ
|
|
22
|
-
|
|
21
|
+
RabbitMQ服务封装,管理多个客户端实例(适配单通道连接池)
|
|
22
|
+
核心特性:基于单通道连接池复用资源,简化状态管理,依赖原生自动重连
|
|
23
23
|
"""
|
|
24
24
|
|
|
25
|
-
#
|
|
25
|
+
# 保存多个客户端实例(共享单通道,仅维护配置差异)
|
|
26
26
|
_clients: Dict[str, RabbitMQClient] = {}
|
|
27
27
|
# 保存消息处理器
|
|
28
|
-
_message_handlers: Dict[str, Callable
|
|
28
|
+
_message_handlers: Dict[str, Callable[[
|
|
29
|
+
MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]]] = {}
|
|
29
30
|
# 保存消费者任务
|
|
30
31
|
_consumer_tasks: Dict[str, asyncio.Task] = {}
|
|
31
32
|
# 保存配置信息
|
|
@@ -36,8 +37,6 @@ class RabbitMQService:
|
|
|
36
37
|
_consumer_events: Dict[str, asyncio.Event] = {}
|
|
37
38
|
# 存储消费者标签
|
|
38
39
|
_consumer_tags: Dict[str, ConsumerTag] = {}
|
|
39
|
-
# 跟踪已初始化的队列
|
|
40
|
-
_initialized_queues: Set[str] = set()
|
|
41
40
|
# 异步锁,确保初始化安全
|
|
42
41
|
_init_locks: Dict[str, asyncio.Lock] = {}
|
|
43
42
|
# 标记是否有监听器和发送器
|
|
@@ -45,14 +44,27 @@ class RabbitMQService:
|
|
|
45
44
|
_has_senders: bool = False
|
|
46
45
|
# 消费启动超时设置
|
|
47
46
|
CONSUMER_START_TIMEOUT = 30 # 30秒超时
|
|
47
|
+
# 连接池实例(单通道)
|
|
48
|
+
_connection_pool: Optional[RabbitMQConnectionPool] = None
|
|
49
|
+
# 服务关闭标记
|
|
50
|
+
_is_shutdown: bool = False
|
|
51
|
+
# 服务关闭锁
|
|
52
|
+
_shutdown_lock = asyncio.Lock()
|
|
53
|
+
# 连接状态监控任务
|
|
54
|
+
_connection_monitor_task: Optional[asyncio.Task] = None
|
|
55
|
+
# 重连配置
|
|
56
|
+
RECONNECT_INTERVAL = 15 # 重连基础间隔(秒)
|
|
48
57
|
|
|
49
58
|
@classmethod
|
|
50
59
|
def init(cls, config: dict, has_listeners: bool = False, has_senders: bool = False) -> Type['RabbitMQService']:
|
|
51
|
-
"""
|
|
52
|
-
初始化RabbitMQ服务(支持集群配置)
|
|
53
|
-
"""
|
|
60
|
+
"""初始化RabbitMQ服务(支持集群配置),同时创建单通道连接池"""
|
|
54
61
|
from sycommon.synacos.nacos_service import NacosService
|
|
55
62
|
|
|
63
|
+
# 防止重复初始化
|
|
64
|
+
if cls._config:
|
|
65
|
+
logger.warning("RabbitMQService已初始化,无需重复调用")
|
|
66
|
+
return cls
|
|
67
|
+
|
|
56
68
|
# 获取MQ配置
|
|
57
69
|
cls._config = NacosService(config).share_configs.get(
|
|
58
70
|
"mq.yml", {}).get('spring', {}).get('rabbitmq', {})
|
|
@@ -63,24 +75,169 @@ class RabbitMQService:
|
|
|
63
75
|
f"RabbitMQ服务初始化 - 集群节点: {cls._config.get('host')}, "
|
|
64
76
|
f"端口: {cls._config.get('port')}, "
|
|
65
77
|
f"虚拟主机: {cls._config.get('virtual-host')}, "
|
|
66
|
-
f"应用名: {cls._config.get('APP_NAME')}"
|
|
78
|
+
f"应用名: {cls._config.get('APP_NAME')}, "
|
|
79
|
+
f"心跳: {cls._config.get('heartbeat', 30)}s"
|
|
67
80
|
)
|
|
68
81
|
|
|
69
82
|
# 保存发送器和监听器存在状态
|
|
70
83
|
cls._has_listeners = has_listeners
|
|
71
84
|
cls._has_senders = has_senders
|
|
85
|
+
cls._is_shutdown = False
|
|
86
|
+
|
|
87
|
+
# 初始化连接池(在单独的异步方法中启动)
|
|
88
|
+
asyncio.create_task(cls._init_connection_pool())
|
|
89
|
+
|
|
90
|
+
# 启动连接监控任务(监听连接状态,自动重连)
|
|
91
|
+
cls._connection_monitor_task = asyncio.create_task(
|
|
92
|
+
cls._monitor_connections())
|
|
72
93
|
|
|
73
94
|
return cls
|
|
74
95
|
|
|
75
96
|
@classmethod
|
|
76
|
-
async def
|
|
77
|
-
"""
|
|
78
|
-
|
|
79
|
-
|
|
97
|
+
async def _init_connection_pool(cls):
|
|
98
|
+
"""初始化单通道连接池(异步操作,带重试)"""
|
|
99
|
+
if cls._connection_pool or not cls._config or cls._is_shutdown:
|
|
100
|
+
return
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
# 解析集群节点
|
|
104
|
+
hosts_str = cls._config.get('host', "")
|
|
105
|
+
hosts_list = [host.strip()
|
|
106
|
+
for host in hosts_str.split(',') if host.strip()]
|
|
107
|
+
if not hosts_list:
|
|
108
|
+
raise ValueError("RabbitMQ集群配置为空,请检查host参数")
|
|
109
|
+
|
|
110
|
+
global_prefetch_count = cls._config.get('prefetch_count', 2)
|
|
111
|
+
|
|
112
|
+
# 创建单通道连接池(已移除channel_pool_size参数)
|
|
113
|
+
cls._connection_pool = RabbitMQConnectionPool(
|
|
114
|
+
hosts=hosts_list,
|
|
115
|
+
port=cls._config.get('port', 5672),
|
|
116
|
+
username=cls._config.get('username', ""),
|
|
117
|
+
password=cls._config.get('password', ""),
|
|
118
|
+
virtualhost=cls._config.get('virtual-host', "/"),
|
|
119
|
+
app_name=cls._config.get("APP_NAME", ""),
|
|
120
|
+
prefetch_count=global_prefetch_count,
|
|
121
|
+
heartbeat=cls._config.get('heartbeat', 30),
|
|
122
|
+
connection_timeout=cls._config.get('connection_timeout', 30),
|
|
123
|
+
reconnect_interval=cls._config.get('reconnect_interval', 5),
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
# 初始化连接池
|
|
127
|
+
await asyncio.wait_for(cls._connection_pool.init_pools(), timeout=30)
|
|
128
|
+
logger.info("RabbitMQ单通道连接池初始化成功")
|
|
129
|
+
|
|
130
|
+
except Exception as e:
|
|
131
|
+
logger.error(f"RabbitMQ连接池初始化失败: {str(e)}", exc_info=True)
|
|
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
|
+
pool_alive = await cls._connection_pool.is_alive
|
|
151
|
+
if not pool_alive:
|
|
152
|
+
logger.error("RabbitMQ连接池已断开,等待原生自动重连")
|
|
153
|
+
continue
|
|
154
|
+
|
|
155
|
+
# 检查所有客户端连接(单通道场景下,客户端状态依赖通道有效性)
|
|
156
|
+
for client_name, client in list(cls._clients.items()):
|
|
157
|
+
try:
|
|
158
|
+
client_connected = await client.is_connected
|
|
159
|
+
if not client_connected:
|
|
160
|
+
logger.warning(
|
|
161
|
+
f"客户端 '{client_name}' 连接异常,触发重连")
|
|
162
|
+
asyncio.create_task(
|
|
163
|
+
cls._reconnect_client(client_name, client))
|
|
164
|
+
except Exception as e:
|
|
165
|
+
logger.error(
|
|
166
|
+
f"监控客户端 '{client_name}' 连接状态失败: {str(e)}", exc_info=True)
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
logger.error("RabbitMQ连接监控任务异常", exc_info=True)
|
|
170
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL) # 异常后延迟重启监控
|
|
171
|
+
|
|
172
|
+
logger.info("RabbitMQ连接监控任务停止")
|
|
173
|
+
|
|
174
|
+
@classmethod
|
|
175
|
+
async def _clean_client_resources(cls, client: RabbitMQClient):
|
|
176
|
+
"""清理客户端无效资源(单通道场景下仅重置状态,无需归还通道)"""
|
|
177
|
+
try:
|
|
178
|
+
# 先停止消费(避免消费中操作资源)
|
|
179
|
+
if client._consumer_tag:
|
|
180
|
+
await client.stop_consuming()
|
|
181
|
+
logger.debug("客户端无效资源清理完成(单通道无需归还)")
|
|
182
|
+
except Exception as e:
|
|
183
|
+
logger.warning(f"释放客户端无效资源失败: {str(e)}")
|
|
184
|
+
finally:
|
|
185
|
+
# 强制重置客户端状态(通道由连接池自动恢复)
|
|
186
|
+
client._channel = None
|
|
187
|
+
client._channel_conn = None
|
|
188
|
+
client._exchange = None
|
|
189
|
+
client._queue = None
|
|
190
|
+
client._consumer_tag = None
|
|
191
|
+
|
|
192
|
+
@classmethod
|
|
193
|
+
async def _reconnect_client(cls, client_name: str, client: RabbitMQClient) -> bool:
|
|
194
|
+
"""客户端重连(依赖连接池原生重连,仅重建客户端资源)"""
|
|
195
|
+
if cls._is_shutdown or not await cls._connection_pool.is_alive:
|
|
196
|
+
return False
|
|
197
|
+
|
|
198
|
+
# 重连冷却
|
|
199
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
200
|
+
|
|
201
|
+
try:
|
|
202
|
+
# 清理旧资源
|
|
203
|
+
await cls._clean_client_resources(client)
|
|
204
|
+
|
|
205
|
+
# 执行重连(客户端内部会从连接池获取新通道)
|
|
206
|
+
await client.connect()
|
|
207
|
+
|
|
208
|
+
# 验证重连结果
|
|
209
|
+
if await client.is_connected:
|
|
210
|
+
logger.info(f"客户端 '{client_name}' 重连成功")
|
|
211
|
+
# 如果是消费者,重新启动消费
|
|
212
|
+
if client_name in cls._message_handlers:
|
|
213
|
+
await cls.start_consumer(client_name)
|
|
214
|
+
return True
|
|
215
|
+
else:
|
|
216
|
+
logger.warning(f"客户端 '{client_name}' 重连失败:资源未完全初始化")
|
|
217
|
+
return False
|
|
218
|
+
except Exception as e:
|
|
219
|
+
logger.error(f"客户端 '{client_name}' 重连失败: {str(e)}", exc_info=True)
|
|
220
|
+
# 重连失败后,由监控任务再次触发(避免死循环)
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
@classmethod
|
|
224
|
+
async def _create_client(cls, queue_name: str, **kwargs) -> RabbitMQClient:
|
|
225
|
+
"""创建客户端实例(适配单通道连接池的RabbitMQClient)"""
|
|
226
|
+
if cls._is_shutdown:
|
|
227
|
+
raise RuntimeError("RabbitMQService已关闭,无法创建客户端")
|
|
228
|
+
|
|
229
|
+
if not cls._connection_pool or not cls._connection_pool._initialized:
|
|
230
|
+
# 等待连接池初始化
|
|
231
|
+
start_time = asyncio.get_event_loop().time()
|
|
232
|
+
while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
|
|
233
|
+
if asyncio.get_event_loop().time() - start_time > 30:
|
|
234
|
+
raise TimeoutError("等待连接池初始化超时")
|
|
235
|
+
await asyncio.sleep(1)
|
|
236
|
+
if cls._is_shutdown:
|
|
237
|
+
raise RuntimeError("服务关闭中,取消创建客户端")
|
|
238
|
+
|
|
80
239
|
app_name = kwargs.get('app_name', cls._config.get(
|
|
81
240
|
"APP_NAME", "")) if cls._config else ""
|
|
82
|
-
|
|
83
|
-
# 确定是否为发送器
|
|
84
241
|
is_sender = not cls._has_listeners
|
|
85
242
|
|
|
86
243
|
# 根据组件类型决定是否允许创建队列
|
|
@@ -95,25 +252,15 @@ class RabbitMQService:
|
|
|
95
252
|
else:
|
|
96
253
|
logger.info(f"监听器队列已包含app-name: {processed_queue_name}")
|
|
97
254
|
|
|
98
|
-
logger.
|
|
255
|
+
logger.info(
|
|
99
256
|
f"创建客户端 - 队列: {processed_queue_name}, 发送器: {is_sender}, "
|
|
100
257
|
f"允许创建: {create_if_not_exists}"
|
|
101
258
|
)
|
|
102
259
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if not hosts_list:
|
|
108
|
-
raise ValueError("RabbitMQ集群配置为空,请检查host参数")
|
|
109
|
-
|
|
110
|
-
return RabbitMQClient(
|
|
111
|
-
hosts=hosts_list,
|
|
112
|
-
port=mq_config.get('port', 5672),
|
|
113
|
-
username=mq_config.get('username', ""),
|
|
114
|
-
password=mq_config.get('password', ""),
|
|
115
|
-
virtualhost=mq_config.get('virtual-host', "/"),
|
|
116
|
-
exchange_name=mq_config.get(
|
|
260
|
+
# 创建客户端实例(适配精简版RabbitMQClient参数)
|
|
261
|
+
client = RabbitMQClient(
|
|
262
|
+
connection_pool=cls._connection_pool,
|
|
263
|
+
exchange_name=cls._config.get(
|
|
117
264
|
'exchange_name', "system.topic.exchange"),
|
|
118
265
|
exchange_type=kwargs.get('exchange_type', "topic"),
|
|
119
266
|
queue_name=processed_queue_name,
|
|
@@ -123,33 +270,36 @@ class RabbitMQService:
|
|
|
123
270
|
auto_delete=kwargs.get('auto_delete', False),
|
|
124
271
|
auto_parse_json=kwargs.get('auto_parse_json', True),
|
|
125
272
|
create_if_not_exists=create_if_not_exists,
|
|
126
|
-
connection_timeout=kwargs.get('connection_timeout', 10),
|
|
127
|
-
rpc_timeout=kwargs.get('rpc_timeout', 5),
|
|
128
|
-
app_name=app_name,
|
|
129
|
-
reconnection_delay=kwargs.get('reconnection_delay', 1),
|
|
130
|
-
max_reconnection_attempts=kwargs.get(
|
|
131
|
-
'max_reconnection_attempts', 5),
|
|
132
|
-
heartbeat=kwargs.get('heartbeat', 10),
|
|
133
273
|
prefetch_count=kwargs.get('prefetch_count', 2),
|
|
134
|
-
consumption_stall_threshold=kwargs.get(
|
|
135
|
-
'consumption_stall_threshold', 10)
|
|
136
274
|
)
|
|
137
275
|
|
|
276
|
+
# 连接客户端(单通道场景下快速连接)
|
|
277
|
+
await client.connect()
|
|
278
|
+
|
|
279
|
+
return client
|
|
280
|
+
|
|
138
281
|
@classmethod
|
|
139
282
|
async def get_client(
|
|
140
283
|
cls,
|
|
141
|
-
client_name: str = "default", **
|
|
284
|
+
client_name: str = "default", **kwargs
|
|
142
285
|
) -> RabbitMQClient:
|
|
143
|
-
"""
|
|
144
|
-
|
|
286
|
+
"""获取或创建RabbitMQ客户端(单通道池,线程安全)"""
|
|
287
|
+
if cls._is_shutdown:
|
|
288
|
+
raise RuntimeError("RabbitMQService已关闭,无法获取客户端")
|
|
145
289
|
|
|
146
|
-
:param client_name: 客户端名称
|
|
147
|
-
:param kwargs: 客户端参数
|
|
148
|
-
:return: RabbitMQClient实例
|
|
149
|
-
"""
|
|
150
290
|
if not cls._config:
|
|
151
291
|
raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
|
|
152
292
|
|
|
293
|
+
# 等待连接池就绪
|
|
294
|
+
if not cls._connection_pool or not cls._connection_pool._initialized:
|
|
295
|
+
start_time = asyncio.get_event_loop().time()
|
|
296
|
+
while not (cls._connection_pool and cls._connection_pool._initialized) and not cls._is_shutdown:
|
|
297
|
+
if asyncio.get_event_loop().time() - start_time > 30:
|
|
298
|
+
raise TimeoutError("等待连接池初始化超时")
|
|
299
|
+
await asyncio.sleep(1)
|
|
300
|
+
if cls._is_shutdown:
|
|
301
|
+
raise RuntimeError("服务关闭中,取消获取客户端")
|
|
302
|
+
|
|
153
303
|
# 确保锁存在
|
|
154
304
|
if client_name not in cls._init_locks:
|
|
155
305
|
cls._init_locks[client_name] = asyncio.Lock()
|
|
@@ -161,18 +311,18 @@ class RabbitMQService:
|
|
|
161
311
|
is_sender = not cls._has_listeners or (
|
|
162
312
|
not kwargs.get('create_if_not_exists', True))
|
|
163
313
|
|
|
164
|
-
if client.is_connected:
|
|
314
|
+
if await client.is_connected:
|
|
165
315
|
# 如果是监听器但队列未初始化,重新连接
|
|
166
|
-
if not is_sender and not client.
|
|
167
|
-
logger.
|
|
316
|
+
if not is_sender and not client._queue:
|
|
317
|
+
logger.info(f"客户端 '{client_name}' 队列未初始化,重新连接")
|
|
168
318
|
client.create_if_not_exists = True
|
|
169
|
-
await client.connect(
|
|
319
|
+
await client.connect()
|
|
170
320
|
return client
|
|
171
321
|
else:
|
|
172
|
-
logger.
|
|
322
|
+
logger.info(f"客户端 '{client_name}' 连接已断开,重新连接")
|
|
173
323
|
if not is_sender:
|
|
174
324
|
client.create_if_not_exists = True
|
|
175
|
-
await client.connect(
|
|
325
|
+
await client.connect()
|
|
176
326
|
return client
|
|
177
327
|
|
|
178
328
|
# 创建新客户端
|
|
@@ -184,66 +334,42 @@ class RabbitMQService:
|
|
|
184
334
|
if is_sender:
|
|
185
335
|
kwargs['create_if_not_exists'] = False
|
|
186
336
|
client = await cls._create_client(
|
|
187
|
-
cls._config,
|
|
188
337
|
initial_queue_name,
|
|
189
338
|
app_name=cls._config.get("APP_NAME", ""),
|
|
190
339
|
**kwargs
|
|
191
340
|
)
|
|
192
|
-
await client.connect(declare_queue=False)
|
|
193
341
|
cls._clients[client_name] = client
|
|
194
342
|
return client
|
|
195
343
|
|
|
196
|
-
#
|
|
344
|
+
# 监听器逻辑(单通道支持多队列声明,无需跟踪已初始化队列)
|
|
197
345
|
kwargs['create_if_not_exists'] = True
|
|
198
346
|
|
|
199
|
-
# 检查队列是否已初始化
|
|
200
|
-
if initial_queue_name in cls._initialized_queues:
|
|
201
|
-
logger.debug(f"队列 '{initial_queue_name}' 已初始化,直接创建客户端")
|
|
202
|
-
client = await cls._create_client(
|
|
203
|
-
cls._config,
|
|
204
|
-
initial_queue_name, ** kwargs
|
|
205
|
-
)
|
|
206
|
-
await client.connect(declare_queue=True)
|
|
207
|
-
cls._clients[client_name] = client
|
|
208
|
-
return client
|
|
209
|
-
|
|
210
347
|
# 创建并连接客户端
|
|
211
348
|
client = await cls._create_client(
|
|
212
|
-
cls._config,
|
|
213
349
|
initial_queue_name,
|
|
214
350
|
app_name=cls._config.get("APP_NAME", ""),
|
|
215
351
|
**kwargs
|
|
216
352
|
)
|
|
217
353
|
|
|
218
|
-
client.create_if_not_exists = True
|
|
219
|
-
await client.connect(declare_queue=True)
|
|
220
|
-
|
|
221
354
|
# 验证队列是否创建成功
|
|
222
|
-
if not client.
|
|
355
|
+
if not client._queue:
|
|
223
356
|
logger.error(f"队列 '{initial_queue_name}' 创建失败,尝试重新创建")
|
|
224
357
|
client.create_if_not_exists = True
|
|
225
|
-
await client.connect(
|
|
226
|
-
if not client.
|
|
358
|
+
await client.connect()
|
|
359
|
+
if not client._queue:
|
|
227
360
|
raise Exception(f"无法创建队列 '{initial_queue_name}'")
|
|
228
361
|
|
|
229
|
-
# 记录已初始化的队列
|
|
230
|
-
final_queue_name = client.queue_name
|
|
231
|
-
if final_queue_name:
|
|
232
|
-
cls._initialized_queues.add(final_queue_name)
|
|
233
|
-
|
|
234
362
|
cls._clients[client_name] = client
|
|
235
363
|
return client
|
|
236
364
|
|
|
237
365
|
@classmethod
|
|
238
|
-
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False) -> None:
|
|
239
|
-
"""
|
|
240
|
-
|
|
366
|
+
async def setup_senders(cls, senders: List[RabbitMQSendConfig], has_listeners: bool = False, **kwargs) -> None:
|
|
367
|
+
"""设置消息发送器(适配单通道客户端)"""
|
|
368
|
+
if cls._is_shutdown:
|
|
369
|
+
logger.warning("服务已关闭,无法设置发送器")
|
|
370
|
+
return
|
|
241
371
|
|
|
242
|
-
:param senders: 发送器配置列表
|
|
243
|
-
:param has_listeners: 是否同时存在监听器
|
|
244
|
-
"""
|
|
245
372
|
cls._has_senders = True
|
|
246
|
-
# 保存监听器存在状态
|
|
247
373
|
cls._has_listeners = has_listeners
|
|
248
374
|
logger.info(f"开始设置 {len(senders)} 个消息发送器")
|
|
249
375
|
|
|
@@ -252,6 +378,7 @@ class RabbitMQService:
|
|
|
252
378
|
if not sender_config.queue_name:
|
|
253
379
|
raise ValueError(f"发送器配置第{idx+1}项缺少queue_name")
|
|
254
380
|
|
|
381
|
+
prefetch_count = sender_config.prefetch_count
|
|
255
382
|
queue_name = sender_config.queue_name
|
|
256
383
|
app_name = cls._config.get(
|
|
257
384
|
"APP_NAME", "") if cls._config else ""
|
|
@@ -260,18 +387,18 @@ class RabbitMQService:
|
|
|
260
387
|
normalized_name = queue_name
|
|
261
388
|
if app_name and normalized_name.endswith(f".{app_name}"):
|
|
262
389
|
normalized_name = normalized_name[:-len(f".{app_name}")]
|
|
263
|
-
logger.
|
|
390
|
+
logger.info(f"发送器队列名称移除app-name后缀: {normalized_name}")
|
|
264
391
|
|
|
265
392
|
# 检查是否已初始化
|
|
266
393
|
if normalized_name in cls._sender_client_names:
|
|
267
|
-
logger.
|
|
394
|
+
logger.info(f"发送客户端 '{normalized_name}' 已存在,跳过")
|
|
268
395
|
continue
|
|
269
396
|
|
|
270
397
|
# 获取或创建客户端
|
|
271
398
|
if normalized_name in cls._clients:
|
|
272
399
|
client = cls._clients[normalized_name]
|
|
273
|
-
if not client.is_connected:
|
|
274
|
-
await client.connect(
|
|
400
|
+
if not await client.is_connected:
|
|
401
|
+
await client.connect()
|
|
275
402
|
else:
|
|
276
403
|
client = await cls.get_client(
|
|
277
404
|
client_name=normalized_name,
|
|
@@ -280,7 +407,9 @@ class RabbitMQService:
|
|
|
280
407
|
auto_delete=sender_config.auto_delete,
|
|
281
408
|
auto_parse_json=sender_config.auto_parse_json,
|
|
282
409
|
queue_name=queue_name,
|
|
283
|
-
create_if_not_exists=False
|
|
410
|
+
create_if_not_exists=False,
|
|
411
|
+
prefetch_count=prefetch_count,
|
|
412
|
+
**kwargs
|
|
284
413
|
)
|
|
285
414
|
|
|
286
415
|
# 记录客户端
|
|
@@ -299,15 +428,13 @@ class RabbitMQService:
|
|
|
299
428
|
logger.info(f"消息发送器设置完成,共 {len(cls._sender_client_names)} 个发送器")
|
|
300
429
|
|
|
301
430
|
@classmethod
|
|
302
|
-
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False) -> None:
|
|
303
|
-
"""
|
|
304
|
-
|
|
431
|
+
async def setup_listeners(cls, listeners: List[RabbitMQListenerConfig], has_senders: bool = False, **kwargs) -> None:
|
|
432
|
+
"""设置消息监听器(适配单通道客户端)"""
|
|
433
|
+
if cls._is_shutdown:
|
|
434
|
+
logger.warning("服务已关闭,无法设置监听器")
|
|
435
|
+
return
|
|
305
436
|
|
|
306
|
-
:param listeners: 监听器配置列表
|
|
307
|
-
:param has_senders: 是否同时存在发送器
|
|
308
|
-
"""
|
|
309
437
|
cls._has_listeners = True
|
|
310
|
-
# 保存发送器存在状态
|
|
311
438
|
cls._has_senders = has_senders
|
|
312
439
|
logger.info(f"开始设置 {len(listeners)} 个消息监听器")
|
|
313
440
|
|
|
@@ -316,9 +443,11 @@ class RabbitMQService:
|
|
|
316
443
|
# 转换配置并强制设置create_if_not_exists为True
|
|
317
444
|
listener_dict = listener_config.model_dump()
|
|
318
445
|
listener_dict['create_if_not_exists'] = True
|
|
446
|
+
listener_dict['prefetch_count'] = listener_config.prefetch_count
|
|
319
447
|
queue_name = listener_dict['queue_name']
|
|
320
448
|
|
|
321
|
-
logger.info(
|
|
449
|
+
logger.info(
|
|
450
|
+
f"设置监听器 {idx+1}/{len(listeners)}: {queue_name} (prefetch_count: {listener_config.prefetch_count})")
|
|
322
451
|
|
|
323
452
|
# 添加监听器
|
|
324
453
|
await cls.add_listener(**listener_dict)
|
|
@@ -337,18 +466,14 @@ class RabbitMQService:
|
|
|
337
466
|
|
|
338
467
|
@classmethod
|
|
339
468
|
async def _verify_consumers_started(cls, timeout: int = 30) -> None:
|
|
340
|
-
"""
|
|
341
|
-
验证消费者是否成功启动
|
|
342
|
-
|
|
343
|
-
:param timeout: 超时时间(秒)
|
|
344
|
-
"""
|
|
469
|
+
"""验证消费者是否成功启动"""
|
|
345
470
|
start_time = asyncio.get_event_loop().time()
|
|
346
471
|
required_clients = list(cls._message_handlers.keys())
|
|
347
472
|
running_clients = []
|
|
348
473
|
|
|
349
|
-
# 等待所有消费者启动或超时
|
|
350
474
|
while len(running_clients) < len(required_clients) and \
|
|
351
|
-
(asyncio.get_event_loop().time() - start_time) < timeout
|
|
475
|
+
(asyncio.get_event_loop().time() - start_time) < timeout and \
|
|
476
|
+
not cls._is_shutdown:
|
|
352
477
|
|
|
353
478
|
running_clients = [
|
|
354
479
|
name for name, task in cls._consumer_tasks.items()
|
|
@@ -357,14 +482,12 @@ class RabbitMQService:
|
|
|
357
482
|
|
|
358
483
|
logger.info(
|
|
359
484
|
f"消费者启动验证: {len(running_clients)}/{len(required_clients)} 已启动")
|
|
360
|
-
await asyncio.sleep(
|
|
485
|
+
await asyncio.sleep(1)
|
|
361
486
|
|
|
362
|
-
# 检查未成功启动的消费者
|
|
363
487
|
failed_clients = [
|
|
364
|
-
name for name in required_clients if name not in running_clients]
|
|
488
|
+
name for name in required_clients if name not in running_clients and not cls._is_shutdown]
|
|
365
489
|
if failed_clients:
|
|
366
490
|
logger.error(f"以下消费者启动失败: {', '.join(failed_clients)}")
|
|
367
|
-
# 尝试重新启动失败的消费者
|
|
368
491
|
for client_name in failed_clients:
|
|
369
492
|
logger.info(f"尝试重新启动消费者: {client_name}")
|
|
370
493
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
@@ -373,20 +496,18 @@ class RabbitMQService:
|
|
|
373
496
|
async def add_listener(
|
|
374
497
|
cls,
|
|
375
498
|
queue_name: str,
|
|
376
|
-
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **
|
|
499
|
+
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]], **kwargs
|
|
377
500
|
) -> None:
|
|
378
|
-
"""
|
|
379
|
-
|
|
501
|
+
"""添加消息监听器(线程安全,单通道场景)"""
|
|
502
|
+
if cls._is_shutdown:
|
|
503
|
+
logger.warning("服务已关闭,无法添加监听器")
|
|
504
|
+
return
|
|
380
505
|
|
|
381
|
-
:param queue_name: 队列名称
|
|
382
|
-
:param handler: 消息处理函数
|
|
383
|
-
:param kwargs: 其他参数
|
|
384
|
-
"""
|
|
385
506
|
if not cls._config:
|
|
386
507
|
raise ValueError("RabbitMQService尚未初始化,请先调用init方法")
|
|
387
508
|
|
|
388
509
|
if queue_name in cls._message_handlers:
|
|
389
|
-
logger.
|
|
510
|
+
logger.info(f"监听器 '{queue_name}' 已存在,跳过重复添加")
|
|
390
511
|
return
|
|
391
512
|
|
|
392
513
|
# 创建并初始化客户端
|
|
@@ -402,21 +523,35 @@ class RabbitMQService:
|
|
|
402
523
|
|
|
403
524
|
@classmethod
|
|
404
525
|
async def start_all_consumers(cls) -> None:
|
|
405
|
-
"""
|
|
526
|
+
"""启动所有已注册的消费者(单通道场景,避免阻塞)"""
|
|
527
|
+
if cls._is_shutdown:
|
|
528
|
+
logger.warning("服务已关闭,无法启动消费者")
|
|
529
|
+
return
|
|
530
|
+
|
|
406
531
|
for client_name in cls._message_handlers:
|
|
407
532
|
await cls.start_consumer(client_name)
|
|
408
533
|
|
|
409
534
|
@classmethod
|
|
410
535
|
async def start_consumer(cls, client_name: str) -> None:
|
|
411
|
-
"""
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
:param client_name: 客户端名称
|
|
415
|
-
"""
|
|
416
|
-
if client_name in cls._consumer_tasks and not cls._consumer_tasks[client_name].done():
|
|
417
|
-
logger.debug(f"消费者 '{client_name}' 已在运行中,无需重复启动")
|
|
536
|
+
"""启动指定客户端的消费者(单通道消费,需确保回调非阻塞)"""
|
|
537
|
+
if cls._is_shutdown:
|
|
538
|
+
logger.warning("服务已关闭,无法启动消费者")
|
|
418
539
|
return
|
|
419
540
|
|
|
541
|
+
# 检查任务状态,避免重复创建
|
|
542
|
+
if client_name in cls._consumer_tasks:
|
|
543
|
+
existing_task = cls._consumer_tasks[client_name]
|
|
544
|
+
if not existing_task.done():
|
|
545
|
+
# 检查任务是否处于异常状态,仅在异常时重启
|
|
546
|
+
if existing_task.exception() is not None:
|
|
547
|
+
logger.info(f"消费者 '{client_name}' 任务异常,重启")
|
|
548
|
+
existing_task.cancel()
|
|
549
|
+
else:
|
|
550
|
+
logger.info(f"消费者 '{client_name}' 已在运行中,无需重复启动")
|
|
551
|
+
return
|
|
552
|
+
else:
|
|
553
|
+
logger.info(f"消费者 '{client_name}' 任务已完成,重新启动")
|
|
554
|
+
|
|
420
555
|
if client_name not in cls._clients:
|
|
421
556
|
raise ValueError(f"RabbitMQ客户端 '{client_name}' 未初始化")
|
|
422
557
|
|
|
@@ -428,31 +563,44 @@ class RabbitMQService:
|
|
|
428
563
|
handler = cls.default_message_handler
|
|
429
564
|
|
|
430
565
|
# 设置消息处理器
|
|
431
|
-
client.set_message_handler(handler)
|
|
566
|
+
await client.set_message_handler(handler)
|
|
432
567
|
|
|
433
568
|
# 确保客户端已连接
|
|
434
569
|
start_time = asyncio.get_event_loop().time()
|
|
435
|
-
while not client.is_connected:
|
|
570
|
+
while not await client.is_connected and not cls._is_shutdown:
|
|
436
571
|
if asyncio.get_event_loop().time() - start_time > cls.CONSUMER_START_TIMEOUT:
|
|
437
572
|
raise TimeoutError(f"等待客户端 '{client_name}' 连接超时")
|
|
438
573
|
|
|
439
|
-
logger.
|
|
574
|
+
logger.info(f"等待客户端 '{client_name}' 连接就绪...")
|
|
440
575
|
await asyncio.sleep(1)
|
|
576
|
+
if cls._is_shutdown:
|
|
577
|
+
return
|
|
441
578
|
|
|
442
579
|
# 创建停止事件
|
|
443
580
|
stop_event = asyncio.Event()
|
|
444
581
|
cls._consumer_events[client_name] = stop_event
|
|
445
582
|
|
|
446
|
-
#
|
|
583
|
+
# 定义消费任务(单通道场景下,消费回调需非阻塞)
|
|
447
584
|
async def consume_task():
|
|
448
585
|
try:
|
|
449
586
|
# 启动消费,带重试机制
|
|
450
|
-
max_attempts =
|
|
587
|
+
max_attempts = 3
|
|
451
588
|
attempt = 0
|
|
452
589
|
consumer_tag = None
|
|
453
590
|
|
|
454
|
-
while attempt < max_attempts and not stop_event.is_set():
|
|
591
|
+
while attempt < max_attempts and not stop_event.is_set() and not cls._is_shutdown:
|
|
455
592
|
try:
|
|
593
|
+
# 启动消费前再次校验连接和队列状态
|
|
594
|
+
if not await client.is_connected:
|
|
595
|
+
logger.info(f"消费者 '{client_name}' 连接断开,尝试重连")
|
|
596
|
+
await client.connect()
|
|
597
|
+
|
|
598
|
+
# 确保队列和处理器已就绪
|
|
599
|
+
if not client._queue:
|
|
600
|
+
raise Exception("队列未初始化完成")
|
|
601
|
+
if not client._message_handler:
|
|
602
|
+
raise Exception("消息处理器未设置")
|
|
603
|
+
|
|
456
604
|
consumer_tag = await client.start_consuming()
|
|
457
605
|
if consumer_tag:
|
|
458
606
|
break
|
|
@@ -463,12 +611,18 @@ class RabbitMQService:
|
|
|
463
611
|
if attempt < max_attempts:
|
|
464
612
|
await asyncio.sleep(2)
|
|
465
613
|
|
|
614
|
+
if cls._is_shutdown:
|
|
615
|
+
return
|
|
616
|
+
|
|
466
617
|
if not consumer_tag:
|
|
467
618
|
raise Exception(f"经过 {max_attempts} 次尝试仍无法启动消费者")
|
|
468
619
|
|
|
469
620
|
# 记录消费者标签
|
|
470
621
|
cls._consumer_tags[client_name] = consumer_tag
|
|
471
|
-
logger.info(
|
|
622
|
+
logger.info(
|
|
623
|
+
f"消费者 '{client_name}' 开始消费(单通道),tag: {consumer_tag},"
|
|
624
|
+
f"注意:消费回调需非阻塞,避免影响其他客户端"
|
|
625
|
+
)
|
|
472
626
|
|
|
473
627
|
# 等待停止事件
|
|
474
628
|
await stop_event.wait()
|
|
@@ -480,8 +634,9 @@ class RabbitMQService:
|
|
|
480
634
|
logger.error(
|
|
481
635
|
f"消费者 '{client_name}' 错误: {str(e)}", exc_info=True)
|
|
482
636
|
# 非主动停止时尝试重启
|
|
483
|
-
if not stop_event.is_set():
|
|
637
|
+
if not stop_event.is_set() and not cls._is_shutdown:
|
|
484
638
|
logger.info(f"尝试重启消费者 '{client_name}'")
|
|
639
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
485
640
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
486
641
|
finally:
|
|
487
642
|
# 清理资源
|
|
@@ -510,8 +665,7 @@ class RabbitMQService:
|
|
|
510
665
|
t.result()
|
|
511
666
|
except Exception as e:
|
|
512
667
|
logger.error(f"消费者任务 '{client_name}' 异常结束: {str(e)}")
|
|
513
|
-
|
|
514
|
-
if client_name in cls._message_handlers: # 检查处理器是否仍存在
|
|
668
|
+
if client_name in cls._message_handlers and not cls._is_shutdown:
|
|
515
669
|
asyncio.create_task(cls.start_consumer(client_name))
|
|
516
670
|
|
|
517
671
|
task.add_done_callback(task_done_callback)
|
|
@@ -527,57 +681,93 @@ class RabbitMQService:
|
|
|
527
681
|
logger.info("===================\n")
|
|
528
682
|
|
|
529
683
|
@classmethod
|
|
530
|
-
def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
531
|
-
"""
|
|
532
|
-
|
|
684
|
+
async def get_sender(cls, queue_name: str) -> Optional[RabbitMQClient]:
|
|
685
|
+
"""获取发送客户端(适配单通道池)"""
|
|
686
|
+
if cls._is_shutdown:
|
|
687
|
+
logger.warning("服务已关闭,无法获取发送器")
|
|
688
|
+
return None
|
|
533
689
|
|
|
534
|
-
:param queue_name: 队列名称
|
|
535
|
-
:return: RabbitMQClient实例或None
|
|
536
|
-
"""
|
|
537
690
|
if not queue_name:
|
|
538
691
|
logger.warning("发送器名称不能为空")
|
|
539
692
|
return None
|
|
540
693
|
|
|
541
694
|
# 检查是否在已注册的发送器中
|
|
542
695
|
if queue_name in cls._sender_client_names and queue_name in cls._clients:
|
|
543
|
-
|
|
696
|
+
client = cls._clients[queue_name]
|
|
697
|
+
if await client.is_connected:
|
|
698
|
+
return client
|
|
699
|
+
else:
|
|
700
|
+
logger.info(f"发送器 '{queue_name}' 连接已断开,尝试重连")
|
|
701
|
+
try:
|
|
702
|
+
await client.connect()
|
|
703
|
+
if await client.is_connected:
|
|
704
|
+
return client
|
|
705
|
+
except Exception as e:
|
|
706
|
+
logger.error(f"发送器 '{queue_name}' 重连失败: {str(e)}")
|
|
707
|
+
return None
|
|
544
708
|
|
|
545
709
|
# 检查是否带有app-name后缀
|
|
546
710
|
app_name = cls._config.get("APP_NAME", "") if cls._config else ""
|
|
547
|
-
if app_name
|
|
548
|
-
|
|
711
|
+
if app_name:
|
|
712
|
+
suffixed_name = f"{queue_name}.{app_name}"
|
|
713
|
+
if suffixed_name in cls._sender_client_names and suffixed_name in cls._clients:
|
|
714
|
+
client = cls._clients[suffixed_name]
|
|
715
|
+
if await client.is_connected:
|
|
716
|
+
return client
|
|
717
|
+
else:
|
|
718
|
+
logger.info(f"发送器 '{suffixed_name}' 连接已断开,尝试重连")
|
|
719
|
+
try:
|
|
720
|
+
await client.connect()
|
|
721
|
+
if await client.is_connected:
|
|
722
|
+
return client
|
|
723
|
+
except Exception as e:
|
|
724
|
+
logger.error(f"发送器 '{suffixed_name}' 重连失败: {str(e)}")
|
|
549
725
|
|
|
550
|
-
logger.
|
|
726
|
+
logger.info(f"未找到可用的发送器 '{queue_name}'")
|
|
551
727
|
return None
|
|
552
728
|
|
|
553
729
|
@classmethod
|
|
554
730
|
async def send_message(
|
|
555
731
|
cls,
|
|
556
732
|
data: Union[BaseModel, str, Dict[str, Any], None],
|
|
557
|
-
queue_name: str, **
|
|
733
|
+
queue_name: str, **kwargs
|
|
558
734
|
) -> None:
|
|
559
|
-
"""
|
|
560
|
-
|
|
735
|
+
"""发送消息到指定队列(单通道场景下快速发送)"""
|
|
736
|
+
if cls._is_shutdown:
|
|
737
|
+
raise RuntimeError("RabbitMQService已关闭,无法发送消息")
|
|
561
738
|
|
|
562
|
-
:param data: 消息数据
|
|
563
|
-
:param queue_name: 队列名称
|
|
564
|
-
:param kwargs: 其他参数
|
|
565
|
-
"""
|
|
566
739
|
# 获取发送客户端
|
|
567
|
-
sender = cls.get_sender(queue_name)
|
|
740
|
+
sender = await cls.get_sender(queue_name)
|
|
568
741
|
if not sender:
|
|
569
742
|
error_msg = f"未找到可用的RabbitMQ发送器 (queue_name: {queue_name})"
|
|
570
743
|
logger.error(error_msg)
|
|
571
744
|
raise ValueError(error_msg)
|
|
572
745
|
|
|
573
746
|
# 确保连接有效
|
|
574
|
-
if not sender.is_connected:
|
|
747
|
+
if not await sender.is_connected:
|
|
575
748
|
logger.info(f"发送器 '{queue_name}' 连接已关闭,尝试重新连接")
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
749
|
+
max_retry = 3
|
|
750
|
+
retry_count = 0
|
|
751
|
+
last_exception = None
|
|
752
|
+
|
|
753
|
+
while retry_count < max_retry and not cls._is_shutdown:
|
|
754
|
+
try:
|
|
755
|
+
await sender.connect()
|
|
756
|
+
if await sender.is_connected:
|
|
757
|
+
logger.info(
|
|
758
|
+
f"发送器 '{queue_name}' 第 {retry_count + 1} 次重连成功")
|
|
759
|
+
break
|
|
760
|
+
except Exception as e:
|
|
761
|
+
last_exception = e
|
|
762
|
+
retry_count += 1
|
|
763
|
+
logger.warning(
|
|
764
|
+
f"发送器 '{queue_name}' 第 {retry_count} 次重连失败: {str(e)}")
|
|
765
|
+
await asyncio.sleep(cls.RECONNECT_INTERVAL)
|
|
766
|
+
|
|
767
|
+
if retry_count >= max_retry and not await sender.is_connected:
|
|
768
|
+
error_msg = f"发送器 '{queue_name}' 经过 {max_retry} 次重连仍失败"
|
|
769
|
+
logger.error(f"{error_msg}: {str(last_exception)}")
|
|
770
|
+
raise Exception(error_msg) from last_exception
|
|
581
771
|
|
|
582
772
|
try:
|
|
583
773
|
# 处理消息数据
|
|
@@ -587,19 +777,18 @@ class RabbitMQService:
|
|
|
587
777
|
elif isinstance(data, BaseModel):
|
|
588
778
|
msg_content = data.model_dump_json()
|
|
589
779
|
elif isinstance(data, dict):
|
|
590
|
-
|
|
591
|
-
msg_content = json.dumps(data)
|
|
780
|
+
msg_content = json.dumps(data, ensure_ascii=False)
|
|
592
781
|
|
|
593
782
|
# 创建标准消息模型
|
|
594
783
|
mq_message = MQMsgModel(
|
|
595
784
|
topicCode=queue_name.split('.')[0] if queue_name else "",
|
|
596
785
|
msg=msg_content,
|
|
597
786
|
correlationDataId=kwargs.get(
|
|
598
|
-
'correlationDataId',
|
|
787
|
+
'correlationDataId', logger.get_trace_id()),
|
|
599
788
|
groupId=kwargs.get('groupId', ''),
|
|
600
789
|
dataKey=kwargs.get('dataKey', ""),
|
|
601
790
|
manualFlag=kwargs.get('manualFlag', False),
|
|
602
|
-
traceId=
|
|
791
|
+
traceId=logger.get_trace_id()
|
|
603
792
|
)
|
|
604
793
|
|
|
605
794
|
# 构建消息头
|
|
@@ -611,11 +800,11 @@ class RabbitMQService:
|
|
|
611
800
|
user_name="SYSTEM",
|
|
612
801
|
request_path="",
|
|
613
802
|
req_type="SYSTEM",
|
|
614
|
-
trace_id=
|
|
803
|
+
trace_id=logger.get_trace_id(),
|
|
615
804
|
).model_dump_json()
|
|
616
805
|
}
|
|
617
806
|
|
|
618
|
-
#
|
|
807
|
+
# 发送消息(单通道场景下依赖原生异步确认)
|
|
619
808
|
await sender.publish(
|
|
620
809
|
message_body=mq_message.model_dump_json(),
|
|
621
810
|
headers=mq_header,
|
|
@@ -627,69 +816,62 @@ class RabbitMQService:
|
|
|
627
816
|
raise
|
|
628
817
|
|
|
629
818
|
@classmethod
|
|
630
|
-
async def shutdown(cls, timeout: float =
|
|
631
|
-
"""
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
for task in pending:
|
|
658
|
-
task_name = task.get_name()
|
|
659
|
-
logger.warning(f"任务 '{task_name}' 关闭超时,强制取消")
|
|
819
|
+
async def shutdown(cls, timeout: float = 15.0) -> None:
|
|
820
|
+
"""优雅关闭所有资源(单通道场景下简化关闭流程)"""
|
|
821
|
+
async with cls._shutdown_lock:
|
|
822
|
+
if cls._is_shutdown:
|
|
823
|
+
logger.info("RabbitMQService已关闭,无需重复操作")
|
|
824
|
+
return
|
|
825
|
+
|
|
826
|
+
cls._is_shutdown = True
|
|
827
|
+
logger.info("开始关闭RabbitMQ服务...")
|
|
828
|
+
|
|
829
|
+
# 1. 停止连接监控任务
|
|
830
|
+
if cls._connection_monitor_task and not cls._connection_monitor_task.done():
|
|
831
|
+
cls._connection_monitor_task.cancel()
|
|
832
|
+
try:
|
|
833
|
+
await asyncio.wait_for(cls._connection_monitor_task, timeout=timeout)
|
|
834
|
+
except asyncio.TimeoutError:
|
|
835
|
+
logger.warning("连接监控任务关闭超时")
|
|
836
|
+
except Exception as e:
|
|
837
|
+
logger.error(f"关闭连接监控任务失败: {str(e)}")
|
|
838
|
+
|
|
839
|
+
# 2. 停止所有消费者任务
|
|
840
|
+
for client_name, task in cls._consumer_tasks.items():
|
|
841
|
+
if not task.done():
|
|
842
|
+
# 触发停止事件
|
|
843
|
+
if client_name in cls._consumer_events:
|
|
844
|
+
cls._consumer_events[client_name].set()
|
|
845
|
+
# 取消任务
|
|
660
846
|
task.cancel()
|
|
661
847
|
try:
|
|
662
|
-
await task
|
|
663
|
-
except
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
except Exception as e:
|
|
667
|
-
logger.error(f"等待消费者任务完成时出错: {str(e)}")
|
|
848
|
+
await asyncio.wait_for(task, timeout=timeout)
|
|
849
|
+
except Exception as e:
|
|
850
|
+
logger.error(f"关闭消费者 '{client_name}' 失败: {str(e)}")
|
|
668
851
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
852
|
+
# 3. 关闭所有客户端(单通道场景下客户端关闭仅清理状态)
|
|
853
|
+
for client in cls._clients.values():
|
|
854
|
+
try:
|
|
855
|
+
await client.close()
|
|
856
|
+
except Exception as e:
|
|
857
|
+
logger.error(f"关闭客户端失败: {str(e)}")
|
|
675
858
|
|
|
676
|
-
|
|
859
|
+
# 4. 关闭连接池(单通道池关闭会释放所有资源)
|
|
860
|
+
if cls._connection_pool and cls._connection_pool._initialized:
|
|
677
861
|
try:
|
|
678
|
-
await
|
|
862
|
+
await cls._connection_pool.close()
|
|
863
|
+
logger.info("RabbitMQ单通道连接池已关闭")
|
|
679
864
|
except Exception as e:
|
|
680
|
-
logger.
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
cls._has_senders = False
|
|
694
|
-
|
|
695
|
-
logger.info("RabbitMQ服务已完全关闭")
|
|
865
|
+
logger.error(f"关闭连接池失败: {str(e)}")
|
|
866
|
+
|
|
867
|
+
# 5. 清理状态
|
|
868
|
+
cls._clients.clear()
|
|
869
|
+
cls._message_handlers.clear()
|
|
870
|
+
cls._consumer_tasks.clear()
|
|
871
|
+
cls._consumer_events.clear()
|
|
872
|
+
cls._consumer_tags.clear()
|
|
873
|
+
cls._sender_client_names.clear()
|
|
874
|
+
cls._init_locks.clear()
|
|
875
|
+
cls._config = None
|
|
876
|
+
|
|
877
|
+
logger.info("RabbitMQService已完全关闭")
|