mdbq 4.0.110__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.110 → mdbq-4.0.112}/PKG-INFO +1 -1
  2. mdbq-4.0.112/mdbq/__version__.py +1 -0
  3. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/redis/redis_cache.py +358 -92
  4. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq.egg-info/PKG-INFO +1 -1
  5. mdbq-4.0.110/mdbq/__version__.py +0 -1
  6. {mdbq-4.0.110 → mdbq-4.0.112}/README.txt +0 -0
  7. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/__init__.py +0 -0
  8. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/auth/__init__.py +0 -0
  9. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/auth/auth_backend.py +0 -0
  10. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/auth/rate_limiter.py +0 -0
  11. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/js/__init__.py +0 -0
  12. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/js/jc.py +0 -0
  13. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/log/__init__.py +0 -0
  14. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/log/mylogger.py +0 -0
  15. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/myconf/__init__.py +0 -0
  16. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/myconf/myconf.py +0 -0
  17. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/__init__.py +0 -0
  18. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/deduplicator.py +0 -0
  19. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/mysql.py +0 -0
  20. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/s_query.py +0 -0
  21. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/unique_.py +0 -0
  22. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/mysql/uploader.py +0 -0
  23. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/__init__.py +0 -0
  24. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/download_sku_picture.py +0 -0
  25. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/error_handler.py +0 -0
  26. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/otk.py +0 -0
  27. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/pov_city.py +0 -0
  28. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/other/ua_sj.py +0 -0
  29. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/pbix/__init__.py +0 -0
  30. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/pbix/pbix_refresh.py +0 -0
  31. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/pbix/refresh_all.py +0 -0
  32. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/redis/__init__.py +0 -0
  33. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/redis/getredis.py +0 -0
  34. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/route/__init__.py +0 -0
  35. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/route/analytics.py +0 -0
  36. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/route/monitor.py +0 -0
  37. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/route/routes.py +0 -0
  38. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/selenium/__init__.py +0 -0
  39. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/selenium/get_driver.py +0 -0
  40. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq/spider/__init__.py +0 -0
  41. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq.egg-info/SOURCES.txt +0 -0
  42. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq.egg-info/dependency_links.txt +0 -0
  43. {mdbq-4.0.110 → mdbq-4.0.112}/mdbq.egg-info/top_level.txt +0 -0
  44. {mdbq-4.0.110 → mdbq-4.0.112}/setup.cfg +0 -0
  45. {mdbq-4.0.110 → 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.110
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):
@@ -73,6 +89,64 @@ class MySQLDataEncoder(json.JSONEncoder):
73
89
  return super().default(obj)
74
90
 
75
91
 
92
+ class SmartTTLConfig:
93
+ """智能TTL配置策略"""
94
+
95
+ # TTL策略映射表(秒)
96
+ TTL_STRATEGIES = {
97
+ # 数据库相关缓存
98
+ 'sycm_database': 1800, # 数据库列表:30分钟
99
+ 'sycm_tables': 1200, # 表列表:20分钟
100
+ 'sycm_table_data': 300, # 表数据:5分钟
101
+
102
+ # 用户会话相关
103
+ 'user_session': 3600, # 用户会话:1小时
104
+ 'user_info': 1800, # 用户信息:30分钟
105
+
106
+ # 静态配置
107
+ 'static_config': 86400, # 静态配置:24小时
108
+ 'system_config': 43200, # 系统配置:12小时
109
+
110
+ # API相关
111
+ 'api_response': 600, # API响应:10分钟
112
+ 'api_cache': 900, # API缓存:15分钟
113
+
114
+ # 临时数据
115
+ 'temp_data': 300, # 临时数据:5分钟
116
+ 'short_cache': 60, # 短期缓存:1分钟
117
+ }
118
+
119
+ @classmethod
120
+ def get_ttl(cls, namespace: str, default: int = 3600) -> int:
121
+ """
122
+ 根据命名空间获取对应的TTL值
123
+
124
+ Args:
125
+ namespace: 缓存命名空间
126
+ default: 默认TTL值(秒)
127
+
128
+ Returns:
129
+ int: TTL值(秒)
130
+ """
131
+ return cls.TTL_STRATEGIES.get(namespace, default)
132
+
133
+ @classmethod
134
+ def add_strategy(cls, namespace: str, ttl: int):
135
+ """
136
+ 添加新的TTL策略
137
+
138
+ Args:
139
+ namespace: 命名空间
140
+ ttl: TTL值(秒)
141
+ """
142
+ cls.TTL_STRATEGIES[namespace] = ttl
143
+
144
+ @classmethod
145
+ def get_all_strategies(cls) -> Dict[str, int]:
146
+ """获取所有TTL策略"""
147
+ return cls.TTL_STRATEGIES.copy()
148
+
149
+
76
150
  class CacheConfig:
