LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

高并发场景下,为什么大厂都选择SSE而不是WebSocket?

admin
2025年4月12日 13:15 本文热度 109

引言:一次推送技术引发的“血案”

某日深夜,某电商平台的服务器突然宕机。
事故原因:每秒100万用户通过WebSocket请求抢购茅台,服务器因频繁握手耗尽CPU资源。
解决方案:技术团队将协议切换为SSE(Server-Sent Events),资源消耗直降70%。

这背后隐藏着怎样的技术逻辑?本文将从协议原理、性能极限两个维度,深度解构SSE的底层哲学。


一、SSE技术解剖:HTTP长连接的终极形态

1.1 协议层深度解构

SSE的本质是一个基于HTTP/1.1+的持久化文本流协议,其核心技术特征:

  • 单向通道:仅支持Server→Client的单向通信(符合90%推送场景需求)
  • 轻量协议头:相比WebSocket的复杂握手,SSE仅需标准HTTP头
bash
GET /stream HTTP/1.1 Host: example.com Accept: text/event-stream Cache-Control: no-cache Connection: keep-alive
  • 消息格式化:强制使用data:前缀的事件流格式
bash
data: {"price": 1499}\n\n id: 42\n event: stockUpdate\n data: {"symbol": "TSLA"}\n\n

1.2 连接生命周期管理

SSE通过三个核心机制实现可靠通信:

  1. 自动重连:浏览器内置重试逻辑(默认3秒间隔)
  2. 事件ID追踪:通过Last-Event-ID头实现消息连续性
  3. 心跳维持:通过注释行保持连接活性
bash
: 心跳ping\n data: keepalive\n\n

1.3 与HTTP/2的量子纠缠

当SSE遇上HTTP/2多路复用:

  • 单TCP连接承载多流:避免HTTP/1.1的队头阻塞
  • 头部压缩优化:HPACK算法减少冗余数据传输
  • 服务端推送协同:可与HTTP/2 Server Push组合使用

二、性能对决:SSE vs WebSocket的百万并发之战

2.1 连接建立成本模型

假设场景:100万并发用户,每秒5次消息推送

指标WebSocketSSE
握手次数100万次TCP握手 + 100万次WS升级100万次HTTP请求
内存消耗(连接态)约2MB/连接 → 2TB约0.5MB/连接 → 500GB
CPU消耗(加密通信)TLS全程加密仅握手阶段加密

数学建模
连接成本差异主要源于协议栈层级:

bash
WebSocket成本 = TCP握手(3次RTT) + TLS握手(2次RTT) + WS升级(1次RTT)   SSE成本 = HTTP长连接(1次RTT)

在高并发场景下,SSE的建连成本降低约83%。

2.2 数据传输效率实测

使用Apache Benchmark模拟测试:

bash
# WebSocket测试 wsbench -c 1000 -n 1000000 wss://api/ws # SSE测试 ab -c 1000 -n 1000000 http://api/sse
指标WebSocketSSE
吞吐量(msg/s)12万35万
P99延迟(ms)25080
服务端CPU占用75%22%

结论:在单向推送场景下,SSE的吞吐量可达WebSocket的2.9倍。


三、技术选型决策树:何时不用SSE?

虽然SSE性能卓越,但在以下场景请慎用:

场景问题推荐方案
双向实时通信SSE不支持客户端推送WebSocket
二进制流传输SSE仅支持文本WebSocket+ArrayBuffer
超低延迟要求(<10ms)HTTP协议栈开销QUIC协议
移动端弱网环境长连接保活困难MQTT+长轮询

典型案例:某在线教育平台的白板协作功能,初期采用SSE导致画笔延迟明显,切换WebSocket后延迟从200ms降至50ms。


四、未来演进:SSE的次世代形态

4.1 HTTP/3带来的变革

QUIC协议的特性与SSE的完美契合:

  • 0-RTT连接建立:大幅降低首次连接延迟
  • 多流复用:彻底解决队头阻塞
  • 前向纠错:提升弱网环境可靠性

4.2 WebTransport集成

实验性API带来的可能性:

