mdbq 4.0.109__py3-none-any.whl → 4.0.111__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.

Potentially problematic release.


This version of mdbq might be problematic. Click here for more details.

mdbq/__version__.py CHANGED
@@ -1 +1 @@
1
- VERSION = '4.0.109'
1
+ VERSION = '4.0.111'
mdbq/redis/redis_cache.py CHANGED
@@ -16,6 +16,8 @@ import time
16
16
  import threading
17
17
  import socket
18
18
  from datetime import datetime, date
19
+ from decimal import Decimal
20
+ from uuid import UUID
19
21
  from typing import Optional, Dict, Any, List, Union
20
22
  import redis
21
23
  from mdbq.log import mylogger
@@ -32,16 +34,103 @@ logger = mylogger.MyLogger(
32
34
  )
33
35
 
34
36
 
35
- class DateTimeEncoder(json.JSONEncoder):
36
- """自定义JSON编码器,支持datetime和date对象"""
37
+ class MySQLDataEncoder(json.JSONEncoder):
38
+ """自定义JSON编码器,支持MySQL常见数据类型"""
37
39
  def default(self, obj):
38
- if isinstance(obj, datetime):
39
- return obj.isoformat()
40
- elif isinstance(obj, date):
41
- return obj.isoformat()
40
+ try:
41
+ if isinstance(obj, datetime):
42
+ return obj.isoformat()
43
+ elif isinstance(obj, date):
44
+ return obj.isoformat()
45
+ elif isinstance(obj, Decimal):
46
+ # Decimal转换为字符串保持精度
47
+ return str(obj)
48
+ elif isinstance(obj, UUID):
49
+ return str(obj)
50
+ elif isinstance(obj, bytes):
51
+ # 二进制数据转换为base64
52
+ import base64
53
+ return base64.b64encode(obj).decode('utf-8')
54
+ elif isinstance(obj, (set, frozenset)):
55
+ # 集合类型转换为列表
56
+ return list(obj)
57
+ elif hasattr(obj, 'isoformat'):
58
+ # 其他日期时间类型
59
+ return obj.isoformat()
60
+ elif hasattr(obj, '__float__'):
61
+ # 数值类型
62
+ return float(obj)
63
+ elif hasattr(obj, '__str__'):
64
+ # 其他有字符串表示的对象
65
+ return str(obj)
66
+ elif hasattr(obj, '__dict__'):
67
+ # 处理其他对象,转换为字典
68
+ return obj.__dict__
69
+ except Exception as e:
70
+ # 如果所有方法都失败,返回对象的字符串表示
71
+ return f"<{type(obj).__name__}: {str(obj)[:100]}>"
72
+
42
73
  return super().default(obj)
43
74
 
44
75
 
76
+ class SmartTTLConfig:
77
+ """智能TTL配置策略"""
78
+
79
+ # TTL策略映射表(秒)
80
+ TTL_STRATEGIES = {
81
+ # 数据库相关缓存
82
+ 'sycm_database': 1800, # 数据库列表:30分钟
83
+ 'sycm_tables': 1200, # 表列表:20分钟
84
+ 'sycm_table_data': 300, # 表数据:5分钟
85
+
86
+ # 用户会话相关
87
+ 'user_session': 3600, # 用户会话:1小时
88
+ 'user_info': 1800, # 用户信息:30分钟
89
+
90
+ # 静态配置
91
+ 'static_config': 86400, # 静态配置:24小时
92
+ 'system_config': 43200, # 系统配置:12小时
93
+
94
+ # API相关
95
+ 'api_response': 600, # API响应:10分钟
96
+ 'api_cache': 900, # API缓存:15分钟
97
+
98
+ # 临时数据
99
+ 'temp_data': 300, # 临时数据:5分钟
100
+ 'short_cache': 60, # 短期缓存:1分钟
101
+ }
102
+
103
+ @classmethod
104
+ def get_ttl(cls, namespace: str, default: int = 3600) -> int:
105
+ """
106
+ 根据命名空间获取对应的TTL值
107
+
108
+ Args:
109
+ namespace: 缓存命名空间
110
+ default: 默认TTL值(秒)
111
+
112
+ Returns:
113
+ int: TTL值(秒)
114
+ """
115
+ return cls.TTL_STRATEGIES.get(namespace, default)
116
+
117
+ @classmethod
118
+ def add_strategy(cls, namespace: str, ttl: int):
119
+ """
120
+ 添加新的TTL策略
121
+
122
+ Args:
123
+ namespace: 命名空间
124
+ ttl: TTL值(秒)
125
+ """
126
+ cls.TTL_STRATEGIES[namespace] = ttl
127
+
128
+ @classmethod
129
+ def get_all_strategies(cls) -> Dict[str, int]:
130
+ """获取所有TTL策略"""
131
+ return cls.TTL_STRATEGIES.copy()
132
+
133
+
45
134
  class CacheConfig:
46
135
  """缓存系统配置类"""
47
136
 
@@ -67,6 +156,10 @@ class CacheConfig:
67
156
  self.max_value_size = 1024 * 1024 # 1MB
68
157
  self.batch_size = 100
69
158
 
159
+ # 热点键配置
160
+ self.max_hot_keys = 1000 # 最大热点键数量
161
+ self.hot_keys_cleanup_threshold = 800 # 热点键清理阈值
162
+
70
163
  # MySQL数据库配置
71
164
  self.db_name = db_name
72
165
  self.table_name = table_name
@@ -223,6 +316,20 @@ class SmartCacheSystem:
223
316
  """记录热点键"""
224
317
  cache_key = self._generate_cache_key(key, namespace)
225
318
  with self.hot_keys_lock:
319
+ # 检查是否需要清理热点键(防止内存泄漏)
320
+ if len(self.hot_keys) >= self.config.hot_keys_cleanup_threshold:
321
+ # 保留访问次数最高的键,移除访问次数最少的键
322
+ sorted_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)
323
+ # 保留前600个热点键,为新键留出空间
324
+ self.hot_keys = dict(sorted_keys[:600])
325
+
326
+ self.logger.info("热点键内存清理", {
327
+ '清理前数量': len(sorted_keys),
328
+ '清理后数量': len(self.hot_keys),
329
+ '清理阈值': self.config.hot_keys_cleanup_threshold
330
+ })
331
+
332
+ # 记录或更新热点键访问次数
226
333
  self.hot_keys[cache_key] = self.hot_keys.get(cache_key, 0) + 1
227
334
 
228
335
  def get(self, key: str, namespace: str = "", default=None) -> Any:
@@ -259,16 +366,19 @@ class SmartCacheSystem:
259
366
  return default
260
367
 
261
368
  def set(self, key: str, value: Any, ttl: int = None, namespace: str = "") -> bool:
262
- """设置缓存值"""
369
+ """设置缓存值 - 支持智能TTL策略"""
263
370
  start_time = time.time()
264
371
 
265
372
  try:
266
373
  cache_key = self._generate_cache_key(key, namespace)
267
- ttl = ttl or self.config.default_ttl
374
+
375
+ # 使用智能TTL策略
376
+ if ttl is None:
377
+ ttl = SmartTTLConfig.get_ttl(namespace, self.config.default_ttl)
268
378
 
269
379
  # 序列化值
270
380
  if isinstance(value, (dict, list, tuple)):
271
- serialized_value = json.dumps(value, ensure_ascii=False, sort_keys=True, cls=DateTimeEncoder)
381
+ serialized_value = json.dumps(value, ensure_ascii=False, sort_keys=True, cls=MySQLDataEncoder)
272
382
  else:
273
383
  serialized_value = str(value)
274
384
 
@@ -282,6 +392,15 @@ class SmartCacheSystem:
282
392
  response_time = (time.time() - start_time) * 1000
283
393
 
284
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
+
285
404
  return bool(result)
286
405
 
287
406
  except Exception as e:
@@ -481,7 +600,7 @@ class SmartCacheSystem:
481
600
  stats_data['ops_per_second'],
482
601
  stats_data['unique_keys_count'],
483
602
  stats_data['uptime_seconds'],
484
- json.dumps(stats_data['hot_keys'], ensure_ascii=False, cls=DateTimeEncoder),
603
+ json.dumps(stats_data['hot_keys'], ensure_ascii=False, cls=MySQLDataEncoder),
485
604
  socket.gethostname(),
486
605
  self.instance_name
487
606
  ))
@@ -501,12 +620,23 @@ class SmartCacheSystem:
501
620
  self.logger.error(f"提交统计数据到MySQL失败: {e}")
502
621
 
503
622
  def _cleanup_hot_keys(self):
504
- """清理热点键统计(保留访问次数最高的1000个)"""
623
+ """清理热点键统计 """
505
624
  with self.hot_keys_lock:
506
- if len(self.hot_keys) > 1000:
507
- # 保留访问次数最高的1000个键
625
+ current_count = len(self.hot_keys)
626
+
627
+ # 如果热点键数量超过最大限制,进行清理
628
+ if current_count > self.config.max_hot_keys:
629
+ # 保留访问次数最高的键
508
630
  sorted_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)