77
151
  """缓存系统配置类"""
78
152
 
@@ -98,6 +172,10 @@ class CacheConfig:
98
172
  self.max_value_size = 1024 * 1024 # 1MB
99
173
  self.batch_size = 100
100
174
 
175
+ # 热点键配置
176
+ self.max_hot_keys = 1000 # 最大热点键数量
177
+ self.hot_keys_cleanup_threshold = 800 # 热点键清理阈值
178
+
101
179
  # MySQL数据库配置
102
180
  self.db_name = db_name
103
181
  self.table_name = table_name
@@ -129,6 +207,11 @@ class SmartCacheSystem:
129
207
 
130
208
  self.logger = logger
131
209
 
210
+ # 状态管理
211
+ self._state = CacheSystemState.INITIALIZING
212
+ self._ready_event = Event()
213
+ self._mysql_ready_event = Event()
214
+
132
215
  # 统计数据
133
216
  self.stats = {
134
217
  'hits': 0,
@@ -150,16 +233,69 @@ class SmartCacheSystem:
150
233
  self._stats_thread = None
151
234
  self._stats_lock = threading.RLock()
152
235
 
153
- # 初始化
154
- self._init_mysql_db()
155
- 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)
156
246
 
157
247
  self.logger.info("智能缓存系统初始化完成", {
158
248
  'instance_name': self.instance_name,
159
- 'mysql_enabled': self.mysql_pool is not None,
160
- '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
161
252
  })
162
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
+
163
299
  def _test_redis_connection(self) -> bool:
164
300
  """测试Redis连接"""
165
301
  try:
@@ -176,9 +312,23 @@ class SmartCacheSystem:
176
312
  return False
177
313
 
178
314
  try:
315
+ # 设置连接超时,避免长时间阻塞
179
316
  connection = self.mysql_pool.connection()
317
+ # 设置查询超时(如果支持)
318
+ try:
319
+ connection.autocommit(False) # 确保事务控制
320
+ except:
321
+ pass # 忽略不支持的操作
322
+
180
323
  try:
181
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
+
182
332
  # 创建数据库
