sycommon-python-lib 0.1.46__py3-none-any.whl → 0.1.56b5__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/llm/__init__.py +0 -0
- sycommon/llm/embedding.py +149 -0
- sycommon/llm/get_llm.py +246 -0
- sycommon/llm/llm_logger.py +126 -0
- sycommon/llm/llm_tokens.py +119 -0
- sycommon/logging/async_sql_logger.py +65 -0
- sycommon/logging/kafka_log.py +21 -9
- sycommon/logging/logger_levels.py +23 -0
- sycommon/middleware/context.py +2 -0
- sycommon/middleware/traceid.py +155 -32
- sycommon/notice/__init__.py +0 -0
- sycommon/notice/uvicorn_monitor.py +195 -0
- sycommon/rabbitmq/rabbitmq_client.py +144 -152
- sycommon/rabbitmq/rabbitmq_pool.py +213 -479
- sycommon/rabbitmq/rabbitmq_service.py +77 -127
- sycommon/services.py +78 -75
- sycommon/synacos/feign.py +18 -7
- sycommon/synacos/feign_client.py +26 -8
- sycommon/synacos/nacos_service.py +18 -2
- sycommon/tools/merge_headers.py +97 -0
- sycommon/tools/snowflake.py +290 -23
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/METADATA +15 -10
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/RECORD +30 -18
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/WHEEL +0 -0
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/entry_points.txt +0 -0
- {sycommon_python_lib-0.1.46.dist-info → sycommon_python_lib-0.1.56b5.dist-info}/top_level.txt +0 -0
|
@@ -1,19 +1,30 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
-
|
|
3
|
-
from
|
|
2
|
+
import random
|
|
3
|
+
from typing import Optional, List, Dict, Callable, Tuple
|
|
4
|
+
from aio_pika import connect_robust, RobustChannel, Message
|
|
4
5
|
from aio_pika.abc import (
|
|
5
6
|
AbstractRobustConnection, AbstractQueue, AbstractExchange, AbstractMessage
|
|
6
7
|
)
|
|
7
|
-
from aio_pika.exceptions import ChannelClosed
|
|
8
|
-
import aiormq.exceptions
|
|
9
|
-
|
|
10
8
|
from sycommon.logging.kafka_log import SYLogger
|
|
11
9
|
|
|
12
10
|
logger = SYLogger
|
|
13
11
|
|
|
14
12
|
|
|
13
|
+
class AsyncProperty:
|
|
14
|
+
"""实现 await obj.attr 的支持"""
|
|
15
|
+
|
|
16
|
+
def __init__(self, method):
|
|
17
|
+
self.method = method
|
|
18
|
+
|
|
19
|
+
def __get__(self, obj, objtype=None):
|
|
20
|
+
if obj is None:
|
|
21
|
+
return self
|
|
22
|
+
# 关键:当访问 obj.attr 时,直接返回协程对象,而不是方法本身
|
|
23
|
+
return self.method(obj)
|
|
24
|
+
|
|
25
|
+
|
|
15
26
|
class RabbitMQConnectionPool:
|
|
16
|
-
"""
|
|
27
|
+
"""单连接单通道RabbitMQ客户端 (增强版日志)"""
|
|
17
28
|
|
|
18
29
|
def __init__(
|
|
19
30
|
self,
|
|
@@ -22,14 +33,12 @@ class RabbitMQConnectionPool:
|
|
|
22
33
|
username: str,
|
|
23
34
|
password: str,
|
|
24
35
|
virtualhost: str = "/",
|
|
25
|
-
channel_pool_size: int = 1,
|
|
26
36
|
heartbeat: int = 30,
|
|
27
37
|
app_name: str = "",
|
|
28
38
|
connection_timeout: int = 30,
|
|
29
|
-
reconnect_interval: int =
|
|
39
|
+
reconnect_interval: int = 5,
|
|
30
40
|
prefetch_count: int = 2,
|
|
31
41
|
):
|
|
32
|
-
# 基础配置校验与初始化
|
|
33
42
|
self.hosts = [host.strip() for host in hosts if host.strip()]
|
|
34
43
|
if not self.hosts:
|
|
35
44
|
raise ValueError("至少需要提供一个RabbitMQ主机地址")
|
|
@@ -43,544 +52,269 @@ class RabbitMQConnectionPool:
|
|
|
43
52
|
self.connection_timeout = connection_timeout
|
|
44
53
|
self.reconnect_interval = reconnect_interval
|
|
45
54
|
self.prefetch_count = prefetch_count
|
|
46
|
-
self.channel_pool_size = max(1, channel_pool_size) # 确保池大小不小于1
|
|
47
55
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
self._current_host: Optional[str] = None
|
|
56
|
+
self._current_host: str = random.choice(self.hosts)
|
|
57
|
+
logger.info(f"[INIT] 随机选择RabbitMQ主机: {self._current_host}")
|
|
51
58
|
|
|
52
|
-
#
|
|
53
|
-
self._connection: Optional[AbstractRobustConnection] = None
|
|
54
|
-
self.
|
|
55
|
-
self.
|
|
56
|
-
self._consumer_channels: Dict[str, Tuple[Channel,
|
|
57
|
-
AbstractRobustConnection, Callable, bool, dict]] = {} # 消费者通道跟踪
|
|
59
|
+
# 核心资源
|
|
60
|
+
self._connection: Optional[AbstractRobustConnection] = None
|
|
61
|
+
self._channel: Optional[RobustChannel] = None
|
|
62
|
+
self._consumer_channels: Dict[str, RobustChannel] = {}
|
|
58
63
|
|
|
59
|
-
#
|
|
60
|
-
self._lock = asyncio.Lock()
|
|
64
|
+
# 状态控制
|
|
65
|
+
self._lock = asyncio.Lock()
|
|
61
66
|
self._initialized = False
|
|
62
67
|
self._is_shutdown = False
|
|
63
|
-
self._reconnecting = False # 避免并发重连
|
|
64
|
-
self._connection_version = 0 # 连接版本号(区分新旧连接/通道)
|
|
65
|
-
|
|
66
|
-
def _create_host_iterator(self) -> Iterator[str]:
|
|
67
|
-
"""创建无限循环的节点轮询迭代器"""
|
|
68
|
-
while True:
|
|
69
|
-
for host in self.hosts:
|
|
70
|
-
yield host
|
|
71
|
-
|
|
72
|
-
async def _is_connection_valid(self) -> bool:
|
|
73
|
-
"""原子化检查连接有效性(加锁保证无竞态)"""
|
|
74
|
-
async with self._lock:
|
|
75
|
-
return (
|
|
76
|
-
self._connection is not None
|
|
77
|
-
and not self._connection.is_closed
|
|
78
|
-
and not self._reconnecting
|
|
79
|
-
)
|
|
80
68
|
|
|
81
|
-
@
|
|
69
|
+
@AsyncProperty
|
|
82
70
|
async def is_alive(self) -> bool:
|
|
83
|
-
"""
|
|
84
|
-
if self._is_shutdown:
|
|
85
|
-
return False
|
|
86
|
-
return await self._is_connection_valid()
|
|
87
|
-
|
|
88
|
-
async def _safe_close_old_resources(self):
|
|
89
|
-
"""强制关闭所有旧资源(加锁保证原子性,重连前必调用)"""
|
|
90
|
-
async with self._lock:
|
|
91
|
-
logger.info(f"开始释放旧资源(连接版本: {self._connection_version})...")
|
|
92
|
-
|
|
93
|
-
# 1. 关闭所有消费者通道(独立管理,终止旧自动恢复)
|
|
94
|
-
for queue_name, (channel, _, _, _, _) in self._consumer_channels.items():
|
|
95
|
-
try:
|
|
96
|
-
if not channel.is_closed:
|
|
97
|
-
await channel.close()
|
|
98
|
-
logger.info(f"已关闭队列 {queue_name} 的旧消费者通道(自动恢复终止)")
|
|
99
|
-
except Exception as e:
|
|
100
|
-
logger.warning(f"关闭消费者通道 {queue_name} 失败: {str(e)}")
|
|
101
|
-
self._consumer_channels.clear()
|
|
102
|
-
|
|
103
|
-
# 2. 关闭所有普通通道(空闲+使用中,终止旧自动恢复)
|
|
104
|
-
all_channels = self._free_channels + list(self._used_channels)
|
|
105
|
-
for channel in all_channels:
|
|
106
|
-
try:
|
|
107
|
-
if not channel.is_closed:
|
|
108
|
-
await channel.close()
|
|
109
|
-
except Exception as e:
|
|
110
|
-
logger.warning(f"关闭旧通道失败: {str(e)}")
|
|
111
|
-
self._free_channels.clear()
|
|
112
|
-
self._used_channels.clear()
|
|
113
|
-
|
|
114
|
-
# 3. 强制关闭旧连接(彻底终止旧连接的所有自动恢复)
|
|
115
|
-
if self._connection:
|
|
116
|
-
try:
|
|
117
|
-
if not self._connection.is_closed:
|
|
118
|
-
await self._connection.close()
|
|
119
|
-
logger.info(
|
|
120
|
-
f"已关闭旧连接: {self._current_host}:{self.port}(版本: {self._connection_version})")
|
|
121
|
-
except Exception as e:
|
|
122
|
-
logger.warning(f"关闭旧连接失败: {str(e)}")
|
|
123
|
-
self._connection = None # 置空,确保单连接
|
|
124
|
-
|
|
125
|
-
logger.info("旧资源释放完成(所有旧自动恢复逻辑已终止)")
|
|
126
|
-
|
|
127
|
-
async def _create_single_connection(self) -> AbstractRobustConnection:
|
|
128
|
-
"""创建唯一活跃连接(重连前已释放旧资源,确保单连接)"""
|
|
129
|
-
max_attempts = len(self.hosts) * 2 # 每个节点尝试2次
|
|
130
|
-
attempts = 0
|
|
131
|
-
last_error: Optional[Exception] = None
|
|
132
|
-
|
|
133
|
-
while attempts < max_attempts and not self._is_shutdown:
|
|
134
|
-
self._current_host = next(self._host_iterator)
|
|
135
|
-
conn_url = f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/{self.virtualhost}"
|
|
136
|
-
|
|
137
|
-
try:
|
|
138
|
-
target_version = self._connection_version + 1
|
|
139
|
-
logger.info(
|
|
140
|
-
f"尝试创建连接: {self._current_host}:{self.port} "
|
|
141
|
-
f"(目标版本: {target_version},{attempts+1}/{max_attempts})"
|
|
142
|
-
)
|
|
143
|
-
# 创建连接(保留aio-pika原生自动恢复)
|
|
144
|
-
conn = await connect_robust(
|
|
145
|
-
conn_url,
|
|
146
|
-
properties={
|
|
147
|
-
"connection_name": f"{self.app_name}_conn_v{target_version}",
|
|
148
|
-
"product": self.app_name
|
|
149
|
-
},
|
|
150
|
-
heartbeat=self.heartbeat,
|
|
151
|
-
timeout=self.connection_timeout,
|
|
152
|
-
reconnect_interval=5, # 单节点内部短间隔重连(原生自动恢复)
|
|
153
|
-
max_reconnect_attempts=3, # 单节点最大重试3次
|
|
154
|
-
)
|
|
155
|
-
logger.info(
|
|
156
|
-
f"连接创建成功: {self._current_host}:{self.port}(版本: {target_version})")
|
|
157
|
-
return conn
|
|
158
|
-
except Exception as e:
|
|
159
|
-
attempts += 1
|
|
160
|
-
last_error = e
|
|
161
|
-
logger.error(
|
|
162
|
-
f"连接节点 {self._current_host}:{self.port} 失败({attempts}/{max_attempts}): {str(e)}",
|
|
163
|
-
exc_info=True
|
|
164
|
-
)
|
|
165
|
-
await asyncio.sleep(min(5 * attempts, self.reconnect_interval))
|
|
166
|
-
|
|
167
|
-
raise ConnectionError(
|
|
168
|
-
f"所有节点创建连接失败(节点列表: {self.hosts})"
|
|
169
|
-
) from last_error
|
|
170
|
-
|
|
171
|
-
async def _init_channel_pool(self):
|
|
172
|
-
"""初始化通道池(加锁保证原子性,绑定当前连接)"""
|
|
71
|
+
"""对外暴露的连接存活状态(原子化判断)"""
|
|
173
72
|
async with self._lock:
|
|
174
73
|
if self._is_shutdown:
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
# 校验当前连接有效性
|
|
178
|
-
if not self._connection or self._connection.is_closed:
|
|
179
|
-
raise RuntimeError("无有效连接,无法初始化通道池")
|
|
74
|
+
return False
|
|
180
75
|
|
|
181
|
-
self.
|
|
182
|
-
|
|
76
|
+
if not self._initialized:
|
|
77
|
+
return False
|
|
183
78
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
try:
|
|
187
|
-
channel = await self._connection.channel() # 新通道自带自动恢复
|
|
188
|
-
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
189
|
-
self._free_channels.append(channel)
|
|
190
|
-
except Exception as e:
|
|
191
|
-
logger.error(
|
|
192
|
-
f"创建通道失败(第{i+1}个,连接版本: {self._connection_version}): {str(e)}",
|
|
193
|
-
exc_info=True
|
|
194
|
-
)
|
|
195
|
-
continue
|
|
79
|
+
if self._connection is None or self._connection.is_closed:
|
|
80
|
+
return False
|
|
196
81
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
82
|
+
# 可选:检查主通道是否存活
|
|
83
|
+
if self._channel is None or self._channel.is_closed:
|
|
84
|
+
# 如果你认为通道断了连接也算死,就保留这行;否则删除
|
|
85
|
+
return False
|
|
201
86
|
|
|
202
|
-
|
|
203
|
-
"""连接失效时重连(加锁保护,严格单连接+释放旧资源)"""
|
|
204
|
-
# 快速判断,避免无效加锁
|
|
205
|
-
if self._is_shutdown or self._reconnecting:
|
|
206
|
-
return False
|
|
87
|
+
return True
|
|
207
88
|
|
|
208
|
-
|
|
89
|
+
async def _create_connection_impl(self) -> AbstractRobustConnection:
|
|
90
|
+
"""
|
|
91
|
+
连接创建入口
|
|
92
|
+
"""
|
|
93
|
+
conn_url = (
|
|
94
|
+
f"amqp://{self.username}:{self.password}@{self._current_host}:{self.port}/"
|
|
95
|
+
f"{self.virtualhost}?name={self.app_name}&heartbeat={self.heartbeat}"
|
|
96
|
+
f"&reconnect_interval={self.reconnect_interval}&fail_fast=1"
|
|
97
|
+
)
|
|
98
|
+
logger.info(
|
|
99
|
+
f"🔌 [CONNECT_START] 尝试创建连接 -> {self._current_host}:{self.port}")
|
|
209
100
|
try:
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
self._connection_version += 1
|
|
218
|
-
target_version = self._connection_version
|
|
219
|
-
|
|
220
|
-
# 3. 创建新连接(保留原生自动恢复)
|
|
221
|
-
new_conn = await self._create_single_connection()
|
|
222
|
-
|
|
223
|
-
# 4. 绑定新连接(加锁保证原子性)
|
|
224
|
-
async with self._lock:
|
|
225
|
-
self._connection = new_conn
|
|
226
|
-
|
|
227
|
-
# 5. 重新初始化通道池(新通道带自动恢复)
|
|
228
|
-
await self._init_channel_pool()
|
|
229
|
-
|
|
230
|
-
# 6. 恢复消费者通道(新通道带自动恢复)
|
|
231
|
-
await self._restore_consumer_channels()
|
|
101
|
+
conn = await connect_robust(conn_url, timeout=self.connection_timeout)
|
|
102
|
+
# 注意:connect_robust 返回时,底层 TCP 可能还在握手,但对象已创建
|
|
103
|
+
logger.info(f"✅ [CONNECT_OK] 连接对象创建成功: {id(conn)}")
|
|
104
|
+
return conn
|
|
105
|
+
except Exception as e:
|
|
106
|
+
logger.error(f"❌ [CONNECT_FAIL] 连接创建失败: {str(e)}")
|
|
107
|
+
raise ConnectionError(f"无法连接RabbitMQ {self._current_host}") from e
|
|
232
108
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
109
|
+
async def _create_channel_impl(self, connection: AbstractRobustConnection) -> RobustChannel:
|
|
110
|
+
"""创建通道"""
|
|
111
|
+
try:
|
|
112
|
+
channel = await connection.channel()
|
|
113
|
+
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
114
|
+
logger.debug(f"✅ [CHANNEL_OK] 通道创建成功: {id(channel)}")
|
|
115
|
+
return channel
|
|
237
116
|
except Exception as e:
|
|
238
|
-
logger.error(f"
|
|
239
|
-
|
|
240
|
-
self._initialized = False
|
|
241
|
-
return False
|
|
242
|
-
finally:
|
|
243
|
-
self._reconnecting = False
|
|
117
|
+
logger.error(f"❌ [CHANNEL_FAIL] 通道创建失败: {str(e)}")
|
|
118
|
+
raise
|
|
244
119
|
|
|
245
|
-
async def
|
|
246
|
-
"""
|
|
120
|
+
async def _ensure_main_channel(self) -> RobustChannel:
|
|
121
|
+
"""确保主通道有效 (原子操作)"""
|
|
247
122
|
async with self._lock:
|
|
248
|
-
if
|
|
249
|
-
|
|
250
|
-
logger.info(
|
|
251
|
-
f"开始恢复 {len(self._consumer_channels)} 个消费者通道(连接版本: {self._connection_version})")
|
|
252
|
-
|
|
253
|
-
# 临时保存消费者配置(队列名、回调、auto_ack、参数)
|
|
254
|
-
consumer_configs = list(self._consumer_channels.items())
|
|
255
|
-
self._consumer_channels.clear()
|
|
256
|
-
|
|
257
|
-
# 重新创建消费者(不加锁,避免阻塞其他操作)
|
|
258
|
-
for queue_name, (_, _, callback, auto_ack, kwargs) in consumer_configs:
|
|
259
|
-
try:
|
|
260
|
-
await self.consume_queue(queue_name, callback, auto_ack, **kwargs)
|
|
261
|
-
except Exception as e:
|
|
262
|
-
logger.error(
|
|
263
|
-
f"恢复消费者队列 {queue_name} 失败: {str(e)}", exc_info=True)
|
|
123
|
+
if self._is_shutdown:
|
|
124
|
+
raise RuntimeError("客户端已关闭")
|
|
264
125
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
126
|
+
# 1. 确保底层连接存在
|
|
127
|
+
conn = self._connection
|
|
128
|
+
if conn is None or conn.is_closed:
|
|
129
|
+
logger.info("⚠️ [RECONNECT] 检测到连接不存在或已关闭,开始重建...")
|
|
130
|
+
conn = await self._create_connection_impl()
|
|
131
|
+
self._connection = conn
|
|
132
|
+
logger.info(f"🔗 [UPDATE] 连接对象已更新: {id(conn)}")
|
|
270
133
|
|
|
271
|
-
#
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
and not self._reconnecting
|
|
276
|
-
)
|
|
277
|
-
if not current_valid:
|
|
278
|
-
# 连接失效,触发重连(不加锁,避免死锁)
|
|
279
|
-
asyncio.create_task(self._reconnect_if_needed())
|
|
280
|
-
return
|
|
134
|
+
# 2. 确保主通道存在
|
|
135
|
+
if self._channel is None or self._channel.is_closed:
|
|
136
|
+
logger.info("⚠️ [RECOVER_CHANNEL] 检测到主通道不存在或已关闭,开始恢复...")
|
|
137
|
+
self._channel = await self._create_channel_impl(conn)
|
|
281
138
|
|
|
282
|
-
|
|
283
|
-
valid_free = []
|
|
284
|
-
for chan in self._free_channels:
|
|
285
|
-
try:
|
|
286
|
-
if not chan.is_closed and chan.connection == self._connection:
|
|
287
|
-
valid_free.append(chan)
|
|
288
|
-
else:
|
|
289
|
-
logger.warning(f"清理失效空闲通道(连接版本不匹配或已关闭)")
|
|
290
|
-
except Exception:
|
|
291
|
-
logger.warning(f"清理异常空闲通道")
|
|
292
|
-
self._free_channels = valid_free
|
|
293
|
-
|
|
294
|
-
# 3. 清理使用中通道(仅保留当前连接的有效通道)
|
|
295
|
-
valid_used = set()
|
|
296
|
-
for chan in self._used_channels:
|
|
297
|
-
try:
|
|
298
|
-
if not chan.is_closed and chan.connection == self._connection:
|
|
299
|
-
valid_used.add(chan)
|
|
300
|
-
else:
|
|
301
|
-
logger.warning(f"清理失效使用中通道(连接版本不匹配或已关闭)")
|
|
302
|
-
except Exception:
|
|
303
|
-
logger.warning(f"清理异常使用中通道")
|
|
304
|
-
self._used_channels = valid_used
|
|
305
|
-
|
|
306
|
-
# 4. 补充通道到指定大小(新通道带自动恢复)
|
|
307
|
-
total_valid = len(self._free_channels) + len(self._used_channels)
|
|
308
|
-
missing = self.channel_pool_size - total_valid
|
|
309
|
-
if missing > 0:
|
|
310
|
-
logger.info(
|
|
311
|
-
f"通道池缺少{missing}个通道,补充中(连接版本: {self._connection_version})...")
|
|
312
|
-
for _ in range(missing):
|
|
313
|
-
try:
|
|
314
|
-
channel = await self._connection.channel() # 新通道带自动恢复
|
|
315
|
-
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
316
|
-
self._free_channels.append(channel)
|
|
317
|
-
except Exception as e:
|
|
318
|
-
logger.error(f"补充通道失败: {str(e)}", exc_info=True)
|
|
319
|
-
break
|
|
139
|
+
return self._channel
|
|
320
140
|
|
|
321
141
|
async def init_pools(self):
|
|
322
|
-
"""
|
|
142
|
+
"""
|
|
143
|
+
初始化入口与异常处理 (修复泄漏的关键)
|
|
144
|
+
"""
|
|
323
145
|
async with self._lock:
|
|
146
|
+
if self._is_shutdown:
|
|
147
|
+
raise RuntimeError("客户端已关闭")
|
|
324
148
|
if self._initialized:
|
|
325
|
-
logger.warning("通道池已初始化,无需重复调用")
|
|
326
149
|
return
|
|
327
|
-
if self._is_shutdown:
|
|
328
|
-
raise RuntimeError("通道池已关闭,无法初始化")
|
|
329
150
|
|
|
151
|
+
conn_created_in_this_try = None
|
|
330
152
|
try:
|
|
331
|
-
#
|
|
332
|
-
|
|
153
|
+
# 步骤 A: 创建连接 (在锁外进行,避免阻塞其他操作)
|
|
154
|
+
conn = await self._create_connection_impl()
|
|
155
|
+
conn_created_in_this_try = conn # 记录本次创建的对象,用于失败回滚
|
|
333
156
|
|
|
334
|
-
#
|
|
157
|
+
# 步骤 B: 更新状态和初始化通道 (在锁内进行,保证原子性)
|
|
335
158
|
async with self._lock:
|
|
336
|
-
self.
|
|
337
|
-
|
|
159
|
+
if self._is_shutdown:
|
|
160
|
+
# 如果在创建连接期间,外部调用了 close,则必须立即清理刚创建的连接
|
|
161
|
+
logger.warning("⚠️ [ABORT] 检测到关闭信号,放弃初始化并清理资源")
|
|
162
|
+
raise RuntimeError("客户端已关闭")
|
|
338
163
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
# 4. 标记为已初始化(加锁保证原子性)
|
|
343
|
-
async with self._lock:
|
|
164
|
+
self._connection = conn
|
|
165
|
+
self._channel = await self._create_channel_impl(conn)
|
|
344
166
|
self._initialized = True
|
|
167
|
+
logger.info(
|
|
168
|
+
f"🚀 [INIT_SUCCESS] 客户端初始化完成. ConnID: {id(self._connection)}")
|
|
345
169
|
|
|
346
|
-
logger.info("RabbitMQ单连接通道池初始化完成(所有通道均带自动恢复)")
|
|
347
170
|
except Exception as e:
|
|
348
|
-
logger.error(f"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
async def acquire_channel(self) -> Tuple[Channel, AbstractRobustConnection]:
|
|
353
|
-
"""获取通道(加锁保证原子性,返回当前连接+带自动恢复的通道)"""
|
|
354
|
-
# 快速判断,避免无效加锁
|
|
355
|
-
async with self._lock:
|
|
356
|
-
if not self._initialized:
|
|
357
|
-
raise RuntimeError("通道池未初始化,请先调用init_pools()")
|
|
358
|
-
if self._is_shutdown:
|
|
359
|
-
raise RuntimeError("通道池已关闭,无法获取通道")
|
|
360
|
-
|
|
361
|
-
# 先清理失效通道(加锁保证原子性)
|
|
362
|
-
await self._clean_invalid_channels()
|
|
363
|
-
|
|
364
|
-
async with self._lock:
|
|
365
|
-
# 双重校验连接有效性
|
|
366
|
-
current_valid = (
|
|
367
|
-
self._connection is not None
|
|
368
|
-
and not self._connection.is_closed
|
|
369
|
-
and not self._reconnecting
|
|
370
|
-
)
|
|
371
|
-
if not current_valid:
|
|
372
|
-
# 连接失效,触发重连(不加锁,避免死锁)
|
|
373
|
-
reconnect_success = await self._reconnect_if_needed()
|
|
374
|
-
if not reconnect_success:
|
|
375
|
-
raise RuntimeError("连接失效且重连失败,无法获取通道")
|
|
376
|
-
|
|
377
|
-
# 优先从空闲池获取(带自动恢复的通道)
|
|
378
|
-
if self._free_channels:
|
|
379
|
-
channel = self._free_channels.pop()
|
|
380
|
-
self._used_channels.add(channel)
|
|
381
|
-
return channel, self._connection
|
|
382
|
-
|
|
383
|
-
# 通道池已满,创建临时通道(带自动恢复,用完关闭)
|
|
384
|
-
try:
|
|
385
|
-
channel = await self._connection.channel() # 临时通道带自动恢复
|
|
386
|
-
await channel.set_qos(prefetch_count=self.prefetch_count)
|
|
387
|
-
self._used_channels.add(channel)
|
|
171
|
+
logger.error(f"💥 [INIT_ERROR] 初始化流程异常: {str(e)}", exc_info=True)
|
|
172
|
+
# 如果步骤A成功但步骤B失败(例如通道创建失败),或者步骤B中出错,
|
|
173
|
+
# 必须显式关闭在步骤A中创建的连接,否则它会变成“游离连接”。
|
|
174
|
+
if conn_created_in_this_try:
|
|
388
175
|
logger.warning(
|
|
389
|
-
f"
|
|
390
|
-
)
|
|
391
|
-
return channel, self._connection
|
|
392
|
-
except Exception as e:
|
|
393
|
-
logger.error(f"获取通道失败: {str(e)}", exc_info=True)
|
|
394
|
-
raise
|
|
395
|
-
|
|
396
|
-
async def release_channel(self, channel: Channel, conn: AbstractRobustConnection):
|
|
397
|
-
"""释放通道(加锁保证原子性,仅归还当前连接的有效通道)"""
|
|
398
|
-
# 快速判断,避免无效加锁
|
|
399
|
-
if not channel or not conn or self._is_shutdown:
|
|
400
|
-
return
|
|
401
|
-
|
|
402
|
-
async with self._lock:
|
|
403
|
-
# 仅处理当前连接的通道(旧连接的通道直接关闭)
|
|
404
|
-
if conn != self._connection:
|
|
405
|
-
try:
|
|
406
|
-
if not channel.is_closed:
|
|
407
|
-
await channel.close()
|
|
408
|
-
logger.warning(f"已关闭非当前连接的通道(版本不匹配,自动恢复终止)")
|
|
409
|
-
except Exception as e:
|
|
410
|
-
logger.warning(f"关闭非当前连接通道失败: {str(e)}")
|
|
411
|
-
return
|
|
412
|
-
|
|
413
|
-
# 通道不在使用中,直接返回
|
|
414
|
-
if channel not in self._used_channels:
|
|
415
|
-
return
|
|
416
|
-
|
|
417
|
-
# 移除使用中标记
|
|
418
|
-
self._used_channels.remove(channel)
|
|
419
|
-
|
|
420
|
-
# 仅归还有效通道(当前连接有效+通道未关闭+池未满)
|
|
421
|
-
current_valid = (
|
|
422
|
-
self._connection is not None
|
|
423
|
-
and not self._connection.is_closed
|
|
424
|
-
and not self._reconnecting
|
|
425
|
-
)
|
|
426
|
-
if current_valid and not channel.is_closed and len(self._free_channels) < self.channel_pool_size:
|
|
427
|
-
self._free_channels.append(channel)
|
|
428
|
-
else:
|
|
429
|
-
# 无效通道直接关闭(终止自动恢复)
|
|
176
|
+
f"🧹 [LEAK_PREVENTION] 检测到初始化失败,正在显式关闭刚创建的连接: {id(conn_created_in_this_try)}")
|
|
430
177
|
try:
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
178
|
+
await conn_created_in_this_try.close()
|
|
179
|
+
logger.info(
|
|
180
|
+
f"✅ [CLOSE_OK] 泄漏连接已关闭: {id(conn_created_in_this_try)}")
|
|
181
|
+
except Exception as close_err:
|
|
182
|
+
logger.error(f"❌ [CLOSE_ERR] 关闭泄漏连接时出错: {str(close_err)}")
|
|
435
183
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
return await channel.declare_queue(queue_name, **kwargs)
|
|
441
|
-
finally:
|
|
442
|
-
await self.release_channel(channel, conn)
|
|
184
|
+
# 如果是因为中途关闭导致的错误,不需要再次调用全局 close,否则调用
|
|
185
|
+
if not self._is_shutdown:
|
|
186
|
+
await self.close()
|
|
187
|
+
raise
|
|
443
188
|
|
|
444
|
-
async def
|
|
445
|
-
"""
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
finally:
|
|
450
|
-
await self.release_channel(channel, conn)
|
|
189
|
+
async def acquire_channel(self) -> Tuple[RobustChannel, AbstractRobustConnection]:
|
|
190
|
+
"""获取主通道"""
|
|
191
|
+
if not self._initialized and not self._is_shutdown:
|
|
192
|
+
await self.init_pools()
|
|
193
|
+
return await self._ensure_main_channel(), self._connection
|
|
451
194
|
|
|
452
195
|
async def publish_message(self, routing_key: str, message_body: bytes, exchange_name: str = "", **kwargs):
|
|
453
|
-
"""
|
|
454
|
-
channel,
|
|
196
|
+
"""发布消息"""
|
|
197
|
+
channel, _ = await self.acquire_channel()
|
|
455
198
|
try:
|
|
456
199
|
exchange = channel.default_exchange if not exchange_name else await channel.get_exchange(exchange_name)
|
|
457
200
|
message = Message(body=message_body, **kwargs)
|
|
458
201
|
await exchange.publish(message, routing_key=routing_key)
|
|
459
|
-
logger.debug(
|
|
460
|
-
f"消息发布成功 - 连接: {self._current_host}:{self.port}(版本: {self._connection_version}), "
|
|
461
|
-
f"交换机: {exchange.name}, 路由键: {routing_key}"
|
|
462
|
-
)
|
|
202
|
+
logger.debug(f"📤 [PUBLISH] 消息发布成功 - RK: {routing_key}")
|
|
463
203
|
except Exception as e:
|
|
464
|
-
logger.error(f"
|
|
465
|
-
# 发布失败触发重连(下次使用新通道)
|
|
466
|
-
asyncio.create_task(self._reconnect_if_needed())
|
|
204
|
+
logger.error(f"❌ [PUBLISH_FAIL] 发布失败: {str(e)}")
|
|
467
205
|
raise
|
|
468
|
-
finally:
|
|
469
|
-
await self.release_channel(channel, conn)
|
|
470
206
|
|
|
471
207
|
async def consume_queue(self, queue_name: str, callback: Callable[[AbstractMessage], asyncio.Future], auto_ack: bool = False, **kwargs):
|
|
472
|
-
"""
|
|
473
|
-
|
|
208
|
+
"""消费队列"""
|
|
209
|
+
if not self._initialized:
|
|
210
|
+
await self.init_pools()
|
|
211
|
+
|
|
474
212
|
async with self._lock:
|
|
475
|
-
if not self._initialized:
|
|
476
|
-
raise RuntimeError("通道池未初始化,请先调用init_pools()")
|
|
477
213
|
if self._is_shutdown:
|
|
478
|
-
raise RuntimeError("
|
|
214
|
+
raise RuntimeError("客户端已关闭")
|
|
215
|
+
if queue_name in self._consumer_channels:
|
|
216
|
+
logger.warning(f"⚠️ [CONSUMER_EXISTS] 队列 {queue_name} 已在消费中")
|
|
217
|
+
return
|
|
218
|
+
if not self._connection or self._connection.is_closed:
|
|
219
|
+
raise RuntimeError("连接不可用,无法启动消费")
|
|
479
220
|
|
|
480
|
-
# 先声明队列(确保队列存在)
|
|
481
221
|
await self.declare_queue(queue_name, **kwargs)
|
|
482
222
|
|
|
483
|
-
|
|
484
|
-
|
|
223
|
+
try:
|
|
224
|
+
# 获取原始连接对象创建新通道
|
|
225
|
+
conn = self._connection
|
|
226
|
+
consumer_channel = await conn.channel()
|
|
227
|
+
await consumer_channel.set_qos(prefetch_count=self.prefetch_count)
|
|
228
|
+
logger.info(
|
|
229
|
+
f"✅ [CONSUMER_CHANNEL_OK] 消费者通道创建: {id(consumer_channel)}")
|
|
485
230
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
231
|
+
async with self._lock:
|
|
232
|
+
if self._is_shutdown:
|
|
233
|
+
await consumer_channel.close()
|
|
234
|
+
return
|
|
235
|
+
self._consumer_channels[queue_name] = consumer_channel
|
|
490
236
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
self._connection is not None
|
|
500
|
-
and not self._connection.is_closed
|
|
501
|
-
and not self._reconnecting
|
|
502
|
-
)
|
|
503
|
-
|
|
504
|
-
# 通道/连接失效,触发重连恢复
|
|
505
|
-
if not conn_matched or not channel_valid or not current_conn_valid:
|
|
506
|
-
logger.warning(
|
|
507
|
-
f"消费者通道 {queue_name} 失效(连接版本不匹配/通道关闭),触发重连恢复")
|
|
508
|
-
|
|
509
|
-
# 移除旧消费者记录(加锁保证原子性)
|
|
510
|
-
async with self._lock:
|
|
511
|
-
if self._consumer_channels.get(queue_name) == (channel, conn, callback, auto_ack, kwargs):
|
|
512
|
-
del self._consumer_channels[queue_name]
|
|
513
|
-
|
|
514
|
-
# 释放旧通道(加锁保证原子性)
|
|
515
|
-
await self.release_channel(channel, conn)
|
|
516
|
-
|
|
517
|
-
# 重新创建消费者(新通道带自动恢复)
|
|
518
|
-
asyncio.create_task(self.consume_queue(
|
|
519
|
-
queue_name, callback, auto_ack, **kwargs))
|
|
520
|
-
|
|
521
|
-
# Nack消息(避免丢失)
|
|
237
|
+
async def consume_callback_wrapper(message: AbstractMessage):
|
|
238
|
+
try:
|
|
239
|
+
await callback(message)
|
|
240
|
+
if not auto_ack:
|
|
241
|
+
await message.ack()
|
|
242
|
+
except Exception as e:
|
|
243
|
+
logger.error(
|
|
244
|
+
f"❌ [CALLBACK_ERR] 消费回调异常 {queue_name}: {str(e)}")
|
|
522
245
|
if not auto_ack:
|
|
523
246
|
await message.nack(requeue=True)
|
|
524
|
-
return
|
|
525
|
-
|
|
526
|
-
# 执行业务回调
|
|
527
|
-
await callback(message)
|
|
528
|
-
if not auto_ack:
|
|
529
|
-
await message.ack()
|
|
530
|
-
except ChannelClosed as e:
|
|
531
|
-
logger.error(f"消费者通道 {queue_name} 关闭: {str(e)}", exc_info=True)
|
|
532
|
-
if not auto_ack:
|
|
533
|
-
await message.nack(requeue=True)
|
|
534
|
-
asyncio.create_task(self._reconnect_if_needed())
|
|
535
|
-
except aiormq.exceptions.ChannelInvalidStateError as e:
|
|
536
|
-
logger.error(
|
|
537
|
-
f"消费者通道 {queue_name} 状态异常: {str(e)}", exc_info=True)
|
|
538
|
-
if not auto_ack:
|
|
539
|
-
await message.nack(requeue=True)
|
|
540
|
-
asyncio.create_task(self.consume_queue(
|
|
541
|
-
queue_name, callback, auto_ack, **kwargs))
|
|
542
|
-
except Exception as e:
|
|
543
|
-
logger.error(
|
|
544
|
-
f"消费消息失败(队列: {queue_name}): {str(e)}", exc_info=True)
|
|
545
|
-
if not auto_ack:
|
|
546
|
-
await message.nack(requeue=True)
|
|
547
|
-
|
|
548
|
-
# 日志输出(加锁获取当前连接信息)
|
|
549
|
-
async with self._lock:
|
|
550
|
-
current_host = self._current_host
|
|
551
|
-
current_version = self._connection_version
|
|
552
|
-
|
|
553
|
-
logger.info(
|
|
554
|
-
f"开始消费队列: {queue_name} - 连接: {current_host}:{self.port}(版本: {current_version}), "
|
|
555
|
-
f"通道带自动恢复"
|
|
556
|
-
)
|
|
557
247
|
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
await channel.basic_consume(
|
|
561
|
-
queue_name,
|
|
562
|
-
consumer_callback=consume_callback_wrapper,
|
|
563
|
-
auto_ack=auto_ack,
|
|
564
|
-
**kwargs
|
|
248
|
+
await consumer_channel.basic_consume(
|
|
249
|
+
queue_name, consumer_callback=consume_callback_wrapper, auto_ack=auto_ack, **kwargs
|
|
565
250
|
)
|
|
251
|
+
logger.info(f"🎧 [CONSUME_START] 开始消费队列: {queue_name}")
|
|
252
|
+
|
|
566
253
|
except Exception as e:
|
|
567
|
-
logger.error(f"
|
|
568
|
-
# 清理异常的消费者记录和通道(加锁保证原子性)
|
|
569
|
-
await self.release_channel(channel, conn)
|
|
254
|
+
logger.error(f"💥 [CONSUME_ERR] 启动消费失败 {queue_name}: {str(e)}")
|
|
570
255
|
async with self._lock:
|
|
571
|
-
if self._consumer_channels
|
|
256
|
+
if queue_name in self._consumer_channels:
|
|
572
257
|
del self._consumer_channels[queue_name]
|
|
573
258
|
raise
|
|
574
259
|
|
|
575
260
|
async def close(self):
|
|
576
|
-
"""
|
|
261
|
+
"""
|
|
262
|
+
资源销毁入口
|
|
263
|
+
"""
|
|
577
264
|
async with self._lock:
|
|
578
265
|
if self._is_shutdown:
|
|
579
|
-
logger.warning("通道池已关闭,无需重复操作")
|
|
580
266
|
return
|
|
581
267
|
self._is_shutdown = True
|
|
268
|
+
self._initialized = False
|
|
269
|
+
# 记录即将关闭的连接ID
|
|
270
|
+
conn_to_close_id = id(
|
|
271
|
+
self._connection) if self._connection else None
|
|
272
|
+
|
|
273
|
+
logger.info(f"🛑 [CLOSE_START] 开始关闭客户端... (准备关闭连接: {conn_to_close_id})")
|
|
274
|
+
|
|
275
|
+
# 1. 关闭消费者通道
|
|
276
|
+
channels_to_close = []
|
|
277
|
+
async with self._lock:
|
|
278
|
+
channels_to_close = list(self._consumer_channels.values())
|
|
279
|
+
self._consumer_channels.clear()
|
|
280
|
+
|
|
281
|
+
for ch in channels_to_close:
|
|
282
|
+
try:
|
|
283
|
+
if not ch.is_closed:
|
|
284
|
+
await ch.close()
|
|
285
|
+
logger.debug(f"✅ [CLOSE_CHANNEL] 消费者通道已关闭")
|
|
286
|
+
except Exception as e:
|
|
287
|
+
logger.warning(f"❌ [CLOSE_CHANNEL_ERR] 关闭消费者通道失败: {str(e)}")
|
|
288
|
+
|
|
289
|
+
# 2. 关闭主通道
|
|
290
|
+
if self._channel:
|
|
291
|
+
try:
|
|
292
|
+
if not self._channel.is_closed:
|
|
293
|
+
await self._channel.close()
|
|
294
|
+
logger.info(f"✅ [CLOSE_CHANNEL] 主通道已关闭")
|
|
295
|
+
except Exception:
|
|
296
|
+
pass
|
|
297
|
+
self._channel = None
|
|
298
|
+
|
|
299
|
+
# 3. 关闭连接
|
|
300
|
+
# 确保在 finally 或显式 close 中调用 connection.close()
|
|
301
|
+
if self._connection:
|
|
302
|
+
try:
|
|
303
|
+
# 打印关闭操作
|
|
304
|
+
logger.info(f"🔌 [CLOSE_CONN] 正在关闭连接: {id(self._connection)}")
|
|
305
|
+
await self._connection.close()
|
|
306
|
+
logger.info(f"✅ [CLOSE_OK] 连接已成功关闭: {id(self._connection)}")
|
|
307
|
+
except Exception as e:
|
|
308
|
+
logger.warning(f"❌ [CLOSE_ERR] 关闭连接失败: {str(e)}")
|
|
309
|
+
self._connection = None
|
|
582
310
|
|
|
583
|
-
logger.info("
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
311
|
+
logger.info("🏁 [CLOSE_DONE] RabbitMQ客户端已完全关闭")
|
|
312
|
+
|
|
313
|
+
# --- 辅助方法省略 (declare_queue 等) ---
|
|
314
|
+
async def declare_queue(self, queue_name: str, **kwargs) -> AbstractQueue:
|
|
315
|
+
channel, _ = await self.acquire_channel()
|
|
316
|
+
return await channel.declare_queue(queue_name, **kwargs)
|
|
317
|
+
|
|
318
|
+
async def declare_exchange(self, exchange_name: str, exchange_type: str = "direct", **kwargs) -> AbstractExchange:
|
|
319
|
+
channel, _ = await self.acquire_channel()
|
|
320
|
+
return await channel.declare_exchange(exchange_name, exchange_type, **kwargs)
|