509
- self.hot_keys = dict(sorted_keys[:1000])
631
+ # 保留80%的热点键
632
+ keep_count = int(self.config.max_hot_keys * 0.8)
633
+ self.hot_keys = dict(sorted_keys[:keep_count])
634
+
635
+ self.logger.info("定期热点键清理", {
636
+ '清理前数量': current_count,
637
+ '清理后数量': len(self.hot_keys),
638
+ '保留比例': '80%'
639
+ })
510
640
 
511
641
  def _cleanup_expired_mysql_data(self):
512
642
  """清理过期的MySQL统计数据"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.109
3
+ Version: 4.0.111
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -1,5 +1,5 @@
1
1
  mdbq/__init__.py,sha256=Il5Q9ATdX8yXqVxtP_nYqUhExzxPC_qk_WXQ_4h0exg,16
2
- mdbq/__version__.py,sha256=isMXV0CRhxtt0uFUp2iQA8kCLhOxtlSnZABiy7X6FW8,19
2
+ mdbq/__version__.py,sha256=nKcO0o5cZBWmqPghn8vvDzV6hZj4cC8f1ESFBAKaOmA,19
3
3
  mdbq/auth/__init__.py,sha256=pnPMAt63sh1B6kEvmutUuro46zVf2v2YDAG7q-jV_To,24
4
4
  mdbq/auth/auth_backend.py,sha256=ZxKRXPXa2t9ngRZEXKM72MzcMvN-0OtiVDOhZRTrm3w,85948
5
5
  mdbq/auth/rate_limiter.py,sha256=1m_Paxp8pDNpmyoFGRpFMVOJpbmeIvfVcfiQ2oH72qM,32850
@@ -26,7 +26,7 @@ mdbq/pbix/pbix_refresh.py,sha256=JUjKW3bNEyoMVfVfo77UhguvS5AWkixvVhDbw4_MHco,239
26
26
  mdbq/pbix/refresh_all.py,sha256=OBT9EewSZ0aRS9vL_FflVn74d4l2G00wzHiikCC4TC0,5926
27
27
  mdbq/redis/__init__.py,sha256=YtgBlVSMDphtpwYX248wGge1x-Ex_mMufz4-8W0XRmA,12
28
28
  mdbq/redis/getredis.py,sha256=vpBuNc22uj9Vr-_Dh25_wpwWM1e-072EAAIBdB_IpL0,23494
29
- mdbq/redis/redis_cache.py,sha256=C1AsTi0oaQeq4t-hSx11b-1ZN7NXmRLsTlrjZ8KuhOw,25772
29
+ mdbq/redis/redis_cache.py,sha256=OxtbqHDRO2ko8VS6KkwtqJgPuOFsWvnzIyMIWasnmps,30719
30
30
  mdbq/route/__init__.py,sha256=BT_dAY7V-U2o72bevq1B9Mq9QA7GodwtkxyLNdGaoE8,22
31
31
  mdbq/route/analytics.py,sha256=dngj5hVwKddEUy59nSYbOoJ9C7OVrtCmCkvW6Uj9RYM,28097
32
32
  mdbq/route/monitor.py,sha256=7gLyeq7TqnbhPwhUw0dg-hw9-0OWeKoMdMhcANSDGVs,42255
@@ -34,7 +34,7 @@ mdbq/route/routes.py,sha256=QVGfTvDgu0CpcKCvk1ra74H8uojgqTLUav1fnVAqLEA,29433
34
34
  mdbq/selenium/__init__.py,sha256=AKzeEceqZyvqn2dEDoJSzDQnbuENkJSHAlbHAD0u0ZI,10
35
35
  mdbq/selenium/get_driver.py,sha256=1NTlVUE6QsyjTrVVVqTO2LOnYf578ccFWlWnvIXGtic,20903
36
36
  mdbq/spider/__init__.py,sha256=RBMFXGy_jd1HXZhngB2T2XTvJqki8P_Fr-pBcwijnew,18
37
- mdbq-4.0.109.dist-info/METADATA,sha256=lQkQNnJ-hH1d8H6y4RDz0tmbzH41ZH0J8qWNEa_YZCk,365
38
- mdbq-4.0.109.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
39
- mdbq-4.0.109.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
40
- mdbq-4.0.109.dist-info/RECORD,,
37
+ mdbq-4.0.111.dist-info/METADATA,sha256=fyVLMYRQ4ArdtViITdjLvQtRlQLtT_lWomGcFhMYzAo,365
38
+ mdbq-4.0.111.dist-info/WHEEL,sha256=jB7zZ3N9hIM9adW7qlTAyycLYW9npaWKLRzaoVcLKcM,91
39
+ mdbq-4.0.111.dist-info/top_level.txt,sha256=2FQ-uLnCSB-OwFiWntzmwosW3X2Xqsg0ewh1axsaylA,5
40
+ mdbq-4.0.111.dist-info/RECORD,,
File without changes