sycommon-python-lib 0.1.45__tar.gz → 0.1.52a0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/PKG-INFO +1 -1
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/pyproject.toml +1 -1
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/middleware.py +1 -1
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/rabbitmq/rabbitmq_client.py +129 -144
- sycommon_python_lib-0.1.52a0/src/sycommon/rabbitmq/rabbitmq_pool.py +338 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/rabbitmq/rabbitmq_service.py +85 -131
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/services.py +12 -22
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/feign.py +2 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/feign_client.py +2 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/PKG-INFO +1 -1
- sycommon_python_lib-0.1.45/src/sycommon/rabbitmq/rabbitmq_pool.py +0 -404
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/README.md +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/setup.cfg +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/command/cli.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/Config.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/DatabaseConfig.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/EmbeddingConfig.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/LLMConfig.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/MQConfig.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/RerankerConfig.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/config/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/database/base_db_service.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/database/database_service.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/health/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/health/health_check.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/health/metrics.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/health/ping.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/logging/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/logging/kafka_log.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/logging/logger_wrapper.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/logging/sql_logger.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/context.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/cors.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/docs.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/exception.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/monitor_memory.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/mq.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/timeout.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/middleware/traceid.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/base_http.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/log.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/mqlistener_config.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/mqmsg_model.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/mqsend_config.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/models/sso_user.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/sse/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/sse/event.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/sse/sse.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/example.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/example2.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/nacos_service.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/synacos/param.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/tools/__init__.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/tools/docs.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/tools/snowflake.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/tools/timing.py +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/SOURCES.txt +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/dependency_links.txt +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/requires.txt +0 -0
- {sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon_python_lib.egg-info/top_level.txt +0 -0
{sycommon_python_lib-0.1.45 → sycommon_python_lib-0.1.52a0}/src/sycommon/rabbitmq/rabbitmq_client.py
RENAMED
|
@@ -15,7 +15,6 @@ from sycommon.rabbitmq.rabbitmq_pool import RabbitMQConnectionPool
|
|
|
15
15
|
from sycommon.logging.kafka_log import SYLogger
|
|
16
16
|
from sycommon.models.mqmsg_model import MQMsgModel
|
|
17
17
|
|
|
18
|
-
|
|
19
18
|
logger = SYLogger
|
|
20
19
|
|
|
21
20
|
|
|
@@ -23,8 +22,8 @@ class RabbitMQClient:
|
|
|
23
22
|
"""
|
|
24
23
|
RabbitMQ 客户端(支持消息发布、消费、自动重连、异常重试)
|
|
25
24
|
核心特性:
|
|
26
|
-
1.
|
|
27
|
-
2.
|
|
25
|
+
1. 基于单通道连接池复用资源,性能优化
|
|
26
|
+
2. 依赖连接池原生自动重连,客户端仅重建自身资源
|
|
28
27
|
3. 消息发布支持重试+mandatory机制+超时控制,确保路由有效
|
|
29
28
|
4. 消费支持手动ACK/NACK
|
|
30
29
|
5. 兼容JSON/字符串/字典消息格式
|
|
@@ -41,7 +40,6 @@ class RabbitMQClient:
|
|
|
41
40
|
auto_delete: bool = False,
|
|
42
41
|
auto_parse_json: bool = True,
|
|
43
42
|
create_if_not_exists: bool = True,
|
|
44
|
-
prefetch_count: int = 2,
|
|
45
43
|
**kwargs,
|
|
46
44
|
):
|
|
47
45
|
# 依赖注入:连接池(必须已初始化)
|
|
@@ -54,8 +52,8 @@ class RabbitMQClient:
|
|
|
54
52
|
try:
|
|
55
53
|
self.exchange_type = ExchangeType(exchange_type.lower())
|
|
56
54
|
except ValueError:
|
|
57
|
-
|
|
58
|
-
self.exchange_type = ExchangeType.
|
|
55
|
+
logger.warning(f"无效的exchange_type: {exchange_type},默认使用'topic'")
|
|
56
|
+
self.exchange_type = ExchangeType.TOPIC
|
|
59
57
|
|
|
60
58
|
# 队列配置
|
|
61
59
|
self.queue_name = queue_name.strip() if queue_name else None
|
|
@@ -65,9 +63,6 @@ class RabbitMQClient:
|
|
|
65
63
|
self.auto_parse_json = auto_parse_json # 自动解析JSON消息体
|
|
66
64
|
self.create_if_not_exists = create_if_not_exists # 不存在则创建交换机/队列
|
|
67
65
|
|
|
68
|
-
# 消费配置
|
|
69
|
-
self.prefetch_count = max(1, prefetch_count) # 每次预取消息数(避免消息堆积)
|
|
70
|
-
|
|
71
66
|
# 内部状态(资源+连接)
|
|
72
67
|
self._channel: Optional[Channel] = None
|
|
73
68
|
self._channel_conn: Optional[AbstractRobustConnection] = None # 通道所属连接
|
|
@@ -83,12 +78,10 @@ class RabbitMQClient:
|
|
|
83
78
|
self._connect_lock = asyncio.Lock()
|
|
84
79
|
# 跟踪连接关闭回调(用于后续移除)
|
|
85
80
|
self._conn_close_callback: Optional[Callable] = None
|
|
86
|
-
#
|
|
81
|
+
# 控制重连频率的信号量(限制并发重连数)
|
|
87
82
|
self._reconnect_semaphore = asyncio.Semaphore(1)
|
|
88
83
|
# 固定重连间隔15秒(全局统一)
|
|
89
84
|
self._RECONNECT_INTERVAL = 15
|
|
90
|
-
# 重连任务锁(确保同一时间只有一个重连任务)
|
|
91
|
-
self._reconnect_task_lock = asyncio.Lock()
|
|
92
85
|
# 跟踪当前重连任务(避免重复创建)
|
|
93
86
|
self._current_reconnect_task: Optional[asyncio.Task] = None
|
|
94
87
|
# 连接失败计数器(用于告警)
|
|
@@ -102,7 +95,7 @@ class RabbitMQClient:
|
|
|
102
95
|
if self._closed:
|
|
103
96
|
return False
|
|
104
97
|
try:
|
|
105
|
-
#
|
|
98
|
+
# 单通道场景:校验通道+连接+核心资源都有效
|
|
106
99
|
return (
|
|
107
100
|
self._channel and not self._channel.is_closed
|
|
108
101
|
and self._channel_conn and not self._channel_conn.is_closed
|
|
@@ -110,23 +103,52 @@ class RabbitMQClient:
|
|
|
110
103
|
and (not self.queue_name or self._queue is not None)
|
|
111
104
|
)
|
|
112
105
|
except Exception as e:
|
|
113
|
-
|
|
106
|
+
logger.warning(f"检查连接状态失败: {str(e)}")
|
|
114
107
|
return False
|
|
115
108
|
|
|
109
|
+
async def _rebuild_resources(self) -> None:
|
|
110
|
+
"""重建交换机/队列等资源(依赖已有的通道)"""
|
|
111
|
+
if not self._channel or self._channel.is_closed:
|
|
112
|
+
raise RuntimeError("无有效通道,无法重建资源")
|
|
113
|
+
|
|
114
|
+
# 1. 声明交换机
|
|
115
|
+
self._exchange = await self._channel.declare_exchange(
|
|
116
|
+
name=self.exchange_name,
|
|
117
|
+
type=self.exchange_type,
|
|
118
|
+
durable=self.durable,
|
|
119
|
+
auto_delete=self.auto_delete,
|
|
120
|
+
passive=not self.create_if_not_exists,
|
|
121
|
+
)
|
|
122
|
+
logger.info(
|
|
123
|
+
f"交换机重建成功: {self.exchange_name}(类型: {self.exchange_type.value})")
|
|
124
|
+
|
|
125
|
+
# 2. 声明队列(如果配置了队列名)
|
|
126
|
+
if self.queue_name:
|
|
127
|
+
self._queue = await self._channel.declare_queue(
|
|
128
|
+
name=self.queue_name,
|
|
129
|
+
durable=self.durable,
|
|
130
|
+
auto_delete=self.auto_delete,
|
|
131
|
+
passive=not self.create_if_not_exists,
|
|
132
|
+
)
|
|
133
|
+
# 绑定队列到交换机
|
|
134
|
+
await self._queue.bind(
|
|
135
|
+
exchange=self._exchange,
|
|
136
|
+
routing_key=self.routing_key,
|
|
137
|
+
)
|
|
138
|
+
logger.info(
|
|
139
|
+
f"队列重建成功: {self.queue_name} "
|
|
140
|
+
f"(绑定交换机: {self.exchange_name}, routing_key: {self.routing_key})"
|
|
141
|
+
)
|
|
142
|
+
|
|
116
143
|
async def connect(self) -> None:
|
|
117
144
|
if self._closed:
|
|
118
145
|
raise RuntimeError("客户端已关闭,无法重新连接")
|
|
119
146
|
|
|
120
147
|
async with self._connect_lock:
|
|
121
|
-
#
|
|
122
|
-
if self.
|
|
123
|
-
|
|
124
|
-
|
|
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)}")
|
|
148
|
+
# 释放旧资源(回调+通道,单通道无需归还,仅清理状态)
|
|
149
|
+
if self._conn_close_callback and self._channel_conn:
|
|
150
|
+
self._channel_conn.close_callbacks.discard(
|
|
151
|
+
self._conn_close_callback)
|
|
130
152
|
self._channel = None
|
|
131
153
|
self._channel_conn = None
|
|
132
154
|
self._exchange = None
|
|
@@ -134,17 +156,17 @@ class RabbitMQClient:
|
|
|
134
156
|
self._conn_close_callback = None
|
|
135
157
|
|
|
136
158
|
try:
|
|
137
|
-
#
|
|
159
|
+
# 从单通道池获取通道+连接(连接池自动确保通道有效)
|
|
138
160
|
self._channel, self._channel_conn = await self.connection_pool.acquire_channel()
|
|
139
161
|
|
|
140
162
|
def on_conn_closed(conn: AbstractRobustConnection, exc: Optional[BaseException]):
|
|
141
163
|
"""连接关闭回调:触发固定间隔重连"""
|
|
142
|
-
|
|
164
|
+
logger.warning(
|
|
143
165
|
f"客户端连接关闭: {conn!r},原因: {exc}", exc_info=exc)
|
|
144
166
|
self._reconnect_fail_count += 1
|
|
145
167
|
# 超过阈值告警
|
|
146
168
|
if self._reconnect_fail_count >= self._reconnect_alert_threshold:
|
|
147
|
-
|
|
169
|
+
logger.error(
|
|
148
170
|
f"连接失败次数已达阈值({self._reconnect_alert_threshold}),请检查MQ服务状态")
|
|
149
171
|
if not self._closed:
|
|
150
172
|
asyncio.create_task(self._safe_reconnect())
|
|
@@ -154,97 +176,54 @@ class RabbitMQClient:
|
|
|
154
176
|
self._channel_conn.close_callbacks.add(
|
|
155
177
|
self._conn_close_callback)
|
|
156
178
|
|
|
157
|
-
#
|
|
158
|
-
await self.
|
|
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
|
-
)
|
|
184
|
-
# 绑定队列到交换机
|
|
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
|
-
)
|
|
179
|
+
# 重建交换机/队列资源
|
|
180
|
+
await self._rebuild_resources()
|
|
193
181
|
|
|
194
182
|
# 重连成功,重置失败计数器
|
|
195
183
|
self._reconnect_fail_count = 0
|
|
196
|
-
|
|
184
|
+
logger.info("客户端连接初始化完成")
|
|
197
185
|
except Exception as e:
|
|
198
|
-
|
|
186
|
+
logger.error(f"客户端连接失败: {str(e)}", exc_info=True)
|
|
199
187
|
# 清理异常状态
|
|
200
188
|
if self._conn_close_callback and self._channel_conn:
|
|
201
189
|
self._channel_conn.close_callbacks.discard(
|
|
202
190
|
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
191
|
self._channel = None
|
|
209
192
|
self._channel_conn = None
|
|
210
|
-
#
|
|
193
|
+
# 触发重连
|
|
211
194
|
if not self._closed:
|
|
212
195
|
asyncio.create_task(self._safe_reconnect())
|
|
213
196
|
raise
|
|
214
197
|
|
|
215
198
|
async def _safe_reconnect(self):
|
|
216
|
-
"""安全重连:信号量控制并发+固定15
|
|
217
|
-
# 1. 信号量控制:限制同时进行的重连任务数(默认1个)
|
|
199
|
+
"""安全重连:信号量控制并发+固定15秒间隔"""
|
|
218
200
|
async with self._reconnect_semaphore:
|
|
219
|
-
#
|
|
201
|
+
# 检查是否已有重连任务在运行
|
|
220
202
|
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
221
|
-
|
|
203
|
+
logger.debug("已有重连任务在运行,跳过重复触发")
|
|
222
204
|
return
|
|
223
205
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return
|
|
206
|
+
if self._closed or await self.is_connected:
|
|
207
|
+
logger.debug("客户端已关闭或已连接,取消重连")
|
|
208
|
+
return
|
|
228
209
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
210
|
+
# 固定15秒重连间隔
|
|
211
|
+
logger.info(f"将在{self._RECONNECT_INTERVAL}秒后尝试重连...")
|
|
212
|
+
await asyncio.sleep(self._RECONNECT_INTERVAL)
|
|
232
213
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
214
|
+
if self._closed or await self.is_connected:
|
|
215
|
+
logger.debug("重连等待期间客户端状态变化,取消重连")
|
|
216
|
+
return
|
|
236
217
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
self.
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
finally:
|
|
247
|
-
self._current_reconnect_task = None
|
|
218
|
+
try:
|
|
219
|
+
logger.info("开始重连RabbitMQ客户端...")
|
|
220
|
+
self._current_reconnect_task = asyncio.create_task(
|
|
221
|
+
self.connect())
|
|
222
|
+
await self._current_reconnect_task
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.warning(f"重连失败: {str(e)}")
|
|
225
|
+
finally:
|
|
226
|
+
self._current_reconnect_task = None
|
|
248
227
|
|
|
249
228
|
async def set_message_handler(
|
|
250
229
|
self,
|
|
@@ -256,7 +235,7 @@ class RabbitMQClient:
|
|
|
256
235
|
|
|
257
236
|
async with self._consume_lock:
|
|
258
237
|
self._message_handler = handler
|
|
259
|
-
|
|
238
|
+
logger.info("消息处理器设置成功")
|
|
260
239
|
|
|
261
240
|
async def start_consuming(self) -> Optional[ConsumerTag]:
|
|
262
241
|
"""启动消息消费(支持自动重连)"""
|
|
@@ -270,7 +249,7 @@ class RabbitMQClient:
|
|
|
270
249
|
if not await self.is_connected:
|
|
271
250
|
await self.connect()
|
|
272
251
|
if not self._queue:
|
|
273
|
-
raise RuntimeError("
|
|
252
|
+
raise RuntimeError("未配置队列名或队列未创建,无法启动消费")
|
|
274
253
|
|
|
275
254
|
# 2. 定义消费回调(包含异常处理和重连逻辑)
|
|
276
255
|
async def consume_callback(message: AbstractIncomingMessage):
|
|
@@ -282,7 +261,7 @@ class RabbitMQClient:
|
|
|
282
261
|
message.body.decode("utf-8"))
|
|
283
262
|
msg_obj = MQMsgModel(**body_dict)
|
|
284
263
|
except json.JSONDecodeError as e:
|
|
285
|
-
|
|
264
|
+
logger.error(
|
|
286
265
|
f"JSON消息解析失败: {str(e)},消息体: {message.body[:100]}...")
|
|
287
266
|
await message.nack(requeue=False) # 解析失败,不重入队
|
|
288
267
|
return
|
|
@@ -293,51 +272,66 @@ class RabbitMQClient:
|
|
|
293
272
|
delivery_tag=message.delivery_tag,
|
|
294
273
|
)
|
|
295
274
|
|
|
296
|
-
#
|
|
275
|
+
# 调用消息处理器
|
|
297
276
|
await self._message_handler(msg_obj, message)
|
|
298
277
|
|
|
299
|
-
# 手动ACK
|
|
278
|
+
# 手动ACK
|
|
300
279
|
await message.ack()
|
|
301
|
-
|
|
280
|
+
logger.debug(
|
|
302
281
|
f"消息处理成功,delivery_tag: {message.delivery_tag}")
|
|
303
282
|
|
|
304
283
|
except Exception as e:
|
|
305
|
-
|
|
284
|
+
logger.error(
|
|
306
285
|
f"消息处理失败,delivery_tag: {message.delivery_tag}",
|
|
307
286
|
exc_info=True
|
|
308
287
|
)
|
|
309
288
|
# 处理失败逻辑:首次失败重入队,再次失败丢弃
|
|
310
289
|
if message.redelivered:
|
|
311
|
-
|
|
290
|
+
logger.warning(
|
|
312
291
|
f"消息已重入队过,本次拒绝入队: {message.delivery_tag}")
|
|
313
292
|
await message.reject(requeue=False)
|
|
314
293
|
else:
|
|
315
|
-
|
|
294
|
+
logger.warning(f"消息重入队: {message.delivery_tag}")
|
|
316
295
|
await message.nack(requeue=True)
|
|
317
296
|
|
|
318
|
-
#
|
|
297
|
+
# 连接失效则触发重连
|
|
319
298
|
if not await self.is_connected:
|
|
320
|
-
|
|
299
|
+
logger.warning("连接已失效,触发客户端重连")
|
|
321
300
|
asyncio.create_task(self._safe_reconnect())
|
|
322
301
|
|
|
323
|
-
# 3.
|
|
302
|
+
# 3. 启动消费(单通道消费,避免阻塞发布需确保业务回调非阻塞)
|
|
324
303
|
self._consumer_tag = await self._queue.consume(consume_callback)
|
|
325
|
-
|
|
304
|
+
logger.info(
|
|
326
305
|
f"开始消费队列: {self._queue.name},consumer_tag: {self._consumer_tag}"
|
|
327
306
|
)
|
|
328
307
|
return self._consumer_tag
|
|
329
308
|
|
|
330
309
|
async def stop_consuming(self) -> None:
|
|
331
|
-
"""
|
|
310
|
+
"""停止消息消费(适配 RobustChannel)"""
|
|
332
311
|
async with self._consume_lock:
|
|
333
|
-
|
|
334
|
-
|
|
312
|
+
try:
|
|
313
|
+
# 校验核心条件:消费标签、队列、通道均有效
|
|
314
|
+
if self._consumer_tag and self._queue and self._channel and not self._channel.is_closed:
|
|
315
|
+
# 使用队列的 cancel 方法(适配 RobustChannel)
|
|
335
316
|
await self._queue.cancel(self._consumer_tag)
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
317
|
+
logger.info(
|
|
318
|
+
f"停止消费成功,consumer_tag: {self._consumer_tag},队列: {self._queue.name}"
|
|
319
|
+
)
|
|
320
|
+
elif self._consumer_tag:
|
|
321
|
+
# 部分资源无效时的日志提示
|
|
322
|
+
if not self._queue:
|
|
323
|
+
logger.warning(
|
|
324
|
+
f"消费标签存在但队列为空,无法取消消费(consumer_tag: {self._consumer_tag})")
|
|
325
|
+
elif not self._channel or self._channel.is_closed:
|
|
326
|
+
logger.warning(
|
|
327
|
+
f"通道已关闭,消费已自动停止(consumer_tag: {self._consumer_tag},队列: {self._queue.name if self._queue else '未知'})"
|
|
328
|
+
)
|
|
329
|
+
except Exception as e:
|
|
330
|
+
logger.error(
|
|
331
|
+
f"停止消费者 '{self._queue.name if self._queue else '未知队列'}' 时出错: {str(e)}", exc_info=True
|
|
332
|
+
)
|
|
333
|
+
finally:
|
|
334
|
+
self._consumer_tag = None # 无论成败,清理消费标签
|
|
341
335
|
|
|
342
336
|
async def publish(
|
|
343
337
|
self,
|
|
@@ -371,7 +365,7 @@ class RabbitMQClient:
|
|
|
371
365
|
else:
|
|
372
366
|
raise TypeError(f"不支持的消息体类型: {type(message_body)}")
|
|
373
367
|
except Exception as e:
|
|
374
|
-
|
|
368
|
+
logger.error(f"消息体序列化失败: {str(e)}", exc_info=True)
|
|
375
369
|
raise
|
|
376
370
|
|
|
377
371
|
# 构建消息对象
|
|
@@ -387,49 +381,47 @@ class RabbitMQClient:
|
|
|
387
381
|
try:
|
|
388
382
|
# 确保连接有效
|
|
389
383
|
if not await self.is_connected:
|
|
390
|
-
|
|
384
|
+
logger.warning(f"发布消息前连接失效,触发重连(retry: {retry})")
|
|
391
385
|
await self.connect()
|
|
392
386
|
|
|
393
|
-
#
|
|
387
|
+
# 核心:发布消息(mandatory=True 确保路由有效,timeout=5s 避免阻塞)
|
|
394
388
|
publish_result = await self._exchange.publish(
|
|
395
389
|
message=message,
|
|
396
390
|
routing_key=self.routing_key or self.queue_name or "#",
|
|
397
|
-
mandatory=True,
|
|
398
|
-
timeout=5.0
|
|
391
|
+
mandatory=True,
|
|
392
|
+
timeout=5.0
|
|
399
393
|
)
|
|
400
394
|
|
|
401
|
-
# 处理 mandatory
|
|
395
|
+
# 处理 mandatory 未路由场景
|
|
402
396
|
if publish_result is None:
|
|
403
397
|
raise RuntimeError(
|
|
404
398
|
f"消息未找到匹配的队列(routing_key: {self.routing_key}),mandatory=True 触发失败"
|
|
405
399
|
)
|
|
406
400
|
|
|
407
|
-
|
|
408
|
-
SYLogger.info(
|
|
401
|
+
logger.info(
|
|
409
402
|
f"消息发布成功(retry: {retry}),routing_key: {self.routing_key},"
|
|
410
403
|
f"delivery_mode: {delivery_mode.value},mandatory: True,timeout: 5.0s"
|
|
411
404
|
)
|
|
412
405
|
return
|
|
413
406
|
except asyncio.TimeoutError:
|
|
414
|
-
|
|
407
|
+
logger.error(
|
|
415
408
|
f"消息发布超时(retry: {retry}/{retry_count-1}),超时时间: 5.0s"
|
|
416
409
|
)
|
|
417
410
|
except RuntimeError as e:
|
|
418
|
-
|
|
419
|
-
SYLogger.error(
|
|
411
|
+
logger.error(
|
|
420
412
|
f"消息发布业务失败(retry: {retry}/{retry_count-1}): {str(e)}"
|
|
421
413
|
)
|
|
422
414
|
except Exception as e:
|
|
423
|
-
|
|
415
|
+
logger.error(
|
|
424
416
|
f"消息发布失败(retry: {retry}/{retry_count-1}): {str(e)}",
|
|
425
417
|
exc_info=True
|
|
426
418
|
)
|
|
427
419
|
# 清理失效状态,下次重试重连
|
|
428
420
|
self._exchange = None
|
|
429
|
-
#
|
|
421
|
+
# 指数退避重试间隔
|
|
430
422
|
await asyncio.sleep(0.5 * (2 ** retry))
|
|
431
423
|
|
|
432
|
-
#
|
|
424
|
+
# 所有重试失败
|
|
433
425
|
raise RuntimeError(
|
|
434
426
|
f"消息发布失败(已重试{retry_count}次),routing_key: {self.routing_key},"
|
|
435
427
|
f"mandatory: True,timeout: 5.0s"
|
|
@@ -438,7 +430,7 @@ class RabbitMQClient:
|
|
|
438
430
|
async def close(self) -> None:
|
|
439
431
|
"""关闭客户端(移除回调+释放资源)"""
|
|
440
432
|
self._closed = True
|
|
441
|
-
|
|
433
|
+
logger.info("开始关闭RabbitMQ客户端...")
|
|
442
434
|
|
|
443
435
|
# 停止重连任务
|
|
444
436
|
if self._current_reconnect_task and not self._current_reconnect_task.done():
|
|
@@ -446,27 +438,20 @@ class RabbitMQClient:
|
|
|
446
438
|
try:
|
|
447
439
|
await self._current_reconnect_task
|
|
448
440
|
except asyncio.CancelledError:
|
|
449
|
-
|
|
441
|
+
logger.debug("重连任务已取消")
|
|
450
442
|
|
|
451
443
|
# 1. 停止消费
|
|
452
444
|
await self.stop_consuming()
|
|
453
445
|
|
|
454
|
-
# 2.
|
|
446
|
+
# 2. 清理回调+状态(单通道无需归还,连接池统一管理)
|
|
455
447
|
async with self._connect_lock:
|
|
456
|
-
if self.
|
|
457
|
-
|
|
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)
|
|
448
|
+
if self._conn_close_callback and self._channel_conn:
|
|
449
|
+
self._channel_conn.close_callbacks.discard(
|
|
450
|
+
self._conn_close_callback)
|
|
466
451
|
self._channel = None
|
|
467
452
|
self._channel_conn = None
|
|
468
453
|
self._exchange = None
|
|
469
454
|
self._queue = None
|
|
470
455
|
self._message_handler = None
|
|
471
456
|
|
|
472
|
-
|
|
457
|
+
logger.info("RabbitMQ客户端已完全关闭")
|