Files
java-template/docs/focused_optimization_plan.md
T
BrunoGaoandClaude 3248bb0a2a feat: 完成消息系统V2优化 - 基于user_id的高性能索引重构
## 主要更改

### 1. 数据库表结构增强
- **t_device_message_v2**: 新增7个关键字段
  - priority_level: 优先级数值(1-5)
  - channels: 分发渠道JSON
  - require_ack: 是否需要确认
  - metadata: 扩展元数据JSON
  - target_count: 目标用户总数
  - version: 乐观锁版本号

- **t_device_message_detail_v2**: 新增8个关键字段
  - distribution_id: 分发唯一ID
  - device_sn: 设备序列号
  - user_id: 用户ID(核心查询字段)
  - response_duration: 响应耗时
  - delivery_details: 分发详情JSON
  - client_info: 客户端信息JSON
  - location_info: 位置信息JSON
  - last_retry_time: 最后重试时间

### 2. 索引优化重构 (基于user_id)
- 删除原有device_sn相关索引
- 新建5个user_id高性能复合索引:
  - idx_customer_user_time: 客户+用户+时间
  - idx_customer_user_status: 客户+用户+状态
  - idx_customer_user_type: 客户+用户+类型
  - idx_user_org_priority: 用户+组织+优先级
  - idx_user_status_time: 用户+状态+时间

- detail表新建4个user_id相关索引:
  - uk_message_user_target: 唯一约束
  - idx_customer_user_status: 客户+用户+状态
  - idx_user_channel_time: 用户+渠道+时间
  - idx_user_delivery_status: 用户+分发状态

### 3. 数据迁移和初始化
- 自动生成distribution_id
- 根据urgency设置priority_level
- 初始化默认channels配置
- 保持现有数据完整性

### 4. 性能提升预期
- 用户消息查询: 预计10-20倍性能提升
- 确认状态查询: 支持快速人员清单检索
- 存储空间: JSON字段替代冗余VARCHAR
- 查询覆盖: 所有user_id查询都能命中索引

### 5. 文档和脚本
- 完整的V2优化SQL脚本
- 消息机制设计文档
- 数据库备份(22MB)
- 性能验证查询

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <[email protected]>
2025-09-15 07:56:03 +08:00

1050 lines
38 KiB
Markdown

