---
URL: "/zh-CN/blog/2026-04/ECharts-SSE.html"
LLMS_URL: "/zh-CN/blog/2026-04/ECharts-SSE.md"
lastUpdated: true
commentabled: true
recommended: true
title: "ECharts 万级数据渲染实战"
description: "SSE 流式方案深度解析"
date:

pageClass: "blog-page-class"
cover: "/covers/platform.svg"
---

---
lastUpdated: true
commentabled: true
recommended: true
title: ECharts 万级数据渲染实战
description: SSE 流式方案深度解析
date: 2026-04-30 13:00:00 
pageClass: blog-page-class
cover: /covers/platform.svg
---

在大数据可视化场景中，1 万～10 万条数据的图表渲染常面临 “白屏卡顿”“崩溃闪退” 等问题。传统一次性加载方案早已无法满足需求，而 SSE（Server-Sent Events）流式推送 + ECharts appendData 增量渲染 成为业界公认的最优解。

本文将从底层原理到实战代码，全方位拆解这一方案，所有内容基于 W3C 标准与 ECharts 官方文档，可直接复制到生产环境。

## 一、核心概念：先搞懂 2 个关键技术 ##

### SSE（Server-Sent Events）：服务器单向流式推送技术 ###

#### 什么是 SSE？ ####

SSE 是 HTML5 标准规范的 HTTP 长连接单向推送技术，允许服务器通过持久连接，将数据以 “数据块（Chunk）” 形式持续推送给客户端，客户端无需反复请求。

#### SSE 核心特性（基于 W3C 标准） ####

- 协议基础：复用 HTTP/1.1 协议，无需新增端口或协议（兼容 80/443 端口、代理 / 防火墙）；

- 通信方向：仅服务器 → 客户端单向推送（完美匹配图表数据传输场景）；

- 自动重连：网络中断时，客户端原生支持 3 秒后自动重连（可自定义间隔）；

- 数据格式：仅支持 UTF-8 文本（二进制需 Base64 编码），MIME 类型固定为 `text/event-stream`；

- 轻量易用：客户端通过原生 EventSource API 即可实现，无需第三方库。

### SSE 与 WebSocket 区别（为什么选 SSE？） ###

|  对比维度   |      SSE  |   WebSocket  |
| :-----------: | :-----------: | :-----------: |
|  通信方向   |      单向（服务端→客户端）  |   全双工（双向）  |
|  协议基础   |      HTTP 协议  |   独立 WebSocket 协议  |
|  实现成本   |      极低（原生 API，几行代码）  |   较高（需处理握手、协议解析）  |
|  适用场景   |      图表数据、通知、日志流式输出  |   聊天、实时协作、游戏  |
|  兼容性   |      除 IE 外所有现代浏览器原生支持  |   大部分现代浏览器支持  |

结论：图表数据仅需 “服务端推送给客户端”，SSE 比 WebSocket 更轻量、更简单，无需多余的双向通信能力。

### ECharts 增量渲染：appendData API（官方大数据方案） ###

#### 为什么不用 setOption？ ####

ECharts 的 setOption 是 “全量重绘” API：每次调用会重新解析所有数据、计算图表布局、渲染整个画布。当数据量 > 5000 条时，会阻塞主线程，导致页面卡顿；> 1 万条时，极易出现崩溃。

#### appendData 核心优势（基于 ECharts 官方文档） ####

appendData 是 ECharts 4.0+ 推出的 增量渲染 API，专为大数据设计：

- 仅追加新数据，不重绘整个图表，只渲染新增的图形元素（性能提升 10～100 倍）；

- 不重新计算整体布局，仅更新数据顶点和关联渲染；

- 支持同时追加系列数据（series）和坐标轴数据（xAxis/yAxis）。

官方文档明确说明： “大数据量场景下，appendData 是唯一推荐的渲染方式” （出处：ECharts 大数据最佳实践）。

## 二、万级数据渲染底层原理 ##

### 传统方案的 3 个核心瓶颈 ###

- 网络传输瓶颈：一次性传输 1 万条数据的 JSON 串，体积大、响应慢，前端长时间等待；

- JSON 解析瓶颈：浏览器解析超大 JSON 串时会阻塞主线程，页面无法交互；

- 渲染瓶颈：setOption 全量重绘，CPU 负载飙升，导致卡顿 / 崩溃。

### SSE + appendData 解决方案 ###

通过 “流式传输 + 增量渲染”，从根源解决上述问题：