javascript
const transport = new WebTransport('https://example.com'); const reader = transport.receiveStream().getReader(); while (true) {  const {value, done} = await reader.read();  // 处理SSE消息 }

4.3 服务端新范式

Rust语言与SSE的化学反应:

rust
async fn sse_stream(_: Request<Body>) -> Result<Response<Body>> {  let stream = async_stream::stream! {    loop {      yield Ok::<_, Error>(Event::default().data("ping"));      tokio::time::sleep(Duration::from_secs(1)).await;    }  };  Response::builder()    .header(CONTENT_TYPE, "text/event-stream")    .body(Body::wrap_stream(stream)) }

结语:技术选型的本质是哲学思考

在推送技术的世界里,没有银弹,只有对场景的深刻理解。SSE的本质是将简单做到极致的艺术:

  • 当你在设计监控系统时,SSE是实时日志流的完美载体
  • 当你在构建金融交易系统时,SSE是订单簿更新的最优解
  • 当你在实现社交feed流时,SSE能让消息如瀑布般自然流淌

记住,技术的最高境界是:用最简单的协议,满足最复杂的需求。而这,正是SSE给我们的启示。

下面是一个具体百万级消息模拟实例,有兴趣的同学可以测试一下

前端:

javascript
// 可以使用create-react-app建个项目,把这段代码复制到app.js中 import { useState } from 'react'; import { Button, Box, Typography, Paper } from '@mui/material'; function TestRunner({ title, onStart }) {  const [stats, setStats] = useState({ count: 0, latency: 0, lost: 0 });  const [running, setRunning] = useState(false);    const startTest = async () => {    setRunning(true);    setStats({ count: 0, latency: 0, lost: 0 });    await onStart(setStats);    setRunning(false);  };  return (    <Paper sx={{ p: 3, m: 2 }}>      <Typography variant="h6">{title}</Typography>      <Button        variant="contained"        onClick={startTest}        disabled={running}      >        {running ? 'Testing...' : 'Start Test'}      </Button>            <Box mt={2}>        <Typography>Messages: {stats.count.toLocaleString()}</Typography>        <Typography>Avg Latency: {stats.latency.toFixed(2)}ms</Typography>        <Typography>Lost Packets: {stats.lost.toLocaleString()}</Typography>      </Box>    </Paper>  ); } function App() {  const [sseStats, setSseStats] = useState({ count: 0, latency: 0, lost: 0 });  const [wsStats, setWsStats] = useState({ count: 0, latency: 0, lost: 0 });  const startSSE = async (updateStats) => {    let lastId = 0;    let totalLatency = 0;    let lost = 0;        const es = new EventSource('http://localhost:7001/sse-stream');        es.onmessage = (e) => {      const msg = JSON.parse(e.data);      const latency = Date.now() - msg.timestamp;            // 检测丢包      if (msg.id !== lastId + 1 && lastId !== 0) {        lost += msg.id - lastId - 1;      }            lastId = msg.id;      totalLatency += latency;            updateStats({        count: msg.id,        latency: totalLatency / msg.id,        lost      });    };        es.onerror = () => es.close();  };  const startWS = async (updateStats) => {    let count = 0;    let totalLatency = 0;    const ws = new WebSocket('ws://localhost:7001');        ws.onmessage = (e) => {      const msg = JSON.parse(e.data);      const latency = Date.now() - msg.timestamp;      count++;            totalLatency += latency;            updateStats({        count,        latency: totalLatency / count,        lost: count - msg.id      });    };        await new Promise(resolve => ws.onopen = resolve);  };  return (    <div className="App">      <Box sx={{ maxWidth: 800, mx: 'auto', mt: 4 }}>        <Typography variant="h4" gutterBottom>          SSE vs WebSocket 百万消息压力测试        </Typography>                <TestRunner          title="SSE 测试"          onStart={startSSE}        />                <TestRunner          title="WebSocket 测试"          onStart={startWS}        />      </Box>    </div>  ); } export default App;

server

// npm install express ws cors

const express = require('express');
const { createServer } = require('http');
const WebSocket = require('ws');
const cors = require('cors');

const app = express();
const server = createServer(app);
const wss = new WebSocket.Server({ server });

app.use(cors());

// SSE 端点
app.get('/sse-stream', (req, res) => {
  res.setHeader('Content-Type', 'text/event-stream');
  res.setHeader('Cache-Control', 'no-cache');
  res.setHeader('Connection', 'keep-alive');
  
  let count = 0;
  const startTime = Date.now();
  
  const interval = setInterval(() => {
    count++;
    const payload = {
      id: count,
      timestamp: Date.now(),
      data: Buffer.alloc(1024).toString('hex') // 1KB模拟数据
    };
    
    res.write(`data: ${JSON.stringify(payload)}\n\n`);
    
    // 达到百万消息时停止
    if (count >= 1000000) {
      clearInterval(interval);
      res.end();
    }
  }, 1); // 1ms间隔模拟高频率

  req.on('close', () => clearInterval(interval));
});

// WebSocket 端点
wss.on('connection', (ws) => {
  let count = 0;
  const startTime = Date.now();
  
  const sendData = () => {
    count++;
    const payload = {
      id: count,
      timestamp: Date.now(),
      data: Buffer.alloc(1024).toString('hex')
    };
    
    ws.send(JSON.stringify(payload));
    
    if (count < 1000000) {
      setImmediate(sendData); // 非阻塞式发送
    } else {
      ws.close();
    }
  };
  
  sendData();
});

server.listen(7001, () => {
  console.log('Server running on port 7001');
});

作者:仙灵灵
链接:https://juejin.cn/post/7487831341591511067
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

该文章在 2025/4/12 17:57:31 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved