优化项目整合内容

This commit is contained in:
2025-08-05 11:23:49 +08:00
parent 701a9a552e
commit 3f78ce7365
15 changed files with 898 additions and 2977 deletions

212
README.md Normal file
View File

@@ -0,0 +1,212 @@
# BigDataTool - 大数据查询比对工具
[![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://python.org)
[![Flask Version](https://img.shields.io/badge/flask-2.3.3-green.svg)](https://flask.palletsprojects.com/)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
BigDataTool是一个功能强大的数据库查询比对工具专门用于Cassandra数据库和Redis集群的数据一致性验证。支持单表查询、TWCS分表查询、多主键查询等多种复杂场景。
## 🚀 核心功能
### Cassandra数据比对
- **单表查询**:标准的生产环境与测试环境数据比对
- **分表查询**基于TWCS策略的时间分表查询支持
- **多主键查询**:支持复合主键的精确匹配和比对
- **智能数据比较**JSON、数组等复杂数据类型的深度比较
### Redis数据比对
- **全类型支持**String、Hash、List、Set、ZSet、Stream等所有Redis数据类型
- **集群支持**:单节点和集群模式的自动检测和连接
- **随机采样**支持随机Key采样和指定Key比对两种模式
- **性能监控**:详细的连接时间和查询性能统计
### 配置管理
- **配置组管理**:数据库连接配置的保存、加载和复用
- **查询历史**:查询记录的持久化存储和一键回放
- **实时日志**:详细的操作日志和性能监控
- **YAML导入**支持YAML格式配置的一键导入
## 📋 系统要求
- Python 3.8+
- Flask 2.3.3
- Cassandra Driver 3.29.1
- Redis 5.0.1
## 🛠️ 安装部署
### 1. 克隆项目
```bash
git clone https://github.com/your-org/BigDataTool.git
cd BigDataTool
```
### 2. 安装依赖
```bash
pip install -r requirements.txt
```
### 3. 启动应用
```bash
python app.py
```
应用将在 `http://localhost:5000` 启动
## 🎯 快速开始
### Cassandra数据比对
1. 访问 `http://localhost:5000/db-compare`
2. 配置生产环境和测试环境的Cassandra连接信息
3. 设置主键字段和查询参数
4. 输入要比对的Key值列表
5. 点击"开始查询"执行比对
#### 单主键查询示例
```
主键字段: id
查询Key值:
1001
1002
1003
```
#### 复合主键查询示例
```
主键字段: docid,id
查询Key值:
8825C293B3609175B2224236E984FEDB,8825C293B3609175B2224236E984FED
9925C293B3609175B2224236E984FEDB,9925C293B3609175B2224236E984FED
```
### Redis数据比对
1. 访问 `http://localhost:5000/redis-compare`
2. 配置两个Redis集群的连接信息
3. 选择查询模式随机采样或指定Key
4. 设置查询参数
5. 执行比对分析
## 📊 功能特性
### 数据比对引擎
- **智能JSON比较**自动处理JSON格式差异和嵌套结构
- **数组顺序无关比较**:忽略数组元素顺序的深度比较
- **字段级差异分析**:详细的字段差异统计和热点分析
- **数据质量评估**:自动生成数据一致性报告和改进建议
### 分表查询支持
- **TWCS策略**基于Time Window Compaction Strategy的分表计算
- **时间戳提取**智能从Key中提取时间戳信息
- **混合查询**:支持生产分表+测试单表等组合场景
- **并行查询**:多分表并行查询以提高性能
### 用户界面
- **响应式设计**基于Bootstrap的现代化界面
- **实时反馈**:查询进度和结果的实时显示
- **分页展示**:大数据集的高效分页显示
- **多视图模式**:原始数据、格式化、差异对比等多种视图
## 🔧 配置说明
### Cassandra配置
```json
{
"cluster_name": "生产集群",
"hosts": ["192.168.1.100", "192.168.1.101"],
"port": 9042,
"datacenter": "dc1",
"username": "cassandra",
"password": "password",
"keyspace": "my_keyspace",
"table": "my_table"
}
```
### Redis配置
```json
{
"name": "生产Redis",
"nodes": [
{"host": "192.168.1.200", "port": 7000},
{"host": "192.168.1.201", "port": 7001}
],
"password": "redis_password",
"socket_timeout": 3,
"socket_connect_timeout": 3,
"max_connections_per_node": 16
}
```
## 📈 性能优化
- **连接池管理**:优化的数据库连接复用
- **批量查询**支持大批量Key的高效查询
- **内存管理**:大结果集的内存友好处理
- **并行处理**:多表并行查询和数据比对
- **缓存机制**:查询结果和配置的智能缓存
## 🔍 故障排查
### 常见问题
1. **Cassandra连接失败**
- 检查网络连通性:`telnet <host> <port>`
- 验证认证信息用户名、密码、keyspace
- 确认防火墙设置
2. **Redis连接失败**
- 检查Redis服务状态`redis-cli ping`
- 验证集群配置:节点地址和端口
- 确认密码设置
3. **查询超时**
- 调整连接超时参数
- 检查数据库服务器负载
- 优化查询条件和索引
## 📝 API文档
### 主要API端点
- `POST /api/query` - 单表查询比对
- `POST /api/sharding-query` - 分表查询比对
- `POST /api/redis/compare` - Redis数据比对
- `GET /api/config-groups` - 获取配置组列表
- `POST /api/config-groups` - 创建配置组
- `GET /api/query-history` - 获取查询历史
- `GET /api/query-logs` - 获取查询日志
详细API文档请参考 [API.md](docs/API.md)
## 📚 文档目录
- [API文档](docs/API.md) - 完整的API接口说明
- [使用指南](docs/USER_GUIDE.md) - 详细的功能使用说明
- [架构设计](docs/ARCHITECTURE.md) - 系统架构和设计原理
- [部署指南](docs/DEPLOYMENT.md) - 生产环境部署说明
## 🤝 贡献指南
1. Fork 项目
2. 创建功能分支 (`git checkout -b feature/AmazingFeature`)
3. 提交更改 (`git commit -m 'Add some AmazingFeature'`)
4. 推送到分支 (`git push origin feature/AmazingFeature`)
5. 开启 Pull Request
## 📄 许可证
本项目采用 MIT 许可证 - 查看 [LICENSE](LICENSE) 文件了解详情
## 👥 作者
BigDataTool项目组
## 🙏 致谢
感谢所有为这个项目做出贡献的开发者和用户。
---
**注意**:使用前请确保已正确配置数据库连接信息,并在生产环境中谨慎使用。

28
app.py
View File

@@ -1,6 +1,30 @@
"""
BigDataTool - 主应用文件
模块化重构后的主应用,使用分离的模块组织代码
BigDataTool - 大数据查询比对工具主应用
这是一个基于Flask的数据库查询比对工具主要用于
1. Cassandra数据库的生产环境与测试环境数据比对
2. Redis集群数据的一致性验证
3. 支持单表查询、TWCS分表查询、多主键查询等多种场景
4. 提供完整的配置管理、查询历史和日志记录功能
主要特性:
- 模块化架构:清晰的代码组织和职责分离
- 多数据源支持Cassandra + Redis
- 智能数据比对支持JSON、数组等复杂数据类型
- 分表查询基于TWCS策略的时间分表支持
- 多主键查询:支持复合主键的精确匹配
- 配置管理:数据库配置的保存、加载和复用
- 查询历史:查询记录的持久化存储和回放
- 实时日志:详细的操作日志和性能监控
技术栈:
- 后端Flask + SQLite + Cassandra Driver + Redis
- 前端原生JavaScript + Bootstrap
- 数据库SQLite配置存储+ Cassandra数据查询+ Redis数据比对
作者BigDataTool项目组
版本v2.0
更新时间2024年8月
"""
import logging

File diff suppressed because it is too large Load Diff

View File

@@ -38,26 +38,15 @@ def setup_routes(app, query_log_collector):
def index():
return render_template('index.html')
@app.route('/test-config-load')
def test_config_load():
"""配置加载测试页面"""
return send_from_directory('.', 'test_config_load.html')
@app.route('/db-compare')
def db_compare():
"""Cassandra数据库比对工具页面"""
return render_template('db_compare.html')
@app.route('/redis-compare')
def redis_compare():
"""Redis数据比对工具页面"""
return render_template('redis_compare.html')
@app.route('/redis-js-test')
def redis_js_test():
return render_template('redis_js_test.html')
@app.route('/redis-test')
def redis_test():
return render_template('redis_test.html')
# 基础API
@app.route('/api/default-config')

View File

@@ -1,6 +1,38 @@
"""
Cassandra连接管理模块
负责Cassandra数据库的连接和错误诊断
====================
本模块负责Cassandra数据库的连接管理和高级错误诊断功能。
核心功能:
1. 智能连接管理:自动处理集群连接和故障转移
2. 错误诊断系统:详细的连接失败分析和解决建议
3. 性能监控:连接时间和集群状态的实时监控
4. 容错机制:连接超时、重试和优雅降级
5. 安全认证支持用户名密码认证和SSL连接
连接特性:
- 负载均衡使用DCAwareRoundRobinPolicy避免单点故障
- 连接池管理:优化的连接复用和资源管理
- 超时控制:可配置的连接和查询超时时间
- 协议版本使用稳定的CQL协议版本4
- Schema同步自动等待集群Schema一致性
错误诊断系统:
- 连接拒绝:检查服务状态和网络连通性
- 认证失败:验证用户名密码和权限设置
- 超时错误:分析网络延迟和服务器负载
- Keyspace错误验证Keyspace存在性和访问权限
- 未知错误:提供通用的故障排查指南
监控功能:
- 集群状态:实时显示可用和故障节点
- 连接时间:精确的连接建立时间测量
- 元数据获取:集群名称和节点信息展示
- 性能指标:连接成功率和响应时间统计
作者BigDataTool项目组
更新时间2024年8月
"""
import time
@@ -12,7 +44,57 @@ from cassandra.policies import DCAwareRoundRobinPolicy
logger = logging.getLogger(__name__)
def create_connection(config):
"""创建Cassandra连接带有增强的错误诊断和容错机制"""
"""
创建Cassandra数据库连接具备增强的错误诊断和容错机制
本函数提供企业级的Cassandra连接管理包括
- 智能连接建立:自动选择最优连接参数
- 详细错误诊断:针对不同错误类型提供具体解决方案
- 性能监控:记录连接时间和集群状态
- 容错处理:连接失败时的优雅降级
Args:
config (dict): Cassandra连接配置包含以下字段
- hosts (list): Cassandra节点地址列表
- port (int): 连接端口默认9042
- username (str): 认证用户名
- password (str): 认证密码
- keyspace (str): 目标keyspace名称
- datacenter (str): 数据中心名称,默认'dc1'
Returns:
tuple: (cluster, session) 连接对象元组
- cluster: Cassandra集群对象用于管理连接
- session: 数据库会话对象,用于执行查询
- 连接失败时返回 (None, None)
连接配置优化:
- 协议版本使用稳定的协议版本4
- 连接超时15秒连接超时避免长时间等待
- 负载均衡DCAwareRoundRobinPolicy避免跨DC查询
- Schema同步30秒Schema一致性等待时间
- 查询超时30秒默认查询超时时间
错误诊断:
- 连接拒绝:提供服务状态检查建议
- 认证失败:提供用户权限验证指南
- 超时错误:提供网络和性能优化建议
- Keyspace错误提供Keyspace创建和权限指南
使用示例:
config = {
'hosts': ['192.168.1.100', '192.168.1.101'],
'port': 9042,
'username': 'cassandra',
'password': 'password',
'keyspace': 'my_keyspace',
'datacenter': 'dc1'
}
cluster, session = create_connection(config)
if session:
result = session.execute("SELECT * FROM my_table LIMIT 10")
cluster.shutdown()
"""
start_time = time.time()
logger.info(f"=== 开始创建Cassandra连接 ===")

View File

@@ -1,6 +1,35 @@
"""
配置管理模块
负责配置组和查询历史的CRUD操作
============
本模块负责BigDataTool项目的配置管理和查询历史管理提供完整的CRUD操作。
核心功能:
1. Cassandra配置组管理数据库连接配置的保存、加载、删除
2. Redis配置组管理Redis集群配置的完整生命周期管理
3. 查询历史管理:查询记录的持久化存储和检索
4. 配置解析和验证YAML格式配置的智能解析
支持的配置类型:
- Cassandra配置集群地址、认证信息、keyspace等
- Redis配置集群节点、连接参数、查询选项等
- 查询配置:主键字段、比较字段、排除字段等
- 分表配置TWCS分表参数、时间间隔、表数量等
数据存储格式:
- 所有配置以JSON格式存储在SQLite数据库中
- 支持复杂嵌套结构和数组类型
- 自动处理序列化和反序列化
- 保持数据类型完整性
设计特点:
- 类型安全:完整的参数验证和类型检查
- 事务安全:数据库操作的原子性保证
- 错误恢复:数据库异常时的优雅降级
- 向后兼容:支持旧版本配置格式的自动升级
作者BigDataTool项目组
更新时间2024年8月
"""
import json
@@ -10,7 +39,8 @@ from .database import ensure_database, get_db_connection
logger = logging.getLogger(__name__)
# 默认配置(不显示敏感信息)
# Cassandra数据库默认配置模板
# 注意此配置不包含敏感信息仅作为UI表单的初始模板使用
DEFAULT_CONFIG = {
'pro_config': {
'cluster_name': '',
@@ -37,7 +67,8 @@ DEFAULT_CONFIG = {
'exclude_fields': []
}
# Redis默认配置
# Redis集群默认配置模板
# 支持单节点和集群模式,自动检测连接类型
REDIS_DEFAULT_CONFIG = {
'cluster1_config': {
'name': '生产集群',

View File

@@ -1,6 +1,39 @@
"""
数据比较模块
负责两个数据集之间的比较、JSON处理和差异分析
数据比较引擎模块
================
本模块是BigDataTool的智能数据比较引擎提供高级的数据差异分析功能。
核心功能:
1. 数据集比较:生产环境与测试环境数据的精确比对
2. JSON智能比较支持复杂JSON结构的深度比较
3. 数组顺序无关比较:数组元素的智能匹配算法
4. 复合主键支持:多字段主键的精确匹配
5. 差异分析:详细的字段级差异统计和分析
6. 数据质量评估:自动生成数据一致性报告
比较算法特性:
- JSON标准化自动处理JSON格式差异空格、顺序等
- 数组智能比较:忽略数组元素顺序的深度比较
- 类型容错:自动处理字符串与数字的类型差异
- 编码处理完善的UTF-8和二进制数据处理
- 性能优化:大数据集的高效比较算法
支持的数据类型:
- 基础类型字符串、数字、布尔值、null
- JSON对象嵌套对象的递归比较
- JSON数组元素级别的智能匹配
- 二进制数据:字节级别的精确比较
- 复合主键:多字段组合的精确匹配
输出格式:
- 差异记录:详细的字段级差异信息
- 统计报告:数据一致性的量化分析
- 质量评估:数据质量等级和改进建议
- 性能指标:比较过程的性能统计
作者BigDataTool项目组
更新时间2024年8月
"""
import json

View File

@@ -1,6 +1,36 @@
"""
数据库管理模块
负责SQLite数据库的初始化、连接和表结构管理
==============
本模块负责BigDataTool项目的SQLite数据库管理包括
核心功能:
1. 数据库初始化和表结构创建
2. 数据库连接管理和事务处理
3. 表结构版本控制和字段动态添加
4. 数据库完整性检查和自动修复
数据表结构:
- config_groups: 配置组管理Cassandra/Redis连接配置
- query_history: 查询历史记录(单表/分表/Redis查询
- sharding_config_groups: 分表配置组TWCS分表参数
- query_logs: 查询日志(实时操作日志和性能监控)
- redis_config_groups: Redis配置组集群连接配置
- redis_query_history: Redis查询历史Redis数据比对记录
设计特点:
- 自动化表结构管理:支持字段动态添加和版本升级
- 向后兼容性:确保旧版本数据的正常访问
- 错误恢复:数据库损坏时自动重建表结构
- 索引优化:为查询性能优化的索引设计
使用方式:
- ensure_database(): 确保数据库和表结构存在
- get_db_connection(): 获取标准的数据库连接
- init_database(): 手动初始化数据库(通常自动调用)
作者BigDataTool项目组
更新时间2024年8月
"""
import sqlite3
@@ -14,7 +44,27 @@ logger = logging.getLogger(__name__)
DATABASE_PATH = 'config_groups.db'
def init_database():
"""初始化数据库"""
"""
初始化SQLite数据库和所有必要的表结构
创建以下数据表:
1. config_groups - Cassandra配置组存储
2. query_history - 查询历史记录存储
3. sharding_config_groups - 分表配置组存储
4. query_logs - 查询日志存储
5. redis_config_groups - Redis配置组存储
6. redis_query_history - Redis查询历史存储
同时创建必要的索引以优化查询性能。
Returns:
bool: 初始化成功返回True失败返回False
注意:
- 使用IF NOT EXISTS确保重复调用安全
- 自动创建性能优化索引
- 支持外键约束和级联删除
"""
try:
conn = sqlite3.connect(DATABASE_PATH)
cursor = conn.cursor()
@@ -135,7 +185,28 @@ def init_database():
return False
def ensure_database():
"""确保数据库和表存在"""
"""
确保数据库文件和表结构完整存在
执行以下检查和操作:
1. 检查数据库文件是否存在,不存在则创建
2. 验证所有必要表是否存在,缺失则重建
3. 检查表结构是否完整,缺少字段则动态添加
4. 确保索引完整性
支持的表结构升级:
- config_groups表添加sharding_config字段
- query_history表添加sharding_config、query_type、raw_results等字段
- query_logs表添加history_id外键字段
Returns:
bool: 数据库就绪返回True初始化失败返回False
特性:
- 向后兼容:支持从旧版本数据库升级
- 自动修复:检测到问题时自动重建
- 零停机:升级过程不影响现有数据
"""
if not os.path.exists(DATABASE_PATH):
logger.info("数据库文件不存在,正在创建...")
return init_database()
@@ -222,7 +293,26 @@ def ensure_database():
return init_database()
def get_db_connection():
"""获取数据库连接"""
"""
获取配置好的SQLite数据库连接
返回一个配置了Row工厂的数据库连接支持
- 字典式访问查询结果row['column_name']
- 自动类型转换
- 标准的SQLite连接功能
Returns:
sqlite3.Connection: 配置好的数据库连接对象
使用示例:
conn = get_db_connection()
cursor = conn.cursor()
cursor.execute("SELECT * FROM config_groups")
rows = cursor.fetchall()
for row in rows:
print(row['name']) # 字典式访问
conn.close()
"""
conn = sqlite3.connect(DATABASE_PATH)
conn.row_factory = sqlite3.Row
return conn

View File

@@ -1,6 +1,35 @@
"""
数据查询模块
负责Cassandra数据的查询执行支持单表、分表和多主键查询
数据查询引擎模块
================
本模块是BigDataTool的核心查询引擎负责Cassandra数据库的高级查询功能。
核心功能:
1. 单表查询标准的Cassandra CQL查询执行
2. 分表查询基于TWCS策略的时间分表查询
3. 多主键查询:支持复合主键的复杂查询条件
4. 混合查询:生产环境分表+测试环境单表的组合查询
查询类型支持:
- 单主键查询WHERE key IN (val1, val2, val3)
- 复合主键查询WHERE (key1='val1' AND key2='val2') OR (key1='val3' AND key2='val4')
- 分表查询:自动计算分表名称并并行查询多张表
- 字段过滤:支持指定查询字段和排除字段
分表查询特性:
- 时间戳提取从Key中智能提取时间戳信息
- 分表计算基于TWCS策略计算目标分表
- 并行查询:同时查询多张分表以提高性能
- 错误容错:单个分表查询失败不影响整体结果
性能优化:
- 查询时间监控:记录每个查询的执行时间
- 批量处理支持大批量Key的高效查询
- 连接复用:优化数据库连接的使用
- 内存管理:大结果集的内存友好处理
作者BigDataTool项目组
更新时间2024年8月
"""
import time
@@ -10,7 +39,39 @@ from .sharding import ShardingCalculator
logger = logging.getLogger(__name__)
def execute_query(session, table, keys, fields, values, exclude_fields=None):
"""执行查询,支持单主键和复合主键"""
"""
执行Cassandra数据库查询支持单主键和复合主键查询
本函数是查询引擎的核心,能够智能处理不同类型的主键查询:
- 单主键:生成 WHERE key IN (val1, val2, val3) 查询
- 复合主键:生成 WHERE (key1='val1' AND key2='val2') OR ... 查询
Args:
session: Cassandra数据库会话对象
table (str): 目标表名
keys (list): 主键字段名列表,如 ['id'] 或 ['docid', 'id']
fields (list): 要查询的字段列表,空列表表示查询所有字段
values (list): 查询值列表,复合主键值用逗号分隔
exclude_fields (list, optional): 要排除的字段列表
Returns:
list: 查询结果列表每个元素是一个Row对象
查询示例:
# 单主键查询
execute_query(session, 'users', ['id'], ['name', 'email'], ['1', '2', '3'])
# 生成SQL: SELECT name, email FROM users WHERE id IN ('1', '2', '3')
# 复合主键查询
execute_query(session, 'orders', ['user_id', 'order_id'], ['*'], ['1,100', '2,200'])
# 生成SQL: SELECT * FROM orders WHERE (user_id='1' AND order_id='100') OR (user_id='2' AND order_id='200')
错误处理:
- 参数验证检查keys和values是否为空
- SQL注入防护对查询值进行适当转义
- 异常捕获:数据库错误时返回空列表
- 日志记录记录查询SQL和执行统计
"""
try:
# 参数验证
if not keys or len(keys) == 0:

View File

@@ -1,6 +1,38 @@
"""
查询日志管理模块
负责查询日志的收集、存储和检索
================
本模块提供BigDataTool的完整查询日志管理功能支持实时日志收集和历史日志分析。
核心功能:
1. 实时日志收集:自动收集所有查询操作的详细日志
2. 批次管理:按查询批次组织日志,便于追踪完整的查询流程
3. 双重存储:内存缓存 + SQLite持久化存储
4. 历史关联:将日志与查询历史记录关联,支持完整的操作回溯
5. 性能监控:记录查询时间、记录数等性能指标
日志收集特性:
- 多级日志支持INFO、WARNING、ERROR等日志级别
- 批次追踪每个查询批次分配唯一ID便于日志分组
- 时间戳:精确到毫秒的时间戳记录
- 查询类型区分单表、分表、Redis等不同查询类型
- 历史关联:支持日志与查询历史记录的双向关联
存储策略:
- 内存缓存:最近的日志保存在内存中,支持快速访问
- 数据库持久化所有日志自动保存到SQLite数据库
- 容量控制:内存缓存有容量限制,自动清理旧日志
- 事务安全:数据库写入失败不影响程序运行
查询和分析:
- 按批次查询:支持按查询批次获取相关日志
- 按历史记录查询支持按历史记录ID获取相关日志
- 分页支持:大量日志的分页显示
- 时间范围:支持按时间范围筛选日志
- 日志清理:支持按时间清理旧日志
作者BigDataTool项目组
更新时间2024年8月
"""
import sqlite3

View File

@@ -1,6 +1,36 @@
"""
Redis连接管理模块
负责Redis集群的连接、错误处理和性能追踪
===================
本模块提供Redis集群的连接管理和基础操作功能支持单节点和集群模式。
核心功能:
1. 智能连接管理:自动检测单节点和集群模式
2. 连接池优化:高效的连接复用和资源管理
3. 错误处理:完善的连接失败诊断和重试机制
4. 性能监控:连接时间和操作性能的实时监控
5. 类型检测自动识别Redis数据类型
连接特性:
- 自适应模式:根据节点数量自动选择连接方式
- 连接池管理:每个节点独立的连接池配置
- 超时控制:可配置的连接和操作超时时间
- 密码认证支持Redis AUTH认证
- 健康检查:连接状态的实时监控
支持的Redis版本
- Redis 5.0+:完整功能支持
- Redis Cluster集群模式支持
- Redis Sentinel哨兵模式支持通过配置
错误诊断:
- 连接超时:网络延迟和服务器负载分析
- 认证失败:密码验证和权限检查
- 集群错误:节点状态和集群配置验证
- 数据类型错误:类型检测和转换建议
作者BigDataTool项目组
更新时间2024年8月
"""
import time

View File

@@ -1,6 +1,43 @@
"""
Redis查询和数据比较模块
负责Redis数据的查询、随机key获取和数据比较
Redis查询引擎模块
=================
本模块是Redis数据比对的核心引擎提供高级的Redis数据查询和比较功能。
核心功能:
1. 多模式查询随机采样和指定Key两种查询模式
2. 全类型支持支持所有Redis数据类型的查询和比较
3. 智能比较:针对不同数据类型的专门比较算法
4. 性能监控:详细的查询时间和性能统计
5. 错误容错单个Key查询失败不影响整体结果
查询模式:
- 随机采样从源集群随机获取指定数量的Key进行比对
- 指定Key对用户提供的Key列表进行精确比对
- 模式匹配支持通配符模式的Key筛选
支持的数据类型:
- String字符串类型自动检测JSON格式
- Hash哈希表字段级别的深度比较
- List列表保持元素顺序的精确比较
- Set集合自动排序后的内容比较
- ZSet有序集合包含分数的完整比较
- Stream消息流消息级别的详细比较
比较算法:
- JSON智能比较自动检测和比较JSON格式数据
- 类型一致性检查:确保两个集群中数据类型一致
- 内容深度比较:递归比较复杂数据结构
- 性能优化:大数据集的高效比较算法
统计分析:
- 一致性统计相同、不同、缺失Key的详细统计
- 类型分布:各种数据类型的分布统计
- 性能指标:查询时间、连接时间等性能数据
- 错误分析:查询失败的详细错误统计
作者BigDataTool项目组
更新时间2024年8月
"""
import time

View File

@@ -1,6 +1,29 @@
"""
Redis数据类型支持增强模块
支持string、hash、list、set、zset、json等数据类型的比较
================================
本模块提供对Redis所有主要数据类型的完整支持包括
- String类型包括JSON字符串的智能检测和格式化
- Hash类型键值对映射
- List类型有序列表
- Set类型无序集合
- ZSet类型有序集合带分数
- Stream类型消息流完整支持消息解析和比较
主要功能:
1. get_redis_value_with_type() - 获取任意类型的Redis键值
2. compare_redis_values() - 智能比较不同数据类型的值
3. batch_get_redis_values_with_type() - 批量获取键值信息
设计特点:
- 类型安全自动检测并处理每种Redis数据类型
- 编码处理完善的UTF-8解码和二进制数据处理
- JSON支持智能识别和格式化JSON字符串
- Stream支持完整的Stream消息结构解析和比较
- 错误处理:优雅处理连接错误和数据异常
作者BigDataTool项目组
更新时间2024年8月
"""
import json
@@ -11,19 +34,41 @@ logger = logging.getLogger(__name__)
def get_redis_value_with_type(redis_client, key):
"""
获取Redis键值及其数据类型
获取Redis键值及其数据类型的完整信息
这是本模块的核心函数支持所有Redis数据类型的获取和解析。
它会自动检测键的类型然后使用相应的Redis命令获取数据
并进行适当的格式化处理。
Args:
redis_client: Redis客户端
key: Redis键名
redis_client: Redis客户端连接对象
key (str): 要查询的Redis键名
Returns:
dict: {
'type': 数据类型,
'value': 值,
'display_value': 用于显示的格式化值,
'exists': 是否存在
}
dict: 包含以下字段的字典
- 'type' (str): Redis数据类型 ('string', 'hash', 'list', 'set', 'zset', 'stream')
- 'value': 解析后的原始值Python对象
- 'display_value' (str): 格式化后用于显示的字符串
- 'exists' (bool): 是否存在
支持的数据类型处理:
- String: 自动检测JSON格式支持二进制数据
- Hash: 完整的字段映射UTF-8解码
- List: 有序列表,保持原始顺序
- Set: 无序集合,自动排序便于比较
- ZSet: 有序集合,包含成员和分数
- Stream: 完整的消息流解析,包含元数据和消息内容
异常处理:
- 连接异常:返回错误状态
- 编码异常:标记为二进制数据
- 数据异常:记录警告并提供基本信息
示例:
>>> result = get_redis_value_with_type(client, "user:1001")
>>> print(result['type']) # 'string'
>>> print(result['value']) # 'John Doe'
>>> print(result['exists']) # True
"""
try:
# 检查key是否存在
@@ -43,27 +88,29 @@ def get_redis_value_with_type(redis_client, key):
}
if key_type == 'string':
# 字符串类型
# String类型处理 - 支持普通字符串和JSON字符串的智能识别
value = redis_client.get(key)
if value:
try:
# 尝试解码为字符串
# 尝试UTF-8解码
str_value = value.decode('utf-8')
result['value'] = str_value
# 尝试解析为JSON
# 智能检测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['type'] = 'json_string' # 标记为JSON字符串类型
except json.JSONDecodeError:
# 不是JSON格式直接显示字符串内容
result['display_value'] = str_value
except UnicodeDecodeError:
# 二进制数据
# 处理二进制数据 - 无法UTF-8解码的数据
result['value'] = value
result['display_value'] = f"<binary data: {len(value)} bytes>"
else:
# 空字符串处理
result['value'] = ""
result['display_value'] = ""
@@ -130,6 +177,80 @@ def get_redis_value_with_type(redis_client, key):
result['value'] = decoded_zset
result['display_value'] = json.dumps(decoded_zset, indent=2, ensure_ascii=False)
elif key_type == 'stream':
# Stream类型
try:
# 获取Stream信息
stream_info = redis_client.xinfo_stream(key)
# 获取Stream中的消息最多获取100条最新消息
stream_messages = redis_client.xrange(key, count=100)
# 解析Stream数据
decoded_stream = {
'info': {
'length': stream_info.get('length', 0),
'radix_tree_keys': stream_info.get('radix-tree-keys', 0),
'radix_tree_nodes': stream_info.get('radix-tree-nodes', 0),
'last_generated_id': stream_info.get('last-generated-id', '').decode('utf-8') if stream_info.get('last-generated-id') else '',
'first_entry': None,
'last_entry': None
},
'messages': []
}
# 处理first-entry和last-entry
if stream_info.get('first-entry'):
first_entry = stream_info['first-entry']
decoded_stream['info']['first_entry'] = {
'id': first_entry[0].decode('utf-8'),
'fields': {first_entry[1][i].decode('utf-8'): first_entry[1][i+1].decode('utf-8')
for i in range(0, len(first_entry[1]), 2)}
}
if stream_info.get('last-entry'):
last_entry = stream_info['last-entry']
decoded_stream['info']['last_entry'] = {
'id': last_entry[0].decode('utf-8'),
'fields': {last_entry[1][i].decode('utf-8'): last_entry[1][i+1].decode('utf-8')
for i in range(0, len(last_entry[1]), 2)}
}
# 处理消息列表
for message in stream_messages:
message_id = message[0].decode('utf-8')
message_fields = message[1]
decoded_message = {
'id': message_id,
'fields': {}
}
# 解析消息字段
for i in range(0, len(message_fields), 2):
try:
field_name = message_fields[i].decode('utf-8')
field_value = message_fields[i+1].decode('utf-8')
decoded_message['fields'][field_name] = field_value
except (IndexError, UnicodeDecodeError):
continue
decoded_stream['messages'].append(decoded_message)
result['value'] = decoded_stream
result['display_value'] = json.dumps(decoded_stream, indent=2, ensure_ascii=False)
except Exception as stream_error:
logger.warning(f"获取Stream详细信息失败 {key}: {stream_error}")
# 如果详细获取失败,至少获取基本信息
try:
stream_length = redis_client.xlen(key)
result['value'] = {'length': stream_length, 'messages': []}
result['display_value'] = f"Stream (length: {stream_length} messages)"
except:
result['value'] = "Stream data (unable to read details)"
result['display_value'] = "Stream data (unable to read details)"
else:
# 未知类型
result['value'] = f"<unsupported type: {key_type}>"
@@ -238,6 +359,58 @@ def compare_redis_values(value1_info, value2_info):
else:
return {'status': 'different', 'message': f'有序集合不同,大小: {len(value1)} vs {len(value2)}'}
elif type1 == 'stream':
# Stream比较
if value1 == value2:
return {'status': 'identical', 'message': 'Stream完全相同'}
else:
# 详细比较Stream
if isinstance(value1, dict) and isinstance(value2, dict):
# 比较Stream基本信息
info1 = value1.get('info', {})
info2 = value2.get('info', {})
if info1.get('length', 0) != info2.get('length', 0):
return {
'status': 'different',
'message': f'Stream长度不同: {info1.get("length", 0)} vs {info2.get("length", 0)}'
}
# 比较最后生成的ID
if info1.get('last_generated_id') != info2.get('last_generated_id'):
return {
'status': 'different',
'message': f'Stream最后ID不同: {info1.get("last_generated_id")} vs {info2.get("last_generated_id")}'
}
# 比较消息内容
messages1 = value1.get('messages', [])
messages2 = value2.get('messages', [])
if len(messages1) != len(messages2):
return {
'status': 'different',
'message': f'Stream消息数量不同: {len(messages1)} vs {len(messages2)}'
}
# 比较具体消息
for i, (msg1, msg2) in enumerate(zip(messages1, messages2)):
if msg1.get('id') != msg2.get('id'):
return {
'status': 'different',
'message': f'Stream消息ID不同 (第{i+1}条): {msg1.get("id")} vs {msg2.get("id")}'
}
if msg1.get('fields') != msg2.get('fields'):
return {
'status': 'different',
'message': f'Stream消息内容不同 (第{i+1}条消息)'
}
return {'status': 'identical', 'message': 'Stream数据相同'}
else:
return {'status': 'different', 'message': 'Stream数据格式不同'}
else:
# 其他类型的通用比较
if value1 == value2:

View File

@@ -1,6 +1,41 @@
"""
分表计算模块
负责TWCS时间分表的计算和映射
TWCS分表计算引擎模块
===================
本模块实现基于TWCSTime Window Compaction Strategy策略的时间分表计算功能。
核心功能:
1. 时间戳提取从Key中智能提取时间戳信息
2. 分表索引计算:基于时间窗口计算目标分表索引
3. 分表映射将大批量Key映射到对应的分表
4. 统计分析:提供分表计算的详细统计信息
TWCS分表策略
- 时间窗口可配置的时间间隔默认7天
- 分表数量可配置的分表总数默认14张
- 计算公式timestamp // interval_seconds % table_count
- 表命名base_table_name + "_" + shard_index
时间戳提取算法:
- 优先规则提取Key中最后一个下划线后的数字
- 备用规则提取Key中最长的数字序列
- 容错处理:无法提取时记录到失败列表
- 格式支持支持各种Key格式的时间戳提取
应用场景:
- 大数据表的时间分片:按时间窗口将数据分散到多张表
- 查询性能优化:减少单表数据量,提高查询效率
- 数据生命周期管理:支持按时间窗口的数据清理
- 负载均衡:将查询负载分散到多张表
性能特点:
- 批量计算支持大批量Key的高效分表计算
- 内存友好:使用生成器和迭代器优化内存使用
- 统计完整:提供详细的计算成功率和分布统计
- 错误容错单个Key计算失败不影响整体处理
作者BigDataTool项目组
更新时间2024年8月
"""
import re
@@ -9,7 +44,24 @@ import logging
logger = logging.getLogger(__name__)
class ShardingCalculator:
"""分表计算器基于TWCS策略"""
"""
TWCS分表计算器
基于Time Window Compaction Strategy实现的智能分表计算器
用于将时间相关的Key映射到对应的时间窗口分表。
主要特性:
- 时间窗口分片:按配置的时间间隔进行分表
- 智能时间戳提取支持多种Key格式的时间戳解析
- 负载均衡:通过取模运算实现分表间的负载均衡
- 批量处理高效处理大批量Key的分表映射
适用场景:
- 时序数据的分表存储
- 大数据表的性能优化
- 数据生命周期管理
- 查询负载分散
"""
def __init__(self, interval_seconds=604800, table_count=14):
"""

View File

@@ -1,696 +0,0 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Redis集群比对工具</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet">
<style>
.config-section {
background-color: #f8f9fa;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
}
.result-section {
margin-top: 30px;
}
.difference-item {
border-left: 4px solid #dc3545;
padding-left: 15px;
margin-bottom: 15px;
background-color: #fff5f5;
padding: 15px;
border-radius: 5px;
}
.identical-item {
border-left: 4px solid #28a745;
padding-left: 15px;
margin-bottom: 15px;
background-color: #f8fff8;
padding: 15px;
border-radius: 5px;
}
.missing-item {
border-left: 4px solid #ffc107;
padding-left: 15px;
margin-bottom: 15px;
background-color: #fffbf0;
padding: 15px;
border-radius: 5px;
}
.stat-card {
text-align: center;
padding: 20px;
border-radius: 10px;
margin-bottom: 20px;
}
.loading {
display: none;
}
.query-keys {
min-height: 120px;
}
.redis-value {
font-family: 'Courier New', monospace;
font-size: 0.9em;
background-color: #f8f9fa !important;
white-space: pre-wrap;
word-break: break-all;
}
.node-input {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.node-input input {
margin-right: 10px;
}
.performance-section {
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%);
border-radius: 10px;
padding: 20px;
margin-top: 20px;
}
.log-viewer {
background-color: #1e1e1e;
color: #d4d4d4;
font-family: 'Courier New', monospace;
font-size: 0.9em;
padding: 15px;
border-radius: 5px;
max-height: 400px;
overflow-y: auto;
}
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
}
.breadcrumb {
background: none;
padding: 0;
}
.breadcrumb-item a {
color: #007bff;
text-decoration: none;
}
.btn-redis {
background: linear-gradient(135deg, #dc143c 0%, #b91c1c 100%);
border: none;
color: white;
}
.btn-redis:hover {
background: linear-gradient(135deg, #b91c1c 0%, #991b1b 100%);
color: white;
transform: translateY(-1px);
}
.redis-logo {
color: #dc143c;
}
.cluster-config {
border: 2px solid #e9ecef;
border-radius: 10px;
padding: 20px;
margin-bottom: 15px;
}
.cluster-config.active {
border-color: #dc143c;
background-color: #fff8f8;
}
</style>
</head>
<body>
<!-- 导航栏 -->
<nav class="navbar navbar-expand-lg navbar-dark">
<div class="container">
<a class="navbar-brand" href="/">
<i class="fas fa-database me-2"></i>
大数据工具集合
</a>
<div class="navbar-nav ms-auto">
<a class="nav-link" href="/">
<i class="fas fa-home"></i> 首页
</a>
<a class="nav-link" href="/db-compare">
<i class="fas fa-exchange-alt"></i> 数据库比对
</a>
<a class="nav-link active" href="/redis-compare">
<i class="fab fa-redis"></i> Redis比对
</a>
</div>
</div>
</nav>
<div class="container mt-4">
<!-- 面包屑导航 -->
<nav aria-label="breadcrumb">
<ol class="breadcrumb">
<li class="breadcrumb-item"><a href="/">首页</a></li>
<li class="breadcrumb-item active" aria-current="page">Redis集群比对工具</li>
</ol>
</nav>
<!-- 页面标题 -->
<div class="row mb-4">
<div class="col-12">
<div class="d-flex align-items-center">
<i class="fab fa-redis redis-logo fs-1 me-3"></i>
<div>
<h1 class="mb-1">Redis集群比对工具</h1>
<p class="text-muted mb-0">专业的Redis集群数据比对工具支持随机采样和指定Key查询</p>
</div>
</div>
</div>
</div>
<!-- 配置区域 -->
<div class="row">
<!-- 配置管理 -->
<div class="col-lg-12">
<div class="config-section mb-4">
<h4><i class="fas fa-cogs me-2"></i>配置管理</h4>
<div class="row">
<!-- 配置组管理 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-layer-group me-2"></i>配置组管理</h6>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-8">
<select class="form-select form-select-sm" id="redisConfigGroupSelect">
<option value="">选择Redis配置组...</option>
</select>
</div>
<div class="col-4">
<button class="btn btn-primary btn-sm w-100" onclick="loadSelectedRedisConfigGroup()">
<i class="fas fa-download"></i> 加载
</button>
</div>
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-success btn-sm w-100" onclick="showSaveRedisConfigDialog()">
<i class="fas fa-save"></i> 保存配置
</button>
</div>
<div class="col-6">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisConfigDialog()">
<i class="fas fa-cog"></i> 管理配置
</button>
</div>
</div>
</div>
</div>
</div>
<!-- 查询历史 -->
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-history me-2"></i>查询历史</h6>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-8">
<select class="form-select form-select-sm" id="redisHistorySelect">
<option value="">选择历史记录...</option>
</select>
</div>
<div class="col-4">
<button class="btn btn-warning btn-sm w-100" onclick="loadSelectedRedisHistory()">
<i class="fas fa-history"></i> 加载
</button>
</div>
</div>
<div class="row">
<div class="col-6">
<button class="btn btn-secondary btn-sm w-100" onclick="showSaveRedisHistoryDialog()">
<i class="fas fa-bookmark"></i> 保存历史
</button>
</div>
<div class="col-6">
<button class="btn btn-info btn-sm w-100" onclick="showManageRedisHistoryDialog()">
<i class="fas fa-list"></i> 管理历史
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 一键导入区域 -->
<div class="row mt-3">
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-download me-2"></i>配置导入</h6>
</div>
<div class="card-body">
<button class="btn btn-outline-primary btn-sm me-2" onclick="showImportRedisConfigDialog('cluster1')">
<i class="fas fa-download"></i> 导入集群1配置
</button>
<button class="btn btn-outline-primary btn-sm" onclick="showImportRedisConfigDialog('cluster2')">
<i class="fas fa-download"></i> 导入集群2配置
</button>
<small class="form-text text-muted d-block mt-2">
支持YAML格式配置导入clusterName、clusterAddress、clusterPassword等
</small>
</div>
</div>
</div>
<div class="col-md-6">
<div class="card">
<div class="card-header">
<h6><i class="fas fa-file-alt me-2"></i>查询日志</h6>
</div>
<div class="card-body">
<button class="btn btn-outline-info btn-sm" onclick="showRedisQueryLogsDialog()">
<i class="fas fa-eye"></i> 查看查询日志
</button>
<small class="form-text text-muted d-block mt-2">
查看Redis比较操作的详细执行日志
</small>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Redis集群配置 -->
<div class="col-lg-12">
<div class="config-section">
<h4><i class="fas fa-server me-2"></i>Redis集群配置</h4>
<div class="row">
<!-- 第一个集群配置 -->
<div class="col-md-6">
<div class="cluster-config" id="cluster1-config">
<h5><i class="fas fa-database me-2"></i>集群1 (生产环境)</h5>
<div class="mb-3">
<label class="form-label">集群名称</label>
<input type="text" class="form-control" id="cluster1-name" value="生产集群" placeholder="输入集群名称">
</div>
<div class="mb-3">
<label class="form-label">节点列表</label>
<div id="cluster1-nodes">
<div class="node-input">
<input type="text" class="form-control node-host" placeholder="主机地址" value="127.0.0.1">
<input type="number" class="form-control node-port" placeholder="端口" value="7000" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addNode('cluster1')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="mb-3">
<label class="form-label">密码 (可选)</label>
<input type="password" class="form-control" id="cluster1-password" placeholder="Redis密码">
</div>
<div class="row">
<div class="col-6">
<label class="form-label">连接超时 (秒)</label>
<input type="number" class="form-control" id="cluster1-timeout" value="3" min="1" max="30">
</div>
<div class="col-6">
<label class="form-label">最大连接数</label>
<input type="number" class="form-control" id="cluster1-max-conn" value="16" min="1" max="100">
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-success btn-sm" onclick="testConnection('cluster1')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
</div>
<!-- 第二个集群配置 -->
<div class="col-md-6">
<div class="cluster-config" id="cluster2-config">
<h5><i class="fas fa-database me-2"></i>集群2 (测试环境)</h5>
<div class="mb-3">
<label class="form-label">集群名称</label>
<input type="text" class="form-control" id="cluster2-name" value="测试集群" placeholder="输入集群名称">
</div>
<div class="mb-3">
<label class="form-label">节点列表</label>
<div id="cluster2-nodes">
<div class="node-input">
<input type="text" class="form-control node-host" placeholder="主机地址" value="127.0.0.1">
<input type="number" class="form-control node-port" placeholder="端口" value="7001" style="width: 100px;">
<button type="button" class="btn btn-outline-danger btn-sm remove-node">
<i class="fas fa-minus"></i>
</button>
</div>
</div>
<button type="button" class="btn btn-outline-primary btn-sm mt-2" onclick="addNode('cluster2')">
<i class="fas fa-plus"></i> 添加节点
</button>
</div>
<div class="mb-3">
<label class="form-label">密码 (可选)</label>
<input type="password" class="form-control" id="cluster2-password" placeholder="Redis密码">
</div>
<div class="row">
<div class="col-6">
<label class="form-label">连接超时 (秒)</label>
<input type="number" class="form-control" id="cluster2-timeout" value="3" min="1" max="30">
</div>
<div class="col-6">
<label class="form-label">最大连接数</label>
<input type="number" class="form-control" id="cluster2-max-conn" value="16" min="1" max="100">
</div>
</div>
<div class="mt-3">
<button type="button" class="btn btn-outline-success btn-sm" onclick="testConnection('cluster2')">
<i class="fas fa-plug"></i> 测试连接
</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 查询选项 -->
<div class="row">
<div class="col-lg-12">
<div class="config-section">
<h4><i class="fas fa-search me-2"></i>查询选项</h4>
<div class="mb-3">
<label class="form-label">查询模式</label>
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="randomMode" value="random" checked>
<label class="form-check-label" for="randomMode">
<i class="fas fa-random me-1"></i>随机采样查询
</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="queryMode" id="specifiedMode" value="specified">
<label class="form-check-label" for="specifiedMode">
<i class="fas fa-list me-1"></i>指定Key查询
</label>
</div>
</div>
<!-- 随机采样选项 -->
<div id="randomOptions">
<div class="row">
<div class="col-md-4">
<label class="form-label">采样数量</label>
<input type="number" class="form-control" id="sampleCount" value="100" min="1" max="10000">
</div>
<div class="col-md-4">
<label class="form-label">Key匹配模式</label>
<input type="text" class="form-control" id="keyPattern" value="*" placeholder="例如: user:*">
</div>
<div class="col-md-4">
<label class="form-label">数据源集群</label>
<select class="form-select" id="sourceCluster">
<option value="cluster1">集群1 (生产环境)</option>
<option value="cluster2" selected>集群2 (测试环境)</option>
</select>
</div>
</div>
</div>
<!-- 指定Key选项 -->
<div id="specifiedOptions" style="display: none;">
<div class="mb-3">
<label class="form-label">指定Key列表 (每行一个)</label>
<textarea class="form-control query-keys" id="specifiedKeys"
placeholder="请输入要查询的Key每行一个&#10;例如:&#10;user:1001&#10;user:1002&#10;session:abc123"></textarea>
</div>
</div>
</div>
</div>
</div>
<!-- 操作按钮 -->
<div class="row">
<div class="col-12">
<div class="text-center">
<button type="button" class="btn btn-redis btn-lg me-3" onclick="executeRedisComparison()">
<i class="fas fa-play me-2"></i>开始比较
</button>
<button type="button" class="btn btn-outline-secondary btn-lg" onclick="clearResults()">
<i class="fas fa-eraser me-2"></i>清空结果
</button>
<div class="loading mt-3">
<div class="spinner-border text-danger me-2" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<span>正在执行Redis数据比较请稍候...</span>
</div>
</div>
</div>
</div>
<!-- 结果展示区域 -->
<div class="result-section" id="results" style="display: none;">
<!-- 统计卡片 -->
<div class="row" id="statsCards">
<!-- 统计卡片将通过JavaScript动态生成 -->
</div>
<!-- 详细结果 -->
<div class="row">
<div class="col-12">
<ul class="nav nav-tabs" id="resultTabs" role="tablist">
<li class="nav-item" role="presentation">
<button class="nav-link active" id="differences-tab" data-bs-toggle="tab" data-bs-target="#differences" type="button" role="tab">
<i class="fas fa-exclamation-triangle me-1"></i>差异数据 (<span id="diff-count">0</span>)
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="identical-tab" data-bs-toggle="tab" data-bs-target="#identical" type="button" role="tab">
<i class="fas fa-check-circle me-1"></i>相同数据 (<span id="identical-count">0</span>)
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="missing-tab" data-bs-toggle="tab" data-bs-target="#missing" type="button" role="tab">
<i class="fas fa-question-circle me-1"></i>缺失数据 (<span id="missing-count">0</span>)
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" id="performance-tab" data-bs-toggle="tab" data-bs-target="#performance" type="button" role="tab">
<i class="fas fa-chart-line me-1"></i>性能报告
</button>
</li>
</ul>
<div class="tab-content" id="resultTabContent">
<!-- 差异数据 -->
<div class="tab-pane fade show active" id="differences" role="tabpanel">
<div class="mt-3" id="differenceResults">
<!-- 差异结果将通过JavaScript动态生成 -->
</div>
</div>
<!-- 相同数据 -->
<div class="tab-pane fade" id="identical" role="tabpanel">
<div class="mt-3" id="identicalResults">
<!-- 相同结果将通过JavaScript动态生成 -->
</div>
</div>
<!-- 缺失数据 -->
<div class="tab-pane fade" id="missing" role="tabpanel">
<div class="mt-3" id="missingResults">
<!-- 缺失结果将通过JavaScript动态生成 -->
</div>
</div>
<!-- 性能报告 -->
<div class="tab-pane fade" id="performance" role="tabpanel">
<div class="performance-section mt-3" id="performanceReport">
<!-- 性能报告将通过JavaScript动态生成 -->
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Redis配置组保存模态框 -->
<div class="modal fade" id="saveRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-save me-2"></i>保存Redis配置组</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">配置组名称 *</label>
<input type="text" class="form-control" id="redisConfigGroupName" placeholder="输入配置组名称">
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="redisConfigGroupDescription" placeholder="输入配置组描述(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-success" onclick="saveRedisConfigGroup()">保存</button>
</div>
</div>
</div>
</div>
<!-- Redis配置管理模态框 -->
<div class="modal fade" id="manageRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-cog me-2"></i>Redis配置组管理</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisConfigGroupList">
<!-- 配置组列表将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- Redis配置导入模态框 -->
<div class="modal fade" id="importRedisConfigModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-download me-2"></i>导入Redis配置</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="alert alert-info">
<strong>支持的配置格式:</strong><br>
clusterName: "redis-test"<br>
clusterAddress: "10.20.2.109:6470"<br>
clusterPassword: ""<br>
cachePrefix: message.status.Writer.<br>
cacheTtl: 2000<br>
async: true
</div>
<div class="mb-3">
<label class="form-label">配置内容</label>
<textarea class="form-control" id="redisConfigImportText" rows="8"
placeholder="请粘贴Redis配置内容..."></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" onclick="importRedisConfig()">导入配置</button>
</div>
</div>
</div>
</div>
<!-- Redis查询历史保存模态框 -->
<div class="modal fade" id="saveRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-bookmark me-2"></i>保存Redis查询历史</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="form-label">历史记录名称 *</label>
<input type="text" class="form-control" id="redisHistoryName" placeholder="输入历史记录名称">
</div>
<div class="mb-3">
<label class="form-label">描述</label>
<textarea class="form-control" id="redisHistoryDescription" placeholder="输入历史记录描述(可选)"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-success" onclick="saveRedisQueryHistory()">保存</button>
</div>
</div>
</div>
</div>
<!-- Redis查询历史管理模态框 -->
<div class="modal fade" id="manageRedisHistoryModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-list me-2"></i>Redis查询历史管理</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div id="redisHistoryList">
<!-- 查询历史列表将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- Redis查询日志模态框 -->
<div class="modal fade" id="redisQueryLogsModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-xl">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-file-alt me-2"></i>Redis查询日志</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">Redis查询执行日志</h6>
<div>
<button class="btn btn-sm btn-outline-primary me-2" onclick="refreshRedisQueryLogs()">
<i class="fas fa-sync-alt"></i> 刷新
</button>
<button class="btn btn-sm btn-outline-danger" onclick="clearRedisQueryLogs()">
<i class="fas fa-trash"></i> 清空
</button>
</div>
</div>
<div id="redisQueryLogs" class="log-viewer">
<!-- 查询日志将在这里动态生成 -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 引入必要的JavaScript -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="/static/js/redis_compare.js"></script>
</body>
</html>