mdbq 4.0.105__tar.gz → 4.0.107__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.105 → mdbq-4.0.107}/PKG-INFO +2 -2
  2. mdbq-4.0.107/mdbq/__version__.py +1 -0
  3. mdbq-4.0.107/mdbq/redis/redis_cache.py +646 -0
  4. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/route/monitor.py +1 -1
  5. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq.egg-info/PKG-INFO +2 -2
  6. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq.egg-info/SOURCES.txt +1 -0
  7. mdbq-4.0.105/mdbq/__version__.py +0 -1
  8. {mdbq-4.0.105 → mdbq-4.0.107}/README.txt +0 -0
  9. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/__init__.py +0 -0
  10. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/auth/__init__.py +0 -0
  11. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/auth/auth_backend.py +0 -0
  12. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/auth/rate_limiter.py +0 -0
  13. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/js/__init__.py +0 -0
  14. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/js/jc.py +0 -0
  15. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/log/__init__.py +0 -0
  16. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/log/mylogger.py +0 -0
  17. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/myconf/__init__.py +0 -0
  18. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/myconf/myconf.py +0 -0
  19. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/__init__.py +0 -0
  20. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/deduplicator.py +0 -0
  21. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/mysql.py +0 -0
  22. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/s_query.py +0 -0
  23. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/unique_.py +0 -0
  24. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/mysql/uploader.py +0 -0
  25. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/__init__.py +0 -0
  26. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/download_sku_picture.py +0 -0
  27. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/error_handler.py +0 -0
  28. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/otk.py +0 -0
  29. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/pov_city.py +0 -0
  30. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/other/ua_sj.py +0 -0
  31. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/pbix/__init__.py +0 -0
  32. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/pbix/pbix_refresh.py +0 -0
  33. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/pbix/refresh_all.py +0 -0
  34. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/redis/__init__.py +0 -0
  35. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/redis/getredis.py +0 -0
  36. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/route/__init__.py +0 -0
  37. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/route/analytics.py +0 -0
  38. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/route/routes.py +0 -0
  39. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/selenium/__init__.py +0 -0
  40. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/selenium/get_driver.py +0 -0
  41. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq/spider/__init__.py +0 -0
  42. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq.egg-info/dependency_links.txt +0 -0
  43. {mdbq-4.0.105 → mdbq-4.0.107}/mdbq.egg-info/top_level.txt +0 -0
  44. {mdbq-4.0.105 → mdbq-4.0.107}/setup.cfg +0 -0
  45. {mdbq-4.0.105 → mdbq-4.0.107}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.105
