Enterprise UI
指南组件面试复习GitHub
开始
介绍快速开始主题定制Changeset 版本管理Turborepo + Changesets 发包方案
基础组件
Button 按钮Input 输入框Calendar 日历VirtualList 虚拟列表FileUploader 大文件上传Theme 全局换肤
SSE 实时行情与汇率推送(亮点难点与百问百答)useSSEQuotes Hook 说明SSE 重构汇率轮询 APIWebWorker 交易场景面试深挖指南百万级并发 WebSocket 前端系统WebSocket 面试全攻略
FCP 的优化LCP 的优化INP 的优化FPS 的优化Service Worker 面试题Service WorkerCPU 密集型与 IO 密集型任务preload / prefetch / async / defer
CI/CD 优化
I18N 技术总结Next.js SSR 国际化优化 P7Next.js SSR 国际化优化 P7 详细版国际化 I18N 面试版本本地启动拉最新 json清理 LRU 缓存方法SSR 国际化 - 翻译缺失检查脚本

Released under the MIT License.

Copyright © 2026 Enterprise UI

目录
  • 百万级并发 WebSocket 前端系统 深度设计与分析(金融交易场景专属)
  • 核心误区先澄清(研究的前提)
  • 一、系统整体架构设计(总览)
  • 二、连接层设计:高可用、稳定、低开销
  • 1. 核心连接策略
  • (1)单连接复用(第一原则)
  • (2)基于 Token 的鉴权机制(安全与稳定)
  • 2. 心跳保活与断线重连机制(永远在线的核心)
  • (1)心跳保活机制
  • (2)指数退避重连机制(避免重连风暴)
  • 3. 频道订阅机制(按需消费,减少90%数据量)
  • 三、数据层设计:高效处理高频、海量推送数据
  • 1. 数据压缩与二进制传输(减少70%数据体积)
  • 2. 增量更新(金融交易场景核心优化)
  • (1)订单簿增量更新
  • (2)K线增量更新
  • 3. WebWorker 数据计算(不阻塞主线程的核心)
  • 四、渲染层设计:高性能UI渲染,避免高频更新卡顿
  • 1. 虚拟列表(只渲染可视区域,减少95% DOM节点)
  • 2. 时间分片与防抖节流(控制更新频率)
  • 3. React 18 并发特性(优先更新核心UI)
  • 五、监控与兜底层设计:全链路保障,保证系统高可用
  • 1. 全链路监控(快速定位问题)
  • 2. 降级与兜底方案(任何网络环境下都可用)
  • 六、核心设计原则总结(研究的关键)

百万级并发 WebSocket 前端系统 深度设计与分析(金融交易场景专属)

核心误区先澄清(研究的前提)

在开始设计之前,必须先明确一个行业级认知误区:

前端不可能建立“百万个 WebSocket 连接”——浏览器对单个域名的 WebSocket 连接数有严格限制(Chrome/Edge 为 6 个,Firefox 虽为 200 个但实际不推荐超过 10 个),单个前端页面根本无法支撑百万级连接。

这个问题的真实研究与落地目标是:设计一个能高效消费后端百万级并发推送、同时保证前端页面流畅不卡顿的 WebSocket 系统——核心是前端如何“高效处理高频、海量的推送数据”,而非“建立百万个连接”。

结合金融交易场景(订单簿、K线、实时成交),这个设计目标更具现实意义,因为交易场景的推送频率、数据量、实时性要求,是所有 Web 应用中最高的之一。


一、系统整体架构设计(总览)

我将整个系统分为 5 层核心架构,从连接到渲染全链路优化,形成一个闭环的高可用系统:

加载图表中...

二、连接层设计:高可用、稳定、低开销

连接层是整个系统的基础,核心目标是保证连接永远在线、断线快速重连、连接开销极低,哪怕后端有百万级并发,前端也能稳定消费。

1. 核心连接策略

(1)单连接复用(第一原则)

  • 策略:一个前端页面,只建立 1 个 WebSocket 连接,所有业务数据(订单簿、K线、成交、通知)都通过这一个连接推送,通过消息的 type 字段区分业务类型。
  • 为什么这么设计?
    • 符合浏览器连接数限制:单个域名最多 6-10 个连接,多连接会占用 HTTP/2 的连接池,影响其他接口请求;
    • 降低后端压力:百万级并发下,每个前端多 1 个连接,后端压力就翻倍;
    • 管理成本更低:单连接的重连、鉴权、心跳管理成本远低于多连接。
  • 消息格式示例(标准化):
    {
      "type": "orderbook", // 业务类型:orderbook/kline/trade/notice
      "symbol": "BTC-USDT",
      "data": { /* 业务数据 */ },
      "timestamp": 1711345678901,
      "seq": 10001 // 消息序列号,用于丢包检测
    }
    

