mdbq 4.0.111__tar.gz → 4.0.113__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.
Potentially problematic release.
This version of mdbq might be problematic. Click here for more details.
- {mdbq-4.0.111 → mdbq-4.0.113}/PKG-INFO +1 -1
- mdbq-4.0.113/mdbq/__version__.py +1 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/auth/auth_backend.py +14 -5
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/redis/redis_cache.py +268 -101
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq.egg-info/PKG-INFO +1 -1
- mdbq-4.0.111/mdbq/__version__.py +0 -1
- {mdbq-4.0.111 → mdbq-4.0.113}/README.txt +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/auth/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/auth/rate_limiter.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/js/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/js/jc.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/log/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/log/mylogger.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/myconf/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/myconf/myconf.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/deduplicator.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/mysql.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/s_query.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/unique_.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/mysql/uploader.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/download_sku_picture.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/error_handler.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/otk.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/pov_city.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/other/ua_sj.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/pbix/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/pbix/pbix_refresh.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/pbix/refresh_all.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/redis/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/redis/getredis.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/route/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/route/analytics.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/route/monitor.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/route/routes.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/selenium/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/selenium/get_driver.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq/spider/__init__.py +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq.egg-info/SOURCES.txt +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq.egg-info/dependency_links.txt +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/mdbq.egg-info/top_level.txt +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/setup.cfg +0 -0
- {mdbq-4.0.111 → mdbq-4.0.113}/setup.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = '4.0.113'
|
|
@@ -135,19 +135,28 @@ class StandaloneAuthManager:
|
|
|
135
135
|
|
|
136
136
|
self.pool = PooledDB(
|
|
137
137
|
creator=pymysql,
|
|
138
|
-
maxconnections=
|
|
139
|
-
mincached=
|
|
140
|
-
maxcached=
|
|
138
|
+
maxconnections=4, # 最大连接数
|
|
139
|
+
mincached=2, # 初始空闲连接数
|
|
140
|
+
maxcached=4, # 最大缓存连接数
|
|
141
|
+
maxshared=0, # 不使用共享连接
|
|
141
142
|
blocking=True,
|
|
143
|
+
maxusage=1000, # 连接最大使用次数,防止连接泄漏
|
|
144
|
+
setsession=[], # 连接建立时执行的SQL
|
|
145
|
+
reset=True, # 连接归还时重置状态
|
|
146
|
+
failures=None, # 异常处理
|
|
147
|
+
ping=7, # 增加ping间隔:1 -> 7(7秒检查一次连接)
|
|
142
148
|
host=self.db_config['host'],
|
|
143
149
|
port=int(self.db_config['port']),
|
|
144
150
|
user=self.db_config['user'],
|
|
145
151
|
password=self.db_config['password'],
|
|
146
152
|
database=self.db_name,
|
|
147
|
-
ping=1,
|
|
148
153
|
charset='utf8mb4',
|
|
149
154
|
cursorclass=pymysql.cursors.DictCursor,
|
|
150
|
-
autocommit=True
|
|
155
|
+
autocommit=True, # 启用自动提交,避免锁冲突
|
|
156
|
+
# 添加连接超时设置
|
|
157
|
+
connect_timeout=10, # 连接超时10秒
|
|
158
|
+
read_timeout=30, # 读取超时30秒
|
|
159
|
+
write_timeout=30, # 写入超时30秒
|
|
151
160
|
# 使用系统本地时区,不强制设置UTC
|
|
152
161
|
)
|
|
153
162
|
|
|
@@ -18,9 +18,17 @@ import socket
|
|
|
18
18
|
from datetime import datetime, date
|
|
19
19
|
from decimal import Decimal
|
|
20
20
|
from uuid import UUID
|
|
21
|
-
from typing import Optional, Dict, Any, List, Union
|
|
21
|
+
from typing import Optional, Dict, Any, List, Union, Callable
|
|
22
22
|
import redis
|
|
23
23
|
from mdbq.log import mylogger
|
|
24
|
+
|
|
25
|
+
import enum
|
|
26
|
+
from typing import Optional, Dict, Any, List, Union, Callable
|
|
27
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
28
|
+
import asyncio
|
|
29
|
+
from threading import Event
|
|
30
|
+
import functools
|
|
31
|
+
|
|
24
32
|
logger = mylogger.MyLogger(
|
|
25
33
|
logging_mode='file',
|
|
26
34
|
log_level='info',
|
|
@@ -34,6 +42,14 @@ logger = mylogger.MyLogger(
|
|
|
34
42
|
)
|
|
35
43
|
|
|
36
44
|
|
|
45
|
+
class CacheSystemState(enum.Enum):
|
|
46
|
+
"""缓存系统状态枚举"""
|
|
47
|
+
INITIALIZING = "initializing"
|
|
48
|
+
READY = "ready"
|
|
49
|
+
MYSQL_READY = "mysql_ready"
|
|
50
|
+
ERROR = "error"
|
|
51
|
+
|
|
52
|
+
|
|
37
53
|
class MySQLDataEncoder(json.JSONEncoder):
|
|
38
54
|
"""自定义JSON编码器,支持MySQL常见数据类型"""
|
|
39
55
|
def default(self, obj):
|
|
@@ -191,6 +207,11 @@ class SmartCacheSystem:
|
|
|
191
207
|
|
|
192
208
|
self.logger = logger
|
|
193
209
|
|
|
210
|
+
# 状态管理
|
|
211
|
+
self._state = CacheSystemState.INITIALIZING
|
|
212
|
+
self._ready_event = Event()
|
|
213
|
+
self._mysql_ready_event = Event()
|
|
214
|
+
|
|
194
215
|
# 统计数据
|
|
195
216
|
self.stats = {
|
|
196
217
|
'hits': 0,
|
|
@@ -212,16 +233,69 @@ class SmartCacheSystem:
|
|
|
212
233
|
self._stats_thread = None
|
|
213
234
|
self._stats_lock = threading.RLock()
|
|
214
235
|
|
|
215
|
-
#
|
|
216
|
-
self.
|
|
217
|
-
|
|
236
|
+
# 使用线程池异步初始化
|
|
237
|
+
self._executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix=f"cache_{instance_name}")
|
|
238
|
+
|
|
239
|
+
# 立即设置为基础就绪状态(Redis缓存可用)
|
|
240
|
+
if self._test_redis_connection():
|
|
241
|
+
self._state = CacheSystemState.READY
|
|
242
|
+
self._ready_event.set()
|
|
243
|
+
|
|
244
|
+
# 异步初始化MySQL相关功能
|
|
245
|
+
self._executor.submit(self._initialize_mysql_features)
|
|
218
246
|
|
|
219
247
|
self.logger.info("智能缓存系统初始化完成", {
|
|
220
248
|
'instance_name': self.instance_name,
|
|
221
|
-
'
|
|
222
|
-
'
|
|
249
|
+
'state': self._state.value,
|
|
250
|
+
'redis_ready': self._ready_event.is_set(),
|
|
251
|
+
'mysql_enabled': self.mysql_pool is not None
|
|
223
252
|
})
|
|
224
253
|
|
|
254
|
+
def _initialize_mysql_features(self):
|
|
255
|
+
"""在后台初始化MySQL相关功能"""
|
|
256
|
+
try:
|
|
257
|
+
if self.mysql_pool:
|
|
258
|
+
# 延迟初始化MySQL(给系统一些启动时间)
|
|
259
|
+
time.sleep(5) # 等待5秒让主系统完全启动
|
|
260
|
+
|
|
261
|
+
if self._init_mysql_db():
|
|
262
|
+
self._state = CacheSystemState.MYSQL_READY
|
|
263
|
+
self._mysql_ready_event.set()
|
|
264
|
+
|
|
265
|
+
# MySQL就绪后再启动统计线程
|
|
266
|
+
self._start_stats_worker()
|
|
267
|
+
|
|
268
|
+
self.logger.info("MySQL功能初始化完成", {
|
|
269
|
+
'instance_name': self.instance_name,
|
|
270
|
+
'state': self._state.value
|
|
271
|
+
})
|
|
272
|
+
else:
|
|
273
|
+
self.logger.warning("MySQL初始化失败,缓存功能仍可正常使用")
|
|
274
|
+
else:
|
|
275
|
+
self.logger.info("未配置MySQL,跳过统计功能")
|
|
276
|
+
|
|
277
|
+
except Exception as e:
|
|
278
|
+
self._state = CacheSystemState.ERROR
|
|
279
|
+
self.logger.error(f"MySQL功能初始化异常: {e}")
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def is_ready(self) -> bool:
|
|
283
|
+
"""检查缓存系统是否就绪(Redis可用即为就绪)"""
|
|
284
|
+
return self._ready_event.is_set()
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def is_mysql_ready(self) -> bool:
|
|
288
|
+
"""检查MySQL功能是否就绪"""
|
|
289
|
+
return self._mysql_ready_event.is_set()
|
|
290
|
+
|
|
291
|
+
def wait_for_ready(self, timeout: float = 1.0) -> bool:
|
|
292
|
+
"""等待缓存系统就绪(非阻塞,有超时)"""
|
|
293
|
+
return self._ready_event.wait(timeout)
|
|
294
|
+
|
|
295
|
+
def wait_for_mysql_ready(self, timeout: float = 5.0) -> bool:
|
|
296
|
+
"""等待MySQL功能就绪(非阻塞,有超时)"""
|
|
297
|
+
return self._mysql_ready_event.wait(timeout)
|
|
298
|
+
|
|
225
299
|
def _test_redis_connection(self) -> bool:
|
|
226
300
|
"""测试Redis连接"""
|
|
227
301
|
try:
|
|
@@ -238,9 +312,23 @@ class SmartCacheSystem:
|
|
|
238
312
|
return False
|
|
239
313
|
|
|
240
314
|
try:
|
|
315
|
+
# 设置连接超时,避免长时间阻塞
|
|
241
316
|
connection = self.mysql_pool.connection()
|
|
317
|
+
# 设置查询超时(如果支持)
|
|
318
|
+
try:
|
|
319
|
+
connection.autocommit(False) # 确保事务控制
|
|
320
|
+
except:
|
|
321
|
+
pass # 忽略不支持的操作
|
|
322
|
+
|
|
242
323
|
try:
|
|
243
324
|
with connection.cursor() as cursor:
|
|
325
|
+
# 设置会话超时
|
|
326
|
+
try:
|
|
327
|
+
cursor.execute("SET SESSION wait_timeout = 30")
|
|
328
|
+
cursor.execute("SET SESSION interactive_timeout = 30")
|
|
329
|
+
except:
|
|
330
|
+
pass # 忽略不支持的操作
|
|
331
|
+
|
|
244
332
|
# 创建数据库
|
|
245
333
|
cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{self.config.db_name}` DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci")
|
|
246
334
|
cursor.execute(f"USE `{self.config.db_name}`")
|
|
@@ -292,6 +380,7 @@ class SmartCacheSystem:
|
|
|
292
380
|
|
|
293
381
|
except Exception as e:
|
|
294
382
|
self.logger.error(f"MySQL数据库初始化失败: {e}")
|
|
383
|
+
# 不抛出异常,让系统继续运行
|
|
295
384
|
return False
|
|
296
385
|
|
|
297
386
|
def _generate_cache_key(self, key: str, namespace: str = "") -> str:
|
|
@@ -332,85 +421,90 @@ class SmartCacheSystem:
|
|
|
332
421
|
# 记录或更新热点键访问次数
|
|
333
422
|
self.hot_keys[cache_key] = self.hot_keys.get(cache_key, 0) + 1
|
|
334
423
|
|
|
424
|
+
def _cache_operation(operation_name: str):
|
|
425
|
+
"""缓存操作装饰器工厂"""
|
|
426
|
+
def decorator(func):
|
|
427
|
+
@functools.wraps(func)
|
|
428
|
+
def wrapper(self, *args, **kwargs):
|
|
429
|
+
start_time = time.time()
|
|
430
|
+
|
|
431
|
+
# 检查系统是否就绪
|
|
432
|
+
if not self.is_ready:
|
|
433
|
+
self._record_operation('errors')
|
|
434
|
+
self.logger.warning(f"缓存系统未就绪,{operation_name}操作失败", {
|
|
435
|
+
'state': self._state.value
|
|
436
|
+
})
|
|
437
|
+
return None if operation_name == 'get' else False
|
|
438
|
+
|
|
439
|
+
try:
|
|
440
|
+
result = func(self, *args, **kwargs)
|
|
441
|
+
response_time = (time.time() - start_time) * 1000
|
|
442
|
+
self._record_operation(operation_name, response_time)
|
|
443
|
+
return result
|
|
444
|
+
|
|
445
|
+
except Exception as e:
|
|
446
|
+
self._record_operation('errors')
|
|
447
|
+
self.logger.error(f"缓存{operation_name}操作失败: {e}", {
|
|
448
|
+
'args': args[:2] if len(args) > 2 else args, # 避免记录过长的参数
|
|
449
|
+
'operation': operation_name
|
|
450
|
+
})
|
|
451
|
+
return None if operation_name == 'get' else False
|
|
452
|
+
|
|
453
|
+
return wrapper
|
|
454
|
+
return decorator
|
|
455
|
+
|
|
456
|
+
@_cache_operation('get')
|
|
335
457
|
def get(self, key: str, namespace: str = "", default=None) -> Any:
|
|
336
458
|
"""获取缓存值"""
|
|
337
|
-
|
|
459
|
+
cache_key = self._generate_cache_key(key, namespace)
|
|
338
460
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
461
|
+
# 获取缓存值
|
|
462
|
+
value = self.redis_client.get(cache_key)
|
|
463
|
+
|
|
464
|
+
if value is not None:
|
|
465
|
+
# 缓存命中
|
|
466
|
+
self._record_hot_key(key, namespace)
|
|
345
467
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return json.loads(value.decode('utf-8'))
|
|
353
|
-
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
354
|
-
return value.decode('utf-8')
|
|
355
|
-
else:
|
|
356
|
-
# 缓存未命中
|
|
357
|
-
self._record_operation('misses', response_time)
|
|
358
|
-
return default
|
|
359
|
-
|
|
360
|
-
except Exception as e:
|
|
361
|
-
self._record_operation('errors')
|
|
362
|
-
self.logger.error(f"缓存获取失败: {e}", {
|
|
363
|
-
'key': key,
|
|
364
|
-
'namespace': namespace
|
|
365
|
-
})
|
|
468
|
+
try:
|
|
469
|
+
return json.loads(value.decode('utf-8'))
|
|
470
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
471
|
+
return value.decode('utf-8')
|
|
472
|
+
else:
|
|
473
|
+
# 缓存未命中
|
|
366
474
|
return default
|
|
367
475
|
|
|
476
|
+
@_cache_operation('set')
|
|
368
477
|
def set(self, key: str, value: Any, ttl: int = None, namespace: str = "") -> bool:
|
|
369
|
-
"""设置缓存值
|
|
370
|
-
|
|
478
|
+
"""设置缓存值"""
|
|
479
|
+
cache_key = self._generate_cache_key(key, namespace)
|
|
371
480
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
self._record_operation('sets', response_time)
|
|
395
|
-
|
|
396
|
-
# 记录TTL策略使用情况
|
|
397
|
-
if namespace and ttl != self.config.default_ttl:
|
|
398
|
-
self.logger.debug("使用智能TTL策略", {
|
|
399
|
-
'namespace': namespace,
|
|
400
|
-
'ttl': ttl,
|
|
401
|
-
'key': key[:50] + "..." if len(key) > 50 else key
|
|
402
|
-
})
|
|
403
|
-
|
|
404
|
-
return bool(result)
|
|
405
|
-
|
|
406
|
-
except Exception as e:
|
|
407
|
-
self._record_operation('errors')
|
|
408
|
-
self.logger.error(f"缓存设置失败: {e}", {
|
|
409
|
-
'key': key,
|
|
481
|
+
# 使用智能TTL策略
|
|
482
|
+
if ttl is None:
|
|
483
|
+
ttl = SmartTTLConfig.get_ttl(namespace, self.config.default_ttl)
|
|
484
|
+
|
|
485
|
+
# 序列化值
|
|
486
|
+
if isinstance(value, (dict, list, tuple)):
|
|
487
|
+
serialized_value = json.dumps(value, ensure_ascii=False, sort_keys=True, cls=MySQLDataEncoder)
|
|
488
|
+
else:
|
|
489
|
+
serialized_value = str(value)
|
|
490
|
+
|
|
491
|
+
# 检查值大小
|
|
492
|
+
if len(serialized_value.encode('utf-8')) > self.config.max_value_size:
|
|
493
|
+
self.logger.warning(f"缓存值过大,跳过设置: {len(serialized_value)} bytes")
|
|
494
|
+
return False
|
|
495
|
+
|
|
496
|
+
# 设置缓存
|
|
497
|
+
result = self.redis_client.setex(cache_key, ttl, serialized_value)
|
|
498
|
+
|
|
499
|
+
# 记录TTL策略使用情况
|
|
500
|
+
if namespace and ttl != self.config.default_ttl:
|
|
501
|
+
self.logger.debug("使用智能TTL策略", {
|
|
410
502
|
'namespace': namespace,
|
|
411
|
-
'ttl': ttl
|
|
503
|
+
'ttl': ttl,
|
|
504
|
+
'key': key[:50] + "..." if len(key) > 50 else key
|
|
412
505
|
})
|
|
413
|
-
|
|
506
|
+
|
|
507
|
+
return bool(result)
|
|
414
508
|
|
|
415
509
|
def delete(self, key: str, namespace: str = "") -> bool:
|
|
416
510
|
"""删除缓存值"""
|
|
@@ -525,45 +619,87 @@ class SmartCacheSystem:
|
|
|
525
619
|
|
|
526
620
|
def _start_stats_worker(self):
|
|
527
621
|
"""启动统计工作线程"""
|
|
528
|
-
if not self._stats_running:
|
|
622
|
+
if not self._stats_running and self.mysql_pool:
|
|
529
623
|
self._stats_running = True
|
|
530
|
-
self._stats_thread = threading.Thread(
|
|
624
|
+
self._stats_thread = threading.Thread(
|
|
625
|
+
target=self._stats_worker,
|
|
626
|
+
daemon=True,
|
|
627
|
+
name=f"stats_worker_{self.instance_name}"
|
|
628
|
+
)
|
|
531
629
|
self._stats_thread.start()
|
|
532
|
-
self.logger.info("统计工作线程已启动"
|
|
630
|
+
self.logger.info("统计工作线程已启动", {
|
|
631
|
+
'instance_name': self.instance_name,
|
|
632
|
+
'delay_first_run': True
|
|
633
|
+
})
|
|
533
634
|
|
|
534
635
|
def _stats_worker(self):
|
|
535
636
|
"""后台统计工作线程"""
|
|
536
|
-
cleanup_counter = 0
|
|
637
|
+
cleanup_counter = 0
|
|
638
|
+
|
|
639
|
+
# 延迟启动:等待一个完整的统计间隔
|
|
640
|
+
self.logger.info("统计线程启动,等待首个统计间隔", {
|
|
641
|
+
'instance_name': self.instance_name,
|
|
642
|
+
'interval': self.config.stats_interval,
|
|
643
|
+
'delay_reason': '避免初始化时的阻塞操作'
|
|
644
|
+
})
|
|
645
|
+
|
|
646
|
+
# 使用可中断的等待
|
|
647
|
+
if not self._interruptible_sleep(self.config.stats_interval):
|
|
648
|
+
return # 如果被中断则退出
|
|
649
|
+
|
|
537
650
|
while self._stats_running:
|
|
538
651
|
try:
|
|
539
652
|
# 收集统计数据
|
|
540
653
|
stats_data = self.get_stats()
|
|
541
654
|
|
|
542
|
-
# 提交到MySQL
|
|
655
|
+
# 提交到MySQL(带状态检查)
|
|
543
656
|
self._submit_stats_to_mysql(stats_data)
|
|
544
657
|
|
|
545
658
|
# 清理过期的热点键统计
|
|
546
659
|
self._cleanup_hot_keys()
|
|
547
660
|
|
|
548
|
-
#
|
|
661
|
+
# 定期清理过期数据
|
|
549
662
|
cleanup_counter += 1
|
|
550
|
-
if cleanup_counter >= 24: # 24 *
|
|
663
|
+
if cleanup_counter >= 24: # 24 * 1800秒 = 12小时
|
|
551
664
|
self._cleanup_expired_mysql_data()
|
|
552
665
|
cleanup_counter = 0
|
|
553
666
|
|
|
554
667
|
except Exception as e:
|
|
555
668
|
self.logger.error(f"统计工作线程异常: {e}")
|
|
556
669
|
|
|
557
|
-
#
|
|
558
|
-
|
|
670
|
+
# 可中断的等待下一个统计间隔
|
|
671
|
+
if not self._interruptible_sleep(self.config.stats_interval):
|
|
672
|
+
break
|
|
673
|
+
|
|
674
|
+
def _interruptible_sleep(self, duration: float) -> bool:
|
|
675
|
+
"""可中断的睡眠"""
|
|
676
|
+
sleep_interval = 1.0 # 每秒检查一次
|
|
677
|
+
elapsed = 0.0
|
|
678
|
+
|
|
679
|
+
while elapsed < duration and self._stats_running:
|
|
680
|
+
time.sleep(min(sleep_interval, duration - elapsed))
|
|
681
|
+
elapsed += sleep_interval
|
|
682
|
+
|
|
683
|
+
return self._stats_running
|
|
559
684
|
|
|
560
685
|
def _submit_stats_to_mysql(self, stats_data: Dict[str, Any]):
|
|
561
686
|
"""提交统计数据到MySQL"""
|
|
562
687
|
if not self.mysql_pool:
|
|
563
688
|
self.logger.debug("MySQL连接池不可用,跳过统计数据提交")
|
|
564
689
|
return
|
|
565
|
-
|
|
690
|
+
|
|
691
|
+
# 使用状态检查而不是属性检查
|
|
692
|
+
if not self.is_mysql_ready:
|
|
693
|
+
self.logger.debug("MySQL尚未就绪,跳过统计数据提交", {
|
|
694
|
+
'state': self._state.value,
|
|
695
|
+
'instance_name': self.instance_name
|
|
696
|
+
})
|
|
697
|
+
return
|
|
698
|
+
|
|
699
|
+
logger.info("统计数据", {'stats_data': stats_data})
|
|
700
|
+
|
|
566
701
|
try:
|
|
702
|
+
# 使用上下文管理器确保连接正确关闭
|
|
567
703
|
connection = self.mysql_pool.connection()
|
|
568
704
|
try:
|
|
569
705
|
with connection.cursor() as cursor:
|
|
@@ -571,7 +707,7 @@ class SmartCacheSystem:
|
|
|
571
707
|
|
|
572
708
|
# 准备数据
|
|
573
709
|
now = datetime.now()
|
|
574
|
-
date_str = now.strftime("%Y-%m-%d")
|
|
710
|
+
date_str = now.strftime("%Y-%m-%d")
|
|
575
711
|
time_period = now.strftime("%Y%m%d_%H%M")
|
|
576
712
|
|
|
577
713
|
insert_sql = f"""
|
|
@@ -607,17 +743,21 @@ class SmartCacheSystem:
|
|
|
607
743
|
|
|
608
744
|
connection.commit()
|
|
609
745
|
|
|
610
|
-
self.logger.
|
|
746
|
+
self.logger.info("统计数据已提交到MySQL", {
|
|
611
747
|
'time_period': time_period,
|
|
612
748
|
'total_operations': stats_data['total_operations'],
|
|
613
|
-
'hit_rate': stats_data['hit_rate']
|
|
749
|
+
'hit_rate': stats_data['hit_rate'],
|
|
750
|
+
'instance_name': self.instance_name
|
|
614
751
|
})
|
|
615
752
|
|
|
616
753
|
finally:
|
|
617
754
|
connection.close()
|
|
618
755
|
|
|
619
756
|
except Exception as e:
|
|
620
|
-
self.logger.error(f"提交统计数据到MySQL失败: {e}"
|
|
757
|
+
self.logger.error(f"提交统计数据到MySQL失败: {e}", {
|
|
758
|
+
'instance_name': self.instance_name,
|
|
759
|
+
'error_type': type(e).__name__
|
|
760
|
+
})
|
|
621
761
|
|
|
622
762
|
def _cleanup_hot_keys(self):
|
|
623
763
|
"""清理热点键统计 """
|
|
@@ -678,21 +818,46 @@ class SmartCacheSystem:
|
|
|
678
818
|
|
|
679
819
|
def shutdown(self):
|
|
680
820
|
"""关闭缓存系统"""
|
|
681
|
-
self.logger.info("正在关闭缓存系统..."
|
|
821
|
+
self.logger.info("正在关闭缓存系统...", {
|
|
822
|
+
'instance_name': self.instance_name,
|
|
823
|
+
'state': self._state.value
|
|
824
|
+
})
|
|
682
825
|
|
|
683
826
|
# 停止统计线程
|
|
684
827
|
self._stats_running = False
|
|
685
828
|
if self._stats_thread and self._stats_thread.is_alive():
|
|
686
|
-
self.
|
|
829
|
+
self.logger.info("等待统计线程结束...")
|
|
830
|
+
self._stats_thread.join(timeout=10) # 最多等待10秒
|
|
831
|
+
|
|
832
|
+
if self._stats_thread.is_alive():
|
|
833
|
+
self.logger.warning("统计线程未能在超时时间内结束")
|
|
687
834
|
|
|
688
|
-
#
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
self.
|
|
692
|
-
except Exception as e:
|
|
693
|
-
self.logger.error(f"关闭时提交统计数据失败: {e}")
|
|
835
|
+
# 关闭线程池
|
|
836
|
+
if hasattr(self, '_executor'):
|
|
837
|
+
self.logger.info("关闭线程池...")
|
|
838
|
+
self._executor.shutdown(wait=True, timeout=5)
|
|
694
839
|
|
|
695
|
-
|
|
840
|
+
# 最后一次提交统计数据(如果MySQL可用)
|
|
841
|
+
if self.is_mysql_ready:
|
|
842
|
+
try:
|
|
843
|
+
self.logger.info("提交最终统计数据...")
|
|
844
|
+
stats_data = self.get_stats()
|
|
845
|
+
self._submit_stats_to_mysql(stats_data)
|
|
846
|
+
except Exception as e:
|
|
847
|
+
self.logger.error(f"关闭时提交统计数据失败: {e}")
|
|
848
|
+
|
|
849
|
+
self.logger.info("缓存系统已关闭", {
|
|
850
|
+
'instance_name': self.instance_name
|
|
851
|
+
})
|
|
852
|
+
|
|
853
|
+
def __enter__(self):
|
|
854
|
+
"""上下文管理器支持"""
|
|
855
|
+
return self
|
|
856
|
+
|
|
857
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
858
|
+
"""上下文管理器退出时自动关闭"""
|
|
859
|
+
self.shutdown()
|
|
860
|
+
return False
|
|
696
861
|
|
|
697
862
|
|
|
698
863
|
class CacheManager:
|
|
@@ -720,8 +885,9 @@ class CacheManager:
|
|
|
720
885
|
|
|
721
886
|
def initialize(self, redis_client: redis.Redis, mysql_pool=None, instance_name: str = "default",
|
|
722
887
|
config: CacheConfig = None, db_name: str = None, table_name: str = None):
|
|
723
|
-
"""
|
|
888
|
+
"""初始化缓存系统(非阻塞)"""
|
|
724
889
|
try:
|
|
890
|
+
# 立即创建缓存实例,内部会异步初始化MySQL和统计线程
|
|
725
891
|
self.cache_instance = SmartCacheSystem(
|
|
726
892
|
redis_client=redis_client,
|
|
727
893
|
mysql_pool=mysql_pool,
|
|
@@ -731,11 +897,12 @@ class CacheManager:
|
|
|
731
897
|
table_name=table_name
|
|
732
898
|
)
|
|
733
899
|
self.initialization_error = None
|
|
734
|
-
self.logger.info("
|
|
900
|
+
self.logger.info("缓存管理器初始化成功(异步模式)", {
|
|
735
901
|
'instance_name': instance_name,
|
|
736
902
|
'mysql_enabled': mysql_pool is not None,
|
|
737
903
|
'db_name': self.cache_instance.config.db_name,
|
|
738
|
-
'table_name': self.cache_instance.config.table_name
|
|
904
|
+
'table_name': self.cache_instance.config.table_name,
|
|
905
|
+
'note': 'MySQL初始化将在后台异步完成'
|
|
739
906
|
})
|
|
740
907
|
return self # 支持链式调用
|
|
741
908
|
|
mdbq-4.0.111/mdbq/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = '4.0.111'
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|