# 健康数据批处理系统聚焦优化方案
## 📋 优化范围
基于ROI分析,选择实施两个核心优化项:
1. **多队列并行处理** (预期收益2-4倍)
2. **监控和可观测性** (运维能力大幅提升)
## 🎯 优化目标
- **性能提升**: 整体处理能力提升200-400%
- **并发能力**: 支持4倍以上的并发处理
- **可观测性**: 建立完整的性能监控体系
- **稳定性**: 提供实时的系统健康监控
---
## 1. 多队列并行处理实施方案
### 1.1 设计思路
#### 核心理念
- **分片策略**: 基于设备SN进行哈希分片,确保同一设备的数据顺序性
- **并行处理**: 多个独立的批处理器并行工作,避免单点瓶颈
- **负载均衡**: 自动分配设备到不同队列,实现负载均衡
- **隔离性**: 单个队列异常不影响其他队列处理
#### 架构对比
```
# 当前架构 (单队列)
设备A,B,C,D... → 单一队列 → 单个批处理器 → 数据库
# 优化架构 (多队列)
设备A,E,I... → 队列0 → 批处理器0 ↘
设备B,F,J... → 队列1 → 批处理器1 → 数据库
设备C,G,K... → 队列2 → 批处理器2 ↗
设备D,H,L... → 队列3 → 批处理器3 ↙
```
### 1.2 实现方案
#### 1.2.1 ShardedBatchProcessor 核心类
```python
import hashlib
from typing import List, Dict, Optional
from concurrent.futures import ThreadPoolExecutor
import threading
import queue
import time
class ShardedBatchProcessor:
"""分片批处理器 - 多队列并行处理"""
def __init__(self, shard_count: int = 4):
"""
初始化分片批处理器
Args:
shard_count: 分片数量,建议设置为CPU核心数的1/2到1倍
"""
self.shard_count = shard_count
self.running = True
# 为每个分片创建独立队列
self.shard_queues = []
for i in range(shard_count):
shard_queue = queue.Queue(maxsize=1250) # 总容量5000/4
self.shard_queues.append(shard_queue)
# 分片统计信息
self.shard_stats = [
{'processed': 0, 'errors': 0, 'duplicates': 0}
for _ in range(shard_count)
]
# 启动分片处理器
self.processor_threads = []
for shard_id in range(shard_count):
thread = threading.Thread(
target=self._shard_processor,
args=(shard_id,),
name=f"BatchProcessor-Shard-{shard_id}",
daemon=True
)
thread.start()
self.processor_threads.append(thread)
logger.info(f"🚀 ShardedBatchProcessor启动: {shard_count}个分片处理器")
def _get_shard_id(self, device_sn: str) -> int:
"""
基于设备SN计算分片ID
使用一致性哈希确保同一设备总是分配到同一分片
"""
if not device_sn:
return 0
# 使用MD5哈希确保分布均匀
hash_value = int(hashlib.md5(device_sn.encode()).hexdigest(), 16)
return hash_value % self.shard_count
def add_data(self, data_item: Dict, device_sn: str) -> bool:
"""
将数据添加到对应分片队列
Args:
data_item: 批处理数据项
device_sn: 设备序列号
Returns:
bool: 是否成功添加到队列
"""
if not self.running:
return False
shard_id = self._get_shard_id(device_sn)
try:
# 添加分片ID到数据项中,便于处理时识别
data_item['shard_id'] = shard_id
self.shard_queues[shard_id].put(data_item, timeout=1)
print(f"📦 数据已添加到分片{shard_id}: device_sn={device_sn}")
return True
except queue.Full:
logger.error(f'分片{shard_id}队列已满,数据丢弃: {device_sn}')
return False
except Exception as e:
logger.error(f'添加数据到分片{shard_id}失败: {e}')
return False
def _shard_processor(self, shard_id: int):
"""
分片处理器主循环
每个分片独立运行批处理逻辑
"""
shard_queue = self.shard_queues[shard_id]
batch_data = []
last_flush = time.time()
batch_size = max(50, min(250, 500 // self.shard_count)) # 动态批次大小
batch_timeout = 2.0
print(f"🔄 分片{shard_id}处理器启动: batch_size={batch_size}")
while self.running:
try:
# 计算超时时间
timeout = max(0.1, batch_timeout - (time.time() - last_flush))
# 从队列获取数据
try:
item = shard_queue.get(timeout=timeout)
batch_data.append(item)
print(f"📥 分片{shard_id}收到数据: device_sn={item.get('device_sn')}")
except queue.Empty:
# 超时,继续检查是否需要flush
pass
# 检查是否需要flush批次
should_flush = (
len(batch_data) >= batch_size or
(batch_data and (time.time() - last_flush) >= batch_timeout)
)
if should_flush and batch_data:
print(f"🔄 分片{shard_id}开始flush批次: size={len(batch_data)}")
# 执行批处理
success_count = self._flush_shard_batch(shard_id, batch_data)
# 更新统计
self.shard_stats[shard_id]['processed'] += success_count
# 重置批次
batch_data = []
last_flush = time.time()
except Exception as e:
self.shard_stats[shard_id]['errors'] += 1
logger.error(f'分片{shard_id}处理异常: {e}', exc_info=True)
# 发生异常时也要重置批次,避免数据堆积
batch_data = []
last_flush = time.time()
def _flush_shard_batch(self, shard_id: int, batch_data: List[Dict]) -> int:
"""
执行分片批次数据库操作
复用现有的_flush_batch逻辑,但添加分片标识
"""
try:
print(f"💾 分片{shard_id}数据库操作开始: {len(batch_data)}条数据")
# 分离不同类型的数据
main_records = []
daily_records = []
weekly_records = []
for item in batch_data:
if 'main_data' in item:
main_records.append(item['main_data'])
if 'daily_data' in item and item['daily_data']:
daily_records.append(item['daily_data'])
if 'weekly_data' in item and item['weekly_data']:
weekly_records.append(item['weekly_data'])
# 执行数据库操作 (复用现有逻辑)
success_count = self._execute_database_operations(
shard_id, main_records, daily_records, weekly_records
)
# 异步处理Redis和告警 (使用专用线程池)
self._execute_async_operations(shard_id, batch_data)
print(f"✅ 分片{shard_id}批次处理完成: {success_count}条成功")
return success_count
except Exception as e:
self.shard_stats[shard_id]['errors'] += 1
logger.error(f'分片{shard_id}批次处理失败: {e}', exc_info=True)
return 0
def _execute_database_operations(self, shard_id: int, main_records: List,
daily_records: List, weekly_records: List) -> int:
"""
执行数据库操作 (复用现有逻辑)
可以直接调用现有的数据库插入逻辑
"""
# 这里可以直接复用现有的数据库操作逻辑
# 为了避免重复代码,建议将现有的_flush_batch中的数据库操作
# 提取为独立方法,然后在这里调用
# 临时实现:返回处理的记录数
return len(main_records)
def _execute_async_operations(self, shard_id: int, batch_data: List[Dict]):
"""
执行异步操作 (Redis更新和告警检测)
使用专用线程池避免阻塞批处理器
"""
for item in batch_data:
try:
# 提交到异步线程池
if hasattr(self, 'async_executor'):
self.async_executor.submit(self._process_async_item, shard_id, item)
else:
# 如果没有异步线程池,直接处理
self._process_async_item(shard_id, item)
except Exception as e:
logger.error(f'分片{shard_id}异步操作提交失败: {e}')
def _process_async_item(self, shard_id: int, item: Dict):
"""
处理单个数据项的异步操作
"""
try:
device_sn = item.get('device_sn', 'unknown')
# Redis操作
if 'redis_data' in item:
redis.hset_data(f"health_data:{device_sn}", mapping=item['redis_data'])
redis.publish(f"health_data_channel:{device_sn}", device_sn)
# 告警检测
if item.get('enable_alerts', True) and 'redis_data' in item:
generate_alerts(item['redis_data'], item.get('health_data_id'))
except Exception as e:
logger.error(f'分片{shard_id}异步处理失败 device_sn={device_sn}: {e}')
def get_stats(self) -> Dict:
"""获取所有分片的统计信息"""
total_stats = {'processed': 0, 'errors': 0, 'duplicates': 0}
shard_details = {}
for shard_id, stats in enumerate(self.shard_stats):
total_stats['processed'] += stats['processed']
total_stats['errors'] += stats['errors']
total_stats['duplicates'] += stats['duplicates']
shard_details[f'shard_{shard_id}'] = {
'queue_size': self.shard_queues[shard_id].qsize(),
**stats
}
return {
'total': total_stats,
'shards': shard_details,
'shard_count': self.shard_count
}
def shutdown(self):
"""优雅关闭所有分片处理器"""
print("🛑 开始关闭ShardedBatchProcessor...")
self.running = False
# 等待所有线程结束
for thread in self.processor_threads:
thread.join(timeout=5)
print("✅ ShardedBatchProcessor已关闭")
```
#### 1.2.2 集成到现有系统
```python
# 修改HealthDataOptimizer类
class HealthDataOptimizer:
def __init__(self):
# ... 现有初始化代码 ...
# 使用分片批处理器替代单一批处理器
cpu_cores = psutil.cpu_count(logical=True)
shard_count = max(2, min(8, cpu_cores // 2)) # 2-8个分片
self.sharded_processor = ShardedBatchProcessor(shard_count)
# 专用异步处理线程池
self.async_executor = ThreadPoolExecutor(
max_workers=max(4, min(16, cpu_cores)),
thread_name_prefix="async-ops"
)
self.sharded_processor.async_executor = self.async_executor
logger.info(f"🚀 多队列批处理器初始化: {shard_count}个分片")
def add_data(self, raw_data, device_sn, enable_alerts=True):
"""修改add_data方法使用分片处理器"""
print(f"🔧 优化器添加数据: device_sn={device_sn}")
try:
# ... 现有的数据处理逻辑 ...
# 构建数据项
item = {
'device_sn': device_sn,
'main_data': main_data,
'daily_data': daily_data,
'weekly_data': weekly_data,
'redis_data': redis_data,
'enable_alerts': enable_alerts,
'config_info': config_info
}
# 使用分片处理器
success = self.sharded_processor.add_data(item, device_sn)
if success:
return {'success': True, 'reason': 'queued', 'message': '数据已加入分片处理队列'}
else:
return {'success': False, 'reason': 'queue_full', 'message': '队列已满,数据被丢弃'}
except Exception as e:
logger.error(f'分片处理器添加数据失败: {e}')
return {'success': False, 'reason': 'error', 'message': f'数据处理失败: {str(e)}'}
def get_stats(self):
"""获取包含分片信息的统计数据"""
base_stats = {
'processed': self.stats['processed'],
'batches': self.stats['batches'],
'errors': self.stats['errors'],
'duplicates': self.stats['duplicates']
}
# 合并分片统计
shard_stats = self.sharded_processor.get_stats()
return {
**base_stats,
'sharded_processing': shard_stats,
'performance_window_size': len(self.performance_window)
}
```
### 1.3 实施步骤
#### 阶段1: 准备工作 (1天)
1. **代码备份**: 备份现有的健康数据处理逻辑
2. **测试环境**: 在测试环境中部署新的分片处理器
3. **配置调优**: 根据服务器配置调整分片数量
#### 阶段2: 核心实现 (3-5天)
1. **实现ShardedBatchProcessor类** (2天)
2. **集成到HealthDataOptimizer** (1天)
3. **数据库操作逻辑适配** (1-2天)
#### 阶段3: 测试验证 (2-3天)
1. **单元测试**: 验证分片逻辑正确性
2. **压力测试**: 验证并发处理能力
3. **数据一致性测试**: 确保数据不丢失不重复
#### 阶段4: 生产部署 (1天)
1. **灰度发布**: 先在部分设备上启用
2. **监控观察**: 实时监控性能指标
3. **全量切换**: 确认无问题后全量启用
---
## 2. 监控和可观测性实施方案
### 2.1 核心指标体系
#### 2.1.1 业务指标监控
```python
from dataclasses import dataclass
from typing import Dict, List, Optional
import time
import threading
import json
@dataclass
class BatchProcessingMetrics:
"""批处理性能指标"""
batch_id: str
shard_id: int
batch_size: int
processing_time: float
db_insert_time: float
redis_update_time: float
alert_check_time: float
success_count: int
duplicate_count: int
error_count: int
timestamp: float
def to_dict(self) -> Dict:
return {
'batch_id': self.batch_id,
'shard_id': self.shard_id,
'batch_size': self.batch_size,
'processing_time': self.processing_time,
'db_insert_time': self.db_insert_time,
'redis_update_time': self.redis_update_time,
'alert_check_time': self.alert_check_time,
'success_count': self.success_count,
'duplicate_count': self.duplicate_count,
'error_count': self.error_count,
'timestamp': self.timestamp,
'success_rate': self.success_count / self.batch_size if self.batch_size > 0 else 0,
'throughput': self.batch_size / self.processing_time if self.processing_time > 0 else 0
}
class MetricsCollector:
"""指标收集器"""
def __init__(self):
self.metrics_buffer = []
self.buffer_lock = threading.Lock()
self.running = True
# 启动指标输出线程
self.output_thread = threading.Thread(target=self._metrics_output_worker, daemon=True)
self.output_thread.start()
logger.info("📊 MetricsCollector已启动")
def record_batch_metrics(self, metrics: BatchProcessingMetrics):
"""记录批次处理指标"""
with self.buffer_lock:
self.metrics_buffer.append(metrics)
# 如果缓冲区太大,触发立即输出
if len(self.metrics_buffer) > 100:
self._flush_metrics()
def _metrics_output_worker(self):
"""指标输出工作线程,每30秒输出一次汇总指标"""
while self.running:
try:
time.sleep(30) # 30秒间隔
self._flush_metrics()
except Exception as e:
logger.error(f"指标输出异常: {e}")
def _flush_metrics(self):
"""输出并清空指标缓冲区"""
with self.buffer_lock:
if not self.metrics_buffer:
return
current_metrics = self.metrics_buffer.copy()
self.metrics_buffer.clear()
try:
# 计算汇总指标
summary = self._calculate_summary(current_metrics)
# 输出到日志
logger.info(f"📈 批处理性能汇总: {json.dumps(summary, ensure_ascii=False)}")
# 输出到监控系统 (如Prometheus、InfluxDB等)
self._send_to_monitoring_system(summary, current_metrics)
except Exception as e:
logger.error(f"指标汇总计算失败: {e}")
def _calculate_summary(self, metrics: List[BatchProcessingMetrics]) -> Dict:
"""计算指标汇总"""
if not metrics:
return {}
total_batches = len(metrics)
total_records = sum(m.batch_size for m in metrics)
total_success = sum(m.success_count for m in metrics)
total_errors = sum(m.error_count for m in metrics)
total_duplicates = sum(m.duplicate_count for m in metrics)
processing_times = [m.processing_time for m in metrics if m.processing_time > 0]
throughputs = [m.batch_size / m.processing_time for m in metrics if m.processing_time > 0]
# 按分片统计
shard_stats = {}
for m in metrics:
shard_id = m.shard_id
if shard_id not in shard_stats:
shard_stats[shard_id] = {'batches': 0, 'records': 0, 'success': 0}
shard_stats[shard_id]['batches'] += 1
shard_stats[shard_id]['records'] += m.batch_size
shard_stats[shard_id]['success'] += m.success_count
return {
'period_summary': {
'total_batches': total_batches,
'total_records': total_records,
'success_rate': total_success / total_records if total_records > 0 else 0,
'error_rate': total_errors / total_records if total_records > 0 else 0,
'duplicate_rate': total_duplicates / total_records if total_records > 0 else 0
},
'performance_summary': {
'avg_processing_time': sum(processing_times) / len(processing_times) if processing_times else 0,
'max_processing_time': max(processing_times) if processing_times else 0,
'min_processing_time': min(processing_times) if processing_times else 0,
'avg_throughput': sum(throughputs) / len(throughputs) if throughputs else 0,
'max_throughput': max(throughputs) if throughputs else 0
},
'shard_distribution': shard_stats,
'timestamp': time.time()
}
def _send_to_monitoring_system(self, summary: Dict, raw_metrics: List[BatchProcessingMetrics]):
"""发送指标到监控系统"""
try:
# 这里可以集成Prometheus、InfluxDB、CloudWatch等监控系统
# 示例:发送到自定义监控端点
monitoring_data = {
'service': 'ljwx-bigscreen-batch-processor',
'summary': summary,
'detailed_metrics': [m.to_dict() for m in raw_metrics[-10:]] # 只发送最近10条详细数据
}
# 可以通过HTTP POST发送到监控系统
# requests.post('http://monitoring-system/metrics', json=monitoring_data)
# 或者写入文件供外部监控系统采集
with open('/tmp/batch_processor_metrics.json', 'w') as f:
json.dump(monitoring_data, f, ensure_ascii=False, indent=2)
except Exception as e:
logger.error(f"发送监控数据失败: {e}")
```
#### 2.1.2 系统资源监控
```python
import psutil
from typing import Dict
class SystemResourceMonitor:
"""系统资源监控器"""
def __init__(self, optimizer_instance):
self.optimizer = optimizer_instance
self.process = psutil.Process()
def get_resource_metrics(self) -> Dict:
"""获取系统资源指标"""
try:
# 进程级指标
memory_info = self.process.memory_info()
cpu_percent = self.process.cpu_percent()
# 系统级指标
system_memory = psutil.virtual_memory()
system_cpu = psutil.cpu_percent()
# 批处理器相关指标
batch_stats = self.optimizer.get_stats()
return {
'process_metrics': {
'memory_usage_mb': memory_info.rss / 1024 / 1024,
'memory_percent': self.process.memory_percent(),
'cpu_percent': cpu_percent,
'threads_count': self.process.num_threads(),
'open_files': len(self.process.open_files())
},
'system_metrics': {
'total_memory_mb': system_memory.total / 1024 / 1024,
'available_memory_mb': system_memory.available / 1024 / 1024,
'memory_percent': system_memory.percent,
'cpu_percent': system_cpu,
'cpu_count': psutil.cpu_count()
},
'batch_processor_metrics': {
'total_queue_size': sum(q.qsize() for q in self.optimizer.sharded_processor.shard_queues),
'shard_count': self.optimizer.sharded_processor.shard_count,
'processed_total': batch_stats.get('sharded_processing', {}).get('total', {}).get('processed', 0),
'error_total': batch_stats.get('sharded_processing', {}).get('total', {}).get('errors', 0)
},
'timestamp': time.time()
}
except Exception as e:
logger.error(f"获取资源指标失败: {e}")
return {'error': str(e), 'timestamp': time.time()}
```
### 2.2 健康检查API
#### 2.2.1 批处理器健康检查端点
```python
from flask import jsonify
@app.route('/api/batch_processor/health', methods=['GET'])
def batch_processor_health():
"""批处理器健康检查端点"""
try:
# 获取批处理器统计信息
batch_stats = optimizer.get_stats()
# 获取系统资源信息
resource_monitor = SystemResourceMonitor(optimizer)
resource_metrics = resource_monitor.get_resource_metrics()
# 计算健康状态
health_status = _determine_health_status(batch_stats, resource_metrics)
response_data = {
'status': health_status['status'],
'timestamp': time.time(),
'checks': {
'batch_processor': {
'status': health_status['batch_processor_status'],
'details': batch_stats
},
'system_resources': {
'status': health_status['resource_status'],
'details': resource_metrics
},
'queue_health': {
'status': health_status['queue_status'],
'details': _get_queue_health_details(batch_stats)
}
},
'summary': {
'total_processed': batch_stats.get('sharded_processing', {}).get('total', {}).get('processed', 0),
'error_rate': _calculate_error_rate(batch_stats),
'queue_utilization': _calculate_queue_utilization(batch_stats),
'memory_usage_percent': resource_metrics.get('process_metrics', {}).get('memory_percent', 0)
}
}
status_code = 200 if health_status['status'] == 'healthy' else 503
return jsonify(response_data), status_code
except Exception as e:
logger.error(f"健康检查失败: {e}")
return jsonify({
'status': 'error',
'timestamp': time.time(),
'error': str(e)
}), 500
def _determine_health_status(batch_stats: Dict, resource_metrics: Dict) -> Dict:
"""确定系统健康状态"""
# 错误率检查
error_rate = _calculate_error_rate(batch_stats)
batch_processor_status = 'healthy' if error_rate < 0.05 else 'degraded' if error_rate < 0.1 else 'unhealthy'
# 资源使用检查
memory_percent = resource_metrics.get('process_metrics', {}).get('memory_percent', 0)
cpu_percent = resource_metrics.get('process_metrics', {}).get('cpu_percent', 0)
resource_status = 'healthy' if memory_percent < 80 and cpu_percent < 80 else 'degraded' if memory_percent < 90 and cpu_percent < 90 else 'unhealthy'
# 队列状态检查
queue_utilization = _calculate_queue_utilization(batch_stats)
queue_status = 'healthy' if queue_utilization < 0.7 else 'degraded' if queue_utilization < 0.9 else 'unhealthy'
# 综合健康状态
statuses = [batch_processor_status, resource_status, queue_status]
if 'unhealthy' in statuses:
overall_status = 'unhealthy'
elif 'degraded' in statuses:
overall_status = 'degraded'
else:
overall_status = 'healthy'
return {
'status': overall_status,
'batch_processor_status': batch_processor_status,
'resource_status': resource_status,
'queue_status': queue_status
}
def _calculate_error_rate(batch_stats: Dict) -> float:
"""计算错误率"""
total_stats = batch_stats.get('sharded_processing', {}).get('total', {})
processed = total_stats.get('processed', 0)
errors = total_stats.get('errors', 0)
if processed == 0:
return 0.0
return errors / (processed + errors)
def _calculate_queue_utilization(batch_stats: Dict) -> float:
"""计算队列使用率"""
shard_details = batch_stats.get('sharded_processing', {}).get('shards', {})
total_capacity = 0
total_used = 0
for shard_id, shard_data in shard_details.items():
total_capacity += 1250 # 每个分片队列容量
total_used += shard_data.get('queue_size', 0)
if total_capacity == 0:
return 0.0
return total_used / total_capacity
def _get_queue_health_details(batch_stats: Dict) -> Dict:
"""获取队列健康详情"""
shard_details = batch_stats.get('sharded_processing', {}).get('shards', {})
queue_details = {}
for shard_id, shard_data in shard_details.items():
queue_size = shard_data.get('queue_size', 0)
processed = shard_data.get('processed', 0)
errors = shard_data.get('errors', 0)
queue_details[shard_id] = {
'queue_size': queue_size,
'utilization': queue_size / 1250,
'processed': processed,
'errors': errors,
'error_rate': errors / max(processed + errors, 1)
}
return queue_details
```
#### 2.2.2 详细指标查询端点
```python
@app.route('/api/batch_processor/metrics', methods=['GET'])
def batch_processor_metrics():
"""获取详细的批处理器指标"""
try:
# 获取查询参数
include_details = request.args.get('details', 'false').lower() == 'true'
shard_id = request.args.get('shard_id', type=int)
# 获取统计信息
batch_stats = optimizer.get_stats()
resource_metrics = SystemResourceMonitor(optimizer).get_resource_metrics()
response_data = {
'timestamp': time.time(),
'batch_processing': batch_stats,
'system_resources': resource_metrics,
'performance_indicators': {
'error_rate': _calculate_error_rate(batch_stats),
'queue_utilization': _calculate_queue_utilization(batch_stats),
'processing_rate': _calculate_processing_rate(batch_stats)
}
}
# 如果请求特定分片的详细信息
if shard_id is not None:
shard_details = batch_stats.get('sharded_processing', {}).get('shards', {})
if f'shard_{shard_id}' in shard_details:
response_data['shard_detail'] = shard_details[f'shard_{shard_id}']
else:
return jsonify({'error': f'Shard {shard_id} not found'}), 404
# 如果不需要详细信息,简化响应
if not include_details:
response_data = {
'timestamp': response_data['timestamp'],
'summary': {
'total_processed': batch_stats.get('sharded_processing', {}).get('total', {}).get('processed', 0),
'total_errors': batch_stats.get('sharded_processing', {}).get('total', {}).get('errors', 0),
'shard_count': batch_stats.get('sharded_processing', {}).get('shard_count', 0),
**response_data['performance_indicators']
}
}
return jsonify(response_data)
except Exception as e:
logger.error(f"获取指标失败: {e}")
return jsonify({'error': str(e)}), 500
def _calculate_processing_rate(batch_stats: Dict) -> float:
"""计算处理速率(记录数/秒)"""
# 这里需要基于时间窗口计算,简化实现返回0
# 实际实现中可以基于performance_window计算
return 0.0
```
### 2.3 告警规则配置
#### 2.3.1 基于阈值的告警检查
```python
class AlertRuleEngine:
"""告警规则引擎"""
def __init__(self):
self.alert_rules = [
{
'name': 'high_error_rate',
'condition': lambda stats: _calculate_error_rate(stats) > 0.1,
'message': '批处理错误率超过10%',
'severity': 'critical'
},
{
'name': 'queue_overflow_risk',
'condition': lambda stats: _calculate_queue_utilization(stats) > 0.8,
'message': '队列使用率超过80%,存在溢出风险',
'severity': 'warning'
},
{
'name': 'high_memory_usage',
'condition': lambda stats: self._check_memory_usage() > 80,
'message': '内存使用率超过80%',
'severity': 'warning'
},
{
'name': 'processing_lag',
'condition': lambda stats: self._check_processing_lag(stats) > 300,
'message': '批处理延迟超过5分钟',
'severity': 'critical'
}
]
self.alert_history = []
self.last_check_time = time.time()
def check_alerts(self, batch_stats: Dict) -> List[Dict]:
"""检查告警条件"""
current_alerts = []
current_time = time.time()
for rule in self.alert_rules:
try:
if rule['condition'](batch_stats):
alert = {
'rule_name': rule['name'],
'message': rule['message'],
'severity': rule['severity'],
'timestamp': current_time,
'stats_snapshot': batch_stats
}
current_alerts.append(alert)
# 记录告警历史
self.alert_history.append(alert)
# 发送告警通知
self._send_alert_notification(alert)
except Exception as e:
logger.error(f"告警规则检查失败 {rule['name']}: {e}")
# 清理过期告警历史
cutoff_time = current_time - 3600 # 保留1小时历史
self.alert_history = [a for a in self.alert_history if a['timestamp'] > cutoff_time]
return current_alerts
def _check_memory_usage(self) -> float:
"""检查内存使用率"""
try:
process = psutil.Process()
return process.memory_percent()
except:
return 0.0
def _check_processing_lag(self, stats: Dict) -> float:
"""检查处理延迟(秒)"""
# 基于队列大小和处理速率估算延迟
# 这是简化实现,实际可以基于时间戳计算
shard_details = stats.get('sharded_processing', {}).get('shards', {})
total_queue_size = sum(s.get('queue_size', 0) for s in shard_details.values())
# 假设平均处理速率为100记录/秒
estimated_processing_rate = 100
if estimated_processing_rate > 0:
return total_queue_size / estimated_processing_rate
else:
return 0.0
def _send_alert_notification(self, alert: Dict):
"""发送告警通知"""
try:
# 可以发送到不同的通知渠道
alert_message = f"[{alert['severity'].upper()}] {alert['message']}"
# 记录到日志
if alert['severity'] == 'critical':
logger.critical(f"🚨 批处理器告警: {alert_message}")
else:
logger.warning(f"⚠️ 批处理器告警: {alert_message}")
# 可以集成更多通知方式:
# - 发送邮件
# - 发送微信消息
# - 发送到Slack
# - 调用告警系统API
except Exception as e:
logger.error(f"发送告警通知失败: {e}")
```
#### 2.3.2 定期告警检查任务
```python
def start_alert_monitoring():
"""启动告警监控任务"""
alert_engine = AlertRuleEngine()
def alert_check_worker():
while True:
try:
# 每60秒检查一次
time.sleep(60)
# 获取当前统计信息
batch_stats = optimizer.get_stats()
# 检查告警条件
current_alerts = alert_engine.check_alerts(batch_stats)
if current_alerts:
logger.warning(f"检测到 {len(current_alerts)} 个告警")
except Exception as e:
logger.error(f"告警监控任务异常: {e}")
# 启动告警监控线程
alert_thread = threading.Thread(target=alert_check_worker, daemon=True)
alert_thread.start()
logger.info("🚨 告警监控任务已启动")
```
### 2.4 实施步骤
#### 阶段1: 基础指标收集 (2天)
1. **实现MetricsCollector类** (1天)
2. **集成到现有批处理器** (0.5天)
3. **测试指标收集功能** (0.5天)
#### 阶段2: 健康检查API (1天)
1. **实现健康检查端点** (0.5天)
2. **实现详细指标查询端点** (0.5天)
#### 阶段3: 告警系统 (2天)
1. **实现告警规则引擎** (1天)
2. **实现告警通知机制** (0.5天)
3. **测试告警功能** (0.5天)
#### 阶段4: 监控面板集成 (1天)
1. **配置日志输出格式** (0.5天)
2. **文档编写和部署指南** (0.5天)
---
## 3. 预期收益和风险评估
### 3.1 预期收益
#### 3.1.1 性能收益
- **并发处理能力**: 提升2-4倍 (4个分片并行处理)
- **响应延迟**: 降低50-70% (消除单队列瓶颈)
- **系统吞吐量**: 整体TPS提升200-400%
- **资源利用率**: CPU和内存利用率更均衡
#### 3.1.2 运维收益
- **可观测性**: 100%的关键指标可视化
- **故障诊断**: 问题定位时间缩短80%
- **预防性维护**: 通过告警提前发现问题
- **系统稳定性**: 运行状态实时监控
### 3.2 风险评估和应对
#### 3.2.1 技术风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|----------|
| 分片逻辑错误导致数据丢失 | 低 | 高 | 充分测试,灰度发布 |
| 多线程并发问题 | 中 | 中 | 加强线程安全检查 |
| 监控数据过多影响性能 | 低 | 低 | 限制监控频率和数据量 |
#### 3.2.2 业务风险
| 风险 | 概率 | 影响 | 应对措施 |
|------|------|------|----------|
| 短期性能下降 | 中 | 中 | 保留回滚方案 |
| 数据处理延迟增加 | 低 | 中 | 优化分片算法 |
| 现有业务逻辑兼容性 | 低 | 高 | 详细测试验证 |
### 3.3 成功指标
#### 3.3.1 性能指标
- 批处理QPS: 从20提升到50+ batches/s
- 数据处理TPS: 从800提升到2000+ records/s
- P95延迟: 从5-8秒降低到2-3秒
- 成功率: 维持在99.9%以上
#### 3.3.2 监控指标
- 系统健康状态实时可见
- 关键异常告警响应时间<5分钟
- 性能指标数据完整性>95%
- 故障诊断时间缩短到原来的20%
---
## 4. 实施时间表
### 总体时间安排: 2周
#### 第1周: 多队列并行处理
- **周一-周二**: ShardedBatchProcessor实现和集成
- **周三-周四**: 测试和调优
- **周五**: 生产环境部署和验证
#### 第2周: 监控和可观测性
- **周一-周二**: 指标收集和健康检查API
- **周三-周四**: 告警系统和监控面板
- **周五**: 完整测试和文档完善
## 总结
这个聚焦优化方案通过实施**多队列并行处理**和**监控可观测性**,可以以最小的风险获得最大的收益:
1. **性能提升显著**: 2-4倍的并发处理能力提升
2. **实施风险可控**: 核心业务逻辑不变,主要是架构优化
3. **运维能力增强**: 完善的监控体系大幅提升系统可维护性
4. **投入产出比高**: 2周的开发时间获得长期的性能和稳定性收益
建议按照时间表执行,重点关注测试验证环节,确保生产环境的平滑升级。