后台任务与WebSocket实时应用

目录

『宝藏代码胶囊开张啦!』------ 我的 CodeCapsule 来咯!✨写代码不再头疼!我的新站点 CodeCapsule 主打一个 "白菜价"+"量身定制 "!无论是卡脖子的毕设/课设/文献复现 ,需要灵光一现的算法改进 ,还是想给项目加个"外挂",这里都有便宜又好用的代码方案等你发现!低成本,高适配,助你轻松通关!速来围观 👉 CodeCapsule官网

后台任务与WebSocket实时应用开发指南

1. 引言:现代Web应用的实时需求 {#引言}

随着Web应用复杂度的增加,用户对实时性的需求日益增长。根据2024年Stack Overflow开发者调查,实时Web技术已成为增长最快的技能之一,需求同比增长85%。从金融交易到协作工具,从游戏到物联网监控,实时数据传输已成为现代应用的标配。

1.1 实时应用的关键指标

实时应用需要满足以下关键性能指标:

  1. 延迟:<100ms为优秀,100-300ms为可接受
  2. 吞吐量:单节点支持10K+并发连接
  3. 可用性:99.99%以上
  4. 消息丢失率:<0.01%

1.2 技术选型对比

实时通信技术 WebSocket Server-Sent Events 长轮询 WebRTC 全双工 低延迟 适合实时游戏/聊天 单向服务器推送 自动重连 适合股票行情/通知 兼容性好 高延迟 资源消耗大 P2P通信 音视频传输 复杂但强大

2. 技术架构概览 {#技术架构}

2.1 系统架构图

数据层 后台任务层 消息中间件层 应用服务器层 负载均衡层 客户端层 数据库 缓存 文件存储 任务调度器 任务执行器 结果处理器 消息队列 发布订阅系统 WebSocket服务器1 WebSocket服务器2 WebSocket服务器3 负载均衡器 Web浏览器 移动应用 桌面应用

2.2 核心组件交互流程

  1. 客户端连接 → WebSocket握手 → 连接建立 2. 后台任务触发 → 消息队列 → 任务执行 3. 任务完成 → 发布订阅 → WebSocket推送 4. 客户端接收 → UI更新 → 用户交互 \begin{aligned} &1.\ \text{客户端连接} \rightarrow \text{WebSocket握手} \rightarrow \text{连接建立}\\ &2.\ \text{后台任务触发} \rightarrow \text{消息队列} \rightarrow \text{任务执行}\\ &3.\ \text{任务完成} \rightarrow \text{发布订阅} \rightarrow \text{WebSocket推送}\\ &4.\ \text{客户端接收} \rightarrow \text{UI更新} \rightarrow \text{用户交互} \end{aligned} 1. 客户端连接→WebSocket握手→连接建立2. 后台任务触发→消息队列→任务执行3. 任务完成→发布订阅→WebSocket推送4. 客户端接收→UI更新→用户交互

3. 后台任务系统设计与实现 {#后台任务系统}

3.1 任务调度器设计

python 复制代码
"""
后台任务调度系统
支持:定时任务、延迟任务、周期任务、任务依赖、任务重试
"""
import asyncio
import time
import uuid
import pickle
from abc import ABC, abstractmethod
from typing import Any, Dict, List, Optional, Callable, Union
from enum import Enum
from datetime import datetime, timedelta
from dataclasses import dataclass, field
import heapq
import logging
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import redis.asyncio as aioredis
from pydantic import BaseModel, Field, validator


class TaskStatus(Enum):
    """任务状态枚举"""
    PENDING = "pending"
    SCHEDULED = "scheduled"
    RUNNING = "running"
    SUCCESS = "success"
    FAILED = "failed"
    CANCELLED = "cancelled"
    RETRYING = "retrying"


class TaskPriority(Enum):
    """任务优先级"""
    LOW = 0
    NORMAL = 1
    HIGH = 2
    CRITICAL = 3


@dataclass(order=True)
class ScheduledTask:
    """调度任务数据类"""
    execute_at: float  # 执行时间戳
    priority: int = field(compare=False)
    task_id: str = field(compare=False)
    data: Any = field(compare=False)
    
    def __post_init__(self):
        # 确保优先级转换为数值
        if isinstance(self.priority, TaskPriority):
            self.priority = self.priority.value


class Task(BaseModel):
    """任务模型"""
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    name: str
    func_name: str
    args: List[Any] = Field(default_factory=list)
    kwargs: Dict[str, Any] = Field(default_factory=dict)
    
    # 调度相关
    schedule_type: str = "immediate"  # immediate, delayed, cron
    execute_at: Optional[datetime] = None
    cron_expression: Optional[str] = None
    interval_seconds: Optional[int] = None
    
    # 执行控制
    max_retries: int = 3
    retry_delay: int = 60  # 秒
    timeout: Optional[int] = None
    priority: TaskPriority = TaskPriority.NORMAL
    
    # 依赖关系
    depends_on: List[str] = Field(default_factory=list)
    
    # 状态追踪
    status: TaskStatus = TaskStatus.PENDING
    created_at: datetime = Field(default_factory=datetime.now)
    started_at: Optional[datetime] = None
    completed_at: Optional[datetime] = None
    attempts: int = 0
    result: Optional[Any] = None
    error: Optional[str] = None
    traceback: Optional[str] = None
    
    # 元数据
    metadata: Dict[str, Any] = Field(default_factory=dict)
    
    class Config:
        arbitrary_types_allowed = True
        json_encoders = {
            datetime: lambda dt: dt.isoformat(),
            TaskPriority: lambda p: p.value,
            TaskStatus: lambda s: s.value,
        }
    
    @validator('schedule_type')
    def validate_schedule_type(cls, v):
        allowed = ['immediate', 'delayed', 'cron', 'interval']
        if v not in allowed:
            raise ValueError(f'schedule_type must be one of {allowed}')
        return v
    
    def get_execution_time(self) -> Optional[float]:
        """获取执行时间戳"""
        if self.execute_at:
            return self.execute_at.timestamp()
        return None
    
    def should_retry(self) -> bool:
        """判断是否应该重试"""
        return (
            self.status == TaskStatus.FAILED and 
            self.attempts < self.max_retries
        )
    
    def calculate_next_retry(self) -> datetime:
        """计算下次重试时间"""
        delay = self.retry_delay * (2 ** (self.attempts - 1))  # 指数退避
        return datetime.now() + timedelta(seconds=delay)


class TaskResult(BaseModel):
    """任务结果模型"""
    task_id: str
    status: TaskStatus
    result: Optional[Any] = None
    error: Optional[str] = None
    execution_time: Optional[float] = None  # 执行耗时(秒)
    completed_at: datetime = Field(default_factory=datetime.now)
    
    class Config:
        json_encoders = {
            datetime: lambda dt: dt.isoformat(),
            TaskStatus: lambda s: s.value,
        }


class TaskRegistry:
    """任务注册表"""
    
    def __init__(self):
        self.tasks: Dict[str, Callable] = {}
    
    def register(self, name: str = None):
        """注册任务装饰器"""
        def decorator(func):
            task_name = name or func.__name__
            self.tasks[task_name] = func
            return func
        return decorator
    
    def get_task(self, name: str) -> Optional[Callable]:
        """获取任务函数"""
        return self.tasks.get(name)
    
    def list_tasks(self) -> List[str]:
        """列出所有注册的任务"""
        return list(self.tasks.keys())


class BaseTaskBackend(ABC):
    """任务后端基类"""
    
    @abstractmethod
    async def enqueue(self, task: Task) -> str:
        """入队任务"""
        pass
    
    @abstractmethod
    async def dequeue(self) -> Optional[Task]:
        """出队任务"""
        pass
    
    @abstractmethod
    async def get_task(self, task_id: str) -> Optional[Task]:
        """获取任务"""
        pass
    
    @abstractmethod
    async def update_task(self, task: Task) -> bool:
        """更新任务"""
        pass
    
    @abstractmethod
    async def delete_task(self, task_id: str) -> bool:
        """删除任务"""
        pass


class MemoryTaskBackend(BaseTaskBackend):
    """内存任务后端(用于开发和测试)"""
    
    def __init__(self):
        self.tasks: Dict[str, Task] = {}
        self.scheduled_queue: List[ScheduledTask] = []
        self.pending_queue: List[Task] = []
        self.lock = asyncio.Lock()
    
    async def enqueue(self, task: Task) -> str:
        async with self.lock:
            self.tasks[task.id] = task
            
            if task.schedule_type == "immediate":
                heapq.heappush(self.pending_queue, task)
            elif task.schedule_type == "delayed" and task.execute_at:
                scheduled = ScheduledTask(
                    execute_at=task.execute_at.timestamp(),
                    priority=task.priority.value,
                    task_id=task.id,
                    data=task
                )
                heapq.heappush(self.scheduled_queue, scheduled)
            
            return task.id
    
    async def dequeue(self) -> Optional[Task]:
        async with self.lock:
            # 检查是否有到期的定时任务
            now = time.time()
            while self.scheduled_queue and self.scheduled_queue[0].execute_at <= now:
                scheduled = heapq.heappop(self.scheduled_queue)
                task = scheduled.data
                task.status = TaskStatus.PENDING
                heapq.heappush(self.pending_queue, task)
            
            if self.pending_queue:
                return heapq.heappop(self.pending_queue)
            return None
    
    async def get_task(self, task_id: str) -> Optional[Task]:
        return self.tasks.get(task_id)
    
    async def update_task(self, task: Task) -> bool:
        async with self.lock:
            if task.id in self.tasks:
                self.tasks[task.id] = task
                return True
            return False
    
    async def delete_task(self, task_id: str) -> bool:
        async with self.lock:
            if task_id in self.tasks:
                del self.tasks[task_id]
                return True
            return False


class RedisTaskBackend(BaseTaskBackend):
    """Redis任务后端(生产环境)"""
    
    def __init__(self, redis_url: str = "redis://localhost:6379"):
        self.redis_url = redis_url
        self.redis: Optional[aioredis.Redis] = None
        self.pubsub: Optional[aioredis.PubSub] = None
        
        # Redis键名
        self.TASK_PREFIX = "task:"
        self.PENDING_QUEUE = "tasks:pending"
        self.SCHEDULED_SET = "tasks:scheduled"
        self.RESULTS_PREFIX = "task:result:"
    
    async def connect(self):
        """连接Redis"""
        self.redis = await aioredis.from_url(self.redis_url)
        self.pubsub = self.redis.pubsub()
    
    async def disconnect(self):
        """断开Redis连接"""
        if self.redis:
            await self.redis.close()
    
    def _task_key(self, task_id: str) -> str:
        """生成任务键名"""
        return f"{self.TASK_PREFIX}{task_id}"
    
    def _result_key(self, task_id: str) -> str:
        """生成结果键名"""
        return f"{self.RESULTS_PREFIX}{task_id}"
    
    async def enqueue(self, task: Task) -> str:
        """入队任务到Redis"""
        if not self.redis:
            await self.connect()
        
        # 序列化任务
        task_data = task.json()
        
        # 存储任务
        task_key = self._task_key(task.id)
        await self.redis.setex(
            task_key,
            86400,  # 24小时TTL
            task_data
        )
        
        # 根据调度类型放入不同队列
        if task.schedule_type == "immediate":
            # 使用有序集合按优先级排序
            score = task.priority.value * 1000000 + time.time()
            await self.redis.zadd(
                self.PENDING_QUEUE,
                {task.id: score}
            )
            
        elif task.schedule_type == "delayed" and task.execute_at:
            # 放入延迟集合
            execute_at = task.execute_at.timestamp()
            await self.redis.zadd(
                self.SCHEDULED_SET,
                {task.id: execute_at}
            )
        
        # 发布新任务事件
        await self.redis.publish("tasks:new", task.id)
        
        return task.id
    
    async def dequeue(self) -> Optional[Task]:
        """从Redis出队任务"""
        if not self.redis:
            await self.connect()
        
        # 检查延迟任务
        now = time.time()
        delayed_tasks = await self.redis.zrangebyscore(
            self.SCHEDULED_SET,
            "-inf",
            now,
            start=0,
            num=1,
            withscores=True
        )
        
        if delayed_tasks:
            task_id, _ = delayed_tasks[0]
            # 移动到待处理队列
            await self.redis.zrem(self.SCHEDULED_SET, task_id)
            await self.redis.zadd(
                self.PENDING_QUEUE,
                {task_id: now}
            )
        
        # 获取最高优先级的任务
        result = await self.redis.zpopmin(self.PENDING_QUEUE, count=1)
        if not result:
            return None
        
        task_id, _ = result[0]
        task_data = await self.redis.get(self._task_key(task_id))
        
        if not task_data:
            return None
        
        # 反序列化任务
        task_dict = json.loads(task_data)
        return Task(**task_dict)
    
    async def get_task(self, task_id: str) -> Optional[Task]:
        """从Redis获取任务"""
        if not self.redis:
            await self.connect()
        
        task_data = await self.redis.get(self._task_key(task_id))
        if not task_data:
            return None
        
        task_dict = json.loads(task_data)
        return Task(**task_dict)
    
    async def update_task(self, task: Task) -> bool:
        """更新Redis中的任务"""
        if not self.redis:
            await self.connect()
        
        task_data = task.json()
        task_key = self._task_key(task.id)
        
        await self.redis.setex(
            task_key,
            86400,
            task_data
        )
        
        return True
    
    async def delete_task(self, task_id: str) -> bool:
        """从Redis删除任务"""
        if not self.redis:
            await self.connect()
        
        # 删除任务数据
        task_key = self._task_key(task_id)
        result_key = self._result_key(task_id)
        
        await self.redis.delete(task_key, result_key)
        
        # 从所有队列中移除
        await self.redis.zrem(self.PENDING_QUEUE, task_id)
        await self.redis.zrem(self.SCHEDULED_SET, task_id)
        
        return True
    
    async def store_result(self, result: TaskResult):
        """存储任务结果"""
        if not self.redis:
            await self.connect()
        
        result_data = result.json()
        result_key = self._result_key(result.task_id)
        
        # 存储结果,设置TTL
        await self.redis.setex(
            result_key,
            604800,  # 7天TTL
            result_data
        )
        
        # 发布任务完成事件
        await self.redis.publish("tasks:completed", result.task_id)


class TaskScheduler:
    """任务调度器"""
    
    def __init__(
        self,
        backend: BaseTaskBackend = None,
        max_workers: int = 10,
        enable_metrics: bool = True
    ):
        self.backend = backend or MemoryTaskBackend()
        self.registry = TaskRegistry()
        self.executor = ThreadPoolExecutor(max_workers=max_workers)
        self.process_executor = ProcessPoolExecutor(max_workers=max_workers // 2)
        
        # 任务状态追踪
        self.running_tasks: Dict[str, asyncio.Task] = {}
        self.metrics = {
            "tasks_executed": 0,
            "tasks_failed": 0,
            "tasks_succeeded": 0,
            "total_execution_time": 0.0,
            "average_execution_time": 0.0,
        } if enable_metrics else None
        
        # 调度器状态
        self.is_running = False
        self.scheduler_task: Optional[asyncio.Task] = None
        
        # 事件回调
        self.on_task_started: List[Callable] = []
        self.on_task_completed: List[Callable] = []
        self.on_task_failed: List[Callable] = []
        
        self.logger = logging.getLogger("task_scheduler")
    
    def task(self, name: str = None, max_retries: int = 3):
        """任务装饰器"""
        def decorator(func):
            task_name = name or func.__name__
            self.registry.tasks[task_name] = func
            
            # 创建包装函数
            async def wrapper(*args, **kwargs):
                return await func(*args, **kwargs)
            
            wrapper.task_name = task_name
            wrapper.max_retries = max_retries
            
            return wrapper
        return decorator
    
    async def start(self):
        """启动调度器"""
        if self.is_running:
            return
        
        self.is_running = True
        self.scheduler_task = asyncio.create_task(self._scheduler_loop())
        self.logger.info("Task scheduler started")
    
    async def stop(self):
        """停止调度器"""
        if not self.is_running:
            return
        
        self.is_running = False
        if self.scheduler_task:
            self.scheduler_task.cancel()
            try:
                await self.scheduler_task
            except asyncio.CancelledError:
                pass
        
        # 等待所有任务完成
        await asyncio.gather(*self.running_tasks.values(), return_exceptions=True)
        
        # 关闭执行器
        self.executor.shutdown(wait=True)
        self.process_executor.shutdown(wait=True)
        
        self.logger.info("Task scheduler stopped")
    
    async def _scheduler_loop(self):
        """调度器主循环"""
        while self.is_running:
            try:
                # 获取下一个任务
                task = await self.backend.dequeue()
                
                if task:
                    # 检查任务依赖
                    if task.depends_on:
                        deps_met = await self._check_dependencies(task)
                        if not deps_met:
                            # 重新入队,稍后重试
                            await asyncio.sleep(1)
                            await self.backend.enqueue(task)
                            continue
                    
                    # 执行任务
                    task.status = TaskStatus.RUNNING
                    task.started_at = datetime.now()
                    task.attempts += 1
                    
                    await self.backend.update_task(task)
                    
                    # 触发任务开始事件
                    await self._trigger_event(self.on_task_started, task)
                    
                    # 创建异步任务执行
                    task_coro = self._execute_task(task)
                    running_task = asyncio.create_task(task_coro)
                    self.running_tasks[task.id] = running_task
                    
                    # 设置回调清理
                    running_task.add_done_callback(
                        lambda t, tid=task.id: self.running_tasks.pop(tid, None)
                    )
                else:
                    # 没有任务,等待一段时间
                    await asyncio.sleep(0.1)
                    
            except asyncio.CancelledError:
                break
            except Exception as e:
                self.logger.error(f"Scheduler loop error: {e}")
                await asyncio.sleep(1)
    
    async def _execute_task(self, task: Task):
        """执行单个任务"""
        start_time = time.time()
        
        try:
            # 获取任务函数
            task_func = self.registry.get_task(task.func_name)
            if not task_func:
                raise ValueError(f"Task function '{task.func_name}' not found")
            
            # 执行任务
            if asyncio.iscoroutinefunction(task_func):
                # 异步函数
                result = await task_func(*task.args, **task.kwargs)
            else:
                # 同步函数,在线程池中执行
                loop = asyncio.get_event_loop()
                result = await loop.run_in_executor(
                    self.executor,
                    task_func,
                    *task.args,
                    **task.kwargs
                )
            
            # 更新任务状态
            task.status = TaskStatus.SUCCESS
            task.result = result
            task.completed_at = datetime.now()
            
            # 存储结果
            task_result = TaskResult(
                task_id=task.id,
                status=TaskStatus.SUCCESS,
                result=result,
                execution_time=time.time() - start_time
            )
            
            if isinstance(self.backend, RedisTaskBackend):
                await self.backend.store_result(task_result)
            
            # 触发任务完成事件
            await self._trigger_event(self.on_task_completed, task, result)
            
            # 更新指标
            if self.metrics:
                self._update_metrics(task_result)
            
            self.logger.info(f"Task {task.id} completed successfully")
            
        except Exception as e:
            # 任务执行失败
            task.status = TaskStatus.FAILED
            task.error = str(e)
            task.completed_at = datetime.now()
            
            # 检查是否需要重试
            if task.should_retry():
                task.status = TaskStatus.RETRYING
                task.execute_at = task.calculate_next_retry()
                task.schedule_type = "delayed"
                
                # 重新入队
                await self.backend.enqueue(task)
                self.logger.info(f"Task {task.id} scheduled for retry")
            else:
                # 最终失败
                task_result = TaskResult(
                    task_id=task.id,
                    status=TaskStatus.FAILED,
                    error=str(e),
                    execution_time=time.time() - start_time
                )
                
                if isinstance(self.backend, RedisTaskBackend):
                    await self.backend.store_result(task_result)
                
                # 触发任务失败事件
                await self._trigger_event(self.on_task_failed, task, e)
                
                # 更新指标
                if self.metrics:
                    self.metrics["tasks_failed"] += 1
                
                self.logger.error(f"Task {task.id} failed: {e}")
        
        finally:
            # 更新任务状态
            await self.backend.update_task(task)
    
    async def _check_dependencies(self, task: Task) -> bool:
        """检查任务依赖是否满足"""
        for dep_id in task.depends_on:
            dep_task = await self.backend.get_task(dep_id)
            if not dep_task or dep_task.status != TaskStatus.SUCCESS:
                return False
        return True
    
    async def _trigger_event(self, callbacks: List[Callable], *args, **kwargs):
        """触发事件回调"""
        for callback in callbacks:
            try:
                if asyncio.iscoroutinefunction(callback):
                    await callback(*args, **kwargs)
                else:
                    callback(*args, **kwargs)
            except Exception as e:
                self.logger.error(f"Event callback error: {e}")
    
    def _update_metrics(self, result: TaskResult):
        """更新性能指标"""
        self.metrics["tasks_executed"] += 1
        
        if result.status == TaskStatus.SUCCESS:
            self.metrics["tasks_succeeded"] += 1
        
        if result.execution_time:
            self.metrics["total_execution_time"] += result.execution_time
            self.metrics["average_execution_time"] = (
                self.metrics["total_execution_time"] / self.metrics["tasks_executed"]
            )
    
    async def submit_task(
        self,
        func_name: str,
        *args,
        name: str = None,
        schedule_type: str = "immediate",
        execute_at: Optional[datetime] = None,
        max_retries: int = 3,
        priority: TaskPriority = TaskPriority.NORMAL,
        **kwargs
    ) -> str:
        """提交新任务"""
        task = Task(
            name=name or func_name,
            func_name=func_name,
            args=list(args),
            kwargs=kwargs,
            schedule_type=schedule_type,
            execute_at=execute_at,
            max_retries=max_retries,
            priority=priority,
        )
        
        task_id = await self.backend.enqueue(task)
        return task_id
    
    async def get_task_status(self, task_id: str) -> Optional[Dict[str, Any]]:
        """获取任务状态"""
        task = await self.backend.get_task(task_id)
        if not task:
            return None
        
        return {
            "id": task.id,
            "name": task.name,
            "status": task.status.value,
            "created_at": task.created_at,
            "started_at": task.started_at,
            "completed_at": task.completed_at,
            "attempts": task.attempts,
            "error": task.error,
        }
    
    async def cancel_task(self, task_id: str) -> bool:
        """取消任务"""
        task = await self.backend.get_task(task_id)
        if not task:
            return False
        
        # 如果任务正在运行,尝试取消
        if task_id in self.running_tasks:
            self.running_tasks[task_id].cancel()
        
        # 更新任务状态
        task.status = TaskStatus.CANCELLED
        task.completed_at = datetime.now()
        
        await self.backend.update_task(task)
        return True
    
    def get_metrics(self) -> Dict[str, Any]:
        """获取调度器指标"""
        if not self.metrics:
            return {}
        
        return {
            **self.metrics,
            "running_tasks": len(self.running_tasks),
            "registered_tasks": len(self.registry.tasks),
            "is_running": self.is_running,
        }


# 示例任务函数
if __name__ == "__main__":
    # 配置日志
    logging.basicConfig(level=logging.INFO)
    
    # 创建调度器
    scheduler = TaskScheduler(max_workers=5)
    
    # 注册任务
    @scheduler.task(name="process_data", max_retries=3)
    async def process_data(data: str, multiplier: int = 1):
        """示例异步任务"""
        await asyncio.sleep(1)  # 模拟耗时操作
        processed = data.upper() * multiplier
        return {"processed": processed, "length": len(processed)}
    
    @scheduler.task(name="calculate_sum")
    def calculate_sum(numbers: List[int]):
        """示例同步任务"""
        time.sleep(0.5)  # 模拟耗时操作
        return sum(numbers)
    
    # 添加事件监听器
    async def on_task_started(task: Task):
        print(f"Task started: {task.name} ({task.id})")
    
    async def on_task_completed(task: Task, result: Any):
        print(f"Task completed: {task.name} - Result: {result}")
    
    scheduler.on_task_started.append(on_task_started)
    scheduler.on_task_completed.append(on_task_completed)
    
    async def main():
        # 启动调度器
        await scheduler.start()
        
        # 提交任务
        task1_id = await scheduler.submit_task(
            "process_data",
            "hello world",
            multiplier=3,
            priority=TaskPriority.HIGH
        )
        
        task2_id = await scheduler.submit_task(
            "calculate_sum",
            [1, 2, 3, 4, 5],
            name="Calculate numbers sum"
        )
        
        # 提交延迟任务
        from datetime import datetime, timedelta
        future_time = datetime.now() + timedelta(seconds=5)
        
        task3_id = await scheduler.submit_task(
            "process_data",
            "delayed task",
            schedule_type="delayed",
            execute_at=future_time,
            name="Delayed processing"
        )
        
        # 等待一段时间查看结果
        await asyncio.sleep(3)
        
        # 获取任务状态
        status1 = await scheduler.get_task_status(task1_id)
        status2 = await scheduler.get_task_status(task2_id)
        
        print(f"\nTask 1 Status: {status1}")
        print(f"Task 2 Status: {status2}")
        
        # 获取指标
        metrics = scheduler.get_metrics()
        print(f"\nScheduler Metrics: {metrics}")
        
        # 停止调度器
        await scheduler.stop()
    
    asyncio.run(main())

4. WebSocket服务器深度实现 {#websocket服务器}

4.1 WebSocket服务器核心

python 复制代码
"""
高性能WebSocket服务器实现
支持:连接管理、房间系统、广播、心跳检测、消息压缩
"""
import asyncio
import json
import zlib
import time
import uuid
import hashlib
import base64
from typing import Any, Dict, List, Optional, Set, Callable, Union
from dataclasses import dataclass, field
from enum import Enum
from collections import defaultdict
import logging
import struct
import ssl
from datetime import datetime, timedelta

import websockets
from websockets.server import WebSocketServerProtocol
from websockets.exceptions import ConnectionClosed


class ConnectionState(Enum):
    """连接状态"""
    CONNECTING = "connecting"
    CONNECTED = "connected"
    DISCONNECTING = "disconnecting"
    DISCONNECTED = "disconnected"


class MessageType(Enum):
    """消息类型"""
    TEXT = 1
    BINARY = 2
    PING = 9
    PONG = 10
    CLOSE = 8


@dataclass
class WebSocketClient:
    """WebSocket客户端"""
    id: str
    websocket: WebSocketServerProtocol
    state: ConnectionState = ConnectionState.CONNECTING
    connected_at: datetime = field(default_factory=datetime.now)
    last_activity: datetime = field(default_factory=datetime.now)
    
    # 客户端信息
    remote_address: Optional[str] = None
    user_agent: Optional[str] = None
    headers: Dict[str, str] = field(default_factory=dict)
    
    # 会话数据
    session: Dict[str, Any] = field(default_factory=dict)
    subscriptions: Set[str] = field(default_factory=set)  # 订阅的主题/房间
    
    # 消息统计
    messages_sent: int = 0
    messages_received: int = 0
    bytes_sent: int = 0
    bytes_received: int = 0
    
    # 心跳
    last_ping: Optional[datetime] = None
    ping_count: int = 0
    
    def update_activity(self):
        """更新活动时间"""
        self.last_activity = datetime.now()
    
    def is_active(self, timeout: int = 30) -> bool:
        """检查客户端是否活跃"""
        inactive_for = (datetime.now() - self.last_activity).total_seconds()
        return inactive_for < timeout
    
    def subscribe(self, topic: str):
        """订阅主题"""
        self.subscriptions.add(topic)
    
    def unsubscribe(self, topic: str):
        """取消订阅"""
        self.subscriptions.discard(topic)
    
    def is_subscribed(self, topic: str) -> bool:
        """检查是否订阅了主题"""
        return topic in self.subscriptions


class WebSocketMessage:
    """WebSocket消息封装"""
    
    def __init__(
        self,
        type: MessageType = MessageType.TEXT,
        data: Any = None,
        compressed: bool = False,
        metadata: Optional[Dict[str, Any]] = None
    ):
        self.id = str(uuid.uuid4())
        self.type = type
        self.data = data
        self.compressed = compressed
        self.metadata = metadata or {}
        self.timestamp = datetime.now()
        self.sender: Optional[WebSocketClient] = None
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return {
            "id": self.id,
            "type": self.type.value,
            "data": self.data,
            "compressed": self.compressed,
            "metadata": self.metadata,
            "timestamp": self.timestamp.isoformat(),
        }
    
    def to_json(self) -> str:
        """转换为JSON字符串"""
        return json.dumps(self.to_dict(), default=str)
    
    @classmethod
    def from_json(cls, json_str: str) -> 'WebSocketMessage':
        """从JSON创建消息"""
        data = json.loads(json_str)
        return cls(
            type=MessageType(data["type"]),
            data=data["data"],
            compressed=data.get("compressed", False),
            metadata=data.get("metadata", {})
        )
    
    def compress(self, level: int = 6) -> bytes:
        """压缩消息"""
        if self.compressed:
            return self.data if isinstance(self.data, bytes) else self.data.encode()
        
        json_str = self.to_json()
        compressed = zlib.compress(json_str.encode(), level=level)
        self.compressed = True
        self.data = compressed
        return compressed
    
    def decompress(self) -> Dict[str, Any]:
        """解压消息"""
        if not self.compressed:
            return self.to_dict()
        
        if isinstance(self.data, str):
            self.data = self.data.encode()
        
        decompressed = zlib.decompress(self.data).decode()
        data = json.loads(decompressed)
        self.compressed = False
        self.data = data.get("data")
        self.metadata = data.get("metadata", {})
        return data


class WebSocketServer:
    """WebSocket服务器"""
    
    def __init__(
        self,
        host: str = "localhost",
        port: int = 8765,
        ssl_context: Optional[ssl.SSLContext] = None,
        max_connections: int = 10000,
        ping_interval: int = 30,
        ping_timeout: int = 10,
        message_size_limit: int = 16 * 1024 * 1024,  # 16MB
        enable_compression: bool = True,
        enable_stats: bool = True
    ):
        self.host = host
        self.port = port
        self.ssl_context = ssl_context
        self.max_connections = max_connections
        self.ping_interval = ping_interval
        self.ping_timeout = ping_timeout
        
        # 连接管理
        self.clients: Dict[str, WebSocketClient] = {}
        self.rooms: Dict[str, Set[str]] = defaultdict(set)  # room -> client_ids
        self.topic_subscribers: Dict[str, Set[str]] = defaultdict(set)  # topic -> client_ids
        
        # 消息处理器
        self.message_handlers: Dict[str, Callable] = {}
        self.default_handler: Optional[Callable] = None
        
        # 服务器状态
        self.is_running = False
        self.server: Optional[websockets.Server] = None
        self.stats = {
            "connections_total": 0,
            "connections_active": 0,
            "messages_sent": 0,
            "messages_received": 0,
            "bytes_sent": 0,
            "bytes_received": 0,
            "start_time": None,
            "uptime": 0,
        } if enable_stats else None
        
        # 心跳任务
        self.heartbeat_task: Optional[asyncio.Task] = None
        
        # 事件回调
        self.on_connect_callbacks: List[Callable] = []
        self.on_disconnect_callbacks: List[Callable] = []
        self.on_message_callbacks: List[Callable] = []
        
        self.logger = logging.getLogger("websocket_server")
        
        # 配置WebSocket服务器
        self.websocket_server_kwargs = {
            "max_size": message_size_limit,
            "ping_interval": ping_interval,
            "ping_timeout": ping_timeout,
            "compression": "deflate" if enable_compression else None,
        }
    
    async def start(self):
        """启动WebSocket服务器"""
        if self.is_running:
            return
        
        try:
            self.server = await websockets.serve(
                self._handle_connection,
                self.host,
                self.port,
                ssl=self.ssl_context,
                **self.websocket_server_kwargs
            )
            
            self.is_running = True
            self.stats["start_time"] = datetime.now()
            
            # 启动心跳任务
            self.heartbeat_task = asyncio.create_task(self._heartbeat_loop())
            
            self.logger.info(f"WebSocket server started on {self.host}:{self.port}")
            
        except Exception as e:
            self.logger.error(f"Failed to start WebSocket server: {e}")
            raise
    
    async def stop(self):
        """停止WebSocket服务器"""
        if not self.is_running:
            return
        
        self.is_running = False
        
        # 停止心跳任务
        if self.heartbeat_task:
            self.heartbeat_task.cancel()
            try:
                await self.heartbeat_task
            except asyncio.CancelledError:
                pass
        
        # 关闭所有客户端连接
        disconnect_tasks = []
        for client_id, client in list(self.clients.items()):
            disconnect_tasks.append(self.disconnect_client(client_id))
        
        if disconnect_tasks:
            await asyncio.gather(*disconnect_tasks, return_exceptions=True)
        
        # 关闭服务器
        if self.server:
            self.server.close()
            await self.server.wait_closed()
        
        self.logger.info("WebSocket server stopped")
    
    async def _handle_connection(self, websocket: WebSocketServerProtocol, path: str):
        """处理新连接"""
        client_id = self._generate_client_id(websocket)
        
        # 检查连接限制
        if len(self.clients) >= self.max_connections:
            await websocket.close(1008, "Server is at capacity")
            return
        
        # 创建客户端对象
        client = WebSocketClient(
            id=client_id,
            websocket=websocket,
            remote_address=f"{websocket.remote_address[0]}:{websocket.remote_address[1]}",
            headers=dict(websocket.request_headers),
            user_agent=websocket.request_headers.get("User-Agent"),
        )
        
        self.clients[client_id] = client
        
        # 更新统计
        if self.stats:
            self.stats["connections_total"] += 1
            self.stats["connections_active"] += 1
        
        # 触发连接事件
        await self._trigger_event(self.on_connect_callbacks, client)
        
        self.logger.info(f"Client connected: {client_id} from {client.remote_address}")
        
        try:
            # 处理消息循环
            await self._message_loop(client)
            
        except ConnectionClosed as e:
            self.logger.info(f"Client disconnected: {client_id} - {e}")
            
        except Exception as e:
            self.logger.error(f"Error handling client {client_id}: {e}")
            
        finally:
            # 断开连接处理
            await self.disconnect_client(client_id)
    
    async def _message_loop(self, client: WebSocketClient):
        """处理客户端消息循环"""
        client.state = ConnectionState.CONNECTED
        
        async for message in client.websocket:
            client.update_activity()
            
            # 更新统计
            client.messages_received += 1
            client.bytes_received += len(message)
            
            if self.stats:
                self.stats["messages_received"] += 1
                self.stats["bytes_received"] += len(message)
            
            try:
                # 处理消息
                await self._process_message(client, message)
                
            except Exception as e:
                self.logger.error(f"Error processing message from {client.id}: {e}")
                
                # 发送错误响应
                error_msg = WebSocketMessage(
                    type=MessageType.TEXT,
                    data={"error": str(e), "type": "processing_error"},
                    metadata={"timestamp": datetime.now().isoformat()}
                )
                
                await self.send_to_client(client.id, error_msg)
    
    async def _process_message(self, client: WebSocketClient, raw_message: Union[str, bytes]):
        """处理单个消息"""
        ws_message = None
        
        try:
            # 解析消息
            if isinstance(raw_message, str):
                # JSON消息
                data = json.loads(raw_message)
                
                # 检查消息格式
                if isinstance(data, dict) and "type" in data:
                    # 结构化消息
                    message_type = data.get("type", "message")
                    message_data = data.get("data", {})
                    metadata = data.get("metadata", {})
                    
                    ws_message = WebSocketMessage(
                        type=MessageType.TEXT,
                        data=message_data,
                        metadata={
                            **metadata,
                            "message_type": message_type,
                            "client_id": client.id,
                        }
                    )
                else:
                    # 普通文本消息
                    ws_message = WebSocketMessage(
                        type=MessageType.TEXT,
                        data=data,
                        metadata={
                            "client_id": client.id,
                            "raw": True
                        }
                    )
                    
            elif isinstance(raw_message, bytes):
                # 二进制消息
                ws_message = WebSocketMessage(
                    type=MessageType.BINARY,
                    data=raw_message,
                    metadata={"client_id": client.id}
                )
            
            if ws_message:
                ws_message.sender = client
                
                # 触发消息事件
                await self._trigger_event(self.on_message_callbacks, client, ws_message)
                
                # 调用消息处理器
                message_type = ws_message.metadata.get("message_type", "message")
                handler = self.message_handlers.get(message_type, self.default_handler)
                
                if handler:
                    if asyncio.iscoroutinefunction(handler):
                        await handler(client, ws_message)
                    else:
                        handler(client, ws_message)
                        
        except json.JSONDecodeError as e:
            self.logger.warning(f"Invalid JSON from client {client.id}: {e}")
            
            # 发送错误响应
            error_msg = WebSocketMessage(
                type=MessageType.TEXT,
                data={"error": "Invalid JSON format", "type": "validation_error"},
                metadata={"timestamp": datetime.now().isoformat()}
            )
            
            await self.send_to_client(client.id, error_msg)
            
        except Exception as e:
            self.logger.error(f"Error in message processing pipeline: {e}")
            raise
    
    async def _heartbeat_loop(self):
        """心跳检测循环"""
        while self.is_running:
            try:
                await asyncio.sleep(self.ping_interval)
                
                # 检查所有客户端的心跳
                now = datetime.now()
                clients_to_disconnect = []
                
                for client_id, client in list(self.clients.items()):
                    # 检查活动状态
                    if not client.is_active(self.ping_interval * 2):
                        self.logger.warning(f"Client {client_id} inactive, disconnecting")
                        clients_to_disconnect.append(client_id)
                        continue
                    
                    # 发送ping
                    try:
                        if client.websocket.open:
                            # 记录ping时间
                            client.last_ping = now
                            client.ping_count += 1
                            
                            # 发送ping
                            await client.websocket.ping()
                            
                    except ConnectionClosed:
                        clients_to_disconnect.append(client_id)
                    except Exception as e:
                        self.logger.warning(f"Error sending ping to {client_id}: {e}")
                        clients_to_disconnect.append(client_id)
                
                # 断开不活动的客户端
                for client_id in clients_to_disconnect:
                    await self.disconnect_client(client_id)
                    
                # 更新统计
                if self.stats:
                    self.stats["uptime"] = (datetime.now() - self.stats["start_time"]).total_seconds()
                    self.stats["connections_active"] = len(self.clients)
                    
            except asyncio.CancelledError:
                break
            except Exception as e:
                self.logger.error(f"Heartbeat loop error: {e}")
    
    async def disconnect_client(self, client_id: str, code: int = 1000, reason: str = ""):
        """断开客户端连接"""
        client = self.clients.get(client_id)
        if not client:
            return
        
        # 更新状态
        client.state = ConnectionState.DISCONNECTING
        
        # 从所有房间和订阅中移除
        for room in list(client.subscriptions):
            self.leave_room(client_id, room)
        
        for topic in list(client.subscriptions):
            self.unsubscribe(client_id, topic)
        
        # 关闭WebSocket连接
        try:
            if client.websocket.open:
                await client.websocket.close(code=code, reason=reason)
        except Exception as e:
            self.logger.debug(f"Error closing connection for {client_id}: {e}")
        
        # 从客户端列表移除
        self.clients.pop(client_id, None)
        
        # 更新统计
        if self.stats:
            self.stats["connections_active"] = len(self.clients)
        
        # 触发断开连接事件
        await self._trigger_event(self.on_disconnect_callbacks, client)
        
        self.logger.info(f"Client disconnected: {client_id}")
    
    async def send_to_client(self, client_id: str, message: WebSocketMessage) -> bool:
        """发送消息给指定客户端"""
        client = self.clients.get(client_id)
        if not client or not client.websocket.open:
            return False
        
        try:
            # 准备消息数据
            if message.type == MessageType.TEXT:
                if isinstance(message.data, (dict, list)):
                    data = json.dumps(message.to_dict())
                else:
                    data = str(message.data)
            else:
                data = message.data
            
            # 发送消息
            await client.websocket.send(data)
            
            # 更新统计
            client.messages_sent += 1
            client.bytes_sent += len(data) if isinstance(data, str) else len(data)
            
            if self.stats:
                self.stats["messages_sent"] += 1
                self.stats["bytes_sent"] += len(data) if isinstance(data, str) else len(data)
            
            return True
            
        except ConnectionClosed:
            await self.disconnect_client(client_id)
            return False
        except Exception as e:
            self.logger.error(f"Error sending to client {client_id}: {e}")
            return False
    
    async def broadcast(self, message: WebSocketMessage, room: Optional[str] = None):
        """广播消息"""
        if room:
            # 广播到房间
            client_ids = self.rooms.get(room, set()).copy()
        else:
            # 广播到所有客户端
            client_ids = list(self.clients.keys())
        
        # 并发发送
        send_tasks = []
        for client_id in client_ids:
            task = self.send_to_client(client_id, message)
            send_tasks.append(task)
        
        if send_tasks:
            results = await asyncio.gather(*send_tasks, return_exceptions=True)
            
            # 统计发送结果
            success_count = sum(1 for r in results if r is True)
            return success_count
        
        return 0
    
    async def send_to_topic(self, topic: str, message: WebSocketMessage) -> int:
        """发送消息到主题"""
        client_ids = self.topic_subscribers.get(topic, set()).copy()
        
        send_tasks = []
        for client_id in client_ids:
            task = self.send_to_client(client_id, message)
            send_tasks.append(task)
        
        if send_tasks:
            results = await asyncio.gather(*send_tasks, return_exceptions=True)
            success_count = sum(1 for r in results if r is True)
            return success_count
        
        return 0
    
    def join_room(self, client_id: str, room: str):
        """客户端加入房间"""
        client = self.clients.get(client_id)
        if not client:
            return False
        
        self.rooms[room].add(client_id)
        client.subscribe(room)
        return True
    
    def leave_room(self, client_id: str, room: str):
        """客户端离开房间"""
        client = self.clients.get(client_id)
        if not client:
            return False
        
        room_clients = self.rooms.get(room)
        if room_clients:
            room_clients.discard(client_id)
            if not room_clients:
                del self.rooms[room]
        
        client.unsubscribe(room)
        return True
    
    def subscribe(self, client_id: str, topic: str):
        """客户端订阅主题"""
        client = self.clients.get(client_id)
        if not client:
            return False
        
        self.topic_subscribers[topic].add(client_id)
        client.subscribe(topic)
        return True
    
    def unsubscribe(self, client_id: str, topic: str):
        """客户端取消订阅"""
        client = self.clients.get(client_id)
        if not client:
            return False
        
        subscribers = self.topic_subscribers.get(topic)
        if subscribers:
            subscribers.discard(client_id)
            if not subscribers:
                del self.topic_subscribers[topic]
        
        client.unsubscribe(topic)
        return True
    
    def register_handler(self, message_type: str, handler: Callable):
        """注册消息处理器"""
        self.message_handlers[message_type] = handler
    
    def set_default_handler(self, handler: Callable):
        """设置默认消息处理器"""
        self.default_handler = handler
    
    def get_client(self, client_id: str) -> Optional[WebSocketClient]:
        """获取客户端信息"""
        return self.clients.get(client_id)
    
    def get_room_clients(self, room: str) -> List[WebSocketClient]:
        """获取房间内的客户端"""
        client_ids = self.rooms.get(room, set())
        return [self.clients[cid] for cid in client_ids if cid in self.clients]
    
    def get_topic_subscribers(self, topic: str) -> List[WebSocketClient]:
        """获取主题订阅者"""
        client_ids = self.topic_subscribers.get(topic, set())
        return [self.clients[cid] for cid in client_ids if cid in self.clients]
    
    def get_stats(self) -> Dict[str, Any]:
        """获取服务器统计信息"""
        if not self.stats:
            return {}
        
        stats = self.stats.copy()
        stats["clients_total"] = len(self.clients)
        stats["rooms_total"] = len(self.rooms)
        stats["topics_total"] = len(self.topic_subscribers)
        
        # 计算连接信息
        connections_by_time = {}
        now = datetime.now()
        
        for client in self.clients.values():
            connected_minutes = int((now - client.connected_at).total_seconds() / 60)
            bucket = f"{connected_minutes // 5 * 5}-{(connected_minutes // 5 + 1) * 5}min"
            connections_by_time[bucket] = connections_by_time.get(bucket, 0) + 1
        
        stats["connections_by_duration"] = connections_by_time
        
        return stats
    
    async def _trigger_event(self, callbacks: List[Callable], *args, **kwargs):
        """触发事件回调"""
        for callback in callbacks:
            try:
                if asyncio.iscoroutinefunction(callback):
                    await callback(*args, **kwargs)
                else:
                    callback(*args, **kwargs)
            except Exception as e:
                self.logger.error(f"Event callback error: {e}")
    
    def _generate_client_id(self, websocket: WebSocketServerProtocol) -> str:
        """生成客户端ID"""
        # 基于连接信息生成唯一ID
        remote_addr = websocket.remote_address
        timestamp = int(time.time() * 1000)
        
        id_string = f"{remote_addr[0]}:{remote_addr[1]}:{timestamp}"
        return hashlib.md5(id_string.encode()).hexdigest()[:12]


# 使用示例
async def example_usage():
    """WebSocket服务器使用示例"""
    
    # 创建服务器
    server = WebSocketServer(
        host="localhost",
        port=8765,
        max_connections=1000,
        ping_interval=20,
        enable_compression=True
    )
    
    # 注册事件处理器
    async def on_client_connect(client: WebSocketClient):
        print(f"New client connected: {client.id}")
        
        # 发送欢迎消息
        welcome_msg = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "welcome",
                "message": "Welcome to the WebSocket server!",
                "client_id": client.id,
                "timestamp": datetime.now().isoformat()
            }
        )
        
        await server.send_to_client(client.id, welcome_msg)
    
    async def on_client_disconnect(client: WebSocketClient):
        print(f"Client disconnected: {client.id}")
    
    async def on_message_received(client: WebSocketClient, message: WebSocketMessage):
        print(f"Message from {client.id}: {message.data}")
        
        # 回显消息
        echo_msg = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "echo",
                "original": message.data,
                "timestamp": datetime.now().isoformat()
            }
        )
        
        await server.send_to_client(client.id, echo_msg)
    
    # 注册消息处理器
    async def handle_chat_message(client: WebSocketClient, message: WebSocketMessage):
        """处理聊天消息"""
        chat_data = message.data
        room = chat_data.get("room", "general")
        
        # 广播到房间
        broadcast_msg = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "chat_message",
                "sender": client.id,
                "room": room,
                "message": chat_data.get("message"),
                "timestamp": datetime.now().isoformat()
            }
        )
        
        await server.broadcast(broadcast_msg, room=room)
    
    # 设置处理器
    server.on_connect_callbacks.append(on_client_connect)
    server.on_disconnect_callbacks.append(on_client_disconnect)
    server.on_message_callbacks.append(on_message_received)
    server.register_handler("chat", handle_chat_message)
    
    # 启动服务器
    await server.start()
    
    # 运行一段时间
    try:
        # 模拟后台任务发送消息
        async def background_broadcaster():
            """后台广播任务"""
            count = 0
            while server.is_running:
                await asyncio.sleep(10)
                
                count += 1
                broadcast_msg = WebSocketMessage(
                    type=MessageType.TEXT,
                    data={
                        "type": "system_notification",
                        "message": f"System notification #{count}",
                        "timestamp": datetime.now().isoformat()
                    }
                )
                
                sent = await server.broadcast(broadcast_msg)
                print(f"Broadcasted system message to {sent} clients")
        
        # 启动后台任务
        broadcaster_task = asyncio.create_task(background_broadcaster())
        
        # 保持运行
        await asyncio.sleep(300)  # 运行5分钟
        
        # 取消后台任务
        broadcaster_task.cancel()
        
    finally:
        # 停止服务器
        await server.stop()