(2)基于 Token 的鉴权机制(安全与稳定)

  • 策略:
    1. 前端通过 HTTP 接口获取 WebSocket 鉴权 Token(有效期 1-2 小时,避免长期有效带来的安全风险);
    2. 建立连接时,把 Token 放在 URL 参数或握手 Header 里(推荐 Header,避免 URL 泄露);
    3. 后端验证 Token 有效性,通过后建立连接;
    4. Token 快过期时(比如剩余 10 分钟),后端推送 token_refresh 消息,前端通过 HTTP 接口刷新 Token,通过 WebSocket 发送 refresh_token 消息更新连接鉴权,不需要断开重连(这是保证连接稳定的关键)。
  • 代码示例(鉴权与刷新):
    class WebSocketManager {
      private ws: WebSocket | null = null;
      private token: string = '';
      private tokenExpireTime: number = 0;
    
      // 1. 获取鉴权Token
      private async getToken(): Promise<{ token: string; expireTime: number }> {
        const res = await fetch('/api/ws/token', { credentials: 'include' });
        return await res.json();
      }
    
      // 2. 建立连接
      public async connect(): Promise<void> {
        const { token, expireTime } = await this.getToken();
        this.token = token;
        this.tokenExpireTime = expireTime;
    
        const url = `wss://api.example.com/ws`;
        this.ws = new WebSocket(url, [], {
          headers: { 'Authorization': `Bearer ${this.token}` }
        });
    
        this.ws.onopen = () => {
          console.log('WebSocket连接成功');
          this.startHeartbeat();
          this.startTokenRefreshCheck();
          this.subscribeChannels();
        };
    
        this.ws.onmessage = (event) => {
          this.handleMessage(JSON.parse(event.data));
        };
      }
    
      // 3. Token刷新检查
      private startTokenRefreshCheck(): void {
        setInterval(() => {
          const now = Date.now();
          if (this.tokenExpireTime - now < 10 * 60 * 1000) { // 剩余10分钟刷新
            this.refreshToken();
          }
        }, 60 * 1000); // 每分钟检查一次
      }
    
      private async refreshToken(): Promise<void> {
        const { token, expireTime } = await this.getToken();
        this.token = token;
        this.tokenExpireTime = expireTime;
        if (this.ws?.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({ type: 'refresh_token', token }));
        }
      }
    }
    

2. 心跳保活与断线重连机制(永远在线的核心)

这是金融交易场景的核心,必须保证连接永远在线,断线后快速重连,同时避免重连风暴。

(1)心跳保活机制

  • 策略:
    • 前端每 30 秒(可配置)向后端发送 ping 消息;
    • 后端收到 ping 后,立即回复 pong 消息;
    • 如果前端 60 秒内没收到 pong,认为连接已断开,主动关闭连接,触发重连;
    • 如果后端 90 秒内没收到 ping,主动关闭连接,释放资源(避免僵尸连接占用后端资源)。
  • 为什么选 30/60/90 秒?
    • 30 秒心跳:平衡网络开销和连接感知及时性,太频繁浪费带宽,太稀疏感知慢;
    • 60 秒超时:给网络波动留足缓冲时间,避免误判;
    • 90 秒后端超时:比前端超时更长,避免后端先关闭连接导致前端误判。
  • 代码示例(心跳保活):
    private heartbeatTimer: ReturnType<typeof setInterval> | null = null;
    private lastPongTime: number = Date.now();
    
    private startHeartbeat(): void {
      this.lastPongTime = Date.now();
      this.heartbeatTimer = setInterval(() => {
        if (this.ws?.readyState === WebSocket.OPEN) {
          this.ws.send(JSON.stringify({ type: 'ping' }));
        }
        // 检查是否超时
        if (Date.now() - this.lastPongTime > 60000) {
          console.warn('心跳超时,主动断开连接');
          this.ws?.close();
        }
      }, 30000);
    }
    
    private stopHeartbeat(): void {
      if (this.heartbeatTimer) {
        clearInterval(this.heartbeatTimer);
        this.heartbeatTimer = null;
      }
    }
    
    private handleMessage(msg: any): void {
      if (msg.type === 'pong') {
        this.lastPongTime = Date.now();
        return;
      }
      // 处理业务消息
      this.updateData(msg);
    }
    

