# 健康数据批处理系统聚焦优化方案 ## 📋 优化范围 基于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周的开发时间获得长期的性能和稳定性收益 建议按照时间表执行,重点关注测试验证环节,确保生产环境的平滑升级。