if __name__ == "__main__":
    # 配置日志
    logging.basicConfig(level=logging.INFO)
    
    # 运行示例
    asyncio.run(example_usage())

5. 前后端集成与通信协议 {#集成通信}

5.1 通信协议设计

python 复制代码
"""
前后端通信协议
包含:消息格式、错误处理、认证、状态同步
"""
from typing import Dict, Any, Optional, List, Union
from enum import Enum
from pydantic import BaseModel, Field, validator
import json
from datetime import datetime


class MessageType(str, Enum):
    """消息类型枚举"""
    # 系统消息
    CONNECT = "connect"
    DISCONNECT = "disconnect"
    PING = "ping"
    PONG = "pong"
    ERROR = "error"
    
    # 认证消息
    AUTH_REQUEST = "auth_request"
    AUTH_RESPONSE = "auth_response"
    
    # 数据消息
    DATA_UPDATE = "data_update"
    DATA_REQUEST = "data_request"
    DATA_RESPONSE = "data_response"
    
    # 控制消息
    SUBSCRIBE = "subscribe"
    UNSUBSCRIBE = "unsubscribe"
    COMMAND = "command"
    
    # 实时消息
    CHAT_MESSAGE = "chat_message"
    NOTIFICATION = "notification"
    PRESENCE = "presence"


class ErrorCode(str, Enum):
    """错误代码枚举"""
    # 系统错误
    INTERNAL_ERROR = "internal_error"
    TIMEOUT = "timeout"
    RATE_LIMIT = "rate_limit"
    
    # 认证错误
    UNAUTHORIZED = "unauthorized"
    INVALID_TOKEN = "invalid_token"
    PERMISSION_DENIED = "permission_denied"
    
    # 数据错误
    VALIDATION_ERROR = "validation_error"
    NOT_FOUND = "not_found"
    CONFLICT = "conflict"
    
    # 连接错误
    CONNECTION_CLOSED = "connection_closed"
    PROTOCOL_ERROR = "protocol_error"


class Message(BaseModel):
    """基础消息模型"""
    id: str = Field(default_factory=lambda: str(uuid.uuid4()))
    type: MessageType
    data: Optional[Dict[str, Any]] = None
    metadata: Dict[str, Any] = Field(default_factory=dict)
    timestamp: datetime = Field(default_factory=datetime.now)
    
    class Config:
        use_enum_values = True
        json_encoders = {
            datetime: lambda dt: dt.isoformat()
        }
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return json.loads(self.json())
    
    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'Message':
        """从字典创建消息"""
        return cls(**data)


class AuthMessage(Message):
    """认证消息"""
    token: Optional[str] = None
    user_id: Optional[str] = None
    permissions: List[str] = Field(default_factory=list)
    
    @validator('type')
    def validate_type(cls, v):
        allowed = [MessageType.AUTH_REQUEST, MessageType.AUTH_RESPONSE]
        if v not in allowed:
            raise ValueError(f'Auth message type must be one of {allowed}')
        return v


class DataMessage(Message):
    """数据消息"""
    collection: Optional[str] = None
    operation: Optional[str] = None  # create, read, update, delete
    query: Optional[Dict[str, Any]] = None
    payload: Optional[Dict[str, Any]] = None
    
    @validator('type')
    def validate_type(cls, v):
        allowed = [MessageType.DATA_REQUEST, MessageType.DATA_RESPONSE, MessageType.DATA_UPDATE]
        if v not in allowed:
            raise ValueError(f'Data message type must be one of {allowed}')
        return v


class CommandMessage(Message):
    """命令消息"""
    command: str
    parameters: Dict[str, Any] = Field(default_factory=dict)
    expect_response: bool = True
    
    @validator('type')
    def validate_type(cls, v):
        if v != MessageType.COMMAND:
            raise ValueError('Command message type must be "command"')
        return v


class ErrorMessage(Message):
    """错误消息"""
    error_code: ErrorCode
    error_message: str
    details: Optional[Dict[str, Any]] = None
    
    @validator('type')
    def validate_type(cls, v):
        if v != MessageType.ERROR:
            raise ValueError('Error message type must be "error"')
        return v
    
    @classmethod
    def from_exception(cls, exc: Exception, error_code: ErrorCode = ErrorCode.INTERNAL_ERROR):
        """从异常创建错误消息"""
        return cls(
            error_code=error_code,
            error_message=str(exc),
            details={"exception_type": exc.__class__.__name__}
        )


class PresenceMessage(Message):
    """在线状态消息"""
    user_id: str
    status: str  # online, away, offline, busy
    last_seen: Optional[datetime] = None
    custom_status: Optional[str] = None
    
    @validator('type')
    def validate_type(cls, v):
        if v != MessageType.PRESENCE:
            raise ValueError('Presence message type must be "presence"')
        return v


class SubscriptionMessage(Message):
    """订阅消息"""
    topics: List[str]
    unsubscribe: bool = False
    
    @validator('type')
    def validate_type(cls, v):
        allowed = [MessageType.SUBSCRIBE, MessageType.UNSUBSCRIBE]
        if v not in allowed:
            raise ValueError(f'Subscription message type must be one of {allowed}')
        return v


class MessageFactory:
    """消息工厂类"""
    
    @staticmethod
    def create_connect_message(user_id: str = None) -> Message:
        """创建连接消息"""
        return Message(
            type=MessageType.CONNECT,
            data={"user_id": user_id} if user_id else {}
        )
    
    @staticmethod
    def create_ping_message() -> Message:
        """创建ping消息"""
        return Message(type=MessageType.PING)
    
    @staticmethod
    def create_pong_message() -> Message:
        """创建pong消息"""
        return Message(type=MessageType.PONG)
    
    @staticmethod
    def create_auth_request(token: str) -> AuthMessage:
        """创建认证请求"""
        return AuthMessage(
            type=MessageType.AUTH_REQUEST,
            token=token
        )
    
    @staticmethod
    def create_auth_response(
        success: bool,
        user_id: str = None,
        permissions: List[str] = None
    ) -> AuthMessage:
        """创建认证响应"""
        return AuthMessage(
            type=MessageType.AUTH_RESPONSE,
            data={"success": success},
            user_id=user_id,
            permissions=permissions or []
        )
    
    @staticmethod
    def create_data_request(
        collection: str,
        operation: str,
        query: Dict[str, Any] = None,
        payload: Dict[str, Any] = None
    ) -> DataMessage:
        """创建数据请求"""
        return DataMessage(
            type=MessageType.DATA_REQUEST,
            collection=collection,
            operation=operation,
            query=query,
            payload=payload
        )
    
    @staticmethod
    def create_data_response(
        data: Any,
        collection: str = None,
        operation: str = None
    ) -> DataMessage:
        """创建数据响应"""
        return DataMessage(
            type=MessageType.DATA_RESPONSE,
            data={"result": data},
            collection=collection,
            operation=operation
        )
    
    @staticmethod
    def create_error(
        error_code: ErrorCode,
        error_message: str,
        details: Dict[str, Any] = None
    ) -> ErrorMessage:
        """创建错误消息"""
        return ErrorMessage(
            type=MessageType.ERROR,
            error_code=error_code,
            error_message=error_message,
            details=details
        )
    
    @staticmethod
    def create_command(
        command: str,
        parameters: Dict[str, Any] = None,
        expect_response: bool = True
    ) -> CommandMessage:
        """创建命令消息"""
        return CommandMessage(
            type=MessageType.COMMAND,
            command=command,
            parameters=parameters or {},
            expect_response=expect_response
        )
    
    @staticmethod
    def create_subscription(
        topics: List[str],
        unsubscribe: bool = False
    ) -> SubscriptionMessage:
        """创建订阅消息"""
        message_type = MessageType.UNSUBSCRIBE if unsubscribe else MessageType.SUBSCRIBE
        return SubscriptionMessage(
            type=message_type,
            topics=topics
        )
    
    @staticmethod
    def create_presence(
        user_id: str,
        status: str,
        custom_status: str = None
    ) -> PresenceMessage:
        """创建在线状态消息"""
        return PresenceMessage(
            type=MessageType.PRESENCE,
            user_id=user_id,
            status=status,
            custom_status=custom_status,
            last_seen=datetime.now()
        )


class MessageRouter:
    """消息路由器"""
    
    def __init__(self):
        self.handlers: Dict[str, List[Callable]] = defaultdict(list)
        self.middleware: List[Callable] = []
    
    def register_handler(self, message_type: str, handler: Callable):
        """注册消息处理器"""
        self.handlers[message_type].append(handler)
    
    def register_middleware(self, middleware: Callable):
        """注册中间件"""
        self.middleware.append(middleware)
    
    async def route_message(self, client_id: str, message: Message) -> Optional[Message]:
        """路由消息"""
        response = None
        
        try:
            # 执行中间件
            for middleware in self.middleware:
                result = await middleware(client_id, message)
                if isinstance(result, Message):
                    # 中间件返回了响应
                    return result
                elif result is False:
                    # 中间件拒绝处理
                    return MessageFactory.create_error(
                        ErrorCode.UNAUTHORIZED,
                        "Message rejected by middleware"
                    )
            
            # 查找处理器
            handlers = self.handlers.get(message.type, [])
            if not handlers:
                # 没有处理器,返回错误
                return MessageFactory.create_error(
                    ErrorCode.PROTOCOL_ERROR,
                    f"No handler for message type: {message.type}"
                )
            
            # 执行处理器
            for handler in handlers:
                result = await handler(client_id, message)
                if isinstance(result, Message):
                    response = result
                    break
            
        except Exception as e:
            # 处理异常
            response = MessageFactory.create_error(
                ErrorCode.INTERNAL_ERROR,
                str(e),
                {"exception": e.__class__.__name__}
            )
        
        return response

6. 完整实战案例:实时股票监控系统 {#实战案例}

python 复制代码
"""
实时股票监控系统
功能:实时股票数据推送、价格预警、技术指标计算、交易信号生成
"""
import asyncio
import json
import time
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Set
import random
import logging
from dataclasses import dataclass, field
from collections import defaultdict

# 导入之前定义的组件
from task_scheduler import TaskScheduler, Task, TaskPriority
from websocket_server import WebSocketServer, WebSocketMessage, MessageType
from message_protocol import (
    MessageFactory, MessageRouter, MessageType as MsgType,
    ErrorCode, SubscriptionMessage
)


@dataclass
class StockData:
    """股票数据"""
    symbol: str
    price: float
    volume: int
    timestamp: datetime
    change: float = 0.0  # 涨跌幅
    change_percent: float = 0.0
    open: float = 0.0
    high: float = 0.0
    low: float = 0.0
    close: float = 0.0
    bid: float = 0.0
    ask: float = 0.0
    bid_size: int = 0
    ask_size: int = 0
    
    def to_dict(self) -> Dict[str, Any]:
        """转换为字典"""
        return {
            "symbol": self.symbol,
            "price": self.price,
            "volume": self.volume,
            "timestamp": self.timestamp.isoformat(),
            "change": self.change,
            "change_percent": self.change_percent,
            "open": self.open,
            "high": self.high,
            "low": self.low,
            "close": self.close,
            "bid": self.bid,
            "ask": self.ask,
            "bid_size": self.bid_size,
            "ask_size": self.ask_size,
        }


@dataclass
class Alert:
    """价格预警"""
    id: str
    symbol: str
    condition: str  # above, below, cross_above, cross_below
    price: float
    user_id: str
    created_at: datetime = field(default_factory=datetime.now)
    triggered_at: Optional[datetime] = None
    triggered_price: Optional[float] = None
    active: bool = True
    
    def check(self, current_price: float, previous_price: float = None) -> bool:
        """检查预警是否触发"""
        if not self.active:
            return False
        
        if self.condition == "above":
            return current_price > self.price
        elif self.condition == "below":
            return current_price < self.price
        elif self.condition == "cross_above" and previous_price:
            return previous_price <= self.price < current_price
        elif self.condition == "cross_below" and previous_price:
            return previous_price >= self.price > current_price
        
        return False


class TechnicalIndicator:
    """技术指标计算器"""
    
    @staticmethod
    def calculate_sma(prices: List[float], period: int) -> Optional[float]:
        """计算简单移动平均线"""
        if len(prices) < period:
            return None
        return sum(prices[-period:]) / period
    
    @staticmethod
    def calculate_ema(prices: List[float], period: int) -> Optional[float]:
        """计算指数移动平均线"""
        if len(prices) < period:
            return None
        
        sma = TechnicalIndicator.calculate_sma(prices[:period], period)
        if sma is None:
            return None
        
        multiplier = 2 / (period + 1)
        ema = sma
        
        for price in prices[period:]:
            ema = (price - ema) * multiplier + ema
        
        return ema
    
    @staticmethod
    def calculate_rsi(prices: List[float], period: int = 14) -> Optional[float]:
        """计算相对强弱指数"""
        if len(prices) <= period:
            return None
        
        gains = []
        losses = []
        
        for i in range(1, len(prices)):
            change = prices[i] - prices[i-1]
            if change > 0:
                gains.append(change)
                losses.append(0)
            else:
                gains.append(0)
                losses.append(abs(change))
        
        avg_gain = sum(gains[:period]) / period
        avg_loss = sum(losses[:period]) / period
        
        for i in range(period, len(gains)):
            avg_gain = (avg_gain * (period - 1) + gains[i]) / period
            avg_loss = (avg_loss * (period - 1) + losses[i]) / period
        
        if avg_loss == 0:
            return 100
        
        rs = avg_gain / avg_loss
        rsi = 100 - (100 / (1 + rs))
        
        return rsi
    
    @staticmethod
    def calculate_macd(
        prices: List[float],
        fast_period: int = 12,
        slow_period: int = 26,
        signal_period: int = 9
    ) -> Dict[str, Optional[float]]:
        """计算MACD"""
        if len(prices) < slow_period:
            return {"macd": None, "signal": None, "histogram": None}
        
        fast_ema = TechnicalIndicator.calculate_ema(prices, fast_period)
        slow_ema = TechnicalIndicator.calculate_ema(prices, slow_period)
        
        if fast_ema is None or slow_ema is None:
            return {"macd": None, "signal": None, "histogram": None}
        
        macd = fast_ema - slow_ema
        
        # 计算信号线
        macd_values = []
        for i in range(len(prices) - slow_period + 1):
            fast = TechnicalIndicator.calculate_ema(prices[i:], fast_period)
            slow = TechnicalIndicator.calculate_ema(prices[i:], slow_period)
            if fast is not None and slow is not None:
                macd_values.append(fast - slow)
        
        signal = TechnicalIndicator.calculate_ema(macd_values, signal_period)
        histogram = macd - signal if signal is not None else None
        
        return {
            "macd": macd,
            "signal": signal,
            "histogram": histogram
        }


class StockMarketSimulator:
    """股票市场模拟器"""
    
    def __init__(self):
        self.stocks: Dict[str, StockData] = {}
        self.price_history: Dict[str, List[StockData]] = defaultdict(list)
        self.initial_prices: Dict[str, float] = {}
        
        # 初始化一些股票
        self._initialize_stocks()
    
    def _initialize_stocks(self):
        """初始化股票数据"""
        stocks_config = [
            ("AAPL", 175.0, 0.02),
            ("GOOGL", 135.0, 0.015),
            ("MSFT", 330.0, 0.018),
            ("TSLA", 240.0, 0.025),
            ("AMZN", 145.0, 0.022),
            ("META", 320.0, 0.02),
            ("NVDA", 480.0, 0.03),
            ("NFLX", 560.0, 0.019),
            ("AMD", 125.0, 0.021),
            ("INTC", 44.0, 0.012),
        ]
        
        for symbol, price, volatility in stocks_config:
            self.initial_prices[symbol] = price
            
            stock = StockData(
                symbol=symbol,
                price=price,
                volume=random.randint(1000, 10000),
                timestamp=datetime.now(),
                open=price,
                high=price,
                low=price,
                close=price,
                bid=price * 0.999,
                ask=price * 1.001,
                bid_size=random.randint(100, 1000),
                ask_size=random.randint(100, 1000)
            )
            
            self.stocks[symbol] = stock
            self.price_history[symbol].append(stock)
    
    def update_prices(self):
        """更新股票价格(模拟市场波动)"""
        for symbol, stock in self.stocks.items():
            previous_price = stock.price
            
            # 随机波动
            volatility = 0.02  # 2%波动
            change_percent = random.uniform(-volatility, volatility)
            
            # 趋势成分(轻微)
            trend = random.uniform(-0.001, 0.001)
            
            # 随机事件(偶尔大幅波动)
            event = 0.0
            if random.random() < 0.01:  # 1%概率
                event = random.uniform(-0.05, 0.05)
            
            total_change = change_percent + trend + event
            
            # 更新价格
            new_price = previous_price * (1 + total_change)
            
            # 更新股票数据
            stock.price = round(new_price, 2)
            stock.change = round(new_price - previous_price, 2)
            stock.change_percent = round((new_price - previous_price) / previous_price * 100, 2)
            stock.volume = random.randint(1000, 50000)
            stock.timestamp = datetime.now()
            
            # 更新最高价/最低价
            stock.high = max(stock.high, stock.price)
            stock.low = min(stock.low, stock.price)
            
            # 更新买卖价
            stock.bid = round(stock.price * 0.999, 2)
            stock.ask = round(stock.price * 1.001, 2)
            stock.bid_size = random.randint(100, 1000)
            stock.ask_size = random.randint(100, 1000)
            
            # 保存历史
            self.price_history[symbol].append(stock)
            
            # 保持历史数据长度
            if len(self.price_history[symbol]) > 1000:
                self.price_history[symbol].pop(0)
    
    def get_stock(self, symbol: str) -> Optional[StockData]:
        """获取股票数据"""
        return self.stocks.get(symbol)
    
    def get_all_stocks(self) -> List[StockData]:
        """获取所有股票数据"""
        return list(self.stocks.values())
    
    def get_price_history(self, symbol: str, limit: int = 100) -> List[StockData]:
        """获取价格历史"""
        history = self.price_history.get(symbol, [])
        return history[-limit:] if limit else history
    
    def calculate_indicators(self, symbol: str) -> Dict[str, Any]:
        """计算技术指标"""
        history = self.price_history.get(symbol, [])
        if not history:
            return {}
        
        prices = [data.price for data in history]
        
        return {
            "sma_20": TechnicalIndicator.calculate_sma(prices, 20),
            "sma_50": TechnicalIndicator.calculate_sma(prices, 50),
            "ema_12": TechnicalIndicator.calculate_ema(prices, 12),
            "ema_26": TechnicalIndicator.calculate_ema(prices, 26),
            "rsi_14": TechnicalIndicator.calculate_rsi(prices, 14),
            "macd": TechnicalIndicator.calculate_macd(prices),
        }


class RealTimeStockMonitor:
    """实时股票监控系统"""
    
    def __init__(
        self,
        websocket_port: int = 8765,
        redis_url: str = "redis://localhost:6379"
    ):
        # 初始化组件
        self.market_simulator = StockMarketSimulator()
        self.task_scheduler = TaskScheduler(max_workers=10)
        self.websocket_server = WebSocketServer(port=websocket_port)
        self.message_router = MessageRouter()
        
        # 预警系统
        self.alerts: Dict[str, List[Alert]] = defaultdict(list)
        self.triggered_alerts: Dict[str, List[Alert]] = defaultdict(list)
        
        # 客户端订阅
        self.client_subscriptions: Dict[str, Set[str]] = defaultdict(set)  # client_id -> symbols
        
        # 状态追踪
        self.is_running = False
        
        self.logger = logging.getLogger("stock_monitor")
        
        # 注册任务和处理器
        self._register_tasks()
        self._register_message_handlers()
        self._setup_websocket_handlers()
    
    def _register_tasks(self):
        """注册后台任务"""
        
        @self.task_scheduler.task(name="update_market_data")
        async def update_market_data_task():
            """更新市场数据任务"""
            self.market_simulator.update_prices()
            
            # 获取所有股票数据
            stocks = self.market_simulator.get_all_stocks()
            
            # 检查预警
            await self._check_alerts()
            
            # 广播更新
            await self._broadcast_stock_updates(stocks)
            
            return {"stocks_updated": len(stocks)}
        
        @self.task_scheduler.task(name="calculate_indicators")
        async def calculate_indicators_task():
            """计算技术指标任务"""
            indicators = {}
            
            for symbol in self.market_simulator.stocks.keys():
                indicators[symbol] = self.market_simulator.calculate_indicators(symbol)
            
            # 广播技术指标
            await self._broadcast_indicators(indicators)
            
            return {"symbols_processed": len(indicators)}
        
        @self.task_scheduler.task(name="cleanup_old_data")
        async def cleanup_old_data_task():
            """清理旧数据任务"""
            # 清理过期的预警
            await self._cleanup_expired_alerts()
            
            return {"cleaned": True}
    
    def _register_message_handlers(self):
        """注册消息处理器"""
        
        async def handle_subscription(client_id: str, message: SubscriptionMessage):
            """处理订阅消息"""
            if message.unsubscribe:
                # 取消订阅
                for topic in message.topics:
                    if topic in self.client_subscriptions[client_id]:
                        self.client_subscriptions[client_id].remove(topic)
                        self.websocket_server.unsubscribe(client_id, topic)
            else:
                # 订阅
                for topic in message.topics:
                    self.client_subscriptions[client_id].add(topic)
                    self.websocket_server.subscribe(client_id, topic)
                    
                    # 发送当前数据
                    if topic.startswith("stock:"):
                        symbol = topic.replace("stock:", "")
                        stock = self.market_simulator.get_stock(symbol)
                        if stock:
                            await self._send_stock_data(client_id, symbol, stock)
            
            return MessageFactory.create_data_response(
                {"subscribed": message.topics, "unsubscribe": message.unsubscribe}
            )
        
        async def handle_data_request(client_id: str, message: DataMessage):
            """处理数据请求"""
            collection = message.collection
            operation = message.operation
            query = message.query or {}
            
            if collection == "stocks":
                if operation == "list":
                    # 获取股票列表
                    stocks = self.market_simulator.get_all_stocks()
                    stocks_data = [stock.to_dict() for stock in stocks]
                    
                    return MessageFactory.create_data_response(stocks_data)
                
                elif operation == "get":
                    # 获取单个股票
                    symbol = query.get("symbol")
                    if not symbol:
                        return MessageFactory.create_error(
                            ErrorCode.VALIDATION_ERROR,
                            "Symbol is required"
                        )
                    
                    stock = self.market_simulator.get_stock(symbol)
                    if not stock:
                        return MessageFactory.create_error(
                            ErrorCode.NOT_FOUND,
                            f"Stock {symbol} not found"
                        )
                    
                    return MessageFactory.create_data_response(stock.to_dict())
                
                elif operation == "history":
                    # 获取历史数据
                    symbol = query.get("symbol")
                    limit = query.get("limit", 100)
                    
                    if not symbol:
                        return MessageFactory.create_error(
                            ErrorCode.VALIDATION_ERROR,
                            "Symbol is required"
                        )
                    
                    history = self.market_simulator.get_price_history(symbol, limit)
                    history_data = [data.to_dict() for data in history]
                    
                    return MessageFactory.create_data_response(history_data)
            
            elif collection == "alerts":
                if operation == "list":
                    # 获取用户预警
                    user_id = query.get("user_id")
                    alerts = self._get_user_alerts(user_id) if user_id else []
                    alerts_data = [
                        {
                            "id": alert.id,
                            "symbol": alert.symbol,
                            "condition": alert.condition,
                            "price": alert.price,
                            "active": alert.active,
                            "created_at": alert.created_at.isoformat(),
                            "triggered_at": alert.triggered_at.isoformat() if alert.triggered_at else None,
                            "triggered_price": alert.triggered_price
                        }
                        for alert in alerts
                    ]
                    
                    return MessageFactory.create_data_response(alerts_data)
                
                elif operation == "create":
                    # 创建预警
                    symbol = query.get("symbol")
                    condition = query.get("condition")
                    price = query.get("price")
                    user_id = query.get("user_id")
                    
                    if not all([symbol, condition, price, user_id]):
                        return MessageFactory.create_error(
                            ErrorCode.VALIDATION_ERROR,
                            "Missing required fields"
                        )
                    
                    alert = self._create_alert(symbol, condition, price, user_id)
                    
                    return MessageFactory.create_data_response({
                        "id": alert.id,
                        "message": "Alert created successfully"
                    })
                
                elif operation == "delete":
                    # 删除预警
                    alert_id = query.get("id")
                    user_id = query.get("user_id")
                    
                    if not alert_id or not user_id:
                        return MessageFactory.create_error(
                            ErrorCode.VALIDATION_ERROR,
                            "Alert ID and user ID are required"
                        )
                    
                    success = self._delete_alert(alert_id, user_id)
                    
                    return MessageFactory.create_data_response({
                        "success": success,
                        "message": "Alert deleted" if success else "Alert not found"
                    })
            
            return MessageFactory.create_error(
                ErrorCode.NOT_FOUND,
                f"Unknown collection or operation: {collection}/{operation}"
            )
        
        async def handle_command(client_id: str, message: CommandMessage):
            """处理命令消息"""
            command = message.command
            params = message.parameters
            
            if command == "get_stats":
                # 获取系统统计
                stats = {
                    "total_stocks": len(self.market_simulator.stocks),
                    "total_alerts": sum(len(alerts) for alerts in self.alerts.values()),
                    "active_connections": len(self.websocket_server.clients),
                    "server_uptime": time.time() - (self.websocket_server.stats.get("start_time", datetime.now()).timestamp() if self.websocket_server.stats else time.time()),
                }
                
                return MessageFactory.create_data_response(stats)
            
            elif command == "simulate_event":
                # 模拟市场事件
                symbol = params.get("symbol")
                impact = params.get("impact", 0.1)  # 10% impact
                
                if symbol and symbol in self.market_simulator.stocks:
                    stock = self.market_simulator.stocks[symbol]
                    stock.price *= (1 + impact)
                    
                    return MessageFactory.create_data_response({
                        "message": f"Simulated event for {symbol}",
                        "new_price": stock.price
                    })
            
            return MessageFactory.create_error(
                ErrorCode.NOT_FOUND,
                f"Unknown command: {command}"
            )
        
        # 注册处理器
        self.message_router.register_handler(MsgType.SUBSCRIBE, handle_subscription)
        self.message_router.register_handler(MsgType.UNSUBSCRIBE, handle_subscription)
        self.message_router.register_handler(MsgType.DATA_REQUEST, handle_data_request)
        self.message_router.register_handler(MsgType.COMMAND, handle_command)
    
    def _setup_websocket_handlers(self):
        """设置WebSocket处理器"""
        
        async def on_client_connect(client):
            """客户端连接事件"""
            self.logger.info(f"Client connected: {client.id}")
            
            # 发送欢迎消息
            welcome_msg = WebSocketMessage(
                type=MessageType.TEXT,
                data={
                    "type": "welcome",
                    "client_id": client.id,
                    "message": "Connected to Stock Monitor",
                    "timestamp": datetime.now().isoformat()
                }
            )
            
            await self.websocket_server.send_to_client(client.id, welcome_msg)
        
        async def on_client_disconnect(client):
            """客户端断开连接事件"""
            self.logger.info(f"Client disconnected: {client.id}")
            
            # 清理客户端订阅
            if client.id in self.client_subscriptions:
                del self.client_subscriptions[client.id]
        
        async def on_message_received(client, message: WebSocketMessage):
            """消息接收事件"""
            try:
                # 解析消息
                if isinstance(message.data, dict) and "type" in message.data:
                    msg_type = message.data.get("type")
                    
                    # 转换为协议消息
                    protocol_message = Message.from_dict(message.data)
                    
                    # 路由消息
                    response = await self.message_router.route_message(client.id, protocol_message)
                    
                    if response:
                        # 发送响应
                        response_msg = WebSocketMessage(
                            type=MessageType.TEXT,
                            data=response.to_dict()
                        )
                        
                        await self.websocket_server.send_to_client(client.id, response_msg)
            
            except Exception as e:
                self.logger.error(f"Error processing message: {e}")
                
                # 发送错误响应
                error_msg = WebSocketMessage(
                    type=MessageType.TEXT,
                    data={
                        "type": "error",
                        "error": str(e),
                        "timestamp": datetime.now().isoformat()
                    }
                )
                
                await self.websocket_server.send_to_client(client.id, error_msg)
        
        # 设置WebSocket事件处理器
        self.websocket_server.on_connect_callbacks.append(on_client_connect)
        self.websocket_server.on_disconnect_callbacks.append(on_client_disconnect)
        self.websocket_server.on_message_callbacks.append(on_message_received)
        self.websocket_server.set_default_handler(on_message_received)
    
    async def _check_alerts(self):
        """检查预警"""
        for symbol, alerts in self.alerts.items():
            stock = self.market_simulator.get_stock(symbol)
            if not stock:
                continue
            
            # 获取前一个价格
            history = self.market_simulator.get_price_history(symbol, 2)
            previous_price = history[0].price if len(history) > 1 else stock.price
            
            for alert in alerts[:]:  # 使用切片创建副本
                if alert.active and alert.check(stock.price, previous_price):
                    # 触发预警
                    alert.active = False
                    alert.triggered_at = datetime.now()
                    alert.triggered_price = stock.price
                    
                    self.triggered_alerts[symbol].append(alert)
                    
                    # 发送预警通知
                    await self._send_alert_notification(alert, stock)
    
    async def _send_alert_notification(self, alert: Alert, stock: StockData):
        """发送预警通知"""
        notification_msg = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "alert_triggered",
                "alert_id": alert.id,
                "symbol": alert.symbol,
                "condition": alert.condition,
                "target_price": alert.price,
                "current_price": stock.price,
                "triggered_at": alert.triggered_at.isoformat(),
                "user_id": alert.user_id
            }
        )
        
        # 发送给特定用户(这里简化处理)
        await self.websocket_server.broadcast(notification_msg)
    
    async def _broadcast_stock_updates(self, stocks: List[StockData]):
        """广播股票更新"""
        for stock in stocks:
            update_msg = WebSocketMessage(
                type=MessageType.TEXT,
                data={
                    "type": "stock_update",
                    "symbol": stock.symbol,
                    "data": stock.to_dict(),
                    "timestamp": datetime.now().isoformat()
                }
            )
            
            # 广播到订阅该股票的客户端
            topic = f"stock:{stock.symbol}"
            await self.websocket_server.send_to_topic(topic, update_msg)
    
    async def _broadcast_indicators(self, indicators: Dict[str, Dict[str, Any]]):
        """广播技术指标"""
        for symbol, indicator_data in indicators.items():
            if indicator_data:
                indicator_msg = WebSocketMessage(
                    type=MessageType.TEXT,
                    data={
                        "type": "indicators_update",
                        "symbol": symbol,
                        "indicators": indicator_data,
                        "timestamp": datetime.now().isoformat()
                    }
                )
                
                topic = f"indicators:{symbol}"
                await self.websocket_server.send_to_topic(topic, indicator_msg)
    
    async def _send_stock_data(self, client_id: str, symbol: str, stock: StockData):
        """发送股票数据给客户端"""
        stock_msg = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "stock_data",
                "symbol": symbol,
                "data": stock.to_dict(),
                "timestamp": datetime.now().isoformat()
            }
        )
        
        await self.websocket_server.send_to_client(client_id, stock_msg)
    
    def _create_alert(self, symbol: str, condition: str, price: float, user_id: str) -> Alert:
        """创建预警"""
        alert_id = str(uuid.uuid4())
        alert = Alert(
            id=alert_id,
            symbol=symbol,
            condition=condition,
            price=price,
            user_id=user_id
        )
        
        self.alerts[symbol].append(alert)
        return alert
    
    def _delete_alert(self, alert_id: str, user_id: str) -> bool:
        """删除预警"""
        for symbol, alerts in self.alerts.items():
            for alert in alerts:
                if alert.id == alert_id and alert.user_id == user_id:
                    alerts.remove(alert)
                    return True
        return False
    
    def _get_user_alerts(self, user_id: str) -> List[Alert]:
        """获取用户预警"""
        user_alerts = []
        for alerts in self.alerts.values():
            for alert in alerts:
                if alert.user_id == user_id:
                    user_alerts.append(alert)
        return user_alerts
    
    async def _cleanup_expired_alerts(self):
        """清理过期预警"""
        # 这里可以添加清理逻辑,例如删除30天前的已触发预警
        pass
    
    async def start(self):
        """启动监控系统"""
        self.is_running = True
        
        # 启动任务调度器
        await self.task_scheduler.start()
        
        # 启动WebSocket服务器
        await self.websocket_server.start()
        
        # 提交定期任务
        await self.task_scheduler.submit_task(
            "update_market_data",
            schedule_type="interval",
            interval_seconds=1,  # 每秒更新
            name="Market Data Updater",
            priority=TaskPriority.HIGH
        )
        
        await self.task_scheduler.submit_task(
            "calculate_indicators",
            schedule_type="interval",
            interval_seconds=5,  # 每5秒计算
            name="Indicators Calculator",
            priority=TaskPriority.NORMAL
        )
        
        await self.task_scheduler.submit_task(
            "cleanup_old_data",
            schedule_type="interval",
            interval_seconds=60,  # 每分钟清理
            name="Data Cleanup",
            priority=TaskPriority.LOW
        )
        
        self.logger.info("Real-time stock monitor started")
    
    async def stop(self):
        """停止监控系统"""
        self.is_running = False
        
        # 停止任务调度器
        await self.task_scheduler.stop()
        
        # 停止WebSocket服务器
        await self.websocket_server.stop()
        
        self.logger.info("Real-time stock monitor stopped")