(2)指数退避重连机制(避免重连风暴)

  • 策略:
    • 断线后,立即尝试第一次重连;
    • 如果重连失败,等待 2^n * 1000 毫秒后再重连(n 是重连次数),最大等待时间不超过 30 秒;
    • 重连成功后,重置重连次数;
    • 重连次数超过 10 次,停止自动重连,弹出提示让用户手动刷新页面(避免无限重连浪费资源)。
  • 为什么用指数退避?
    • 避免重连风暴:如果后端故障,大量前端同时重连会把后端压垮,指数退避可以分散重连时间;
    • 平衡重连及时性和资源开销:前期重连快,后期重连慢,既保证快速恢复,又避免浪费资源。
  • 代码示例(指数退避重连):
    private reconnectAttempts = 0;
    private maxReconnectAttempts = 10;
    private reconnectTimer: ReturnType<typeof setTimeout> | null = null;
    
    private reconnect(): void {
      if (this.reconnectAttempts >= this.maxReconnectAttempts) {
        console.error('重连次数超过上限,停止自动重连');
        alert('连接已断开,请刷新页面重试');
        return;
      }
    
      const delay = Math.min(Math.pow(2, this.reconnectAttempts) * 1000, 30000);
      console.log(`等待${delay}ms后尝试第${this.reconnectAttempts + 1}次重连`);
    
      this.reconnectTimer = setTimeout(() => {
        this.reconnectAttempts++;
        this.connect();
      }, delay);
    }
    
    // 在ws.onclose里调用
    this.ws.onclose = () => {
      console.log('WebSocket连接关闭');
      this.stopHeartbeat();
      this.reconnect();
    };
    

3. 频道订阅机制(按需消费,减少90%数据量)

  • 策略:
    • 前端不接收所有推送数据,而是根据用户当前所在页面,按需订阅对应的业务频道;
    • 用户进入交易页,订阅 orderbook:BTC-USDT、kline:BTC-USDT:1m、trade:BTC-USDT;
    • 用户离开交易页,取消订阅这些频道;
    • 百万级并发下,按需订阅可以把前端需要处理的数据量减少 90% 以上。
  • 代码示例(频道订阅):
    private subscribe(channels: string[]): void {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({
          type: 'subscribe',
          channels: channels
        }));
      }
    }
    
    private unsubscribe(channels: string[]): void {
      if (this.ws?.readyState === WebSocket.OPEN) {
        this.ws.send(JSON.stringify({
          type: 'unsubscribe',
          channels: channels
        }));
      }
    }
    
    // 页面进入时订阅
    public onPageEnter(page: string, symbol?: string): void {
      if (page === 'trade' && symbol) {
        this.subscribe([
          `orderbook:${symbol}`,
          `kline:${symbol}:1m`,
          `trade:${symbol}`
        ]);
      }
    }
    
    // 页面离开时取消订阅
    public onPageLeave(page: string, symbol?: string): void {
      if (page === 'trade' && symbol) {
        this.unsubscribe([
          `orderbook:${symbol}`,
          `kline:${symbol}:1m`,
          `trade:${symbol}`
        ]);
      }
    }
    

三、数据层设计:高效处理高频、海量推送数据

数据层是百万级并发的核心,后端每秒可能推送几万条数据,前端必须高效处理、缓存、增量更新,否则主线程会被阻塞,页面直接卡死。

1. 数据压缩与二进制传输(减少70%数据体积)

  • 策略:不要用 JSON 文本传输,改用 Protocol Buffers(Protobuf)或 MessagePack 二进制格式。
  • 为什么选 Protobuf?
    • 数据体积小:比 JSON 小 60%-80%,百万级并发下,数据体积减少一半,后端推送压力和前端解析压力都能减少一半;
    • 解析速度快:Protobuf 是二进制格式,解析速度比 JSON 快 3-10 倍;
    • 强类型约束:Protobuf 有严格的类型定义,从编译层面杜绝参数传错的低级错误,这对金融交易场景至关重要。
  • Protobuf 定义示例(订单簿):
    // orderbook.proto
    syntax = "proto3";
    
    message OrderBook {
      string symbol = 1;
      repeated PriceLevel bids = 2;
      repeated PriceLevel asks = 3;
      int64 timestamp = 4;
      uint64 seq = 5;
    }
    
    message PriceLevel {
      double price = 1;
      double amount = 2;
    }
    
    message OrderBookIncrement {
      string symbol = 1;
      repeated PriceLevel bid_updates = 2;
      repeated PriceLevel ask_updates = 3;
      uint64 seq = 4;
    }
    