```mermaid
graph TD
    Start([开始]) --> ReadData{服务端读取全量数据}
    
    ReadData --> BatchProcess[分批处理<br/>每批 500~1000 条]
    
    subgraph Server_Side [服务端逻辑]
        BatchProcess --> EncodeSSE[按 SSE 标准格式封装<br/>event: update\ndata: {...}]
        EncodeSSE --> PushHTTP[通过 HTTP 长连接推送<br/>Content-Type: text/event-stream]
    end
    
    subgraph Network [网络传输]
        PushHTTP --> |持续流推送| Connection[HTTP 长连接通道]
    end
    
    subgraph Client_Side [客户端逻辑]
        Connection --> EventSource[EventSource / Fetch API<br/>监听 onmessage]
        EventSource --> ParseData[解析 JSON 数据块]
        ParseData --> CheckDone{是否推送完毕?}
        
        CheckDone -- 否 --> AppendRender[调用 ECharts.appendData<br/>追加渲染图表]
        AppendRender --> BatchProcess
        
        CheckDone -- 是 --> CloseConn[关闭 SSE 连接<br/>eventSource.close()]
    end
    
    CloseConn --> End([结束])
```

### 关键优势拆解 ###

- 首屏快：无需等待所有数据，收到第一批数据就渲染，用户无白屏感知；

- 不阻塞：数据分块解析，主线程空闲，页面可正常交互；

- 内存稳：数据分批加载，避免一次性占用大量内存；

- 兼容性好：基于 HTTP 协议，无额外配置成本，支持所有现代浏览器。

## 三、完整实战代码（可直接复制运行） ##

### 环境说明 ###

- 前端：ECharts 5.x + 原生 SSE（EventSource）；

- 后端：Node.js + Express（模拟数据，可替换为 Java/Python/Go 等）；

- 数据量：10000 条（支持无缝扩展到 10 万条）。

### 前端代码（HTML + ECharts + SSE） ###

```html
<html lang="zh-CN">
 8">
     name="viewport" content="width=device-width, initial-scale=1.0">
     万级数据 SSE 流式渲染
    Charts -->
    <script src="https://cdn.bootcdn.net/ajax/libs/echarts/5.4.3/echarts.min.js">  { margin: 0; padding: 0; box-sizing: border-box; }
        body { padding: 20px; font-family: Arial, sans-serif; }
        #chart-container { width: 100%; height: 600px; border: 1px solid #eee; }
        #loading { margin: 10px 0; font-size: 16px; color: #333; }
        .progress { color: #1890ff; font-weight: bold; }
    </style>
</head>
    ">加载状态： class="progress">连接中...</span>    -container">  1. 初始化 ECharts 实例
        const chartDom = document.getElementById('chart-container');
        const myChart = echarts.init(chartDom);
        const loadingEl = document.querySelector('.progress');
        // 2. 初始化空图表配置（关键：必须先设置空数据）
        const initOption = {
            tooltip: {
                trigger: 'axis',
                axisPointer: { type: 'shadow' },
                // 大数据优化：关闭悬浮动画
                animation: false
            },
            legend: { data: ['随机数据'] },
            grid: {
                left: '3%',
                right: '4%',
                bottom: '3%',
                containLabel: true
            },
            xAxis: {
                type: 'category',
                data: [], // 初始空数据
                // 大数据优化：关闭坐标轴动画
                axisLabel: { interval: 500 } // 每隔 500 个点显示一个标签，避免重叠
            },
            yAxis: {
                type: 'value',
                animation: false
            },
            series: [{
                name: '随机数据',
                type: 'line',
                smooth: true,
                data: [], // 初始空数据
                showSymbol: false, // 关键优化：关闭数据点显示（极大提升性能）
                lineStyle: { width: 1 }, // 线条变细，减少渲染压力
                animation: false // 关闭系列动画
            }],
            animation: false // 全局关闭动画（大数据必备）
        };
        myChart.setOption(initOption);
        // 3. 配置 SSE 连接与数据处理
        const totalDataCount = 10000; // 总数据量
        let loadedCount = 0; // 已加载数据量
        // 创建 SSE 连接（后端接口地址）
        const eventSource = new EventSource('http://localhost:3000/api/sse/echarts-data');
        // 4. 监听 SSE 消息（接收服务端推送的数据块）
        eventSource.onmessage = function(e) {
            // 解析服务端推送的字符串数据
            const response = e.data.trim();
            // 数据推送完成的标识（与后端约定）
            if (response === 'COMPLETE') {
                loadingEl.textContent = `加载完成：${loadedCount}/${totalDataCount} 条`;
                eventSource.close(); // 关闭 SSE 连接
                return;
            }
            // 解析 JSON 数据块
            const { xData, seriesData } = JSON.parse(response);
            loadedCount += seriesData.length;
            // 5. 增量渲染（核心：调用 appendData 而非 setOption）
            myChart.appendData({
                seriesIndex: 0, // 要追加数据的系列索引（对应 initOption 中的 series[0]）
                data: seriesData // 追加的系列数据
            });
            // 追加 X 轴数据（如果 X 轴是动态的）
            myChart.appendData({
                xAxisIndex: 0, // 要追加数据的 X 轴索引
                data: xData // 追加的 X 轴数据
            });
            // 更新加载进度
            loadingEl.textContent = `加载中：${loadedCount}/${totalDataCount} 条`;
        };
        // 6. 监听 SSE 错误
        eventSource.onerror = function(error) {
            console.error('SSE 连接异常：', error);
            loadingEl.textContent = '加载失败，请刷新重试';
            eventSource.close();
        };
        // 7. 监听页面关闭，主动关闭 SSE 连接
        window.addEventListener('beforeunload', function() {
            eventSource.close();
        });
    
</body>
</html>
```

