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

38 KiB

健康数据批处理系统聚焦优化方案

📋 优化范围

基于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 核心类

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 集成到现有系统

# 修改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 业务指标监控

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 系统资源监控

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 批处理器健康检查端点

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 详细指标查询端点

@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 基于阈值的告警检查

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 定期告警检查任务

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周的开发时间获得长期的性能和稳定性收益

建议按照时间表执行,重点关注测试验证环节,确保生产环境的平滑升级。