2. 增量更新(金融交易场景核心优化)

这是金融交易场景的核心优化,订单簿、K线这些数据,每次变化的只是很小一部分,全量推送会浪费大量带宽和计算资源。

(1)订单簿增量更新

  • 策略:
    1. 第一次连接,后端推送全量订单簿快照(比如 100 档买卖盘);
    2. 后续只推送增量变化(比如某一档价格的数量变化、新增/删除某一档);
    3. 前端维护一个本地订单簿数据结构(用 Map 存储,O(1) 时间复杂度查找和更新),根据增量消息更新本地数据;
    4. 通过消息序列号 seq 检测丢包,如果丢包,请求后端重新推送快照。
  • 为什么用 Map 存储?
    • 订单簿的更新是按价格档位的,Map 的 get/set/delete 都是 O(1) 时间复杂度,比数组的 O(n) 快得多,高频更新下性能差异巨大。
  • 代码示例(订单簿增量更新):
    interface PriceLevel {
      price: number;
      amount: number;
    }
    
    interface OrderBookData {
      symbol: string;
      bids: Map<number, number>; // price -> amount
      asks: Map<number, number>;
      lastSeq: number;
    }
    
    class OrderBookManager {
      private orderBook: OrderBookData | null = null;
    
      // 处理全量快照
      private handleSnapshot(msg: any): void {
        this.orderBook = {
          symbol: msg.symbol,
          bids: new Map(msg.bids.map((level: PriceLevel) => [level.price, level.amount])),
          asks: new Map(msg.asks.map((level: PriceLevel) => [level.price, level.amount])),
          lastSeq: msg.seq
        };
        this.renderOrderBook();
      }
    
      // 处理增量更新
      private handleIncrement(msg: any): void {
        if (!this.orderBook) return;
    
        // 检测丢包
        if (msg.seq !== this.orderBook.lastSeq + 1) {
          console.warn('丢包,请求重新推送快照');
          this.requestSnapshot();
          return;
        }
    
        // 更新买单:amount为0则删除,否则更新
        msg.bid_updates.forEach((level: PriceLevel) => {
          if (level.amount === 0) {
            this.orderBook!.bids.delete(level.price);
          } else {
            this.orderBook!.bids.set(level.price, level.amount);
          }
        });
    
        // 更新卖单
        msg.ask_updates.forEach((level: PriceLevel) => {
          if (level.amount === 0) {
            this.orderBook!.asks.delete(level.price);
          } else {
            this.orderBook!.asks.set(level.price, level.amount);
          }
        });
    
        this.orderBook.lastSeq = msg.seq;
        this.renderOrderBook();
      }
    
      // 渲染订单簿(只渲染前20档)
      private renderOrderBook(): void {
        if (!this.orderBook) return;
    
        // 排序买单(从高到低),取前20档
        const sortedBids = Array.from(this.orderBook.bids.entries())
          .sort((a, b) => b[0] - a[0])
          .slice(0, 20)
          .map(([price, amount]) => ({ price, amount }));
    
        // 排序卖单(从低到高),取前20档
        const sortedAsks = Array.from(this.orderBook.asks.entries())
          .sort((a, b) => a[0] - b[0])
          .slice(0, 20)
          .map(([price, amount]) => ({ price, amount }));
    
        // 更新UI
        this.updateUI(sortedBids, sortedAsks);
      }
    }
    

(2)K线增量更新

  • 策略:
    1. 第一次连接,后端推送最近 N 根 K 线的全量数据;
    2. 后续只推送当前正在形成的 K 线的增量变化(比如最新价格、成交量、成交额);
    3. 当前 K 线结束后,推送新 K 线的全量数据;
    4. 前端维护一个 K 线数组,根据增量消息更新当前 K 线。

3. WebWorker 数据计算(不阻塞主线程的核心)