### 后端代码（Node.js + Express） ###

```javascript
// 1. 安装依赖：npm install express cors
const express = require('express');
const cors = require('cors');
const app = express();
// 2. 跨域配置（前端若与后端不同域，必须配置）
app.use(cors({
    origin: '*', // 生产环境建议指定具体域名，如 'http://your-frontend-domain.com'
    methods: ['GET', 'POST'],
    allowedHeaders: ['Content-Type']
}));
// 3. SSE 接口：推送 1 万条数据
app.get('/api/sse/echarts-data', (req, res) => {
    // 关键：设置 SSE 响应头（必须严格遵循规范）
    res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
    res.setHeader('Cache-Control', 'no-cache'); // 禁止缓存（避免数据重复）
    res.setHeader('Connection', 'keep-alive'); // 保持连接
    res.setHeader('X-Accel-Buffering', 'no'); // 禁用 Nginx 缓冲（关键！否则数据会被批量推送）
    // 配置参数
    const totalDataCount = 10000; // 总数据量
    const batchSize = 1000; // 每批推送数据量（最优值：500～1000 条）
    let currentIndex = 0; // 当前推送索引
    // 4. 分批推送数据
    const pushData = () => {
        // 计算当前批的起始和结束索引
        const start = currentIndex;
        const end = Math.min(currentIndex + batchSize, totalDataCount);
        // 构造当前批数据
        const xData = []; // X 轴数据（这里用索引模拟）
        const seriesData = []; // 系列数据（随机数模拟）
        for (let i = start; i ++) {
            xData.push(`点 ${i + 1}`);
            seriesData.push(Math.floor(Math.random() * 1000)); // 0～999 的随机数
        }
        // 5. 按 SSE 标准格式发送数据（必须严格遵循）
        // 格式要求：data: [JSON字符串]\n\n（双换行结尾）
        const dataStr = JSON.stringify({ xData, seriesData });
        res.write(`data: ${dataStr}\n\n`);
        // 更新当前索引
        currentIndex = end;
        // 6. 所有数据推送完成，发送结束标识
        if (currentIndex >= totalDataCount) {
            res.write(`data: COMPLETE\n\n`); // 结束标识（与前端约定）
            res.end(); // 关闭连接
            return;
        }
        // 7. 控制推送频率（避免服务器压力过大，每 100ms 推一批）
        setTimeout(pushData, 100);
    };
    // 开始推送第一批数据
    pushData();
    // 8. 监听客户端断开连接，清理资源
    req.on('close', () => {
        console.log('客户端断开 SSE 连接');
        res.end();
    });
});
// 9. 启动服务器
const port = 3000;
app.listen(port, () => {
    console.log(`服务器运行在 http://localhost:${port}`);
    console.log(`SSE 接口：http://localhost:${port}/api/sse/echarts-data`);
});
```

## 四、运行步骤 ##

### 后端 ###

- 创建文件夹，新建 `server.js`，复制上述后端代码；

- 执行 `npm init -y` 初始化项目；

- 执行 `npm install express cors` 安装依赖；

- 执行 `node server.js` 启动服务。

### 前端 ###

- 新建 index.html，复制上述前端代码；

- 用浏览器直接打开 index.html（或部署到 Nginx/Apache 服务器）；

- 查看图表加载效果（进度实时更新，无卡顿）。

## 五、关键优化点（生产环境必备） ##

### ECharts 性能优化（基于官方文档） ###

- `showSymbol: false`：关闭数据点显示（大数据场景必关）；

- `animation: false`：全局关闭动画（动画会严重消耗性能）；

- `axisLabel.interval`：设置 X 轴标签间隔，避免文字重叠；

- `lineStyle.width`: 1：线条变细，减少渲染压力；

- 避免使用复杂的图表类型（如 3D、多系列叠加）。

### SSE 优化 ###

- 每批数据量：500～1000 条（太小会增加请求次数，太大仍会阻塞）；

- 推送频率：100～200ms / 批（平衡加载速度和前端压力）；

- 禁用 Nginx 缓冲：添加 `X-Accel-Buffering: no` 响应头（否则 Nginx 会缓存数据，导致 “批量推送” 而非 “流式推送”）；

- 心跳保活：如果数据推送间隔较长，服务器定期发送 `: heartbeat\n\n`（注释行），避免代理 / 防火墙断开连接。

### 网络优化 ###

- 开启 Gzip 压缩（后端 /nginx 配置）：JSON 数据压缩比可达 60%+；

- 避免跨域（若必须跨域，配置 `Access-Control-Allow-Origin` 为具体域名，而非 `*`）。

## 六、踩坑记录（生产环境常见问题） ##

### 坑 1：SSE 数据推送不流式，而是一次性接收 ###

- 原因：Nginx 开启了缓冲（默认开启），会缓存数据直到达到一定大小才推送；

- 解决：后端添加响应头 `X-Accel-Buffering: no`（禁用 Nginx 缓冲）。

### 坑 2：前端收不到 SSE 消息 ###

- 原因：SSE 格式错误（必须满足 `data: [内容]\n\n`）；

- 排查：

  - 确保每条消息以 `data:` 开头；

  - 确保每条消息以 `\n\n`（双换行）结尾；

  - 避免数据中包含未转义的换行符（JSON 序列化时会自动处理）。

### 坑 3：appendData 不生效 ###

- 原因 1：未先调用 `setOption` 初始化图表（appendData 需基于已有图表配置）；

- 原因 2：seriesIndex/xAxisIndex 配置错误（需与 setOption 中的索引对应）；

- 解决：严格按照 “先 init → 再 setOption（空数据）→ 最后 appendData” 的顺序。

### 坑 4：数据量超过 10 万条后卡顿 ###

- 原因：ECharts 画布顶点数过多，CPU 负载过高；

- 解决：

  - 采样展示（服务端对数据降采样，前端只渲染关键节点）；

  - 分页渲染（按时间 / 类别分页，用户切换分页时重新加载数据）；

  - 使用 ECharts GL （针对超大数据量的可视化扩展）。

### 坑 5：服务端内存泄漏 ###

- 原因：客户端断开连接后，服务端未关闭 SSE 连接；

- 解决：监听 `req.on('close', () => res.end())`，客户端断开时主动关闭连接。

## 七、适用场景与扩展 ##

### 适用场景 ###

- 1 万～10 万条数据的折线图、柱状图、曲线图；

- 实时监控面板（如服务器 CPU / 内存监控、股票行情）；

- 日志流式可视化（后端日志实时推送到前端）。

### 扩展方案 ###

- 断点续传：利用 SSE 的 `id` 字段和客户端 `Last-Event-ID` 请求头，实现断网后从断点继续加载；

- 多系列图表：多个系列同时增量渲染（指定不同的 seriesIndex）；

- 后端替换：将 Node.js 后端替换为 Java（Spring Boot）、Python（Flask/Django）、Go 等，核心是遵循 SSE 响应头和数据格式；

- 数据筛选：前端通过 URL 参数传递筛选条件（如时间范围），后端根据条件分批查询数据库并推送。

## 八、总结 ##

ECharts 万级数据渲染的核心是 “分而治之”：通过 SSE 将数据 “分块流式传输”，避免网络和解析瓶颈；通过 ECharts appendData 将渲染 “增量执行”，避免全量重绘的性能消耗。

这一方案完全基于标准技术，无需复杂的第三方库，实现简单且性能稳定，是生产环境中大数据可视化的首选方案。本文代码可直接复制运行，如需扩展到 10 万条以上数据，可结合 “采样 + 分页” 进一步优化。
