优化项目整合内容
This commit is contained in:
212
README.md
Normal file
212
README.md
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
# BigDataTool - 大数据查询比对工具
|
||||||
|
|
||||||
|
[](https://python.org)
|
||||||
|
[](https://flask.palletsprojects.com/)
|
||||||
|
[](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
28
app.py
@@ -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
|
import logging
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -38,26 +38,15 @@ def setup_routes(app, query_log_collector):
|
|||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
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')
|
@app.route('/db-compare')
|
||||||
def db_compare():
|
def db_compare():
|
||||||
|
"""Cassandra数据库比对工具页面"""
|
||||||
return render_template('db_compare.html')
|
return render_template('db_compare.html')
|
||||||
|
|
||||||
@app.route('/redis-compare')
|
@app.route('/redis-compare')
|
||||||
def redis_compare():
|
def redis_compare():
|
||||||
|
"""Redis数据比对工具页面"""
|
||||||
return render_template('redis_compare.html')
|
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
|
# 基础API
|
||||||
@app.route('/api/default-config')
|
@app.route('/api/default-config')
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
"""
|
"""
|
||||||
Cassandra连接管理模块
|
Cassandra连接管理模块
|
||||||
负责Cassandra数据库的连接和错误诊断
|
====================
|
||||||
|
|
||||||
|
本模块负责Cassandra数据库的连接管理和高级错误诊断功能。
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
1. 智能连接管理:自动处理集群连接和故障转移
|
||||||
|
2. 错误诊断系统:详细的连接失败分析和解决建议
|
||||||
|
3. 性能监控:连接时间和集群状态的实时监控
|
||||||
|
4. 容错机制:连接超时、重试和优雅降级
|
||||||
|
5. 安全认证:支持用户名密码认证和SSL连接
|
||||||
|
|
||||||
|
连接特性:
|
||||||
|
- 负载均衡:使用DCAwareRoundRobinPolicy避免单点故障
|
||||||
|
- 连接池管理:优化的连接复用和资源管理
|
||||||
|
- 超时控制:可配置的连接和查询超时时间
|
||||||
|
- 协议版本:使用稳定的CQL协议版本4
|
||||||
|
- Schema同步:自动等待集群Schema一致性
|
||||||
|
|
||||||
|
错误诊断系统:
|
||||||
|
- 连接拒绝:检查服务状态和网络连通性
|
||||||
|
- 认证失败:验证用户名密码和权限设置
|
||||||
|
- 超时错误:分析网络延迟和服务器负载
|
||||||
|
- Keyspace错误:验证Keyspace存在性和访问权限
|
||||||
|
- 未知错误:提供通用的故障排查指南
|
||||||
|
|
||||||
|
监控功能:
|
||||||
|
- 集群状态:实时显示可用和故障节点
|
||||||
|
- 连接时间:精确的连接建立时间测量
|
||||||
|
- 元数据获取:集群名称和节点信息展示
|
||||||
|
- 性能指标:连接成功率和响应时间统计
|
||||||
|
|
||||||
|
作者:BigDataTool项目组
|
||||||
|
更新时间:2024年8月
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
@@ -12,7 +44,57 @@ from cassandra.policies import DCAwareRoundRobinPolicy
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def create_connection(config):
|
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()
|
start_time = time.time()
|
||||||
|
|
||||||
logger.info(f"=== 开始创建Cassandra连接 ===")
|
logger.info(f"=== 开始创建Cassandra连接 ===")
|
||||||
|
@@ -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
|
import json
|
||||||
@@ -10,7 +39,8 @@ from .database import ensure_database, get_db_connection
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# 默认配置(不显示敏感信息)
|
# Cassandra数据库默认配置模板
|
||||||
|
# 注意:此配置不包含敏感信息,仅作为UI表单的初始模板使用
|
||||||
DEFAULT_CONFIG = {
|
DEFAULT_CONFIG = {
|
||||||
'pro_config': {
|
'pro_config': {
|
||||||
'cluster_name': '',
|
'cluster_name': '',
|
||||||
@@ -37,7 +67,8 @@ DEFAULT_CONFIG = {
|
|||||||
'exclude_fields': []
|
'exclude_fields': []
|
||||||
}
|
}
|
||||||
|
|
||||||
# Redis默认配置
|
# Redis集群默认配置模板
|
||||||
|
# 支持单节点和集群模式,自动检测连接类型
|
||||||
REDIS_DEFAULT_CONFIG = {
|
REDIS_DEFAULT_CONFIG = {
|
||||||
'cluster1_config': {
|
'cluster1_config': {
|
||||||
'name': '生产集群',
|
'name': '生产集群',
|
||||||
|
@@ -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
|
import json
|
||||||
|
@@ -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
|
import sqlite3
|
||||||
@@ -14,7 +44,27 @@ logger = logging.getLogger(__name__)
|
|||||||
DATABASE_PATH = 'config_groups.db'
|
DATABASE_PATH = 'config_groups.db'
|
||||||
|
|
||||||
def init_database():
|
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:
|
try:
|
||||||
conn = sqlite3.connect(DATABASE_PATH)
|
conn = sqlite3.connect(DATABASE_PATH)
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
@@ -135,7 +185,28 @@ def init_database():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
def ensure_database():
|
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):
|
if not os.path.exists(DATABASE_PATH):
|
||||||
logger.info("数据库文件不存在,正在创建...")
|
logger.info("数据库文件不存在,正在创建...")
|
||||||
return init_database()
|
return init_database()
|
||||||
@@ -222,7 +293,26 @@ def ensure_database():
|
|||||||
return init_database()
|
return init_database()
|
||||||
|
|
||||||
def get_db_connection():
|
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 = sqlite3.connect(DATABASE_PATH)
|
||||||
conn.row_factory = sqlite3.Row
|
conn.row_factory = sqlite3.Row
|
||||||
return conn
|
return conn
|
@@ -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
|
import time
|
||||||
@@ -10,7 +39,39 @@ from .sharding import ShardingCalculator
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
def execute_query(session, table, keys, fields, values, exclude_fields=None):
|
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:
|
try:
|
||||||
# 参数验证
|
# 参数验证
|
||||||
if not keys or len(keys) == 0:
|
if not keys or len(keys) == 0:
|
||||||
|
@@ -1,6 +1,38 @@
|
|||||||
"""
|
"""
|
||||||
查询日志管理模块
|
查询日志管理模块
|
||||||
负责查询日志的收集、存储和检索
|
================
|
||||||
|
|
||||||
|
本模块提供BigDataTool的完整查询日志管理功能,支持实时日志收集和历史日志分析。
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
1. 实时日志收集:自动收集所有查询操作的详细日志
|
||||||
|
2. 批次管理:按查询批次组织日志,便于追踪完整的查询流程
|
||||||
|
3. 双重存储:内存缓存 + SQLite持久化存储
|
||||||
|
4. 历史关联:将日志与查询历史记录关联,支持完整的操作回溯
|
||||||
|
5. 性能监控:记录查询时间、记录数等性能指标
|
||||||
|
|
||||||
|
日志收集特性:
|
||||||
|
- 多级日志:支持INFO、WARNING、ERROR等日志级别
|
||||||
|
- 批次追踪:每个查询批次分配唯一ID,便于日志分组
|
||||||
|
- 时间戳:精确到毫秒的时间戳记录
|
||||||
|
- 查询类型:区分单表、分表、Redis等不同查询类型
|
||||||
|
- 历史关联:支持日志与查询历史记录的双向关联
|
||||||
|
|
||||||
|
存储策略:
|
||||||
|
- 内存缓存:最近的日志保存在内存中,支持快速访问
|
||||||
|
- 数据库持久化:所有日志自动保存到SQLite数据库
|
||||||
|
- 容量控制:内存缓存有容量限制,自动清理旧日志
|
||||||
|
- 事务安全:数据库写入失败不影响程序运行
|
||||||
|
|
||||||
|
查询和分析:
|
||||||
|
- 按批次查询:支持按查询批次获取相关日志
|
||||||
|
- 按历史记录查询:支持按历史记录ID获取相关日志
|
||||||
|
- 分页支持:大量日志的分页显示
|
||||||
|
- 时间范围:支持按时间范围筛选日志
|
||||||
|
- 日志清理:支持按时间清理旧日志
|
||||||
|
|
||||||
|
作者:BigDataTool项目组
|
||||||
|
更新时间:2024年8月
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
@@ -1,6 +1,36 @@
|
|||||||
"""
|
"""
|
||||||
Redis连接管理模块
|
Redis连接管理模块
|
||||||
负责Redis集群的连接、错误处理和性能追踪
|
===================
|
||||||
|
|
||||||
|
本模块提供Redis集群的连接管理和基础操作功能,支持单节点和集群模式。
|
||||||
|
|
||||||
|
核心功能:
|
||||||
|
1. 智能连接管理:自动检测单节点和集群模式
|
||||||
|
2. 连接池优化:高效的连接复用和资源管理
|
||||||
|
3. 错误处理:完善的连接失败诊断和重试机制
|
||||||
|
4. 性能监控:连接时间和操作性能的实时监控
|
||||||
|
5. 类型检测:自动识别Redis数据类型
|
||||||
|
|
||||||
|
连接特性:
|
||||||
|
- 自适应模式:根据节点数量自动选择连接方式
|
||||||
|
- 连接池管理:每个节点独立的连接池配置
|
||||||
|
- 超时控制:可配置的连接和操作超时时间
|
||||||
|
- 密码认证:支持Redis AUTH认证
|
||||||
|
- 健康检查:连接状态的实时监控
|
||||||
|
|
||||||
|
支持的Redis版本:
|
||||||
|
- Redis 5.0+:完整功能支持
|
||||||
|
- Redis Cluster:集群模式支持
|
||||||
|
- Redis Sentinel:哨兵模式支持(通过配置)
|
||||||
|
|
||||||
|
错误诊断:
|
||||||
|
- 连接超时:网络延迟和服务器负载分析
|
||||||
|
- 认证失败:密码验证和权限检查
|
||||||
|
- 集群错误:节点状态和集群配置验证
|
||||||
|
- 数据类型错误:类型检测和转换建议
|
||||||
|
|
||||||
|
作者:BigDataTool项目组
|
||||||
|
更新时间:2024年8月
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
@@ -1,6 +1,43 @@
|
|||||||
"""
|
"""
|
||||||
Redis查询和数据比较模块
|
Redis查询引擎模块
|
||||||
负责Redis数据的查询、随机key获取和数据比较
|
=================
|
||||||
|
|
||||||
|
本模块是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
|
import time
|
||||||
|
@@ -1,6 +1,29 @@
|
|||||||
"""
|
"""
|
||||||
Redis数据类型支持增强模块
|
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
|
import json
|
||||||
@@ -11,19 +34,41 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def get_redis_value_with_type(redis_client, key):
|
def get_redis_value_with_type(redis_client, key):
|
||||||
"""
|
"""
|
||||||
获取Redis键值及其数据类型
|
获取Redis键值及其数据类型的完整信息
|
||||||
|
|
||||||
|
这是本模块的核心函数,支持所有Redis数据类型的获取和解析。
|
||||||
|
它会自动检测键的类型,然后使用相应的Redis命令获取数据,
|
||||||
|
并进行适当的格式化处理。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
redis_client: Redis客户端
|
redis_client: Redis客户端连接对象
|
||||||
key: Redis键名
|
key (str): 要查询的Redis键名
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: {
|
dict: 包含以下字段的字典
|
||||||
'type': 数据类型,
|
- 'type' (str): Redis数据类型 ('string', 'hash', 'list', 'set', 'zset', 'stream')
|
||||||
'value': 值,
|
- 'value': 解析后的原始值(Python对象)
|
||||||
'display_value': 用于显示的格式化值,
|
- 'display_value' (str): 格式化后用于显示的字符串
|
||||||
'exists': 是否存在
|
- '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:
|
try:
|
||||||
# 检查key是否存在
|
# 检查key是否存在
|
||||||
@@ -43,27 +88,29 @@ def get_redis_value_with_type(redis_client, key):
|
|||||||
}
|
}
|
||||||
|
|
||||||
if key_type == 'string':
|
if key_type == 'string':
|
||||||
# 字符串类型
|
# String类型处理 - 支持普通字符串和JSON字符串的智能识别
|
||||||
value = redis_client.get(key)
|
value = redis_client.get(key)
|
||||||
if value:
|
if value:
|
||||||
try:
|
try:
|
||||||
# 尝试解码为字符串
|
# 尝试UTF-8解码
|
||||||
str_value = value.decode('utf-8')
|
str_value = value.decode('utf-8')
|
||||||
result['value'] = str_value
|
result['value'] = str_value
|
||||||
|
|
||||||
# 尝试解析为JSON
|
# 智能检测JSON格式并格式化显示
|
||||||
try:
|
try:
|
||||||
json_value = json.loads(str_value)
|
json_value = json.loads(str_value)
|
||||||
result['display_value'] = json.dumps(json_value, indent=2, ensure_ascii=False)
|
result['display_value'] = json.dumps(json_value, indent=2, ensure_ascii=False)
|
||||||
result['type'] = 'json_string' # 标记为JSON字符串
|
result['type'] = 'json_string' # 标记为JSON字符串类型
|
||||||
except:
|
except json.JSONDecodeError:
|
||||||
|
# 不是JSON格式,直接显示字符串内容
|
||||||
result['display_value'] = str_value
|
result['display_value'] = str_value
|
||||||
|
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# 二进制数据
|
# 处理二进制数据 - 无法UTF-8解码的数据
|
||||||
result['value'] = value
|
result['value'] = value
|
||||||
result['display_value'] = f"<binary data: {len(value)} bytes>"
|
result['display_value'] = f"<binary data: {len(value)} bytes>"
|
||||||
else:
|
else:
|
||||||
|
# 空字符串处理
|
||||||
result['value'] = ""
|
result['value'] = ""
|
||||||
result['display_value'] = ""
|
result['display_value'] = ""
|
||||||
|
|
||||||
@@ -130,6 +177,80 @@ def get_redis_value_with_type(redis_client, key):
|
|||||||
result['value'] = decoded_zset
|
result['value'] = decoded_zset
|
||||||
result['display_value'] = json.dumps(decoded_zset, indent=2, ensure_ascii=False)
|
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:
|
else:
|
||||||
# 未知类型
|
# 未知类型
|
||||||
result['value'] = f"<unsupported type: {key_type}>"
|
result['value'] = f"<unsupported type: {key_type}>"
|
||||||
@@ -238,6 +359,58 @@ def compare_redis_values(value1_info, value2_info):
|
|||||||
else:
|
else:
|
||||||
return {'status': 'different', 'message': f'有序集合不同,大小: {len(value1)} vs {len(value2)}'}
|
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:
|
else:
|
||||||
# 其他类型的通用比较
|
# 其他类型的通用比较
|
||||||
if value1 == value2:
|
if value1 == value2:
|
||||||
|
@@ -1,6 +1,41 @@
|
|||||||
"""
|
"""
|
||||||
分表计算模块
|
TWCS分表计算引擎模块
|
||||||
负责TWCS时间分表的计算和映射
|
===================
|
||||||
|
|
||||||
|
本模块实现基于TWCS(Time 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
|
import re
|
||||||
@@ -9,7 +44,24 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ShardingCalculator:
|
class ShardingCalculator:
|
||||||
"""分表计算器,基于TWCS策略"""
|
"""
|
||||||
|
TWCS分表计算器
|
||||||
|
|
||||||
|
基于Time Window Compaction Strategy实现的智能分表计算器,
|
||||||
|
用于将时间相关的Key映射到对应的时间窗口分表。
|
||||||
|
|
||||||
|
主要特性:
|
||||||
|
- 时间窗口分片:按配置的时间间隔进行分表
|
||||||
|
- 智能时间戳提取:支持多种Key格式的时间戳解析
|
||||||
|
- 负载均衡:通过取模运算实现分表间的负载均衡
|
||||||
|
- 批量处理:高效处理大批量Key的分表映射
|
||||||
|
|
||||||
|
适用场景:
|
||||||
|
- 时序数据的分表存储
|
||||||
|
- 大数据表的性能优化
|
||||||
|
- 数据生命周期管理
|
||||||
|
- 查询负载分散
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, interval_seconds=604800, table_count=14):
|
def __init__(self, interval_seconds=604800, table_count=14):
|
||||||
"""
|
"""
|
||||||
|
@@ -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,每行一个 例如: user:1001 user:1002 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>
|
|
Reference in New Issue
Block a user