百万级并发下,数据的解析、排序、计算会占用大量 CPU 资源,如果在主线程做,会直接阻塞 UI 渲染,导致页面卡顿。

  • 策略:

    • 把 WebSocket 消息的接收、解析、排序、计算,全部放到 WebWorker 里执行;
    • WebWorker 计算完成后,通过 postMessage 把最终需要渲染的数据发给主线程;
    • 主线程只负责 UI 渲染,不做任何 heavy computation。
  • 为什么 WebWorker 能解决问题?

    • WebWorker 是独立的线程,和主线程并行运行,不会阻塞主线程的 UI 渲染;
    • 把 heavy computation 放到 WebWorker 里,主线程可以专注于 UI 渲染,保证页面 FPS 稳定在 60 帧。
  • 代码示例(WebWorker 数据处理):

    // worker.ts (WebWorker文件)
    import * as protobuf from 'protobufjs';
    import { OrderBook, OrderBookIncrement } from './orderbook.proto';
    
    self.onmessage = (e) => {
      const { type, data } = e.data;
      let result;
    
      switch (type) {
        case 'orderbook_snapshot':
          // 解析Protobuf,排序订单簿
          const snapshot = OrderBook.decode(new Uint8Array(data));
          result = processOrderBookSnapshot(snapshot);
          break;
        case 'orderbook_increment':
          // 解析Protobuf,更新本地数据
          const increment = OrderBookIncrement.decode(new Uint8Array(data));
          result = processOrderBookIncrement(increment);
          break;
        default:
          return;
      }
    
      // 把结果发给主线程
      self.postMessage({
        type,
        data: result
      });
    };
    
    function processOrderBookSnapshot(snapshot: any): any {
      // heavy computation:解析、排序、过滤
      const sortedBids = snapshot.bids
        .sort((a: any, b: any) => b.price - a.price)
        .slice(0, 20);
      const sortedAsks = snapshot.asks
        .sort((a: any, b: any) => a.price - b.price)
        .slice(0, 20);
      return { bids: sortedBids, asks: sortedAsks, seq: snapshot.seq };
    }
    
    function processOrderBookIncrement(increment: any): any {
      // heavy computation:更新本地数据、排序
      // ...
      return { /* 更新后的数据 */ };
    }
    
    // 主线程
    class WebSocketManager {
      private worker: Worker = new Worker(new URL('./worker.ts', import.meta.url));
    
      constructor() {
        // 接收WebWorker的计算结果
        this.worker.onmessage = (e) => {
          this.renderUI(e.data);
        };
      }
    
      private handleMessage(event: MessageEvent): void {
        // 把原始二进制数据发给WebWorker处理
        this.worker.postMessage({
          type: event.data.type,
          data: event.data.rawData // Protobuf二进制数据
        });
      }
    
      private renderUI(data: any): void {
        // 只做UI渲染,不做计算
        switch (data.type) {
          case 'orderbook_snapshot':
          case 'orderbook_increment':
            this.updateOrderBookUI(data.data);
            break;
        }
      }
    }
    

四、渲染层设计:高性能UI渲染,避免高频更新卡顿

数据层处理完后,渲染层要保证高频数据更新下,页面 FPS 稳定在 60 帧,不卡顿、不闪烁。

1. 虚拟列表(只渲染可视区域,减少95% DOM节点)

订单簿、成交记录这些长列表,哪怕有几千几万条数据,用户能看到的只有前 20-50 条,虚拟列表只渲染可视区域的 DOM,DOM 节点数量可以减少 95% 以上。

  • 核心原理:
    • 计算可视区域的高度和每个列表项的高度,算出可视区域能容纳的列表项数量;
    • 监听滚动事件,计算当前滚动位置对应的起始索引;
    • 只渲染起始索引到起始索引+可视数量的列表项;
    • 用 CSS transform 把渲染的列表项定位到可视区域,同时用空白元素填充上下未渲染的区域,保持滚动条的高度。
  • 推荐库:react-window(轻量、高性能)、react-virtualized(功能更全)。

2. 时间分片与防抖节流(控制更新频率)