183
333
  cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{self.config.db_name}` DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci")
184
334
  cursor.execute(f"USE `{self.config.db_name}`")
@@ -230,6 +380,7 @@ class SmartCacheSystem:
230
380
 
231
381
  except Exception as e:
232
382
  self.logger.error(f"MySQL数据库初始化失败: {e}")
383
+ # 不抛出异常,让系统继续运行
233
384
  return False
234
385
 
235
386
  def _generate_cache_key(self, key: str, namespace: str = "") -> str:
@@ -254,75 +405,106 @@ class SmartCacheSystem:
254
405
  """记录热点键"""
255
406
  cache_key = self._generate_cache_key(key, namespace)
256
407
  with self.hot_keys_lock:
408
+ # 检查是否需要清理热点键(防止内存泄漏)
409
+ if len(self.hot_keys) >= self.config.hot_keys_cleanup_threshold:
410
+ # 保留访问次数最高的键,移除访问次数最少的键
411
+ sorted_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)
412
+ # 保留前600个热点键,为新键留出空间
413
+ self.hot_keys = dict(sorted_keys[:600])
414
+
415
+ self.logger.info("热点键内存清理", {
416
+ '清理前数量': len(sorted_keys),
417
+ '清理后数量': len(self.hot_keys),
418
+ '清理阈值': self.config.hot_keys_cleanup_threshold
419
+ })
420
+
421
+ # 记录或更新热点键访问次数
257
422
  self.hot_keys[cache_key] = self.hot_keys.get(cache_key, 0) + 1
258
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')
259
457
  def get(self, key: str, namespace: str = "", default=None) -> Any:
260
458
  """获取缓存值"""
261
- start_time = time.time()
459
+ cache_key = self._generate_cache_key(key, namespace)
262
460
 
263
- try:
264
- cache_key = self._generate_cache_key(key, namespace)
265
-
266
- # 获取缓存值
267
- value = self.redis_client.get(cache_key)
268
- 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)
269
467
 
270
- if value is not None:
271
- # 缓存命中
272
- self._record_operation('hits', response_time)
273
- self._record_hot_key(key, namespace)
274
-
275
- try:
276
- return json.loads(value.decode('utf-8'))
277
- except (json.JSONDecodeError, UnicodeDecodeError):
278
- return value.decode('utf-8')
279
- else:
280
- # 缓存未命中
281
- self._record_operation('misses', response_time)
282
- return default
283
-
284
- except Exception as e:
285
- self._record_operation('errors')
286
- self.logger.error(f"缓存获取失败: {e}", {
287
- 'key': key,
288
- 'namespace': namespace
289
- })
468
+ try:
469
+ return json.loads(value.decode('utf-8'))
470
+ except (json.JSONDecodeError, UnicodeDecodeError):
471
+ return value.decode('utf-8')
472
+ else:
473
+ # 缓存未命中
290
474
  return default
291
475
 
476
+ @_cache_operation('set')
292
477
  def set(self, key: str, value: Any, ttl: int = None, namespace: str = "") -> bool:
293
478
  """设置缓存值"""
294
- start_time = time.time()
479
+ cache_key = self._generate_cache_key(key, namespace)
295
480
 
296
- try:
297
- cache_key = self._generate_cache_key(key, namespace)
298
- ttl = ttl or self.config.default_ttl
299
-
300
- # 序列化值
301
- if isinstance(value, (dict, list, tuple)):
302
- serialized_value = json.dumps(value, ensure_ascii=False, sort_keys=True, cls=MySQLDataEncoder)
303
- else:
304
- serialized_value = str(value)
305
-
306
- # 检查值大小
307
- if len(serialized_value.encode('utf-8')) > self.config.max_value_size:
308
- self.logger.warning(f"缓存值过大,跳过设置: {len(serialized_value)} bytes")
309
- return False
310
-
311
- # 设置缓存
312
- result = self.redis_client.setex(cache_key, ttl, serialized_value)
313
- response_time = (time.time() - start_time) * 1000
314
-
315
- self._record_operation('sets', response_time)
316
- return bool(result)
317
-
318
- except Exception as e:
319
- self._record_operation('errors')
320
- self.logger.error(f"缓存设置失败: {e}", {
321
- '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策略", {
322
502
  'namespace': namespace,
323
- 'ttl': ttl
503
+ 'ttl': ttl,
504
+ 'key': key[:50] + "..." if len(key) > 50 else key
324
505
  })
325
- return False
506
+
507
+ return bool(result)
326
508
 
327
509
  def delete(self, key: str, namespace: str = "") -> bool:
328
510
  """删除缓存值"""
@@ -437,45 +619,87 @@ class SmartCacheSystem:
437
619
 
438
620
  def _start_stats_worker(self):
439
621
  """启动统计工作线程"""
440
- if not self._stats_running:
622
+ if not self._stats_running and self.mysql_pool:
441
623
  self._stats_running = True
442
- 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
+ )
443
629
  self._stats_thread.start()
444
- self.logger.info("统计工作线程已启动")
630
+ self.logger.info("统计工作线程已启动", {
631
+ 'instance_name': self.instance_name,
632
+ 'delay_first_run': True
633
+ })
445
634
 
446
635
  def _stats_worker(self):
447
636
  """后台统计工作线程"""
448
- 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
+
449
650
  while self._stats_running:
450
651
  try:
451
652
  # 收集统计数据
452
653
  stats_data = self.get_stats()
453
654
 
454
- # 提交到MySQL
655
+ # 提交到MySQL(带状态检查)
455
656
  self._submit_stats_to_mysql(stats_data)
456
657
 
457
658
  # 清理过期的热点键统计
458
659
  self._cleanup_hot_keys()
459
660
 
460
- # 每24次统计周期(约2小时)执行一次过期数据清理
661
+ # 定期清理过期数据
461
662
  cleanup_counter += 1
462
- if cleanup_counter >= 24: # 24 * 300秒 = 2小时
663
+ if cleanup_counter >= 24: # 24 * 1800秒 = 12小时
463
664
  self._cleanup_expired_mysql_data()
464
665
  cleanup_counter = 0
465
666
 
466
667
  except Exception as e:
467
668
  self.logger.error(f"统计工作线程异常: {e}")
468
669
 
469
- # 等待下一个统计间隔
470
- 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
471
684
 
472
685
  def _submit_stats_to_mysql(self, stats_data: Dict[str, Any]):
473
686
  """提交统计数据到MySQL"""
474
687
  if not self.mysql_pool:
475
688
  self.logger.debug("MySQL连接池不可用,跳过统计数据提交")
476
689
  return
477
-
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
+
478
701
  try:
702
+ # 使用上下文管理器确保连接正确关闭
479
703
  connection = self.mysql_pool.connection()
480
704
  try:
481
705
  with connection.cursor() as cursor:
@@ -483,7 +707,7 @@ class SmartCacheSystem:
483
707
 
484
708
  # 准备数据
485
709
  now = datetime.now()
486
- date_str = now.strftime("%Y-%m-%d") # 格式: 2024-10-01
710
+ date_str = now.strftime("%Y-%m-%d")
487
711
  time_period = now.strftime("%Y%m%d_%H%M")
488
712
 
489
713
  insert_sql = f"""
@@ -519,25 +743,40 @@ class SmartCacheSystem:
519
743
 
520
744
  connection.commit()
521
745
 
522
- self.logger.debug("统计数据已提交到MySQL", {
746
+ self.logger.info("统计数据已提交到MySQL", {
523
747
  'time_period': time_period,
524
748
  'total_operations': stats_data['total_operations'],
525
- 'hit_rate': stats_data['hit_rate']
749
+ 'hit_rate': stats_data['hit_rate'],
750
+ 'instance_name': self.instance_name
526
751
  })
527
752
 
528
753
  finally:
529
754
  connection.close()
530
755
 
531
756
  except Exception as e:
532
- 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
+ })
533
761
 
534
762
  def _cleanup_hot_keys(self):
535
- """清理热点键统计(保留访问次数最高的1000个)"""
763
+ """清理热点键统计 """
536
764
  with self.hot_keys_lock:
537
- if len(self.hot_keys) > 1000:
538
- # 保留访问次数最高的1000个键
765
+ current_count = len(self.hot_keys)
766
+
767
+ # 如果热点键数量超过最大限制,进行清理
768
+ if current_count > self.config.max_hot_keys:
769
+ # 保留访问次数最高的键
539
770
  sorted_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)
540
- self.hot_keys = dict(sorted_keys[:1000])
771
+ # 保留80%的热点键
772
+ keep_count = int(self.config.max_hot_keys * 0.8)
773
+ self.hot_keys = dict(sorted_keys[:keep_count])
774
+
775
+ self.logger.info("定期热点键清理", {
776
+ '清理前数量': current_count,
777
+ '清理后数量': len(self.hot_keys),
778
+ '保留比例': '80%'
779
+ })
541
780
 
542
781
  def _cleanup_expired_mysql_data(self):
543
782
  """清理过期的MySQL统计数据"""
@@ -579,21 +818,46 @@ class SmartCacheSystem:
579
818
 
580
819
  def shutdown(self):
581
820
  """关闭缓存系统"""
582
- self.logger.info("正在关闭缓存系统...")
821
+ self.logger.info("正在关闭缓存系统...", {
822
+ 'instance_name': self.instance_name,
823
+ 'state': self._state.value
824
+ })
583
825
 
584
826
  # 停止统计线程
585
827
  self._stats_running = False
586
828
  if self._stats_thread and self._stats_thread.is_alive():
587
- 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("统计线程未能在超时时间内结束")
588
834
 
589
- # 最后一次提交统计数据
590
- try:
591
- stats_data = self.get_stats()
592
- self._submit_stats_to_mysql(stats_data)
593
- except Exception as e:
594
- self.logger.error(f"关闭时提交统计数据失败: {e}")
835
+ # 关闭线程池
836
+ if hasattr(self, '_executor'):
837
+ self.logger.info("关闭线程池...")
838
+ self._executor.shutdown(wait=True, timeout=5)
595
839
 
596
- 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
597
861
 
598
862
 
599
863
  class CacheManager:
@@ -621,8 +885,9 @@ class CacheManager:
621
885
 
622
886
  def initialize(self, redis_client: redis.Redis, mysql_pool=None, instance_name: str = "default",
623
887
  config: CacheConfig = None, db_name: str = None, table_name: str = None):
624
- """初始化缓存系统"""
888
+ """初始化缓存系统(非阻塞)"""
625
889
  try:
890
+ # 立即创建缓存实例,内部会异步初始化MySQL和统计线程
626
891
  self.cache_instance = SmartCacheSystem(
627
892
  redis_client=redis_client,
628
893
  mysql_pool=mysql_pool,
@@ -632,11 +897,12 @@ class CacheManager:
632
897
  table_name=table_name
633
898
  )
634
899
  self.initialization_error = None
635
- self.logger.info("缓存管理器初始化成功", {
900
+ self.logger.info("缓存管理器初始化成功(异步模式)", {
636
901
  'instance_name': instance_name,
637
902
  'mysql_enabled': mysql_pool is not None,
638
903
  'db_name': self.cache_instance.config.db_name,
639
- 'table_name': self.cache_instance.config.table_name
904
+ 'table_name': self.cache_instance.config.table_name,
905
+ 'note': 'MySQL初始化将在后台异步完成'
640
906
  })
641
907
  return self # 支持链式调用
642
908
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.110
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.110'
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