mdbq 4.0.111__tar.gz → 4.0.112__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.

Files changed (45) hide show
  1. {mdbq-4.0.111 → mdbq-4.0.112}/PKG-INFO +1 -1
  2. mdbq-4.0.112/mdbq/__version__.py +1 -0
  3. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/redis/redis_cache.py +268 -101
  4. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq.egg-info/PKG-INFO +1 -1
  5. mdbq-4.0.111/mdbq/__version__.py +0 -1
  6. {mdbq-4.0.111 → mdbq-4.0.112}/README.txt +0 -0
  7. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/__init__.py +0 -0
  8. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/auth/__init__.py +0 -0
  9. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/auth/auth_backend.py +0 -0
  10. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/auth/rate_limiter.py +0 -0
  11. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/js/__init__.py +0 -0
  12. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/js/jc.py +0 -0
  13. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/log/__init__.py +0 -0
  14. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/log/mylogger.py +0 -0
  15. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/myconf/__init__.py +0 -0
  16. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/myconf/myconf.py +0 -0
  17. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/__init__.py +0 -0
  18. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/deduplicator.py +0 -0
  19. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/mysql.py +0 -0
  20. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/s_query.py +0 -0
  21. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/unique_.py +0 -0
  22. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/mysql/uploader.py +0 -0
  23. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/__init__.py +0 -0
  24. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/download_sku_picture.py +0 -0
  25. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/error_handler.py +0 -0
  26. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/otk.py +0 -0
  27. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/pov_city.py +0 -0
  28. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/other/ua_sj.py +0 -0
  29. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/pbix/__init__.py +0 -0
  30. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/pbix/pbix_refresh.py +0 -0
  31. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/pbix/refresh_all.py +0 -0
  32. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/redis/__init__.py +0 -0
  33. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/redis/getredis.py +0 -0
  34. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/route/__init__.py +0 -0
  35. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/route/analytics.py +0 -0
  36. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/route/monitor.py +0 -0
  37. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/route/routes.py +0 -0
  38. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/selenium/__init__.py +0 -0
  39. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/selenium/get_driver.py +0 -0
  40. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq/spider/__init__.py +0 -0
  41. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq.egg-info/SOURCES.txt +0 -0
  42. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq.egg-info/dependency_links.txt +0 -0
  43. {mdbq-4.0.111 → mdbq-4.0.112}/mdbq.egg-info/top_level.txt +0 -0
  44. {mdbq-4.0.111 → mdbq-4.0.112}/setup.cfg +0 -0
  45. {mdbq-4.0.111 → mdbq-4.0.112}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.111
3
+ Version: 4.0.112
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -0,0 +1 @@
1
+ VERSION = '4.0.112'
@@ -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._init_mysql_db()
217
- self._start_stats_worker()
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
- 'mysql_enabled': self.mysql_pool is not None,
222
- 'redis_connected': self._test_redis_connection()
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
- start_time = time.time()
459
+ cache_key = self._generate_cache_key(key, namespace)
338
460
 
339
- try:
340
- cache_key = self._generate_cache_key(key, namespace)
341
-
342
- # 获取缓存值
343
- value = self.redis_client.get(cache_key)
344
- response_time = (time.time() - start_time) * 1000
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
- if value is not None:
347
- # 缓存命中
348
- self._record_operation('hits', response_time)
349
- self._record_hot_key(key, namespace)
350
-
351
- try:
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
- """设置缓存值 - 支持智能TTL策略"""
370
- start_time = time.time()
478
+ """设置缓存值"""
479
+ cache_key = self._generate_cache_key(key, namespace)
371
480
 
372
- try:
373
- cache_key = self._generate_cache_key(key, namespace)
374
-
375
- # 使用智能TTL策略
376
- if ttl is None:
377
- ttl = SmartTTLConfig.get_ttl(namespace, self.config.default_ttl)
378
-
379
- # 序列化值
380
- if isinstance(value, (dict, list, tuple)):
381
- serialized_value = json.dumps(value, ensure_ascii=False, sort_keys=True, cls=MySQLDataEncoder)
382
- else:
383
- serialized_value = str(value)
384
-
385
- # 检查值大小
386
- if len(serialized_value.encode('utf-8')) > self.config.max_value_size:
387
- self.logger.warning(f"缓存值过大,跳过设置: {len(serialized_value)} bytes")
388
- return False
389
-
390
- # 设置缓存
391
- result = self.redis_client.setex(cache_key, ttl, serialized_value)
392
- response_time = (time.time() - start_time) * 1000
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
- return False
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(target=self._stats_worker, daemon=True)
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
- # 每24次统计周期(约2小时)执行一次过期数据清理
661
+ # 定期清理过期数据
549
662
  cleanup_counter += 1
550
- if cleanup_counter >= 24: # 24 * 300秒 = 2小时
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
- time.sleep(self.config.stats_interval)
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") # 格式: 2024-10-01
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.debug("统计数据已提交到MySQL", {
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._stats_thread.join(timeout=5)
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
- try:
690
- stats_data = self.get_stats()
691
- self._submit_stats_to_mysql(stats_data)
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
- self.logger.info("缓存系统已关闭")
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
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.111
3
+ Version: 4.0.112
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -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