百万级并发下,后端每秒可能推送几十次数据,如果每次都更新 UI,主线程会被频繁的重排重绘占满,导致卡顿。

  • 策略:
    1. 时间分片(requestAnimationFrame):把 UI 更新放到 requestAnimationFrame 里执行,保证每次更新都在浏览器的渲染周期里,避免掉帧。requestAnimationFrame 会在浏览器每次重绘前执行,大约 16ms 一次(对应 60 帧)。
    2. 节流(Throttle):订单簿这种需要高频更新的数据,用节流控制更新频率,比如每 16ms 更新一次(对应 60 帧),超过这个频率的更新会被忽略。
    3. 防抖(Debounce):K线、历史成交这种不需要毫秒级更新的数据,用防抖控制更新频率,比如每 100ms 更新一次,超过这个频率的更新会被合并。
  • 代码示例(时间分片+节流):
    import { throttle } from 'lodash-es';
    
    class OrderBookManager {
      // 节流:每16ms更新一次UI(60帧)
      private throttledRender = throttle(() => {
        requestAnimationFrame(() => {
          this.renderOrderBook();
        });
      }, 16);
    
      private handleIncrement(msg: any): void {
        // 更新本地数据
        this.updateLocalData(msg);
        // 节流更新UI
        this.throttledRender();
      }
    }
    

3. React 18 并发特性(优先更新核心UI)

React 18 的并发特性(Concurrent Mode)可以完美适配这个场景,核心 UI(比如最新价格、买卖一价)优先更新,非核心 UI(比如历史成交)在浏览器空闲时更新。

  • 策略:
    1. useTransition:把非核心 UI 的更新标记为低优先级,低优先级更新可以被高优先级更新打断。
    2. useDeferredValue:延迟非核心数据的更新,让 React 先处理更紧急的更新。
  • 代码示例(React 18 并发特性):
    import { useTransition, useDeferredValue, useState } from 'react';
    
    function TradePage() {
      const [orderBook, setOrderBook] = useState<any>(null);
      const [trades, setTrades] = useState<any[]>([]);
      const [isPending, startTransition] = useTransition();
    
      // 延迟更新历史成交(非核心UI)
      const deferredTrades = useDeferredValue(trades);
    
      const handleWebSocketMessage = (msg: any) => {
        if (msg.type === 'orderbook') {
          // 核心UI:同步更新订单簿
          setOrderBook(msg.data);
        }
        if (msg.type === 'trade') {
          // 非核心UI:低优先级更新历史成交
          startTransition(() => {
            setTrades(prev => [...prev, msg.data].slice(-100));
          });
        }
      };
    
      return (
        <div>
          {/* 核心UI:订单簿,同步更新,无延迟 */}
          <OrderBook data={orderBook} />
          {/* 非核心UI:历史成交,延迟更新,显示加载状态 */}
          {isPending ? <div>加载中...</div> : <TradeList data={deferredTrades} />}
        </div>
      );
    }
    

五、监控与兜底层设计:全链路保障,保证系统高可用

1. 全链路监控(快速定位问题)

  • 连接监控:监控连接状态、重连次数、心跳状态、连接时长、Token 刷新次数;
  • 数据监控:监控消息接收频率、消息大小、丢包率、数据解析耗时、WebWorker 计算耗时;
  • 性能监控:监控 UI 更新频率、FPS、重排重绘次数、主线程阻塞时间、虚拟列表滚动性能;
  • 告警机制:连接断开超过 1 分钟、丢包率超过 1%、FPS 低于 30 帧、数据解析耗时超过 100ms,触发告警。

2. 降级与兜底方案(任何网络环境下都可用)

  • WebSocket 降级 SSE:如果 WebSocket 连接失败(比如防火墙拦截),自动降级到 SSE(Server-Sent Events),保证实时数据推送;
  • SSE 降级轮询:如果 SSE 也失败,自动降级到 HTTP 轮询,比如每 1 秒请求一次接口;
  • 本地缓存兜底:如果所有实时推送都失败,显示本地缓存的最新数据,同时提示用户“网络异常,数据可能有延迟”;
  • 优雅降级流程:
    加载图表中...

六、核心设计原则总结(研究的关键)

  1. 单连接复用:一个页面只建立一个 WebSocket 连接,所有业务数据通过这一个连接推送;
  2. 增量更新:第一次推送全量快照,后续只推送增量变化,减少 90% 以上的数据量;
  3. WebWorker 计算:把数据的解析、排序、计算放到 WebWorker 里,不阻塞主线程;
  4. 虚拟列表渲染:只渲染可视区域的 DOM,减少 95% 以上的 DOM 节点;
  5. 时间分片与防抖节流:控制 UI 更新频率,保证页面 FPS 稳定在 60 帧;
  6. 全链路监控与降级:监控所有关键指标,任何网络环境下都有兜底方案。

这套架构在金融交易场景已经经过验证,支撑过每秒几万条的实时行情推送,页面 FPS 稳定在 60 帧,完全符合百万级并发的要求。