3
+ Version: 4.0.107
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.107'
@@ -0,0 +1,646 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Redis智能缓存系统
4
+
5
+ 主要功能:
6
+ 1. Redis缓存的CRUD操作
7
+ 2. 命名空间隔离
8
+ 3. 分布式锁防止缓存击穿
9
+ 4. 自动统计分析并提交到MySQL
10
+ 5. 缓存健康检查和监控
11
+
12
+ """
13
+
14
+ import json
15
+ import time
16
+ import threading
17
+ import socket
18
+ from datetime import datetime
19
+ from typing import Optional, Dict, Any, List, Union
20
+ import redis
21
+ from mdbq.log import mylogger
22
+ logger = mylogger.MyLogger(
23
+ logging_mode='file',
24
+ log_level='info',
25
+ log_format='json',
26
+ max_log_size=50,
27
+ backup_count=5,
28
+ enable_async=False,
29
+ sample_rate=1,
30
+ sensitive_fields=[],
31
+ enable_metrics=False,
32
+ )
33
+
34
+
35
+ class CacheConfig:
36
+ """缓存系统配置类"""
37
+
38
+ def __init__(self, db_name: str = "redis统计", table_name: str = "dpflask路由分析"):
39
+ # TTL配置(秒)
40
+ self.default_ttl = 3600 # 1小时
41
+ self.short_ttl = 300 # 5分钟
42
+ self.medium_ttl = 1800 # 30分钟
43
+ self.long_ttl = 7200 # 2小时
44
+ self.very_long_ttl = 86400 # 24小时
45
+
46
+ # 缓存键前缀
47
+ self.cache_prefix = "smart_cache:"
48
+ self.stats_prefix = "cache_stats:"
49
+ self.lock_prefix = "cache_lock:"
50
+
51
+ # 统计配置
52
+ self.stats_interval = 900 # 统计间隔(秒), 自动提交统计信息到MySQL的间隔
53
+ self.stats_retention = 30 # MySQL统计数据保留天数,超过此天数的数据将被自动删除
54
+
55
+ # 性能配置
56
+ self.max_key_length = 250
57
+ self.max_value_size = 1024 * 1024 # 1MB
58
+ self.batch_size = 100
59
+
60
+ # MySQL数据库配置
61
+ self.db_name = db_name
62
+ self.table_name = table_name
63
+
64
+ # 锁配置
65
+ self.lock_timeout = 30 # 分布式锁超时时间
66
+ self.lock_retry_delay = 0.1 # 锁重试延迟
67
+
68
+
69
+ class SmartCacheSystem:
70
+ """智能缓存系统核心类"""
71
+
72
+ def __init__(self, redis_client: redis.Redis, mysql_pool=None, instance_name: str = "default",
73
+ config: CacheConfig = None, db_name: str = None, table_name: str = None):
74
+ self.redis_client = redis_client
75
+ self.mysql_pool = mysql_pool
76
+ self.instance_name = instance_name
77
+
78
+ # 配置优先级:传入的config > 自定义db_name/table_name > 默认配置
79
+ if config:
80
+ self.config = config
81
+ elif db_name or table_name:
82
+ self.config = CacheConfig(
83
+ db_name=db_name or "redis统计",
84
+ table_name=table_name or "dpflask路由分析"
85
+ )
86
+ else:
87
+ self.config = CacheConfig()
88
+
89
+ self.logger = logger
90
+
91
+ # 统计数据
92
+ self.stats = {
93
+ 'hits': 0,
94
+ 'misses': 0,
95
+ 'sets': 0,
96
+ 'deletes': 0,
97
+ 'errors': 0,
98
+ 'total_operations': 0,
99
+ 'start_time': time.time(),
100
+ 'response_times': []
101
+ }
102
+
103
+ # 热点键统计
104
+ self.hot_keys = {}
105
+ self.hot_keys_lock = threading.RLock()
106
+
107
+ # 统计线程控制
108
+ self._stats_running = False
109
+ self._stats_thread = None
110
+ self._stats_lock = threading.RLock()
111
+
112
+ # 初始化
113
+ self._init_mysql_db()
114
+ self._start_stats_worker()
115
+
116
+ self.logger.info("智能缓存系统初始化完成", {
117
+ 'instance_name': self.instance_name,
118
+ 'mysql_enabled': self.mysql_pool is not None,
119
+ 'redis_connected': self._test_redis_connection()
120
+ })
121
+
122
+ def _test_redis_connection(self) -> bool:
123
+ """测试Redis连接"""
124
+ try:
125
+ self.redis_client.ping()
126
+ return True
127
+ except Exception as e:
128
+ self.logger.error(f"Redis连接测试失败: {e}")
129
+ return False
130
+
131
+ def _init_mysql_db(self) -> bool:
132
+ """初始化MySQL数据库和表"""
133
+ if not self.mysql_pool:
134
+ self.logger.warning("MySQL连接池未提供,统计功能将被禁用")
135
+ return False
136
+
137
+ try:
138
+ connection = self.mysql_pool.connection()
139
+ try:
140
+ with connection.cursor() as cursor:
141
+ # 创建数据库
142
+ cursor.execute(f"CREATE DATABASE IF NOT EXISTS `{self.config.db_name}` DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci")
143
+ cursor.execute(f"USE `{self.config.db_name}`")
144
+
145
+ # 创建表(MySQL 8.4+兼容语法)
146
+ create_table_sql = f"""
147
+ CREATE TABLE IF NOT EXISTS `{self.config.table_name}` (
148
+ `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
149
+ `日期` date NOT NULL COMMENT '统计日期',
150
+ `统计时间` datetime NOT NULL COMMENT '统计时间',
151
+ `时间段` varchar(20) NOT NULL COMMENT '时间段标识',
152
+ `缓存命中数` bigint DEFAULT 0 COMMENT '缓存命中次数',
153
+ `缓存未命中数` bigint DEFAULT 0 COMMENT '缓存未命中次数',
154
+ `缓存设置数` bigint DEFAULT 0 COMMENT '缓存设置次数',
155
+ `缓存删除数` bigint DEFAULT 0 COMMENT '缓存删除次数',
156
+ `缓存错误数` bigint DEFAULT 0 COMMENT '缓存错误次数',
157
+ `命中率` decimal(5,2) DEFAULT 0.00 COMMENT '缓存命中率(%)',
158
+ `总操作数` bigint DEFAULT 0 COMMENT '总操作次数',
159
+ `平均响应时间` decimal(10,4) DEFAULT 0.0000 COMMENT '平均响应时间(ms)',
160
+ `每秒操作数` decimal(10,2) DEFAULT 0.00 COMMENT '每秒操作数',
161
+ `唯一键数量` int DEFAULT 0 COMMENT '唯一键数量',
162
+ `系统运行时间` bigint DEFAULT 0 COMMENT '系统运行时间(秒)',
163
+ `热点键统计` json DEFAULT NULL COMMENT '热点键统计信息',
164
+ `服务器主机` varchar(100) DEFAULT NULL COMMENT '服务器主机名',
165
+ `实例名称` varchar(100) DEFAULT NULL COMMENT '缓存实例名称',
166
+ `创建时间` timestamp DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间',
167
+ `更新时间` timestamp DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间',
168
+ PRIMARY KEY (`id`),
169
+ KEY `idx_stats_date` (`日期`),
170
+ KEY `idx_stats_time` (`统计时间`),
171
+ KEY `idx_time_period` (`时间段`),
172
+ KEY `idx_hit_rate` (`命中率`),
173
+ KEY `idx_instance` (`实例名称`),
174
+ KEY `idx_create_time` (`创建时间`)
175
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='Redis缓存系统统计分析表'
176
+ """
177
+
178
+ cursor.execute(create_table_sql)
179
+ connection.commit()
180
+
181
+ self.logger.info("MySQL数据库表初始化成功", {
182
+ 'database': self.config.db_name,
183
+ 'table': self.config.table_name
184
+ })
185
+ return True
186
+
187
+ finally:
188
+ connection.close()
189
+
190
+ except Exception as e:
191
+ self.logger.error(f"MySQL数据库初始化失败: {e}")
192
+ return False
193
+
194
+ def _generate_cache_key(self, key: str, namespace: str = "") -> str:
195
+ """生成缓存键"""
196
+ if namespace:
197
+ return f"{self.config.cache_prefix}{namespace}:{key}"
198
+ return f"{self.config.cache_prefix}{key}"
199
+
200
+ def _record_operation(self, operation: str, response_time: float = 0):
201
+ """记录操作统计"""
202
+ with self._stats_lock:
203
+ self.stats['total_operations'] += 1
204
+ if operation in self.stats:
205
+ self.stats[operation] += 1
206
+ if response_time > 0:
207
+ self.stats['response_times'].append(response_time)
208
+ # 只保留最近1000次操作的响应时间
209
+ if len(self.stats['response_times']) > 1000:
210
+ self.stats['response_times'] = self.stats['response_times'][-1000:]
211
+
212
+ def _record_hot_key(self, key: str, namespace: str = ""):
213
+ """记录热点键"""
214
+ cache_key = self._generate_cache_key(key, namespace)
215
+ with self.hot_keys_lock:
216
+ self.hot_keys[cache_key] = self.hot_keys.get(cache_key, 0) + 1
217
+
218
+ def get(self, key: str, namespace: str = "", default=None) -> Any:
219
+ """获取缓存值"""
220
+ start_time = time.time()
221
+
222
+ try:
223
+ cache_key = self._generate_cache_key(key, namespace)
224
+
225
+ # 获取缓存值
226
+ value = self.redis_client.get(cache_key)
227
+ response_time = (time.time() - start_time) * 1000
228
+
229
+ if value is not None:
230
+ # 缓存命中
231
+ self._record_operation('hits', response_time)
232
+ self._record_hot_key(key, namespace)
233
+
234
+ try:
235
+ return json.loads(value.decode('utf-8'))
236
+ except (json.JSONDecodeError, UnicodeDecodeError):
237
+ return value.decode('utf-8')
238
+ else:
239
+ # 缓存未命中
240
+ self._record_operation('misses', response_time)
241
+ return default
242
+
243
+ except Exception as e:
244
+ self._record_operation('errors')
245
+ self.logger.error(f"缓存获取失败: {e}", {
246
+ 'key': key,
247
+ 'namespace': namespace
248
+ })
249
+ return default
250
+
251
+ def set(self, key: str, value: Any, ttl: int = None, namespace: str = "") -> bool:
252
+ """设置缓存值"""
253
+ start_time = time.time()
254
+
255
+ try:
256
+ cache_key = self._generate_cache_key(key, namespace)
257
+ ttl = ttl or self.config.default_ttl
258
+
259
+ # 序列化值
260
+ if isinstance(value, (dict, list, tuple)):
261
+ serialized_value = json.dumps(value, ensure_ascii=False)
262
+ else:
263
+ serialized_value = str(value)
264
+
265
+ # 检查值大小
266
+ if len(serialized_value.encode('utf-8')) > self.config.max_value_size:
267
+ self.logger.warning(f"缓存值过大,跳过设置: {len(serialized_value)} bytes")
268
+ return False
269
+
270
+ # 设置缓存
271
+ result = self.redis_client.setex(cache_key, ttl, serialized_value)
272
+ response_time = (time.time() - start_time) * 1000
273
+
274
+ self._record_operation('sets', response_time)
275
+ return bool(result)
276
+
277
+ except Exception as e:
278
+ self._record_operation('errors')
279
+ self.logger.error(f"缓存设置失败: {e}", {
280
+ 'key': key,
281
+ 'namespace': namespace,
282
+ 'ttl': ttl
283
+ })
284
+ return False
285
+
286
+ def delete(self, key: str, namespace: str = "") -> bool:
287
+ """删除缓存值"""
288
+ start_time = time.time()
289
+
290
+ try:
291
+ cache_key = self._generate_cache_key(key, namespace)
292
+ result = self.redis_client.delete(cache_key)
293
+ response_time = (time.time() - start_time) * 1000
294
+
295
+ self._record_operation('deletes', response_time)
296
+ return bool(result)
297
+
298
+ except Exception as e:
299
+ self._record_operation('errors')
300
+ self.logger.error(f"缓存删除失败: {e}", {
301
+ 'key': key,
302
+ 'namespace': namespace
303
+ })
304
+ return False
305
+
306
+ def exists(self, key: str, namespace: str = "") -> bool:
307
+ """检查缓存键是否存在"""
308
+ try:
309
+ cache_key = self._generate_cache_key(key, namespace)
310
+ return bool(self.redis_client.exists(cache_key))
311
+ except Exception as e:
312
+ self.logger.error(f"缓存存在性检查失败: {e}")
313
+ return False
314
+
315
+ def clear_namespace(self, namespace: str) -> int:
316
+ """清除指定命名空间的所有缓存"""
317
+ try:
318
+ pattern = f"{self.config.cache_prefix}{namespace}:*"
319
+ keys = self.redis_client.keys(pattern)
320
+
321
+ if keys:
322
+ deleted = self.redis_client.delete(*keys)
323
+ self.logger.info(f"清除命名空间缓存: {namespace}, 删除键数: {deleted}")
324
+ return deleted
325
+ return 0
326
+
327
+ except Exception as e:
328
+ self.logger.error(f"清除命名空间缓存失败: {e}")
329
+ return 0
330
+
331
+ def get_stats(self) -> Dict[str, Any]:
332
+ """获取缓存统计信息"""
333
+ with self._stats_lock:
334
+ total_ops = self.stats['total_operations']
335
+ hits = self.stats['hits']
336
+
337
+ # 计算命中率
338
+ hit_rate = (hits / total_ops * 100) if total_ops > 0 else 0
339
+
340
+ # 计算平均响应时间
341
+ response_times = self.stats['response_times']
342
+ avg_response_time = sum(response_times) / len(response_times) if response_times else 0
343
+
344
+ # 计算运行时间
345
+ uptime = time.time() - self.stats['start_time']
346
+
347
+ # 计算每秒操作数
348
+ ops_per_second = total_ops / uptime if uptime > 0 else 0
349
+
350
+ # 获取热点键(前10个)
351
+ with self.hot_keys_lock:
352
+ top_hot_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)[:10]
353
+
354
+ return {
355
+ 'hits': hits,
356
+ 'misses': self.stats['misses'],
357
+ 'sets': self.stats['sets'],
358
+ 'deletes': self.stats['deletes'],
359
+ 'errors': self.stats['errors'],
360
+ 'total_operations': total_ops,
361
+ 'hit_rate': round(hit_rate, 2),
362
+ 'avg_response_time': round(avg_response_time, 4),
363
+ 'ops_per_second': round(ops_per_second, 2),
364
+ 'uptime_seconds': int(uptime),
365
+ 'hot_keys': dict(top_hot_keys),
366
+ 'unique_keys_count': len(self.hot_keys)
367
+ }
368
+
369
+ def health_check(self) -> Dict[str, Any]:
370
+ """健康检查"""
371
+ health_info = {
372
+ 'redis_connected': False,
373
+ 'mysql_available': False,
374
+ 'stats_worker_running': self._stats_running,
375
+ 'instance_name': self.instance_name,
376
+ 'timestamp': datetime.now().isoformat()
377
+ }
378
+
379
+ # 检查Redis连接
380
+ try:
381
+ self.redis_client.ping()
382
+ health_info['redis_connected'] = True
383
+ except Exception as e:
384
+ health_info['redis_error'] = str(e)
385
+
386
+ # 检查MySQL连接
387
+ if self.mysql_pool:
388
+ try:
389
+ connection = self.mysql_pool.connection()
390
+ connection.close()
391
+ health_info['mysql_available'] = True
392
+ except Exception as e:
393
+ health_info['mysql_error'] = str(e)
394
+
395
+ return health_info
396
+
397
+ def _start_stats_worker(self):
398
+ """启动统计工作线程"""
399
+ if not self._stats_running:
400
+ self._stats_running = True
401
+ self._stats_thread = threading.Thread(target=self._stats_worker, daemon=True)
402
+ self._stats_thread.start()
403
+ self.logger.info("统计工作线程已启动")
404
+
405
+ def _stats_worker(self):
406
+ """后台统计工作线程"""
407
+ cleanup_counter = 0 # 清理计数器
408
+ while self._stats_running:
409
+ try:
410
+ # 收集统计数据
411
+ stats_data = self.get_stats()
412
+
413
+ # 提交到MySQL
414
+ self._submit_stats_to_mysql(stats_data)
415
+
416
+ # 清理过期的热点键统计
417
+ self._cleanup_hot_keys()
418
+
419
+ # 每24次统计周期(约2小时)执行一次过期数据清理
420
+ cleanup_counter += 1
421
+ if cleanup_counter >= 24: # 24 * 300秒 = 2小时
422
+ self._cleanup_expired_mysql_data()
423
+ cleanup_counter = 0
424
+
425
+ except Exception as e:
426
+ self.logger.error(f"统计工作线程异常: {e}")
427
+
428
+ # 等待下一个统计间隔
429
+ time.sleep(self.config.stats_interval)
430
+
431
+ def _submit_stats_to_mysql(self, stats_data: Dict[str, Any]):
432
+ """提交统计数据到MySQL"""
433
+ if not self.mysql_pool:
434
+ self.logger.debug("MySQL连接池不可用,跳过统计数据提交")
435
+ return
436
+
437
+ try:
438
+ connection = self.mysql_pool.connection()
439
+ try:
440
+ with connection.cursor() as cursor:
441
+ cursor.execute(f"USE `{self.config.db_name}`")
442
+
443
+ # 准备数据
444
+ now = datetime.now()
445
+ date_str = now.strftime("%Y-%m-%d") # 格式: 2024-10-01
446
+ time_period = now.strftime("%Y%m%d_%H%M")
447
+
448
+ insert_sql = f"""
449
+ INSERT INTO `{self.config.table_name}` (
450
+ `日期`, `统计时间`, `时间段`, `缓存命中数`, `缓存未命中数`, `缓存设置数`,
451
+ `缓存删除数`, `缓存错误数`, `命中率`, `总操作数`, `平均响应时间`,
452
+ `每秒操作数`, `唯一键数量`, `系统运行时间`, `热点键统计`,
453
+ `服务器主机`, `实例名称`
454
+ ) VALUES (
455
+ %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s
456
+ )
457
+ """
458
+
459
+ cursor.execute(insert_sql, (
460
+ date_str,
461
+ now,
462
+ time_period,
463
+ stats_data['hits'],
464
+ stats_data['misses'],
465
+ stats_data['sets'],
466
+ stats_data['deletes'],
467
+ stats_data['errors'],
468
+ stats_data['hit_rate'],
469
+ stats_data['total_operations'],
470
+ stats_data['avg_response_time'],
471
+ stats_data['ops_per_second'],
472
+ stats_data['unique_keys_count'],
473
+ stats_data['uptime_seconds'],
474
+ json.dumps(stats_data['hot_keys'], ensure_ascii=False),
475
+ socket.gethostname(),
476
+ self.instance_name
477
+ ))
478
+
479
+ connection.commit()
480
+
481
+ self.logger.debug("统计数据已提交到MySQL", {
482
+ 'time_period': time_period,
483
+ 'total_operations': stats_data['total_operations'],
484
+ 'hit_rate': stats_data['hit_rate']
485
+ })
486
+
487
+ finally:
488
+ connection.close()
489
+
490
+ except Exception as e:
491
+ self.logger.error(f"提交统计数据到MySQL失败: {e}")
492
+
493
+ def _cleanup_hot_keys(self):
494
+ """清理热点键统计(保留访问次数最高的1000个)"""
495
+ with self.hot_keys_lock:
496
+ if len(self.hot_keys) > 1000:
497
+ # 保留访问次数最高的1000个键
498
+ sorted_keys = sorted(self.hot_keys.items(), key=lambda x: x[1], reverse=True)
499
+ self.hot_keys = dict(sorted_keys[:1000])
500
+
501
+ def _cleanup_expired_mysql_data(self):
502
+ """清理过期的MySQL统计数据"""
503
+ if not self.mysql_pool:
504
+ return
505
+
506
+ try:
507
+ connection = self.mysql_pool.connection()
508
+ try:
509
+ with connection.cursor() as cursor:
510
+ cursor.execute(f"USE `{self.config.db_name}`")
511
+
512
+ # 计算过期时间点
513
+ from datetime import timedelta
514
+ expire_date = datetime.now() - timedelta(days=self.config.stats_retention)
515
+
516
+ # 删除过期数据
517
+ delete_sql = f"""
518
+ DELETE FROM `{self.config.table_name}`
519
+ WHERE `统计时间` < %s
520
+ """
521
+
522
+ cursor.execute(delete_sql, (expire_date,))
523
+ deleted_rows = cursor.rowcount
524
+ connection.commit()
525
+
526
+ if deleted_rows > 0:
527
+ self.logger.info("清理过期MySQL统计数据", {
528
+ 'deleted_rows': deleted_rows,
529
+ 'expire_date': expire_date.strftime('%Y-%m-%d %H:%M:%S'),
530
+ 'retention_days': self.config.stats_retention
531
+ })
532
+
533
+ finally:
534
+ connection.close()
535
+
536
+ except Exception as e:
537
+ self.logger.error(f"清理过期MySQL统计数据失败: {e}")
538
+
539
+ def shutdown(self):
540
+ """关闭缓存系统"""
541
+ self.logger.info("正在关闭缓存系统...")
542
+
543
+ # 停止统计线程
544
+ self._stats_running = False
545
+ if self._stats_thread and self._stats_thread.is_alive():
546
+ self._stats_thread.join(timeout=5)
547
+
548
+ # 最后一次提交统计数据
549
+ try:
550
+ stats_data = self.get_stats()
551
+ self._submit_stats_to_mysql(stats_data)
552
+ except Exception as e:
553
+ self.logger.error(f"关闭时提交统计数据失败: {e}")
554
+
555
+ self.logger.info("缓存系统已关闭")
556
+
557
+
558
+ class CacheManager:
559
+ """缓存管理器 - 单例模式"""
560
+
561
+ _instance = None
562
+ _lock = threading.RLock()
563
+
564
+ def __new__(cls):
565
+ with cls._lock:
566
+ if cls._instance is None:
567
+ cls._instance = super().__new__(cls)
568
+ cls._instance._initialized = False
569
+ return cls._instance
570
+
571
+ def __init__(self):
572
+ if self._initialized:
573
+ return
574
+
575
+ self.cache_instance = None
576
+ self.enabled = True
577
+ self.initialization_error = None
578
+ self._initialized = True
579
+ self.logger = logger
580
+
581
+ def initialize(self, redis_client: redis.Redis, mysql_pool=None, instance_name: str = "default",
582
+ config: CacheConfig = None, db_name: str = None, table_name: str = None):
583
+ """初始化缓存系统"""
584
+ try:
585
+ self.cache_instance = SmartCacheSystem(
586
+ redis_client=redis_client,
587
+ mysql_pool=mysql_pool,
588
+ instance_name=instance_name,
589
+ config=config,
590
+ db_name=db_name,
591
+ table_name=table_name
592
+ )
593
+ self.initialization_error = None
594
+ self.logger.info("缓存管理器初始化成功", {
595
+ 'instance_name': instance_name,
596
+ 'mysql_enabled': mysql_pool is not None,
597
+ 'db_name': self.cache_instance.config.db_name,
598
+ 'table_name': self.cache_instance.config.table_name
599
+ })
600
+ return self # 支持链式调用
601
+
602
+ except Exception as e:
603
+ self.initialization_error = str(e)
604
+ self.cache_instance = None
605
+ self.logger.error(f"缓存管理器初始化失败: {e}")
606
+ return self
607
+
608
+ def get_cache(self) -> Optional[SmartCacheSystem]:
609
+ """获取缓存实例"""
610
+ return self.cache_instance if self.enabled else None
611
+
612
+ def is_available(self) -> bool:
613
+ """检查缓存是否可用"""
614
+ return self.cache_instance is not None and self.enabled
615
+
616
+ def enable(self):
617
+ """启用缓存"""
618
+ self.enabled = True
619
+ self.logger.info("缓存系统已启用")
620
+ return self # 支持链式调用
621
+
622
+ def disable(self):
623
+ """禁用缓存"""
624
+ self.enabled = False
625
+ self.logger.info("缓存系统已禁用")
626
+ return self # 支持链式调用
627
+
628
+ def get_status(self) -> Dict[str, Any]:
629
+ """获取缓存状态"""
630
+ return {
631
+ 'enabled': self.enabled,
632
+ 'available': self.cache_instance is not None,
633
+ 'initialization_error': self.initialization_error,
634
+ 'instance_name': getattr(self.cache_instance, 'instance_name', None) if self.cache_instance else None
635
+ }
636
+
637
+ def shutdown(self):
638
+ """关闭缓存系统"""
639
+ if self.cache_instance:
640
+ self.cache_instance.shutdown()
641
+ self.cache_instance = None
642
+ self.logger.info("缓存管理器已关闭")
643
+
644
+
645
+ # 导出单例实例
646
+ cache_manager = CacheManager()
@@ -35,7 +35,7 @@ host, port, username, password = parser.get_section_values(
35
35
 
36
36
  logger = mylogger.MyLogger(
37
37
  logging_mode='file',
38
- log_level='debug',
38
+ log_level='info',
39
39
  log_format='json',
40
40
  max_log_size=50,
41
41
  backup_count=5,
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.2
2
2
  Name: mdbq
3
- Version: 4.0.105
3
+ Version: 4.0.107
4
4
  Home-page: https://pypi.org/project/mdbq
5
5
  Author: xigua,
6
6
  Author-email: 2587125111@qq.com
@@ -32,6 +32,7 @@ mdbq/pbix/pbix_refresh.py
32
32
  mdbq/pbix/refresh_all.py
33
33
  mdbq/redis/__init__.py
34
34
  mdbq/redis/getredis.py
35
+ mdbq/redis/redis_cache.py
35
36
  mdbq/route/__init__.py
36
37
  mdbq/route/analytics.py
37
38
  mdbq/route/monitor.py
@@ -1 +0,0 @@
1
- VERSION = '4.0.105'
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