sycommon-python-lib 0.1.21__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.
- sycommon/database/database_service.py +6 -1
- sycommon/logging/kafka_log.py +18 -8
- sycommon/logging/sql_logger.py +53 -0
- sycommon/middleware/middleware.py +1 -1
- sycommon/middleware/traceid.py +1 -1
- sycommon/models/mqlistener_config.py +1 -0
- sycommon/rabbitmq/rabbitmq_client.py +393 -600
- sycommon/rabbitmq/rabbitmq_pool.py +548 -66
- sycommon/rabbitmq/rabbitmq_service.py +381 -174
- sycommon/services.py +65 -22
- sycommon/synacos/example.py +153 -0
- sycommon/synacos/example2.py +129 -0
- sycommon/synacos/feign.py +76 -410
- sycommon/synacos/feign_client.py +317 -0
- sycommon/synacos/nacos_service.py +146 -105
- sycommon/synacos/param.py +75 -0
- {sycommon_python_lib-0.1.21.dist-info → sycommon_python_lib-0.1.46.dist-info}/METADATA +11 -11
- {sycommon_python_lib-0.1.21.dist-info → sycommon_python_lib-0.1.46.dist-info}/RECORD +21 -16
- {sycommon_python_lib-0.1.21.dist-info → sycommon_python_lib-0.1.46.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.21.dist-info → sycommon_python_lib-0.1.46.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.21.dist-info → sycommon_python_lib-0.1.46.dist-info}/top_level.txt +0 -0
|
@@ -1,30 +1,33 @@
|
|
|
1
|
+
from aio_pika import Channel
|
|
2
|
+
from typing import Optional
|
|
1
3
|
import asyncio
|
|
2
4
|
import json
|
|
3
|
-
from typing import Callable, Coroutine,
|
|
5
|
+
from typing import Callable, Coroutine, Dict, Any, Union
|
|
4
6
|
from aio_pika import Message, DeliveryMode, ExchangeType
|
|
5
7
|
from aio_pika.abc import (
|
|
6
|
-
AbstractChannel,
|
|
7
8
|
AbstractExchange,
|
|
8
9
|
AbstractQueue,
|
|
9
10
|
AbstractIncomingMessage,
|
|
10
11
|
ConsumerTag,
|
|
12
|
+
AbstractRobustConnection,
|
|
11
13
|
)
|
|
12
|
-
from
|
|
13
|
-
|
|
14
|
+
from sycommon.rabbitmq.rabbitmq_pool import RabbitMQConnectionPool
|
|
14
15
|
from sycommon.logging.kafka_log import SYLogger
|
|
15
16
|
from sycommon.models.mqmsg_model import MQMsgModel
|
|
16
|
-
from sycommon.rabbitmq.rabbitmq_pool import RabbitMQConnectionPool
|
|
17
17
|
|
|
18
|
-
# 最大重试次数限制
|
|
19
|
-
MAX_RETRY_COUNT = 3
|
|
20
18
|
|
|
21
19
|
logger = SYLogger
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
class RabbitMQClient:
|
|
25
23
|
"""
|
|
26
|
-
RabbitMQ
|
|
27
|
-
|
|
24
|
+
RabbitMQ 客户端(支持消息发布、消费、自动重连、异常重试)
|
|
25
|
+
核心特性:
|
|
26
|
+
1. 基于连接池复用资源,性能优化
|
|
27
|
+
2. 连接/通道失效时自动重建,高可用(限制并发重连)
|
|
28
|
+
3. 消息发布支持重试+mandatory机制+超时控制,确保路由有效
|
|
29
|
+
4. 消费支持手动ACK/NACK
|
|
30
|
+
5. 兼容JSON/字符串/字典消息格式
|
|
28
31
|
"""
|
|
29
32
|
|
|
30
33
|
def __init__(
|
|
@@ -38,642 +41,432 @@ class RabbitMQClient:
|
|
|
38
41
|
auto_delete: bool = False,
|
|
39
42
|
auto_parse_json: bool = True,
|
|
40
43
|
create_if_not_exists: bool = True,
|
|
41
|
-
connection_timeout: int = 10,
|
|
42
|
-
rpc_timeout: int = 10,
|
|
43
|
-
reconnection_delay: int = 1,
|
|
44
|
-
max_reconnection_attempts: int = 5,
|
|
45
44
|
prefetch_count: int = 2,
|
|
46
|
-
|
|
45
|
+
**kwargs,
|
|
47
46
|
):
|
|
48
|
-
|
|
49
|
-
初始化RabbitMQ客户端(依赖连接池)
|
|
50
|
-
|
|
51
|
-
:param connection_pool: 连接池实例
|
|
52
|
-
"""
|
|
53
|
-
# 连接池依赖
|
|
47
|
+
# 依赖注入:连接池(必须已初始化)
|
|
54
48
|
self.connection_pool = connection_pool
|
|
49
|
+
if not self.connection_pool._initialized:
|
|
50
|
+
raise RuntimeError("连接池未初始化,请先调用 connection_pool.init_pools()")
|
|
55
51
|
|
|
56
|
-
#
|
|
57
|
-
self.exchange_name = exchange_name
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
#
|
|
65
|
-
self.
|
|
66
|
-
self.
|
|
67
|
-
self.
|
|
68
|
-
self.
|
|
69
|
-
self.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
self.
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
self.
|
|
80
|
-
self.exchange: Optional[AbstractExchange] = None
|
|
81
|
-
self.queue: Optional[AbstractQueue] = None
|
|
82
|
-
|
|
83
|
-
# 状态跟踪
|
|
84
|
-
self.actual_queue_name: Optional[str] = None
|
|
85
|
-
self._exchange_exists = False
|
|
86
|
-
self._queue_exists = False
|
|
87
|
-
self._queue_bound = False
|
|
88
|
-
self._is_consuming = False
|
|
89
|
-
self._closed = False
|
|
52
|
+
# 交换机配置
|
|
53
|
+
self.exchange_name = exchange_name.strip()
|
|
54
|
+
try:
|
|
55
|
+
self.exchange_type = ExchangeType(exchange_type.lower())
|
|
56
|
+
except ValueError:
|
|
57
|
+
SYLogger.warning(f"无效的exchange_type: {exchange_type},默认使用'topic'")
|
|
58
|
+
self.exchange_type = ExchangeType.topic
|
|
59
|
+
|
|
60
|
+
# 队列配置
|
|
61
|
+
self.queue_name = queue_name.strip() if queue_name else None
|
|
62
|
+
self.routing_key = routing_key.strip() if routing_key else "#"
|
|
63
|
+
self.durable = durable # 消息/队列持久化
|
|
64
|
+
self.auto_delete = auto_delete # 无消费者时自动删除队列/交换机
|
|
65
|
+
self.auto_parse_json = auto_parse_json # 自动解析JSON消息体
|
|
66
|
+
self.create_if_not_exists = create_if_not_exists # 不存在则创建交换机/队列
|
|
67
|
+
|
|
68
|
+
# 消费配置
|
|
69
|
+
self.prefetch_count = max(1, prefetch_count) # 每次预取消息数(避免消息堆积)
|
|
70
|
+
|
|
71
|
+
# 内部状态(资源+连接)
|
|
72
|
+
self._channel: Optional[Channel] = None
|
|
73
|
+
self._channel_conn: Optional[AbstractRobustConnection] = None # 通道所属连接
|
|
74
|
+
self._exchange: Optional[AbstractExchange] = None
|
|
75
|
+
self._queue: Optional[AbstractQueue] = None
|
|
90
76
|
self._consumer_tag: Optional[ConsumerTag] = None
|
|
91
|
-
self.
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# 任务和处理器
|
|
95
|
-
self.message_handler: Optional[Callable[
|
|
96
|
-
[Union[Dict[str, Any], str], AbstractIncomingMessage],
|
|
97
|
-
Coroutine[Any, Any, None]
|
|
98
|
-
]] = None
|
|
99
|
-
self._consuming_task: Optional[asyncio.Task] = None
|
|
100
|
-
self._reconnect_task: Optional[asyncio.Task] = None
|
|
101
|
-
self._keepalive_task: Optional[asyncio.Task] = None
|
|
102
|
-
self._monitor_task: Optional[asyncio.Task] = None
|
|
103
|
-
|
|
104
|
-
# 消息处理跟踪
|
|
105
|
-
self._processing_message_ids: Set[str] = set()
|
|
77
|
+
self._message_handler: Optional[Callable[[
|
|
78
|
+
MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]]] = None
|
|
79
|
+
self._closed = False
|
|
106
80
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
self.
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
self.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"""从通道池获取通道(使用上下文管理器)"""
|
|
125
|
-
if not self.connection_pool.channel_pool:
|
|
126
|
-
raise Exception("连接池未初始化,请先调用init_pools")
|
|
127
|
-
|
|
128
|
-
# 使用async with获取通道,并通过变量返回
|
|
129
|
-
async with self.connection_pool.channel_pool.acquire() as channel:
|
|
130
|
-
return channel
|
|
131
|
-
|
|
132
|
-
async def _check_exchange_exists(self, channel: AbstractChannel) -> bool:
|
|
133
|
-
"""检查交换机是否存在"""
|
|
134
|
-
try:
|
|
135
|
-
# 使用被动模式检查交换机
|
|
136
|
-
await asyncio.wait_for(
|
|
137
|
-
channel.declare_exchange(
|
|
138
|
-
name=self.exchange_name,
|
|
139
|
-
type=self.exchange_type,
|
|
140
|
-
passive=True
|
|
141
|
-
),
|
|
142
|
-
timeout=self.rpc_timeout
|
|
143
|
-
)
|
|
144
|
-
return True
|
|
145
|
-
except Exception:
|
|
146
|
-
return False
|
|
81
|
+
# 线程安全锁
|
|
82
|
+
self._consume_lock = asyncio.Lock()
|
|
83
|
+
self._connect_lock = asyncio.Lock()
|
|
84
|
+
# 跟踪连接关闭回调(用于后续移除)
|
|
85
|
+
self._conn_close_callback: Optional[Callable] = None
|
|
86
|
+
# 控制重连频率的信号量(限制并发重连数,默认1个)
|
|
87
|
+
self._reconnect_semaphore = asyncio.Semaphore(1)
|
|
88
|
+
# 固定重连间隔15秒(全局统一)
|
|
89
|
+
self._RECONNECT_INTERVAL = 15
|
|
90
|
+
# 重连任务锁(确保同一时间只有一个重连任务)
|
|
91
|
+
self._reconnect_task_lock = asyncio.Lock()
|
|
92
|
+
# 跟踪当前重连任务(避免重复创建)
|
|
93
|
+
self._current_reconnect_task: Optional[asyncio.Task] = None
|
|
94
|
+
# 连接失败计数器(用于告警)
|
|
95
|
+
self._reconnect_fail_count = 0
|
|
96
|
+
# 连接失败告警阈值
|
|
97
|
+
self._reconnect_alert_threshold = 5
|
|
147
98
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
99
|
+
@property
|
|
100
|
+
async def is_connected(self) -> bool:
|
|
101
|
+
"""异步检查客户端连接状态(属性,不可调用)"""
|
|
102
|
+
if self._closed:
|
|
151
103
|
return False
|
|
152
104
|
try:
|
|
153
|
-
#
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
159
|
-
timeout=self.rpc_timeout
|
|
105
|
+
# 校验通道+连接+核心资源都有效
|
|
106
|
+
return (
|
|
107
|
+
self._channel and not self._channel.is_closed
|
|
108
|
+
and self._channel_conn and not self._channel_conn.is_closed
|
|
109
|
+
and self._exchange is not None
|
|
110
|
+
and (not self.queue_name or self._queue is not None)
|
|
160
111
|
)
|
|
161
|
-
|
|
162
|
-
|
|
112
|
+
except Exception as e:
|
|
113
|
+
SYLogger.warning(f"检查连接状态失败: {str(e)}")
|
|
163
114
|
return False
|
|
164
115
|
|
|
165
|
-
async def
|
|
166
|
-
|
|
167
|
-
|
|
116
|
+
async def connect(self) -> None:
|
|
117
|
+
if self._closed:
|
|
118
|
+
raise RuntimeError("客户端已关闭,无法重新连接")
|
|
168
119
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if attempt < MAX_RETRY_COUNT:
|
|
185
|
-
await asyncio.sleep(1)
|
|
186
|
-
return False
|
|
187
|
-
|
|
188
|
-
async def connect(self, force_reconnect: bool = False, declare_queue: bool = True) -> None:
|
|
189
|
-
"""
|
|
190
|
-
从连接池获取资源并初始化(交换机、队列)
|
|
191
|
-
"""
|
|
192
|
-
logger.debug(
|
|
193
|
-
f"连接参数 - force_reconnect={force_reconnect}, "
|
|
194
|
-
f"declare_queue={declare_queue}, create_if_not_exists={self.create_if_not_exists}"
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
# 如果已连接且不强制重连,则直接返回
|
|
198
|
-
if self.is_connected and not force_reconnect:
|
|
199
|
-
return
|
|
200
|
-
|
|
201
|
-
# 取消正在进行的重连任务
|
|
202
|
-
if self._reconnect_task and not self._reconnect_task.done():
|
|
203
|
-
self._reconnect_task.cancel()
|
|
204
|
-
|
|
205
|
-
# 重置状态
|
|
206
|
-
self._exchange_exists = False
|
|
207
|
-
self._queue_exists = False
|
|
208
|
-
self._queue_bound = False
|
|
209
|
-
|
|
210
|
-
retries = 0
|
|
211
|
-
last_exception = None
|
|
120
|
+
async with self._connect_lock:
|
|
121
|
+
# 释放旧资源(保留原有回调清理逻辑)
|
|
122
|
+
if self._channel and self._channel_conn:
|
|
123
|
+
try:
|
|
124
|
+
if self._conn_close_callback and self._channel_conn:
|
|
125
|
+
self._channel_conn.close_callbacks.discard(
|
|
126
|
+
self._conn_close_callback)
|
|
127
|
+
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
128
|
+
except Exception as e:
|
|
129
|
+
SYLogger.warning(f"释放旧通道失败: {str(e)}")
|
|
130
|
+
self._channel = None
|
|
131
|
+
self._channel_conn = None
|
|
132
|
+
self._exchange = None
|
|
133
|
+
self._queue = None
|
|
134
|
+
self._conn_close_callback = None
|
|
212
135
|
|
|
213
|
-
while retries < self.max_reconnection_attempts:
|
|
214
136
|
try:
|
|
215
|
-
#
|
|
216
|
-
self.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
logger.info(f"已创建队列 '{self.queue_name}'")
|
|
263
|
-
else:
|
|
264
|
-
# 获取已有队列
|
|
265
|
-
self.queue = await self.channel.get_queue(self.queue_name)
|
|
266
|
-
self.actual_queue_name = self.queue_name
|
|
267
|
-
logger.info(f"使用已存在的队列 '{self.queue_name}'")
|
|
268
|
-
|
|
137
|
+
# 从连接池获取通道+连接(连接池返回的是 RobustChannel)
|
|
138
|
+
self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
|
|
139
|
+
|
|
140
|
+
def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
|
|
141
|
+
"""连接关闭回调:触发固定间隔重连"""
|
|
142
|
+
SYLogger.warning(
|
|
143
|
+
f"客户端连接关闭: {conn!r},原因: {exc}", exc_info=exc)
|
|
144
|
+
self._reconnect_fail_count += 1
|
|
145
|
+
# 超过阈值告警
|
|
146
|
+
if self._reconnect_fail_count >= self._reconnect_alert_threshold:
|
|
147
|
+
SYLogger.error(
|
|
148
|
+
f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
|
|
149
|
+
if not self._closed:
|
|
150
|
+
asyncio.create_task(self._safe_reconnect())
|
|
151
|
+
|
|
152
|
+
self._conn_close_callback = on_conn_closed
|
|
153
|
+
if self._channel_conn:
|
|
154
|
+
self._channel_conn.close_callbacks.add(
|
|
155
|
+
self._conn_close_callback)
|
|
156
|
+
|
|
157
|
+
# 2. 设置预取计数(限流)
|
|
158
|
+
await self._channel.set_qos(prefetch_count=self.prefetch_count)
|
|
159
|
+
SYLogger.debug(f"设置预取计数: {self.prefetch_count}")
|
|
160
|
+
|
|
161
|
+
# 3. 低版本 RobustChannel 说明:默认启用异步发布确认,无显式确认方法
|
|
162
|
+
SYLogger.debug(
|
|
163
|
+
"基于 RobustChannel 异步发布确认(低版本 aio-pika 不支持显式确认方法)")
|
|
164
|
+
|
|
165
|
+
# 4. 声明交换机
|
|
166
|
+
self._exchange = await self._channel.declare_exchange(
|
|
167
|
+
name=self.exchange_name,
|
|
168
|
+
type=self.exchange_type,
|
|
169
|
+
durable=self.durable,
|
|
170
|
+
auto_delete=self.auto_delete,
|
|
171
|
+
passive=not self.create_if_not_exists, # passive=True时,不存在则报错
|
|
172
|
+
)
|
|
173
|
+
SYLogger.info(
|
|
174
|
+
f"交换机初始化成功: {self.exchange_name}(类型: {self.exchange_type.value})")
|
|
175
|
+
|
|
176
|
+
# 5. 声明队列(如果配置了队列名)
|
|
177
|
+
if self.queue_name:
|
|
178
|
+
self._queue = await self._channel.declare_queue(
|
|
179
|
+
name=self.queue_name,
|
|
180
|
+
durable=self.durable,
|
|
181
|
+
auto_delete=self.auto_delete,
|
|
182
|
+
passive=not self.create_if_not_exists,
|
|
183
|
+
)
|
|
269
184
|
# 绑定队列到交换机
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
logger.debug(f"跳过队列 '{self.queue_name}' 的声明和绑定")
|
|
279
|
-
|
|
280
|
-
# 验证连接状态
|
|
281
|
-
if not self.is_connected:
|
|
282
|
-
raise Exception("连接验证失败,状态异常")
|
|
283
|
-
|
|
284
|
-
# 如果之前在消费,重新开始消费
|
|
285
|
-
if self._is_consuming and self.message_handler:
|
|
286
|
-
await self.start_consuming()
|
|
287
|
-
|
|
288
|
-
# 启动连接监控和保活任务
|
|
289
|
-
self._start_monitoring()
|
|
290
|
-
self._start_keepalive()
|
|
291
|
-
|
|
292
|
-
self._update_activity_timestamp()
|
|
293
|
-
logger.info(f"RabbitMQ客户端初始化成功 (队列: {self.actual_queue_name})")
|
|
294
|
-
return
|
|
185
|
+
await self._queue.bind(
|
|
186
|
+
exchange=self._exchange,
|
|
187
|
+
routing_key=self.routing_key,
|
|
188
|
+
)
|
|
189
|
+
SYLogger.info(
|
|
190
|
+
f"队列初始化成功: {self.queue_name} "
|
|
191
|
+
f"(绑定交换机: {self.exchange_name}, routing_key: {self.routing_key})"
|
|
192
|
+
)
|
|
295
193
|
|
|
194
|
+
# 重连成功,重置失败计数器
|
|
195
|
+
self._reconnect_fail_count = 0
|
|
196
|
+
SYLogger.info("客户端连接初始化完成")
|
|
296
197
|
except Exception as e:
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
if
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
# 检查消费停滞
|
|
324
|
-
if self._is_consuming:
|
|
325
|
-
current_time = asyncio.get_event_loop().time()
|
|
326
|
-
if current_time - self._last_message_processed > self.consumption_stall_threshold:
|
|
327
|
-
if self._processing_message_ids:
|
|
328
|
-
logger.warning(
|
|
329
|
-
f"消费停滞,但有 {len(self._processing_message_ids)} 个消息正在处理,暂不重启")
|
|
330
|
-
else:
|
|
331
|
-
# 确实无消息处理,再重启
|
|
332
|
-
await self.stop_consuming()
|
|
333
|
-
await asyncio.sleep(1)
|
|
334
|
-
await self.start_consuming()
|
|
335
|
-
except Exception as e:
|
|
336
|
-
logger.error(f"监控任务出错: {str(e)}")
|
|
337
|
-
await asyncio.sleep(1)
|
|
338
|
-
|
|
339
|
-
await asyncio.sleep(30)
|
|
340
|
-
|
|
341
|
-
self._monitor_task = asyncio.create_task(monitor())
|
|
198
|
+
SYLogger.error(f"客户端连接失败: {str(e)}", exc_info=True)
|
|
199
|
+
# 清理异常状态
|
|
200
|
+
if self._conn_close_callback and self._channel_conn:
|
|
201
|
+
self._channel_conn.close_callbacks.discard(
|
|
202
|
+
self._conn_close_callback)
|
|
203
|
+
if self._channel and self._channel_conn:
|
|
204
|
+
try:
|
|
205
|
+
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
206
|
+
except:
|
|
207
|
+
pass
|
|
208
|
+
self._channel = None
|
|
209
|
+
self._channel_conn = None
|
|
210
|
+
# 触发重连(固定间隔)
|
|
211
|
+
if not self._closed:
|
|
212
|
+
asyncio.create_task(self._safe_reconnect())
|
|
213
|
+
raise
|
|
214
|
+
|
|
215
|
+
async def _safe_reconnect(self):
|
|
216
|
+
"""安全重连:信号量控制并发+固定15秒间隔,避免短时间大量重连"""
|
|
217
|
+
# 1. 信号量控制:限制同时进行的重连任务数(默认1个)
|
|
218
|
+
async with self._reconnect_semaphore:
|
|
219
|
+
# 2. 检查是否已有重连任务在运行(双重保障)
|
|
220
|
+
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
221
|
+
SYLogger.debug("已有重连任务在运行,跳过重复触发")
|
|
222
|
+
return
|
|
342
223
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
await self.channel.set_qos(prefetch_count=self.prefetch_count)
|
|
224
|
+
async with self._reconnect_task_lock:
|
|
225
|
+
if self._closed or await self.is_connected:
|
|
226
|
+
SYLogger.debug("客户端已关闭或已连接,取消重连")
|
|
227
|
+
return
|
|
348
228
|
|
|
349
|
-
|
|
350
|
-
|
|
229
|
+
# 3. 固定15秒重连间隔(避免频繁重试)
|
|
230
|
+
SYLogger.info(f"将在15秒后尝试重连...")
|
|
231
|
+
await asyncio.sleep(self._RECONNECT_INTERVAL)
|
|
351
232
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
if self.queue and self.exchange:
|
|
356
|
-
await self._bind_queue(self.channel, self.queue, self.exchange)
|
|
233
|
+
if self._closed or await self.is_connected:
|
|
234
|
+
SYLogger.debug("重连等待期间客户端状态变化,取消重连")
|
|
235
|
+
return
|
|
357
236
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
237
|
+
try:
|
|
238
|
+
# 4. 执行重连
|
|
239
|
+
SYLogger.info("开始重连RabbitMQ客户端...")
|
|
240
|
+
self._current_reconnect_task = asyncio.create_task(
|
|
241
|
+
self.connect())
|
|
242
|
+
await self._current_reconnect_task
|
|
243
|
+
except Exception as e:
|
|
244
|
+
SYLogger.warning(f"重连失败: {str(e)}")
|
|
245
|
+
# 重连失败后,不主动触发下一次(等待连接关闭回调再次触发,避免死循环)
|
|
246
|
+
finally:
|
|
247
|
+
self._current_reconnect_task = None
|
|
361
248
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
249
|
+
async def set_message_handler(
|
|
250
|
+
self,
|
|
251
|
+
handler: Callable[[MQMsgModel, AbstractIncomingMessage], Coroutine[Any, Any, None]],
|
|
252
|
+
) -> None:
|
|
253
|
+
"""设置消息处理器(必须是协程函数)"""
|
|
254
|
+
if not asyncio.iscoroutinefunction(handler):
|
|
255
|
+
raise TypeError("消息处理器必须是协程函数(使用 async def 定义)")
|
|
256
|
+
|
|
257
|
+
async with self._consume_lock:
|
|
258
|
+
self._message_handler = handler
|
|
259
|
+
SYLogger.info("消息处理器设置成功")
|
|
260
|
+
|
|
261
|
+
async def start_consuming(self) -> Optional[ConsumerTag]:
|
|
262
|
+
"""启动消息消费(支持自动重连)"""
|
|
263
|
+
if self._closed:
|
|
264
|
+
raise RuntimeError("客户端已关闭,无法启动消费")
|
|
265
|
+
|
|
266
|
+
async with self._consume_lock:
|
|
267
|
+
# 1. 校验前置条件
|
|
268
|
+
if not self._message_handler:
|
|
269
|
+
raise RuntimeError("未设置消息处理器,请先调用 set_message_handler()")
|
|
270
|
+
if not await self.is_connected:
|
|
271
|
+
await self.connect()
|
|
272
|
+
if not self._queue:
|
|
273
|
+
raise RuntimeError("未配置队列名,无法启动消费")
|
|
274
|
+
|
|
275
|
+
# 2. 定义消费回调(包含异常处理和重连逻辑)
|
|
276
|
+
async def consume_callback(message: AbstractIncomingMessage):
|
|
277
|
+
try:
|
|
278
|
+
# 解析消息体
|
|
279
|
+
if self.auto_parse_json:
|
|
280
|
+
try:
|
|
281
|
+
body_dict = json.loads(
|
|
282
|
+
message.body.decode("utf-8"))
|
|
283
|
+
msg_obj = MQMsgModel(**body_dict)
|
|
284
|
+
except json.JSONDecodeError as e:
|
|
285
|
+
SYLogger.error(
|
|
286
|
+
f"JSON消息解析失败: {str(e)},消息体: {message.body[:100]}...")
|
|
287
|
+
await message.nack(requeue=False) # 解析失败,不重入队
|
|
384
288
|
return
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
type=self.exchange_type,
|
|
391
|
-
passive=True
|
|
392
|
-
),
|
|
393
|
-
timeout=5
|
|
289
|
+
else:
|
|
290
|
+
msg_obj = MQMsgModel(
|
|
291
|
+
body=message.body.decode("utf-8"),
|
|
292
|
+
routing_key=message.routing_key,
|
|
293
|
+
delivery_tag=message.delivery_tag,
|
|
394
294
|
)
|
|
395
|
-
self._update_activity_timestamp()
|
|
396
|
-
except Exception as e:
|
|
397
|
-
logger.warning(f"保活检查失败: {str(e)},触发重连")
|
|
398
|
-
await self.connect(force_reconnect=True)
|
|
399
|
-
|
|
400
|
-
await asyncio.sleep(self.connection_pool.heartbeat / 2)
|
|
401
|
-
|
|
402
|
-
self._keepalive_task = asyncio.create_task(keepalive())
|
|
403
295
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
if self._reconnect_task and not self._reconnect_task.done():
|
|
407
|
-
return
|
|
408
|
-
|
|
409
|
-
logger.info(f"将在 {self.reconnection_delay} 秒后尝试重新连接...")
|
|
410
|
-
|
|
411
|
-
async def reconnect():
|
|
412
|
-
try:
|
|
413
|
-
await asyncio.sleep(self.reconnection_delay)
|
|
414
|
-
if not self._closed:
|
|
415
|
-
await self.connect(force_reconnect=True)
|
|
416
|
-
except Exception as e:
|
|
417
|
-
logger.error(f"重连任务失败: {str(e)}")
|
|
418
|
-
if not self._closed:
|
|
419
|
-
await self._schedule_reconnect()
|
|
296
|
+
# 调用消息处理器(必须await,避免协程未等待警告)
|
|
297
|
+
await self._message_handler(msg_obj, message)
|
|
420
298
|
|
|
421
|
-
|
|
299
|
+
# 手动ACK(消息处理成功)
|
|
300
|
+
await message.ack()
|
|
301
|
+
SYLogger.debug(
|
|
302
|
+
f"消息处理成功,delivery_tag: {message.delivery_tag}")
|
|
422
303
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
304
|
+
except Exception as e:
|
|
305
|
+
SYLogger.error(
|
|
306
|
+
f"消息处理失败,delivery_tag: {message.delivery_tag}",
|
|
307
|
+
exc_info=True
|
|
308
|
+
)
|
|
309
|
+
# 处理失败逻辑:首次失败重入队,再次失败丢弃
|
|
310
|
+
if message.redelivered:
|
|
311
|
+
SYLogger.warning(
|
|
312
|
+
f"消息已重入队过,本次拒绝入队: {message.delivery_tag}")
|
|
313
|
+
await message.reject(requeue=False)
|
|
314
|
+
else:
|
|
315
|
+
SYLogger.warning(f"消息重入队: {message.delivery_tag}")
|
|
316
|
+
await message.nack(requeue=True)
|
|
317
|
+
|
|
318
|
+
# 检查连接状态,失效则触发重连
|
|
319
|
+
if not await self.is_connected:
|
|
320
|
+
SYLogger.warning("连接已失效,触发客户端重连")
|
|
321
|
+
asyncio.create_task(self._safe_reconnect())
|
|
322
|
+
|
|
323
|
+
# 3. 启动消费
|
|
324
|
+
self._consumer_tag = await self._queue.consume(consume_callback)
|
|
325
|
+
SYLogger.info(
|
|
326
|
+
f"开始消费队列: {self._queue.name},consumer_tag: {self._consumer_tag}"
|
|
327
|
+
)
|
|
328
|
+
return self._consumer_tag
|
|
427
329
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
if
|
|
432
|
-
task.cancel()
|
|
330
|
+
async def stop_consuming(self) -> None:
|
|
331
|
+
"""停止消息消费"""
|
|
332
|
+
async with self._consume_lock:
|
|
333
|
+
if self._consumer_tag and self._queue and not self._queue.is_closed:
|
|
433
334
|
try:
|
|
434
|
-
await
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
self.exchange = None
|
|
441
|
-
self.queue = None
|
|
442
|
-
self._consumer_tag = None
|
|
443
|
-
self._processing_message_ids.clear()
|
|
444
|
-
|
|
445
|
-
logger.info("RabbitMQ客户端已关闭")
|
|
335
|
+
await self._queue.cancel(self._consumer_tag)
|
|
336
|
+
SYLogger.info(f"停止消费成功,consumer_tag: {self._consumer_tag}")
|
|
337
|
+
except Exception as e:
|
|
338
|
+
SYLogger.error(f"停止消费失败: {str(e)}", exc_info=True)
|
|
339
|
+
finally:
|
|
340
|
+
self._consumer_tag = None
|
|
446
341
|
|
|
447
342
|
async def publish(
|
|
448
343
|
self,
|
|
449
|
-
message_body: Union[str, Dict[str, Any]],
|
|
450
|
-
routing_key: Optional[str] = None,
|
|
451
|
-
content_type: str = "application/json",
|
|
344
|
+
message_body: Union[str, Dict[str, Any], MQMsgModel],
|
|
452
345
|
headers: Optional[Dict[str, Any]] = None,
|
|
453
|
-
|
|
346
|
+
content_type: str = "application/json",
|
|
347
|
+
delivery_mode: DeliveryMode = DeliveryMode.PERSISTENT,
|
|
348
|
+
retry_count: int = 3,
|
|
454
349
|
) -> None:
|
|
455
|
-
"""
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
message_body_str = json.dumps(message_body, ensure_ascii=False)
|
|
466
|
-
if content_type == "text/plain":
|
|
467
|
-
content_type = "application/json"
|
|
468
|
-
else:
|
|
469
|
-
message_body_str = str(message_body)
|
|
470
|
-
|
|
471
|
-
# 创建消息对象
|
|
472
|
-
message = Message(
|
|
473
|
-
body=message_body_str.encode(),
|
|
474
|
-
content_type=content_type,
|
|
475
|
-
headers=headers or {},
|
|
476
|
-
delivery_mode=delivery_mode
|
|
477
|
-
)
|
|
478
|
-
|
|
479
|
-
# 发布消息(带重试机制)
|
|
480
|
-
retry_count = 0
|
|
481
|
-
max_retries = 2
|
|
482
|
-
while retry_count < max_retries:
|
|
483
|
-
try:
|
|
484
|
-
# 从池获取新通道用于发布(避免长时间占用消费通道)
|
|
485
|
-
async with self.connection_pool.channel_pool.acquire() as publish_channel:
|
|
486
|
-
exchange = await publish_channel.get_exchange(self.exchange_name)
|
|
487
|
-
confirmed = await exchange.publish(
|
|
488
|
-
message,
|
|
489
|
-
routing_key=routing_key or self.routing_key or '#',
|
|
490
|
-
mandatory=True,
|
|
491
|
-
timeout=5.0
|
|
492
|
-
)
|
|
493
|
-
if not confirmed:
|
|
494
|
-
raise Exception("消息未被服务器确认接收")
|
|
495
|
-
|
|
496
|
-
self._update_activity_timestamp()
|
|
497
|
-
logger.debug(f"消息已发布到交换机 '{self.exchange_name}'")
|
|
498
|
-
return
|
|
499
|
-
except (ConnectionClosed, ChannelInvalidStateError):
|
|
500
|
-
retry_count += 1
|
|
501
|
-
logger.warning(f"连接已关闭,尝试重连后重新发布 (重试次数: {retry_count})")
|
|
502
|
-
await self.connect(force_reconnect=True)
|
|
503
|
-
except Exception as e:
|
|
504
|
-
retry_count += 1
|
|
505
|
-
logger.error(f"消息发布失败 (重试次数: {retry_count}): {str(e)}")
|
|
506
|
-
if retry_count < max_retries:
|
|
507
|
-
await asyncio.sleep(1)
|
|
508
|
-
|
|
509
|
-
raise Exception(f"消息发布失败,经过{retry_count}次重试仍未成功")
|
|
510
|
-
|
|
511
|
-
# 以下方法(消息消费相关)逻辑与原有保持一致,仅适配连接池
|
|
512
|
-
def set_message_handler(self, handler):
|
|
513
|
-
self.message_handler = handler
|
|
514
|
-
|
|
515
|
-
async def start_consuming(self) -> ConsumerTag:
|
|
516
|
-
if self._is_consuming:
|
|
517
|
-
logger.debug("已经在消费中,返回现有consumer_tag")
|
|
518
|
-
if self._consumer_tag:
|
|
519
|
-
return self._consumer_tag
|
|
520
|
-
# 如果_is_consuming为True但无consumer_tag,重置状态并重新启动
|
|
521
|
-
logger.warning("检测到消费状态异常(无consumer_tag),重置状态后重试")
|
|
522
|
-
self._is_consuming = False # 重置状态
|
|
523
|
-
|
|
524
|
-
if not self.is_connected:
|
|
525
|
-
await self.connect()
|
|
526
|
-
|
|
527
|
-
if not self.queue:
|
|
528
|
-
raise Exception("队列未初始化,无法开始消费")
|
|
529
|
-
|
|
530
|
-
if not self.message_handler:
|
|
531
|
-
raise Exception("未设置消息处理函数")
|
|
532
|
-
|
|
533
|
-
self._is_consuming = True
|
|
534
|
-
logger.info(f"开始消费队列: {self.actual_queue_name}")
|
|
350
|
+
"""
|
|
351
|
+
发布消息(支持自动重试、mandatory路由校验、5秒超时控制)
|
|
352
|
+
:param message_body: 消息体(字符串/字典/MQMsgModel)
|
|
353
|
+
:param headers: 消息头(可选)
|
|
354
|
+
:param content_type: 内容类型(默认application/json)
|
|
355
|
+
:param delivery_mode: 投递模式(PERSISTENT=持久化,TRANSIENT=非持久化)
|
|
356
|
+
:param retry_count: 重试次数(默认3次)
|
|
357
|
+
"""
|
|
358
|
+
if self._closed:
|
|
359
|
+
raise RuntimeError("客户端已关闭,无法发布消息")
|
|
535
360
|
|
|
361
|
+
# 处理消息体序列化
|
|
536
362
|
try:
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
logger.info(
|
|
548
|
-
f"消费者已启动,队列: {self.actual_queue_name}, tag: {self._consumer_tag}")
|
|
549
|
-
return self._consumer_tag
|
|
363
|
+
if isinstance(message_body, MQMsgModel):
|
|
364
|
+
body = json.dumps(message_body.to_dict(),
|
|
365
|
+
ensure_ascii=False).encode("utf-8")
|
|
366
|
+
elif isinstance(message_body, dict):
|
|
367
|
+
body = json.dumps(
|
|
368
|
+
message_body, ensure_ascii=False).encode("utf-8")
|
|
369
|
+
elif isinstance(message_body, str):
|
|
370
|
+
body = message_body.encode("utf-8")
|
|
371
|
+
else:
|
|
372
|
+
raise TypeError(f"不支持的消息体类型: {type(message_body)}")
|
|
550
373
|
except Exception as e:
|
|
551
|
-
|
|
552
|
-
logger.error(f"启动消费失败: {str(e)}", exc_info=True)
|
|
374
|
+
SYLogger.error(f"消息体序列化失败: {str(e)}", exc_info=True)
|
|
553
375
|
raise
|
|
554
376
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
timeout=self.rpc_timeout
|
|
563
|
-
)
|
|
564
|
-
logger.info(f"消费者 {self._consumer_tag} 已取消")
|
|
565
|
-
return True
|
|
566
|
-
except Exception as e:
|
|
567
|
-
logger.error(f"取消消费者异常: {str(e)}")
|
|
568
|
-
return False
|
|
569
|
-
|
|
570
|
-
async def stop_consuming(self) -> None:
|
|
571
|
-
if not self._is_consuming:
|
|
572
|
-
return
|
|
573
|
-
|
|
574
|
-
self._is_consuming = False
|
|
575
|
-
|
|
576
|
-
if self._consumer_tag and self.queue:
|
|
577
|
-
await self._safe_cancel_consumer()
|
|
578
|
-
|
|
579
|
-
# 等待所有正在处理的消息完成
|
|
580
|
-
if self._processing_message_ids:
|
|
581
|
-
logger.info(
|
|
582
|
-
f"等待 {len(self._processing_message_ids)} 个正在处理的消息完成...")
|
|
583
|
-
while self._processing_message_ids and not self._closed:
|
|
584
|
-
await asyncio.sleep(0.1)
|
|
585
|
-
|
|
586
|
-
# 清理状态
|
|
587
|
-
self._consumer_tag = None
|
|
588
|
-
self._processing_message_ids.clear()
|
|
589
|
-
|
|
590
|
-
logger.info(f"已停止消费队列: {self.actual_queue_name}")
|
|
591
|
-
|
|
592
|
-
async def _parse_message(self, message: AbstractIncomingMessage) -> Union[Dict[str, Any], str]:
|
|
593
|
-
try:
|
|
594
|
-
body_str = message.body.decode('utf-8')
|
|
595
|
-
self._update_activity_timestamp()
|
|
596
|
-
|
|
597
|
-
if self.auto_parse_json:
|
|
598
|
-
return json.loads(body_str)
|
|
599
|
-
return body_str
|
|
600
|
-
except json.JSONDecodeError:
|
|
601
|
-
logger.warning(f"消息解析JSON失败,返回原始字符串")
|
|
602
|
-
return body_str
|
|
603
|
-
except Exception as e:
|
|
604
|
-
logger.error(f"消息解析出错: {str(e)}")
|
|
605
|
-
return message.body.decode('utf-8')
|
|
606
|
-
|
|
607
|
-
async def _message_wrapper(self, message: AbstractIncomingMessage) -> None:
|
|
608
|
-
if not self.message_handler or not self._is_consuming:
|
|
609
|
-
logger.warning("未设置消息处理器或已停止消费")
|
|
610
|
-
# await message.ack()
|
|
611
|
-
return
|
|
612
|
-
|
|
613
|
-
message_id = message.message_id or str(id(message))
|
|
614
|
-
if message_id in self._processing_message_ids:
|
|
615
|
-
logger.warning(f"检测到重复处理的消息ID: {message_id},直接确认")
|
|
616
|
-
await message.ack()
|
|
617
|
-
return
|
|
618
|
-
|
|
619
|
-
self._processing_message_ids.add(message_id)
|
|
620
|
-
|
|
621
|
-
try:
|
|
622
|
-
logger.debug(f"收到队列 {self.actual_queue_name} 的消息: {message_id}")
|
|
623
|
-
|
|
624
|
-
parsed_data = await self._parse_message(message)
|
|
625
|
-
await self.message_handler(MQMsgModel(** parsed_data), message)
|
|
626
|
-
|
|
627
|
-
await message.ack()
|
|
628
|
-
self._update_activity_timestamp()
|
|
629
|
-
self._update_message_processed_timestamp()
|
|
630
|
-
logger.debug(f"消息 {message_id} 处理完成并确认")
|
|
377
|
+
# 构建消息对象
|
|
378
|
+
message = Message(
|
|
379
|
+
body=body,
|
|
380
|
+
headers=headers or {},
|
|
381
|
+
content_type=content_type,
|
|
382
|
+
delivery_mode=delivery_mode,
|
|
383
|
+
)
|
|
631
384
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
385
|
+
# 发布重试逻辑
|
|
386
|
+
for retry in range(retry_count):
|
|
387
|
+
try:
|
|
388
|
+
# 确保连接有效
|
|
389
|
+
if not await self.is_connected:
|
|
390
|
+
SYLogger.warning(f"发布消息前连接失效,触发重连(retry: {retry})")
|
|
391
|
+
await self.connect()
|
|
392
|
+
|
|
393
|
+
# 核心:发布消息(添加 mandatory=True 和 timeout=5.0)
|
|
394
|
+
publish_result = await self._exchange.publish(
|
|
395
|
+
message=message,
|
|
396
|
+
routing_key=self.routing_key or self.queue_name or "#",
|
|
397
|
+
mandatory=True, # 强制路由到至少一个队列,否则返回None
|
|
398
|
+
timeout=5.0 # 5秒超时控制,避免无限阻塞
|
|
399
|
+
)
|
|
636
400
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
401
|
+
# 处理 mandatory=True 结果:未路由到队列返回 None,直接抛出异常
|
|
402
|
+
if publish_result is None:
|
|
403
|
+
raise RuntimeError(
|
|
404
|
+
f"消息未找到匹配的队列(routing_key: {self.routing_key}),mandatory=True 触发失败"
|
|
405
|
+
)
|
|
641
406
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
f"
|
|
645
|
-
|
|
646
|
-
|
|
407
|
+
# 低版本 RobustChannel 异步确认,无需显式等待,仅日志说明
|
|
408
|
+
SYLogger.info(
|
|
409
|
+
f"消息发布成功(retry: {retry}),routing_key: {self.routing_key},"
|
|
410
|
+
f"delivery_mode: {delivery_mode.value},mandatory: True,timeout: 5.0s"
|
|
411
|
+
)
|
|
647
412
|
return
|
|
413
|
+
except asyncio.TimeoutError:
|
|
414
|
+
SYLogger.error(
|
|
415
|
+
f"消息发布超时(retry: {retry}/{retry_count-1}),超时时间: 5.0s"
|
|
416
|
+
)
|
|
417
|
+
except RuntimeError as e:
|
|
418
|
+
# 捕获 mandatory 未路由等业务异常
|
|
419
|
+
SYLogger.error(
|
|
420
|
+
f"消息发布业务失败(retry: {retry}/{retry_count-1}): {str(e)}"
|
|
421
|
+
)
|
|
422
|
+
except Exception as e:
|
|
423
|
+
SYLogger.error(
|
|
424
|
+
f"消息发布失败(retry: {retry}/{retry_count-1}): {str(e)}",
|
|
425
|
+
exc_info=True
|
|
426
|
+
)
|
|
427
|
+
# 清理失效状态,下次重试重连
|
|
428
|
+
self._exchange = None
|
|
429
|
+
# 指数退避重试间隔(0.5s, 1s, 2s...)
|
|
430
|
+
await asyncio.sleep(0.5 * (2 ** retry))
|
|
431
|
+
|
|
432
|
+
# 所有重试失败,抛出最终异常
|
|
433
|
+
raise RuntimeError(
|
|
434
|
+
f"消息发布失败(已重试{retry_count}次),routing_key: {self.routing_key},"
|
|
435
|
+
f"mandatory: True,timeout: 5.0s"
|
|
436
|
+
)
|
|
648
437
|
|
|
649
|
-
|
|
650
|
-
|
|
438
|
+
async def close(self) -> None:
|
|
439
|
+
"""关闭客户端(移除回调+释放资源)"""
|
|
440
|
+
self._closed = True
|
|
441
|
+
SYLogger.info("开始关闭RabbitMQ客户端...")
|
|
651
442
|
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
443
|
+
# 停止重连任务
|
|
444
|
+
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
445
|
+
self._current_reconnect_task.cancel()
|
|
446
|
+
try:
|
|
447
|
+
await self._current_reconnect_task
|
|
448
|
+
except asyncio.CancelledError:
|
|
449
|
+
SYLogger.debug("重连任务已取消")
|
|
658
450
|
|
|
659
|
-
|
|
451
|
+
# 1. 停止消费
|
|
452
|
+
await self.stop_consuming()
|
|
660
453
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
454
|
+
# 2. 释放通道到连接池
|
|
455
|
+
async with self._connect_lock:
|
|
456
|
+
if self._channel and self._channel_conn:
|
|
457
|
+
try:
|
|
458
|
+
# 移除连接关闭回调
|
|
459
|
+
if self._conn_close_callback:
|
|
460
|
+
self._channel_conn.close_callbacks.discard(
|
|
461
|
+
self._conn_close_callback)
|
|
462
|
+
await self.connection_pool.release_channel(self._channel, self._channel_conn)
|
|
463
|
+
SYLogger.info("通道释放成功")
|
|
464
|
+
except Exception as e:
|
|
465
|
+
SYLogger.error(f"通道释放失败: {str(e)}", exc_info=True)
|
|
466
|
+
self._channel = None
|
|
467
|
+
self._channel_conn = None
|
|
468
|
+
self._exchange = None
|
|
469
|
+
self._queue = None
|
|
470
|
+
self._message_handler = None
|
|
471
|
+
|
|
472
|
+
SYLogger.info("RabbitMQ客户端已完全关闭")
|