# 客户端示例代码(JavaScript/HTML)
CLIENT_HTML = """
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Real-time Stock Monitor</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            padding: 20px;
        }
        
        .container {
            max-width: 1400px;
            margin: 0 auto;
            background: rgba(255, 255, 255, 0.95);
            border-radius: 15px;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
            overflow: hidden;
        }
        
        .header {
            background: linear-gradient(135deg, #4c6ef5 0%, #3b5bdb 100%);
            color: white;
            padding: 25px 30px;
            text-align: center;
        }
        
        .header h1 {
            font-size: 2.5em;
            margin-bottom: 10px;
            font-weight: 600;
        }
        
        .header p {
            opacity: 0.9;
            font-size: 1.1em;
        }
        
        .connection-status {
            display: inline-flex;
            align-items: center;
            padding: 8px 16px;
            border-radius: 20px;
            font-size: 0.9em;
            margin-top: 10px;
            background: rgba(255, 255, 255, 0.2);
        }
        
        .status-dot {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            margin-right: 8px;
        }
        
        .connected {
            background: #51cf66;
            animation: pulse 2s infinite;
        }
        
        .disconnected {
            background: #ff6b6b;
        }
        
        @keyframes pulse {
            0% { opacity: 1; }
            50% { opacity: 0.5; }
            100% { opacity: 1; }
        }
        
        .main-content {
            display: grid;
            grid-template-columns: 1fr 350px;
            gap: 20px;
            padding: 30px;
        }
        
        .stocks-grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
            gap: 20px;
            margin-bottom: 30px;
        }
        
        .stock-card {
            background: white;
            border-radius: 12px;
            padding: 20px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
            transition: all 0.3s ease;
            border-left: 4px solid #4c6ef5;
        }
        
        .stock-card:hover {
            transform: translateY(-5px);
            box-shadow: 0 15px 30px rgba(0, 0, 0, 0.15);
        }
        
        .stock-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 15px;
        }
        
        .stock-symbol {
            font-size: 1.5em;
            font-weight: 700;
            color: #333;
        }
        
        .stock-price {
            font-size: 1.8em;
            font-weight: 700;
        }
        
        .stock-change {
            display: inline-flex;
            align-items: center;
            padding: 4px 10px;
            border-radius: 15px;
            font-size: 0.9em;
            font-weight: 600;
            margin-left: 10px;
        }
        
        .positive {
            background: #d3f9d8;
            color: #2b8a3e;
        }
        
        .negative {
            background: #ffe3e3;
            color: #c92a2a;
        }
        
        .stock-details {
            display: grid;
            grid-template-columns: repeat(2, 1fr);
            gap: 10px;
            margin-top: 15px;
            font-size: 0.9em;
            color: #666;
        }
        
        .detail-item {
            display: flex;
            justify-content: space-between;
            padding: 5px 0;
            border-bottom: 1px solid #f0f0f0;
        }
        
        .detail-label {
            font-weight: 500;
        }
        
        .sidebar {
            background: #f8f9fa;
            border-radius: 12px;
            padding: 25px;
            height: fit-content;
        }
        
        .sidebar-section {
            margin-bottom: 30px;
        }
        
        .section-title {
            font-size: 1.2em;
            font-weight: 600;
            margin-bottom: 15px;
            color: #333;
            padding-bottom: 10px;
            border-bottom: 2px solid #e9ecef;
        }
        
        .alert-form {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }
        
        .form-group {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }
        
        .form-label {
            font-size: 0.9em;
            font-weight: 500;
            color: #495057;
        }
        
        .form-select, .form-input {
            padding: 10px 15px;
            border: 2px solid #e9ecef;
            border-radius: 8px;
            font-size: 1em;
            transition: border-color 0.3s;
        }
        
        .form-select:focus, .form-input:focus {
            outline: none;
            border-color: #4c6ef5;
        }
        
        .btn {
            padding: 12px 20px;
            border: none;
            border-radius: 8px;
            font-size: 1em;
            font-weight: 600;
            cursor: pointer;
            transition: all 0.3s;
            text-align: center;
        }
        
        .btn-primary {
            background: linear-gradient(135deg, #4c6ef5 0%, #3b5bdb 100%);
            color: white;
        }
        
        .btn-primary:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(76, 110, 245, 0.4);
        }
        
        .alerts-list {
            max-height: 300px;
            overflow-y: auto;
        }
        
        .alert-item {
            background: white;
            padding: 15px;
            margin-bottom: 10px;
            border-radius: 8px;
            border-left: 4px solid;
            box-shadow: 0 2px 5px rgba(0, 0, 0, 0.05);
        }
        
        .alert-triggered {
            border-left-color: #ff6b6b;
            background: #fff5f5;
        }
        
        .alert-active {
            border-left-color: #51cf66;
            background: #f8fff9;
        }
        
        .alert-symbol {
            font-weight: 600;
            margin-bottom: 5px;
        }
        
        .alert-condition {
            font-size: 0.9em;
            color: #666;
        }
        
        .notification {
            position: fixed;
            bottom: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 8px;
            color: white;
            font-weight: 500;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            animation: slideIn 0.3s ease;
            z-index: 1000;
        }
        
        .notification-success {
            background: #51cf66;
        }
        
        .notification-warning {
            background: #ff922b;
        }
        
        .notification-error {
            background: #ff6b6b;
        }
        
        @keyframes slideIn {
            from {
                transform: translateX(100%);
                opacity: 0;
            }
            to {
                transform: translateX(0);
                opacity: 1;
            }
        }
        
        .chart-container {
            background: white;
            border-radius: 12px;
            padding: 20px;
            margin-top: 20px;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.08);
        }
        
        .indicators {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 15px;
            margin-top: 20px;
        }
        
        .indicator {
            background: white;
            padding: 15px;
            border-radius: 8px;
            text-align: center;
            box-shadow: 0 3px 10px rgba(0, 0, 0, 0.05);
        }
        
        .indicator-name {
            font-size: 0.9em;
            color: #666;
            margin-bottom: 5px;
        }
        
        .indicator-value {
            font-size: 1.2em;
            font-weight: 700;
            color: #333;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="header">
            <h1>📈 Real-time Stock Monitor</h1>
            <p>Live stock prices, alerts, and technical indicators</p>
            <div class="connection-status">
                <div class="status-dot disconnected" id="statusDot"></div>
                <span id="statusText">Disconnected</span>
            </div>
        </div>
        
        <div class="main-content">
            <div class="left-panel">
                <div class="stocks-grid" id="stocksGrid">
                    <!-- Stock cards will be inserted here -->
                </div>
                
                <div class="chart-container">
                    <h3 class="section-title">Price Chart: <span id="selectedSymbol">AAPL</span></h3>
                    <canvas id="priceChart" width="400" height="200"></canvas>
                </div>
                
                <div class="indicators" id="indicators">
                    <!-- Indicators will be inserted here -->
                </div>
            </div>
            
            <div class="sidebar">
                <div class="sidebar-section">
                    <h3 class="section-title">🔔 Set Price Alert</h3>
                    <form class="alert-form" id="alertForm">
                        <div class="form-group">
                            <label class="form-label">Stock Symbol</label>
                            <select class="form-select" id="alertSymbol" required>
                                <option value="AAPL">Apple (AAPL)</option>
                                <option value="GOOGL">Google (GOOGL)</option>
                                <option value="MSFT">Microsoft (MSFT)</option>
                                <option value="TSLA">Tesla (TSLA)</option>
                                <option value="AMZN">Amazon (AMZN)</option>
                            </select>
                        </div>
                        
                        <div class="form-group">
                            <label class="form-label">Condition</label>
                            <select class="form-select" id="alertCondition" required>
                                <option value="above">Price goes above</option>
                                <option value="below">Price goes below</option>
                                <option value="cross_above">Crosses above</option>
                                <option value="cross_below">Crosses below</option>
                            </select>
                        </div>
                        
                        <div class="form-group">
                            <label class="form-label">Target Price ($)</label>
                            <input type="number" class="form-input" id="alertPrice" 
                                   step="0.01" min="0" required>
                        </div>
                        
                        <button type="submit" class="btn btn-primary">
                            Create Alert
                        </button>
                    </form>
                </div>
                
                <div class="sidebar-section">
                    <h3 class="section-title">📋 Your Alerts</h3>
                    <div class="alerts-list" id="alertsList">
                        <!-- Alerts will be inserted here -->
                    </div>
                </div>
                
                <div class="sidebar-section">
                    <h3 class="section-title">⚙️ System Stats</h3>
                    <div id="systemStats">
                        <p>Connected: <span id="connectedCount">0</span> clients</p>
                        <p>Stocks tracked: <span id="stocksCount">0</span></p>
                        <p>Update frequency: <span id="updateFreq">1s</span></p>
                    </div>
                </div>
            </div>
        </div>
    </div>
    
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script>
        class StockMonitorClient {
            constructor() {
                this.ws = null;
                this.reconnectInterval = 3000;
                this.maxReconnectAttempts = 5;
                this.reconnectAttempts = 0;
                this.stocks = {};
                this.alerts = [];
                this.userId = this.generateUserId();
                this.chart = null;
                this.selectedSymbol = 'AAPL';
                
                this.initialize();
            }
            
            initialize() {
                this.setupEventListeners();
                this.connect();
                this.initializeChart();
                this.loadInitialData();
            }
            
            generateUserId() {
                return 'user_' + Math.random().toString(36).substr(2, 9);
            }
            
            connect() {
                const wsUrl = `ws://${window.location.hostname}:8765`;
                this.ws = new WebSocket(wsUrl);
                
                this.ws.onopen = () => this.onConnectionOpen();
                this.ws.onclose = () => this.onConnectionClose();
                this.ws.onerror = (error) => this.onConnectionError(error);
                this.ws.onmessage = (event) => this.onMessage(event);
            }
            
            onConnectionOpen() {
                this.updateStatus('connected');
                this.reconnectAttempts = 0;
                
                console.log('WebSocket connected');
                
                // 订阅所有股票
                this.subscribeToStocks();
                
                // 获取现有预警
                this.getAlerts();
                
                // 获取系统统计
                this.getSystemStats();
            }
            
            onConnectionClose() {
                this.updateStatus('disconnected');
                
                if (this.reconnectAttempts < this.maxReconnectAttempts) {
                    this.reconnectAttempts++;
                    console.log(`Reconnecting in ${this.reconnectInterval/1000}s... (Attempt ${this.reconnectAttempts})`);
                    
                    setTimeout(() => this.connect(), this.reconnectInterval);
                }
            }
            
            onConnectionError(error) {
                console.error('WebSocket error:', error);
                this.showNotification('Connection error', 'error');
            }
            
            onMessage(event) {
                try {
                    const message = JSON.parse(event.data);
                    this.handleMessage(message);
                } catch (error) {
                    console.error('Error parsing message:', error);
                }
            }
            
            handleMessage(message) {
                const type = message.type;
                
                switch(type) {
                    case 'welcome':
                        console.log('Server welcome:', message.message);
                        break;
                        
                    case 'stock_update':
                        this.handleStockUpdate(message);
                        break;
                        
                    case 'stock_data':
                        this.handleStockData(message);
                        break;
                        
                    case 'indicators_update':
                        this.handleIndicatorsUpdate(message);
                        break;
                        
                    case 'alert_triggered':
                        this.handleAlertTriggered(message);
                        break;
                        
                    case 'data_response':
                        this.handleDataResponse(message);
                        break;
                        
                    case 'error':
                        this.showNotification(message.error_message || 'Error', 'error');
                        break;
                        
                    default:
                        console.log('Unknown message type:', type);
                }
            }
            
            handleStockUpdate(message) {
                const symbol = message.symbol;
                const data = message.data;
                
                // 更新股票数据
                this.stocks[symbol] = data;
                
                // 更新UI
                this.updateStockCard(symbol, data);
                
                // 如果选中的股票更新,更新图表
                if (symbol === this.selectedSymbol) {
                    this.updateChart(symbol);
                    this.updateIndicators(symbol);
                }
            }
            
            handleStockData(message) {
                const symbol = message.symbol;
                const data = message.data;
                
                this.stocks[symbol] = data;
                this.updateStockCard(symbol, data);
            }
            
            handleIndicatorsUpdate(message) {
                const symbol = message.symbol;
                const indicators = message.indicators;
                
                if (symbol === this.selectedSymbol) {
                    this.updateIndicators(symbol, indicators);
                }
            }
            
            handleAlertTriggered(message) {
                if (message.user_id === this.userId) {
                    this.showNotification(
                        `Alert triggered! ${message.symbol} ${message.condition} ${message.target_price}`,
                        'warning'
                    );
                    
                    // 刷新预警列表
                    this.getAlerts();
                }
            }
            
            handleDataResponse(message) {
                const data = message.data.result;
                
                if (Array.isArray(data) && data[0] && data[0].symbol) {
                    // 股票列表数据
                    data.forEach(stock => {
                        this.stocks[stock.symbol] = stock;
                        this.updateStockCard(stock.symbol, stock);
                    });
                } else if (data && data.id) {
                    // 单个项目响应
                    console.log('Operation successful:', data);
                }
            }
            
            subscribeToStocks() {
                const symbols = ['AAPL', 'GOOGL', 'MSFT', 'TSLA', 'AMZN', 'META', 'NVDA', 'NFLX', 'AMD', 'INTC'];
                const topics = symbols.map(symbol => `stock:${symbol}`);
                
                this.sendMessage({
                    type: 'subscribe',
                    topics: topics
                });
                
                // 也订阅技术指标
                const indicatorTopics = symbols.map(symbol => `indicators:${symbol}`);
                this.sendMessage({
                    type: 'subscribe',
                    topics: indicatorTopics
                });
            }
            
            sendMessage(message) {
                if (this.ws && this.ws.readyState === WebSocket.OPEN) {
                    this.ws.send(JSON.stringify(message));
                }
            }
            
            updateStatus(status) {
                const dot = document.getElementById('statusDot');
                const text = document.getElementById('statusText');
                
                dot.className = 'status-dot ' + status;
                text.textContent = status.charAt(0).toUpperCase() + status.slice(1);
            }
            
            updateStockCard(symbol, data) {
                let card = document.getElementById(`stock-${symbol}`);
                
                if (!card) {
                    card = this.createStockCard(symbol, data);
                    document.getElementById('stocksGrid').appendChild(card);
                } else {
                    this.updateStockCardContent(card, data);
                }
            }
            
            createStockCard(symbol, data) {
                const card = document.createElement('div');
                card.className = 'stock-card';
                card.id = `stock-${symbol}`;
                card.onclick = () => this.selectStock(symbol);
                
                const changeClass = data.change >= 0 ? 'positive' : 'negative';
                const changeSign = data.change >= 0 ? '+' : '';
                
                card.innerHTML = `
                    <div class="stock-header">
                        <div class="stock-symbol">${symbol}</div>
                        <div>
                            <span class="stock-price">$${data.price.toFixed(2)}</span>
                            <span class="stock-change ${changeClass}">
                                ${changeSign}${data.change.toFixed(2)} (${changeSign}${data.change_percent.toFixed(2)}%)
                            </span>
                        </div>
                    </div>
                    <div class="stock-details">
                        <div class="detail-item">
                            <span class="detail-label">Volume:</span>
                            <span>${data.volume.toLocaleString()}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">High:</span>
                            <span>$${data.high.toFixed(2)}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">Low:</span>
                            <span>$${data.low.toFixed(2)}</span>
                        </div>
                        <div class="detail-item">
                            <span class="detail-label">Bid/Ask:</span>
                            <span>$${data.bid.toFixed(2)} / $${data.ask.toFixed(2)}</span>
                        </div>
                    </div>
                `;
                
                return card;
            }
            
            updateStockCardContent(card, data) {
                const priceEl = card.querySelector('.stock-price');
                const changeEl = card.querySelector('.stock-change');
                const volumeEl = card.querySelector('.detail-item:nth-child(1) span:last-child');
                const highEl = card.querySelector('.detail-item:nth-child(2) span:last-child');
                const lowEl = card.querySelector('.detail-item:nth-child(3) span:last-child');
                const bidAskEl = card.querySelector('.detail-item:nth-child(4) span:last-child');
                
                const changeClass = data.change >= 0 ? 'positive' : 'negative';
                const changeSign = data.change >= 0 ? '+' : '';
                
                priceEl.textContent = `$${data.price.toFixed(2)}`;
                changeEl.className = `stock-change ${changeClass}`;
                changeEl.textContent = `${changeSign}${data.change.toFixed(2)} (${changeSign}${data.change_percent.toFixed(2)}%)`;
                volumeEl.textContent = data.volume.toLocaleString();
                highEl.textContent = `$${data.high.toFixed(2)}`;
                lowEl.textContent = `$${data.low.toFixed(2)}`;
                bidAskEl.textContent = `$${data.bid.toFixed(2)} / $${data.ask.toFixed(2)}`;
            }
            
            selectStock(symbol) {
                this.selectedSymbol = symbol;
                document.getElementById('selectedSymbol').textContent = symbol;
                document.getElementById('alertSymbol').value = symbol;
                
                // 更新图表
                this.updateChart(symbol);
                
                // 更新指标
                this.updateIndicators(symbol);
            }
            
            initializeChart() {
                const ctx = document.getElementById('priceChart').getContext('2d');
                this.chart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: [],
                        datasets: [{
                            label: 'Price',
                            data: [],
                            borderColor: '#4c6ef5',
                            backgroundColor: 'rgba(76, 110, 245, 0.1)',
                            borderWidth: 2,
                            fill: true,
                            tension: 0.4
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: {
                            legend: {
                                display: false
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: false,
                                grid: {
                                    color: 'rgba(0, 0, 0, 0.05)'
                                }
                            },
                            x: {
                                grid: {
                                    display: false
                                }
                            }
                        }
                    }
                });
            }
            
            updateChart(symbol) {
                // 请求历史数据
                this.sendMessage({
                    type: 'data_request',
                    collection: 'stocks',
                    operation: 'history',
                    query: {
                        symbol: symbol,
                        limit: 50
                    }
                });
            }
            
            updateChartWithData(history) {
                if (!this.chart || !history || !history.length) return;
                
                const labels = history.map(item => {
                    const date = new Date(item.timestamp);
                    return date.toLocaleTimeString();
                });
                
                const prices = history.map(item => item.price);
                
                this.chart.data.labels = labels;
                this.chart.data.datasets[0].data = prices;
                this.chart.data.datasets[0].label = `${this.selectedSymbol} Price`;
                this.chart.update();
            }
            
            updateIndicators(symbol, indicators = null) {
                if (!indicators) {
                    // 从股票数据计算简单指标
                    const stock = this.stocks[symbol];
                    if (!stock) return;
                    
                    indicators = {
                        'Change': `${stock.change >= 0 ? '+' : ''}${stock.change_percent.toFixed(2)}%`,
                        'Volume': (stock.volume / 1000).toFixed(1) + 'K',
                        'Range': `${((stock.high - stock.low) / stock.low * 100).toFixed(2)}%`
                    };
                }
                
                const container = document.getElementById('indicators');
                container.innerHTML = '';
                
                Object.entries(indicators).forEach(([name, value]) => {
                    const indicator = document.createElement('div');
                    indicator.className = 'indicator';
                    indicator.innerHTML = `
                        <div class="indicator-name">${name}</div>
                        <div class="indicator-value">${value}</div>
                    `;
                    container.appendChild(indicator);
                });
            }
            
            getAlerts() {
                this.sendMessage({
                    type: 'data_request',
                    collection: 'alerts',
                    operation: 'list',
                    query: {
                        user_id: this.userId
                    }
                });
            }
            
            getSystemStats() {
                this.sendMessage({
                    type: 'command',
                    command: 'get_stats'
                });
            }
            
            setupEventListeners() {
                // 预警表单
                document.getElementById('alertForm').addEventListener('submit', (e) => {
                    e.preventDefault();
                    this.createAlert();
                });
            }
            
            createAlert() {
                const symbol = document.getElementById('alertSymbol').value;
                const condition = document.getElementById('alertCondition').value;
                const price = parseFloat(document.getElementById('alertPrice').value);
                
                if (!symbol || !condition || !price) {
                    this.showNotification('Please fill all fields', 'error');
                    return;
                }
                
                this.sendMessage({
                    type: 'data_request',
                    collection: 'alerts',
                    operation: 'create',
                    query: {
                        symbol: symbol,
                        condition: condition,
                        price: price,
                        user_id: this.userId
                    }
                });
                
                this.showNotification('Alert created successfully', 'success');
                document.getElementById('alertForm').reset();
                
                // 刷新预警列表
                setTimeout(() => this.getAlerts(), 1000);
            }
            
            showNotification(message, type = 'info') {
                const notification = document.createElement('div');
                notification.className = `notification notification-${type}`;
                notification.textContent = message;
                notification.style.opacity = '1';
                
                document.body.appendChild(notification);
                
                // 自动移除
                setTimeout(() => {
                    notification.style.opacity = '0';
                    setTimeout(() => {
                        if (notification.parentNode) {
                            notification.parentNode.removeChild(notification);
                        }
                    }, 300);
                }, 3000);
            }
            
            loadInitialData() {
                // 加载股票列表
                this.sendMessage({
                    type: 'data_request',
                    collection: 'stocks',
                    operation: 'list'
                });
            }
        }
        
        // 初始化客户端
        const client = new StockMonitorClient();
    </script>
</body>
</html>
"""


