From 3272525c9247d1dfaacfafb300327ee1deea0a6c Mon Sep 17 00:00:00 2001 From: YoVinchen Date: Mon, 4 Aug 2025 21:55:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8Dreids?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/redis_types.py | 287 +++++++++++++ templates/redis_compare_old.html | 696 +++++++++++++++++++++++++++++++ 2 files changed, 983 insertions(+) create mode 100644 modules/redis_types.py create mode 100644 templates/redis_compare_old.html diff --git a/modules/redis_types.py b/modules/redis_types.py new file mode 100644 index 0000000..c7eb207 --- /dev/null +++ b/modules/redis_types.py @@ -0,0 +1,287 @@ +""" +Redis数据类型支持增强模块 +支持string、hash、list、set、zset、json等数据类型的比较 +""" + +import json +import logging +from redis.exceptions import RedisError + +logger = logging.getLogger(__name__) + +def get_redis_value_with_type(redis_client, key): + """ + 获取Redis键值及其数据类型 + + Args: + redis_client: Redis客户端 + key: Redis键名 + + Returns: + dict: { + 'type': 数据类型, + 'value': 值, + 'display_value': 用于显示的格式化值, + 'exists': 是否存在 + } + """ + try: + # 检查key是否存在 + if not redis_client.exists(key): + return { + 'type': None, + 'value': None, + 'display_value': None, + 'exists': False + } + + # 获取数据类型 + key_type = redis_client.type(key).decode('utf-8') + result = { + 'type': key_type, + 'exists': True + } + + if key_type == 'string': + # 字符串类型 + value = redis_client.get(key) + if value: + try: + # 尝试解码为字符串 + str_value = value.decode('utf-8') + result['value'] = str_value + + # 尝试解析为JSON + try: + json_value = json.loads(str_value) + result['display_value'] = json.dumps(json_value, indent=2, ensure_ascii=False) + result['type'] = 'json_string' # 标记为JSON字符串 + except: + result['display_value'] = str_value + + except UnicodeDecodeError: + # 二进制数据 + result['value'] = value + result['display_value'] = f"" + else: + result['value'] = "" + result['display_value'] = "" + + elif key_type == 'hash': + # Hash类型 + hash_data = redis_client.hgetall(key) + decoded_hash = {} + + for field, value in hash_data.items(): + try: + decoded_field = field.decode('utf-8') + decoded_value = value.decode('utf-8') + decoded_hash[decoded_field] = decoded_value + except UnicodeDecodeError: + decoded_hash[str(field)] = f"" + + result['value'] = decoded_hash + result['display_value'] = json.dumps(decoded_hash, indent=2, ensure_ascii=False) + + elif key_type == 'list': + # List类型 + list_data = redis_client.lrange(key, 0, -1) + decoded_list = [] + + for item in list_data: + try: + decoded_item = item.decode('utf-8') + decoded_list.append(decoded_item) + except UnicodeDecodeError: + decoded_list.append(f"") + + result['value'] = decoded_list + result['display_value'] = json.dumps(decoded_list, indent=2, ensure_ascii=False) + + elif key_type == 'set': + # Set类型 + set_data = redis_client.smembers(key) + decoded_set = [] + + for item in set_data: + try: + decoded_item = item.decode('utf-8') + decoded_set.append(decoded_item) + except UnicodeDecodeError: + decoded_set.append(f"") + + # 排序以便比较 + decoded_set.sort() + result['value'] = decoded_set + result['display_value'] = json.dumps(decoded_set, indent=2, ensure_ascii=False) + + elif key_type == 'zset': + # Sorted Set类型 + zset_data = redis_client.zrange(key, 0, -1, withscores=True) + decoded_zset = [] + + for member, score in zset_data: + try: + decoded_member = member.decode('utf-8') + decoded_zset.append([decoded_member, score]) + except UnicodeDecodeError: + decoded_zset.append([f"", score]) + + result['value'] = decoded_zset + result['display_value'] = json.dumps(decoded_zset, indent=2, ensure_ascii=False) + + else: + # 未知类型 + result['value'] = f"" + result['display_value'] = f"" + + return result + + except Exception as e: + logger.error(f"获取Redis键值失败 {key}: {e}") + return { + 'type': 'error', + 'value': None, + 'display_value': f"", + 'exists': False + } + +def compare_redis_values(value1_info, value2_info): + """ + 比较两个Redis值 + + Args: + value1_info: 第一个值的信息字典 + value2_info: 第二个值的信息字典 + + Returns: + dict: 比较结果 + """ + # 检查存在性 + if not value1_info['exists'] and not value2_info['exists']: + return { + 'status': 'both_missing', + 'message': '两个集群都不存在此键' + } + elif not value1_info['exists']: + return { + 'status': 'missing_in_cluster1', + 'message': '集群1中不存在此键' + } + elif not value2_info['exists']: + return { + 'status': 'missing_in_cluster2', + 'message': '集群2中不存在此键' + } + + # 检查类型 + type1 = value1_info['type'] + type2 = value2_info['type'] + + if type1 != type2: + return { + 'status': 'different', + 'message': f'数据类型不同: {type1} vs {type2}' + } + + # 比较值 + value1 = value1_info['value'] + value2 = value2_info['value'] + + if type1 in ['string', 'json_string']: + # 字符串比较 + if value1 == value2: + return {'status': 'identical', 'message': '值相同'} + else: + return {'status': 'different', 'message': '值不同'} + + elif type1 == 'hash': + # Hash比较 + if value1 == value2: + return {'status': 'identical', 'message': '哈希值相同'} + else: + # 详细比较哈希字段 + keys1 = set(value1.keys()) + keys2 = set(value2.keys()) + + if keys1 != keys2: + return {'status': 'different', 'message': f'哈希字段不同: {keys1 - keys2} vs {keys2 - keys1}'} + + diff_fields = [] + for key in keys1: + if value1[key] != value2[key]: + diff_fields.append(key) + + if diff_fields: + return {'status': 'different', 'message': f'哈希字段值不同: {diff_fields}'} + else: + return {'status': 'identical', 'message': '哈希值相同'} + + elif type1 == 'list': + # List比较(顺序敏感) + if value1 == value2: + return {'status': 'identical', 'message': '列表相同'} + else: + return {'status': 'different', 'message': f'列表不同,长度: {len(value1)} vs {len(value2)}'} + + elif type1 == 'set': + # Set比较(顺序无关) + if set(value1) == set(value2): + return {'status': 'identical', 'message': '集合相同'} + else: + return {'status': 'different', 'message': f'集合不同,大小: {len(value1)} vs {len(value2)}'} + + elif type1 == 'zset': + # Sorted Set比较 + if value1 == value2: + return {'status': 'identical', 'message': '有序集合相同'} + else: + return {'status': 'different', 'message': f'有序集合不同,大小: {len(value1)} vs {len(value2)}'} + + else: + # 其他类型的通用比较 + if value1 == value2: + return {'status': 'identical', 'message': '值相同'} + else: + return {'status': 'different', 'message': '值不同'} + +def batch_get_redis_values_with_type(redis_client, keys, cluster_name="Redis集群", performance_tracker=None): + """ + 批量获取Redis键值及类型信息 + + Args: + redis_client: Redis客户端 + keys: 键名列表 + cluster_name: 集群名称 + performance_tracker: 性能追踪器 + + Returns: + list: 每个键的值信息字典列表 + """ + import time + + start_time = time.time() + results = [] + + logger.info(f"开始从{cluster_name}批量获取 {len(keys)} 个键的详细信息") + + try: + for key in keys: + key_info = get_redis_value_with_type(redis_client, key) + results.append(key_info) + + end_time = time.time() + duration = end_time - start_time + + if performance_tracker: + performance_tracker.record_query(f"{cluster_name}_detailed_query", duration) + + successful_count = sum(1 for r in results if r['exists']) + logger.info(f"从{cluster_name}详细查询完成,成功获取 {successful_count}/{len(keys)} 个值,耗时 {duration:.3f} 秒") + + return results + + except Exception as e: + logger.error(f"从{cluster_name}批量详细查询失败: {e}") + # 返回错误占位符 + return [{'type': 'error', 'value': None, 'display_value': f'', 'exists': False} for _ in keys] \ No newline at end of file diff --git a/templates/redis_compare_old.html b/templates/redis_compare_old.html new file mode 100644 index 0000000..b5162ec --- /dev/null +++ b/templates/redis_compare_old.html @@ -0,0 +1,696 @@ + + + + + + Redis集群比对工具 + + + + + + + + +
+ + + + +
+
+
+ +
+

Redis集群比对工具

+

专业的Redis集群数据比对工具,支持随机采样和指定Key查询

+
+
+
+
+ + +
+ +
+
+

配置管理

+ +
+ +
+
+
+
配置组管理
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+ + +
+
+
+
查询历史
+
+
+
+
+ +
+
+ +
+
+
+
+ +
+
+ +
+
+
+
+
+
+ + +
+
+
+
+
配置导入
+
+
+ + + + 支持YAML格式配置导入,如:clusterName、clusterAddress、clusterPassword等 + +
+
+
+
+
+
+
查询日志
+
+
+ + + 查看Redis比较操作的详细执行日志 + +
+
+
+
+
+
+ + +
+
+

Redis集群配置

+ +
+ +
+
+
集群1 (生产环境)
+ +
+ + +
+ +
+ +
+
+ + + +
+
+ +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+ + +
+
+
集群2 (测试环境)
+ +
+ + +
+ +
+ +
+
+ + + +
+
+ +
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+ +
+
+
+
+
+
+
+ + +
+
+
+

查询选项

+ +
+ +
+ + +
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+
+ + +
+
+
+ + + +
+
+ Loading... +
+ 正在执行Redis数据比较,请稍候... +
+
+
+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file