async def run_stock_monitor():
    """运行股票监控系统"""
    # 配置日志
    logging.basicConfig(
        level=logging.INFO,
        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    )
    
    # 创建监控系统
    monitor = RealTimeStockMonitor(
        websocket_port=8765,
        redis_url="redis://localhost:6379"
    )
    
    try:
        # 启动系统
        await monitor.start()
        
        # 保持运行
        print("Stock monitor is running. Press Ctrl+C to stop.")
        print(f"WebSocket server: ws://localhost:8765")
        print(f"Open client.html in browser to connect")
        
        # 运行直到停止
        while True:
            await asyncio.sleep(1)
            
    except KeyboardInterrupt:
        print("\nShutting down...")
    finally:
        # 停止系统
        await monitor.stop()


if __name__ == "__main__":
    asyncio.run(run_stock_monitor())

7. 性能优化与最佳实践 {#性能优化}

7.1 WebSocket性能优化

python 复制代码
class OptimizedWebSocketServer(WebSocketServer):
    """性能优化的WebSocket服务器"""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # 性能优化配置
        self.message_batch_size = kwargs.get('message_batch_size', 100)
        self.broadcast_throttle_ms = kwargs.get('broadcast_throttle_ms', 10)
        self.compression_threshold = kwargs.get('compression_threshold', 1024)  # 1KB
        
        # 消息批处理队列
        self.broadcast_queue: asyncio.Queue = asyncio.Queue(maxsize=1000)
        self.broadcast_worker_task: Optional[asyncio.Task] = None
        
        # 连接池优化
        self.connection_pool = {}
        self.max_messages_per_second = 1000
        
        # 监控
        self.performance_monitor = PerformanceMonitor()
    
    async def start(self):
        """启动优化版本的服务器"""
        await super().start()
        
        # 启动广播工作器
        self.broadcast_worker_task = asyncio.create_task(self._broadcast_worker())
        
        # 启动性能监控
        self.performance_monitor.start()
    
    async def stop(self):
        """停止服务器"""
        if self.broadcast_worker_task:
            self.broadcast_worker_task.cancel()
            try:
                await self.broadcast_worker_task
            except asyncio.CancelledError:
                pass
        
        await super().stop()
        self.performance_monitor.stop()
    
    async def optimized_broadcast(self, message: WebSocketMessage, room: str = None):
        """优化的广播方法"""
        # 消息压缩
        if len(str(message.data)) > self.compression_threshold:
            message.compress()
        
        # 放入批处理队列
        try:
            await self.broadcast_queue.put((message, room))
        except asyncio.QueueFull:
            self.logger.warning("Broadcast queue full, dropping message")
    
    async def _broadcast_worker(self):
        """广播工作器,处理批量消息"""
        batch = []
        last_broadcast = time.time()
        
        while self.is_running:
            try:
                # 收集消息
                timeout = 0.01  # 10ms
                try:
                    item = await asyncio.wait_for(self.broadcast_queue.get(), timeout)
                    batch.append(item)
                except asyncio.TimeoutError:
                    pass
                
                current_time = time.time()
                
                # 检查是否应该发送批次
                if (batch and (
                    len(batch) >= self.message_batch_size or
                    current_time - last_broadcast >= self.broadcast_throttle_ms / 1000
                )):
                    # 分组消息
                    grouped = self._group_messages_by_room(batch)
                    
                    # 并发发送
                    tasks = []
                    for room, messages in grouped.items():
                        if len(messages) == 1:
                            task = super().broadcast(messages[0], room)
                        else:
                            # 批量发送
                            task = self._batch_broadcast(messages, room)
                        tasks.append(task)
                    
                    if tasks:
                        await asyncio.gather(*tasks, return_exceptions=True)
                    
                    # 重置批次
                    batch = []
                    last_broadcast = current_time
                
                await asyncio.sleep(0.001)  # 减少CPU使用
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                self.logger.error(f"Broadcast worker error: {e}")
    
    def _group_messages_by_room(self, batch):
        """按房间分组消息"""
        grouped = defaultdict(list)
        for message, room in batch:
            grouped[room].append(message)
        return grouped
    
    async def _batch_broadcast(self, messages: List[WebSocketMessage], room: str):
        """批量广播消息"""
        if not messages:
            return 0
        
        client_ids = self.rooms.get(room, set()).copy() if room else list(self.clients.keys())
        
        # 准备批量消息
        batch_message = WebSocketMessage(
            type=MessageType.TEXT,
            data={
                "type": "batch",
                "messages": [msg.to_dict() for msg in messages],
                "count": len(messages),
                "timestamp": datetime.now().isoformat()
            }
        )
        
        batch_data = batch_message.to_json()
        
        # 并发发送
        tasks = []
        for client_id in client_ids:
            client = self.clients.get(client_id)
            if client and client.websocket.open:
                task = self._send_raw(client, batch_data)
                tasks.append(task)
        
        if tasks:
            results = await asyncio.gather(*tasks, return_exceptions=True)
            return sum(1 for r in results if r is True)
        
        return 0
    
    async def _send_raw(self, client: WebSocketClient, data: str) -> bool:
        """原始发送方法,避免额外处理"""
        try:
            await client.websocket.send(data)
            return True
        except Exception:
            return False


class PerformanceMonitor:
    """性能监控器"""
    
    def __init__(self):
        self.metrics = {
            "messages_per_second": 0,
            "bytes_per_second": 0,
            "average_latency": 0,
            "connection_churn": 0,
            "error_rate": 0,
        }
        
        self.history = defaultdict(list)
        self.is_monitoring = False
        self.monitor_task = None
    
    def start(self):
        """开始监控"""
        self.is_monitoring = True
        self.monitor_task = asyncio.create_task(self._monitor_loop())
    
    def stop(self):
        """停止监控"""
        self.is_monitoring = False
        if self.monitor_task:
            self.monitor_task.cancel()
    
    async def _monitor_loop(self):
        """监控循环"""
        last_messages = 0
        last_bytes = 0
        last_connections = 0
        last_time = time.time()
        
        while self.is_monitoring:
            try:
                await asyncio.sleep(1)  # 每秒更新
                
                current_time = time.time()
                elapsed = current_time - last_time
                
                # 这里需要从WebSocket服务器获取实际数据
                # current_messages = server.stats["messages_sent"]
                # current_bytes = server.stats["bytes_sent"]
                # current_connections = len(server.clients)
                
                # 计算每秒速率
                # self.metrics["messages_per_second"] = (current_messages - last_messages) / elapsed
                # self.metrics["bytes_per_second"] = (current_bytes - last_bytes) / elapsed
                # self.metrics["connection_churn"] = abs(current_connections - last_connections) / elapsed
                
                # 更新历史记录
                for key, value in self.metrics.items():
                    self.history[key].append(value)
                    if len(self.history[key]) > 300:  # 保存5分钟数据
                        self.history[key].pop(0)
                
                # 更新参考值
                last_time = current_time
                # last_messages = current_messages
                # last_bytes = current_bytes
                # last_connections = current_connections
                
            except asyncio.CancelledError:
                break
            except Exception as e:
                print(f"Performance monitor error: {e}")
    
    def get_performance_report(self) -> Dict[str, Any]:
        """获取性能报告"""
        report = {
            "current": self.metrics.copy(),
            "history": {k: v[-60:] for k, v in self.history.items()},  # 最近60秒
            "summary": self._generate_summary()
        }
        return report
    
    def _generate_summary(self) -> Dict[str, str]:
        """生成性能摘要"""
        summary = {}
        
        mps = self.metrics["messages_per_second"]
        if mps > 1000:
            summary["throughput"] = "Excellent"
        elif mps > 500:
            summary["throughput"] = "Good"
        elif mps > 100:
            summary["throughput"] = "Fair"
        else:
            summary["throughput"] = "Poor"
        
        latency = self.metrics["average_latency"]
        if latency < 10:
            summary["latency"] = "Excellent"
        elif latency < 50:
            summary["latency"] = "Good"
        elif latency < 100:
            summary["latency"] = "Fair"
        else:
            summary["latency"] = "Poor"
        
        return summary

7.2 数据库优化

python 复制代码
class OptimizedTaskBackend(RedisTaskBackend):
    """优化的任务后端"""
    
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        
        # 连接池优化
        self.pool_size = kwargs.get('pool_size', 10)
        self.connection_pool = []
        
        # 缓存优化
        self.task_cache = {}
        self.cache_ttl = 300  # 5分钟
        self.cache_hits = 0
        self.cache_misses = 0
    
    async def connect(self):
        """连接Redis,使用连接池"""
        for _ in range(self.pool_size):
            redis_client = await aioredis.from_url(self.redis_url)
            self.connection_pool.append(redis_client)
        
        self.redis = self.connection_pool[0]  # 主连接
    
    async def get_connection(self):
        """从连接池获取连接"""
        if not self.connection_pool:
            await self.connect()
        
        # 简单的轮询负载均衡
        connection = self.connection_pool.pop(0)
        self.connection_pool.append(connection)
        return connection
    
    async def get_task(self, task_id: str) -> Optional[Task]:
        """获取任务,带缓存"""
        # 检查缓存
        cache_key = f"cache:{task_id}"
        if cache_key in self.task_cache:
            cached_task, cached_time = self.task_cache[cache_key]
            if time.time() - cached_time < self.cache_ttl:
                self.cache_hits += 1
                return cached_task
        
        # 从Redis获取
        task = await super().get_task(task_id)
        
        if task:
            # 更新缓存
            self.task_cache[cache_key] = (task, time.time())
            self.cache_misses += 1
            
            # 清理过期缓存
            self._clean_cache()
        
        return task
    
    def _clean_cache(self):
        """清理过期缓存"""
        current_time = time.time()
        expired_keys = []
        
        for key, (_, cached_time) in self.task_cache.items():
            if current_time - cached_time > self.cache_ttl:
                expired_keys.append(key)
        
        for key in expired_keys:
            del self.task_cache[key]
    
    async def bulk_enqueue(self, tasks: List[Task]) -> List[str]:
        """批量入队任务"""
        if not self.redis:
            await self.connect()
        
        # 使用管道批量操作
        pipe = self.redis.pipeline()
        task_ids = []
        
        for task in tasks:
            task_data = task.json()
            task_key = self._task_key(task.id)
            
            pipe.setex(task_key, 86400, task_data)
            
            if task.schedule_type == "immediate":
                score = task.priority.value * 1000000 + time.time()
                pipe.zadd(self.PENDING_QUEUE, {task.id: score})
            
            task_ids.append(task.id)
        
        await pipe.execute()
        return task_ids
    
    def get_cache_stats(self) -> Dict[str, Any]:
        """获取缓存统计"""
        total = self.cache_hits + self.cache_misses
        hit_rate = self.cache_hits / total if total > 0 else 0
        
        return {
            "cache_size": len(self.task_cache),
            "cache_hits": self.cache_hits,
            "cache_misses": self.cache_misses,
            "hit_rate": f"{hit_rate:.2%}",
            "connection_pool_size": len(self.connection_pool)
        }

8. 监控与调试 {#监控调试}

8.1 全面的监控系统

python 复制代码
class MonitoringSystem:
    """全面的监控系统"""
    
    def __init__(self):
        self.metrics = {
            "websocket": defaultdict(dict),
            "tasks": defaultdict(dict),
            "system": defaultdict(dict),
        }
        
        self.alerts = []
        self.dashboards = {}
        
        # Prometheus风格指标
        self.prometheus_metrics = {
            "websocket_connections_total": 0,
            "websocket_messages_total": 0,
            "task_queue_size": 0,
            "task_execution_time_seconds": [],
            "system_cpu_percent": 0,
            "system_memory_bytes": 0,
        }
        
        self.start_time = time.time()
    
    async def collect_websocket_metrics(self, server: WebSocketServer):
        """收集WebSocket指标"""
        stats = server.get_stats()
        
        self.metrics["websocket"]["connections"] = {
            "total": stats.get("connections_total", 0),
            "active": stats.get("connections_active", 0),
            "by_duration": stats.get("connections_by_duration", {})
        }
        
        self.metrics["websocket"]["messages"] = {
            "sent": stats.get("messages_sent", 0),
            "received": stats.get("messages_received", 0),
            "bytes_sent": stats.get("bytes_sent", 0),
            "bytes_received": stats.get("bytes_received", 0)
        }
        
        # Prometheus指标
        self.prometheus_metrics["websocket_connections_total"] = stats.get("connections_total", 0)
        self.prometheus_metrics["websocket_messages_total"] = stats.get("messages_sent", 0) + stats.get("messages_received", 0)
    
    async def collect_task_metrics(self, scheduler: TaskScheduler):
        """收集任务指标"""
        metrics = scheduler.get_metrics()
        
        self.metrics["tasks"]["execution"] = {
            "executed": metrics.get("tasks_executed", 0),
            "succeeded": metrics.get("tasks_succeeded", 0),
            "failed": metrics.get("tasks_failed", 0),
            "running": metrics.get("running_tasks", 0)
        }
        
        self.metrics["tasks"]["performance"] = {
            "avg_execution_time": metrics.get("average_execution_time", 0),
            "total_execution_time": metrics.get("total_execution_time", 0)
        }
        
        # Prometheus指标
        self.prometheus_metrics["task_queue_size"] = metrics.get("running_tasks", 0)
        
        if metrics.get("average_execution_time"):
            self.prometheus_metrics["task_execution_time_seconds"].append(
                metrics["average_execution_time"]
            )
            if len(self.prometheus_metrics["task_execution_time_seconds"]) > 100:
                self.prometheus_metrics["task_execution_time_seconds"].pop(0)
    
    async def collect_system_metrics(self):
        """收集系统指标"""
        import psutil
        
        # CPU使用率
        cpu_percent = psutil.cpu_percent(interval=1)
        
        # 内存使用
        memory = psutil.virtual_memory()
        
        # 磁盘IO
        disk_io = psutil.disk_io_counters()
        
        # 网络IO
        net_io = psutil.net_io_counters()
        
        self.metrics["system"]["cpu"] = {
            "percent": cpu_percent,
            "cores": psutil.cpu_count(),
            "load_avg": psutil.getloadavg() if hasattr(psutil, 'getloadavg') else []
        }
        
        self.metrics["system"]["memory"] = {
            "total": memory.total,
            "available": memory.available,
            "percent": memory.percent,
            "used": memory.used
        }
        
        self.metrics["system"]["disk"] = {
            "read_bytes": disk_io.read_bytes if disk_io else 0,
            "write_bytes": disk_io.write_bytes if disk_io else 0
        }
        
        self.metrics["system"]["network"] = {
            "bytes_sent": net_io.bytes_sent,
            "bytes_recv": net_io.bytes_recv
        }
        
        # Prometheus指标
        self.prometheus_metrics["system_cpu_percent"] = cpu_percent
        self.prometheus_metrics["system_memory_bytes"] = memory.used
    
    def generate_prometheus_metrics(self) -> str:
        """生成Prometheus格式的指标"""
        lines = []
        
        # WebSocket指标
        lines.append(f'# HELP websocket_connections_total Total WebSocket connections')
        lines.append(f'# TYPE websocket_connections_total counter')
        lines.append(f'websocket_connections_total {self.prometheus_metrics["websocket_connections_total"]}')
        
        lines.append(f'# HELP websocket_messages_total Total WebSocket messages')
        lines.append(f'# TYPE websocket_messages_total counter')
        lines.append(f'websocket_messages_total {self.prometheus_metrics["websocket_messages_total"]}')
        
        lines.append(f'# HELP task_queue_size Current task queue size')
        lines.append(f'# TYPE task_queue_size gauge')
        lines.append(f'task_queue_size {self.prometheus_metrics["task_queue_size"]}')
        
        # 执行时间直方图
        exec_times = self.prometheus_metrics["task_execution_time_seconds"]
        if exec_times:
            avg_time = sum(exec_times) / len(exec_times)
            lines.append(f'# HELP task_execution_time_seconds Average task execution time')
            lines.append(f'# TYPE task_execution_time_seconds gauge')
            lines.append(f'task_execution_time_seconds {avg_time}')
        
        lines.append(f'# HELP system_cpu_percent System CPU usage percentage')
        lines.append(f'# TYPE system_cpu_percent gauge')
        lines.append(f'system_cpu_percent {self.prometheus_metrics["system_cpu_percent"]}')
        
        lines.append(f'# HELP system_memory_bytes System memory usage in bytes')
        lines.append(f'# TYPE system_memory_bytes gauge')
        lines.append(f'system_memory_bytes {self.prometheus_metrics["system_memory_bytes"]}')
        
        # 添加时间戳
        lines.append(f'# HELP scrape_timestamp Unix timestamp of scrape')
        lines.append(f'# TYPE scrape_timestamp gauge')
        lines.append(f'scrape_timestamp {time.time()}')
        
        return '\n'.join(lines)
    
    def check_alerts(self):
        """检查警报条件"""
        new_alerts = []
        
        # 检查WebSocket连接数
        active_connections = self.metrics["websocket"].get("connections", {}).get("active", 0)
        if active_connections > 10000:
            new_alerts.append({
                "level": "warning",
                "message": f"High WebSocket connections: {active_connections}",
                "timestamp": datetime.now().isoformat()
            })
        
        # 检查任务失败率
        executed = self.metrics["tasks"].get("execution", {}).get("executed", 0)
        failed = self.metrics["tasks"].get("execution", {}).get("failed", 0)
        
        if executed > 0:
            failure_rate = failed / executed
            if failure_rate > 0.1:  # 10%失败率
                new_alerts.append({
                    "level": "error",
                    "message": f"High task failure rate: {failure_rate:.2%}",
                    "timestamp": datetime.now().isoformat()
                })
        
        # 检查系统资源
        cpu_percent = self.metrics["system"].get("cpu", {}).get("percent", 0)
        if cpu_percent > 80:
            new_alerts.append({
                "level": "warning",
                "message": f"High CPU usage: {cpu_percent}%",
                "timestamp": datetime.now().isoformat()
            })
        
        memory_percent = self.metrics["system"].get("memory", {}).get("percent", 0)
        if memory_percent > 90:
            new_alerts.append({
                "level": "critical",
                "message": f"High memory usage: {memory_percent}%",
                "timestamp": datetime.now().isoformat()
            })
        
        self.alerts.extend(new_alerts)
        return new_alerts
    
    def get_dashboard_data(self) -> Dict[str, Any]:
        """获取仪表板数据"""
        uptime = time.time() - self.start_time
        
        return {
            "uptime": {
                "seconds": uptime,
                "formatted": self._format_duration(uptime)
            },
            "websocket": self.metrics["websocket"],
            "tasks": self.metrics["tasks"],
            "system": self.metrics["system"],
            "alerts": self.alerts[-20:],  # 最近20个警报
            "timestamp": datetime.now().isoformat()
        }
    
    def _format_duration(self, seconds: float) -> str:
        """格式化持续时间"""
        days = int(seconds // 86400)
        hours = int((seconds % 86400) // 3600)
        minutes = int((seconds % 3600) // 60)
        secs = int(seconds % 60)
        
        if days > 0:
            return f"{days}d {hours}h {minutes}m {secs}s"
        elif hours > 0:
            return f"{hours}h {minutes}m {secs}s"
        elif minutes > 0:
            return f"{minutes}m {secs}s"
        else:
            return f"{secs}s"

8.2 调试工具

python 复制代码
class DebugTools:
    """调试工具集"""
    
    @staticmethod
    async def trace_websocket_message(
        server: WebSocketServer,
        client_id: str,
        message: WebSocketMessage
    ):
        """跟踪WebSocket消息"""
        trace_data = {
            "timestamp": datetime.now().isoformat(),
            "client_id": client_id,
            "message_id": message.id,
            "message_type": message.type.value,
            "message_data": message.data,
            "client_info": None,
            "processing_time": None,
            "result": None,
        }
        
        client = server.get_client(client_id)
        if client:
            trace_data["client_info"] = {
                "remote_address": client.remote_address,
                "user_agent": client.user_agent,
                "connected_at": client.connected_at.isoformat(),
                "subscriptions": list(client.subscriptions)
            }
        
        start_time = time.time()
        
        try:
            # 这里可以添加消息处理逻辑
            result = await server._process_message(client, message)
            trace_data["result"] = result
        except Exception as e:
            trace_data["result"] = {"error": str(e)}
        finally:
            trace_data["processing_time"] = time.time() - start_time
        
        return trace_data
    
    @staticmethod
    def analyze_performance(
        metrics_history: Dict[str, List[float]],
        window_size: int = 60
    ):
        """分析性能数据"""
        analysis = {}
        
        for metric_name, values in metrics_history.items():
            if not values:
                continue
            
            recent_values = values[-window_size:] if len(values) > window_size else values
            
            analysis[metric_name] = {
                "current": recent_values[-1] if recent_values else 0,
                "average": sum(recent_values) / len(recent_values),
                "max": max(recent_values),
                "min": min(recent_values),
                "std_dev": DebugTools._calculate_std_dev(recent_values),
                "trend": DebugTools._calculate_trend(recent_values)
            }
        
        return analysis
    
    @staticmethod
    def _calculate_std_dev(values: List[float]) -> float:
        """计算标准差"""
        if len(values) < 2:
            return 0
        
        mean = sum(values) / len(values)
        variance = sum((x - mean) ** 2 for x in values) / len(values)
        return variance ** 0.5
    
    @staticmethod
    def _calculate_trend(values: List[float]) -> str:
        """计算趋势"""
        if len(values) < 10:
            return "insufficient_data"
        
        # 使用线性回归计算趋势
        n = len(values)
        x = list(range(n))
        
        sum_x = sum(x)
        sum_y = sum(values)
        sum_xy = sum(x[i] * values[i] for i in range(n))
        sum_x2 = sum(x_i * x_i for x_i in x)
        
        numerator = n * sum_xy - sum_x * sum_y
        denominator = n * sum_x2 - sum_x * sum_x
        
        if denominator == 0:
            return "stable"
        
        slope = numerator / denominator
        
        if slope > 0.01:
            return "increasing"
        elif slope < -0.01:
            return "decreasing"
        else:
            return "stable"

9. 部署与扩展 {#部署扩展}

9.1 容器化部署

dockerfile 复制代码
# Dockerfile for Real-time Application
FROM python:3.9-slim

# 设置工作目录
WORKDIR /app

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    gcc \
    g++ \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 安装Python依赖
RUN pip install --no-cache-dir -r requirements.txt

# 复制应用代码
COPY . .

# 创建非root用户
RUN useradd -m -u 1000 appuser && chown -R appuser:appuser /app
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
    CMD python -c "import socket; socket.create_connection(('localhost', 8765), timeout=5)" || exit 1

# 暴露端口
EXPOSE 8765 8000

# 启动命令
CMD ["python", "-m", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
yaml 复制代码
# docker-compose.yml
version: '3.8'

services:
  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 30s
      timeout: 10s
      retries: 3

  websocket-server:
    build: .
    ports:
      - "8765:8765"
    environment:
      - REDIS_URL=redis://redis:6379
      - LOG_LEVEL=INFO
      - MAX_CONNECTIONS=10000
    depends_on:
      redis:
        condition: service_healthy
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3

  load-balancer:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - websocket-server

  monitoring:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin

volumes:
  redis_data:
  prometheus_data:
  grafana_data:

9.2 水平扩展策略

python 复制代码
class DistributedWebSocketManager:
    """分布式WebSocket管理器"""
    
    def __init__(self, redis_url: str, instance_id: str = None):
        self.redis_url = redis_url
        self.instance_id = instance_id or str(uuid.uuid4())
        self.redis: Optional[aioredis.Redis] = None
        
        # 集群状态
        self.instances = {}
        self.shared_rooms = defaultdict(set)  # room -> instance_ids
        
        # 发布订阅频道
        self.BROADCAST_CHANNEL = "websocket:broadcast"
        self.INSTANCE_CHANNEL = "websocket:instances"
        self.ROOM_CHANNEL_PREFIX = "websocket:room:"
        
    async def connect(self):
        """连接到Redis集群"""
        self.redis = await aioredis.from_url(self.redis_url)
        self.pubsub = self.redis.pubsub()
        
        # 订阅频道
        await self.pubsub.subscribe(
            self.BROADCAST_CHANNEL,
            self.INSTANCE_CHANNEL,
            f"{self.ROOM_CHANNEL_PREFIX}*"
        )
        
        # 发布实例上线消息
        await self._register_instance()
        
        # 启动消息处理
        asyncio.create_task(self._handle_pubsub_messages())
    
    async def _register_instance(self):
        """注册实例"""
        instance_data = {
            "id": self.instance_id,
            "timestamp": time.time(),
            "clients": 0,
            "status": "online"
        }
        
        await self.redis.hset(
            "websocket:instances",
            self.instance_id,
            json.dumps(instance_data)
        )
        
        # 设置过期时间
        await self.redis.expire("websocket:instances", 60)
        
        # 发布实例更新
        await self.redis.publish(
            self.INSTANCE_CHANNEL,
            json.dumps({"type": "instance_online", "instance": instance_data})
        )
    
    async def _handle_pubsub_messages(self):
        """处理发布订阅消息"""
        async for message in self.pubsub.listen():
            if message["type"] == "message":
                await self._process_pubsub_message(message)
    
    async def _process_pubsub_message(self, message):
        """处理单个发布订阅消息"""
        channel = message["channel"].decode()
        data = json.loads(message["data"])
        
        if channel == self.BROADCAST_CHANNEL:
            await self._handle_broadcast_message(data)
        elif channel == self.INSTANCE_CHANNEL:
            await self._handle_instance_message(data)
        elif channel.startswith(self.ROOM_CHANNEL_PREFIX):
            await self._handle_room_message(channel, data)
    
    async def distributed_broadcast(self, message: Dict, room: str = None):
        """分布式广播"""
        broadcast_data = {
            "type": "broadcast",
            "message": message,
            "room": room,
            "sender_instance": self.instance_id,
            "timestamp": time.time()
        }
        
        if room:
            # 广播到特定房间
            await self.redis.publish(
                f"{self.ROOM_CHANNEL_PREFIX}{room}",
                json.dumps(broadcast_data)
            )
        else:
            # 全局广播
            await self.redis.publish(
                self.BROADCAST_CHANNEL,
                json.dumps(broadcast_data)
            )
    
    async def join_distributed_room(self, client_id: str, room: str):
        """加入分布式房间"""
        room_key = f"websocket:rooms:{room}"
        
        # 添加客户端到房间
        await self.redis.sadd(room_key, client_id)
        
        # 添加实例到房间的实例列表
        instance_room_key = f"websocket:room_instances:{room}"
        await self.redis.sadd(instance_room_key, self.instance_id)
        
        # 设置过期时间
        await self.redis.expire(room_key, 3600)
        await self.redis.expire(instance_room_key, 3600)
    
    async def get_room_clients(self, room: str) -> Set[str]:
        """获取房间内的所有客户端(跨实例)"""
        room_key = f"websocket:rooms:{room}"
        clients = await self.redis.smembers(room_key)
        return {client.decode() for client in clients}
    
    async def get_room_instances(self, room: str) -> Set[str]:
        """获取房间所在的实例"""
        instance_room_key = f"websocket:room_instances:{room}"
        instances = await self.redis.smembers(instance_room_key)
        return {instance.decode() for instance in instances}

10. 总结与展望 {#总结}

10.1 关键成果

本文详细探讨了后台任务与WebSocket实时应用的完整解决方案,主要成果包括:

  1. 完整的后台任务系统:支持定时、延迟、周期任务,具备依赖管理、重试机制和优先级调度
  2. 高性能WebSocket服务器:支持连接管理、房间系统、消息压缩和分布式广播
  3. 实时股票监控案例:完整的全栈应用,包含前端可视化界面
  4. 性能优化方案:批处理、连接池、缓存等优化策略
  5. 监控调试工具:全面的性能监控和调试工具集
  6. 部署扩展方案:容器化部署和水平扩展策略

10.2 性能基准

根据测试结果,本方案可达到以下性能指标:

指标 单节点性能 集群性能
并发连接数 10,000+ 100,000+
消息延迟 < 50ms < 100ms
吞吐量 10,000 msg/s 100,000+ msg/s
内存使用 2GB/10K连接 线性扩展

10.3 未来发展方向

  1. AI增强:使用机器学习预测任务执行时间,智能调度
  2. 边缘计算:将WebSocket服务器部署到边缘节点,减少延迟
  3. 量子安全:集成后量子密码学,确保通信安全
  4. 5G优化:针对5G网络特性优化协议和传输
  5. 元宇宙集成:支持VR/AR设备的实时通信

10.4 数学优化展望

未来的优化方向可基于以下数学模型:

  1. 排队论优化 :使用 G / G / c G/G/c G/G/c队列模型优化任务调度
    W q ≈ ρ 2 ( c + 1 ) − 1 c ( 1 − ρ ) ⋅ C a 2 + C s 2 2 ⋅ τ W_q \approx \frac{\rho^{\sqrt{2(c+1)}-1}}{c(1-\rho)} \cdot \frac{C_a^2 + C_s^2}{2} \cdot \tau Wq≈c(1−ρ)ρ2(c+1) −1⋅2Ca2+Cs2⋅τ

  2. 网络流优化 :最小化最大流延迟
    min ⁡ max ⁡ p ∈ P ∑ e ∈ p d e ( x e ) \min \max_{p \in P} \sum_{e \in p} d_e(x_e) minp∈Pmaxe∈p∑de(xe)

  3. 机器学习预测 :使用LSTM预测任务负载
    y ^ t = f ( x t − 1 , x t − 2 , . . . , x t − n ; θ ) \hat{y}t = f(x{t-1}, x_{t-2}, ..., x_{t-n};\theta) y^t=f(xt−1,xt−2,...,xt−n;θ)

10.5 结束语

后台任务与WebSocket实时应用是现代Web开发的核心技术。通过本文的深度讲解和实践代码,开发者可以构建出高性能、可扩展的实时应用系统。随着技术的不断发展,实时通信将在更多领域发挥关键作用,掌握这些技术将为您的职业生涯带来显著优势。

记住:优秀的实时系统 = 可靠的后台任务 + 高效的WebSocket通信 + 全面的监控调试


参考文献

  1. RFC 6455: The WebSocket Protocol
  2. Redis Documentation: Pub/Sub and Streams
  3. Asyncio Official Documentation
  4. 《Designing Data-Intensive Applications》 by Martin Kleppmann
  5. 《Building Microservices》 by Sam Newman
相关推荐
教练、我想打篮球1 天前
118 http 协议中如何实现流式的交互数据
websocket·http·流式数据交互
小白勇闯网安圈1 天前
supersqli、web2、fileclude、Web_python_template_injection
python·网络安全·web
小白勇闯网安圈2 天前
Training-WWW-Robots、command_execution、baby_web、xff_referer
网络安全·web
wyjcxyyy2 天前
Polar-MISC-WEB(困难)
web·ctf·misc·polar
小白勇闯网安圈2 天前
unserialize3、php_rce、Web_php_include、warmup、NewsCenter
sql·网络安全·web
2501_921649492 天前
亚太股票数据API:日股、韩股、新加坡股票、印尼股票市场实时行情,实时数据API-python
开发语言·后端·python·websocket·金融
bleach-2 天前
文件描述符及Linux下利用反弹shell的各种方法
linux·websocket·web安全·网络安全·系统安全·信息与通信
SoleMotive.2 天前
sse和websocket的区别
网络·websocket·网络协议
小白勇闯网安圈2 天前
file_include、easyphp、ics-05
网络安全·php·web