WebSockets и SSE
1)简而言之: 什么以及为什么
WebSocket(WS/WSS)是HTTP连接到全双工通道的升级。适用于聊天、现场游戏、协作、双向遥测。
Server-Sent Events(SSE)是从服务器到浏览器的单向流(MIME "text/event-stream")。非常适合股票代码,通知,报价,任务进度。客户端-"EventSource"。
- 需要从客户端WebSocket实时(通常是大量)→输入。
- 仅从服务器进行推送更新、兼容性和简单性→ SSE更为重要。
2)网络和协议
2.1运输和互操作性
WebSocket:以HTTP'GET……Upgrade: websocket` (HTTP/1.1).可以使用RFC 8441 (CONNECT+':protocol=websocket'), HTTP/2支持取决于代理。在TLS (WSS)之上运行-必须在销售中运行。
SSE:流式传输的常规HTTP长响应("200 OK")。穿过HTTP/1很好。1/2/3,与CDN/代理兼容(除非切断长连接)。
2.2 个代理/平衡器/CDN
检查:支持长寿命连接,idla和taymout, sticky会话(如果状态在主机上)。
对于WS:启用"proxy_read_timeout"、"upgrade"-sagil、per连接极限。
对于SSE:确保代理不会缓冲响应(否则客户端不会及时看到事件)。
3)消息模型和流量控制
WebSocket:帧文本或二进制;有"ping/pong",但没有内置的背景信息-在应用程序上实现(队列,窗口,下降策略)。
SSE:文本事件(UTF-8);内置的客户端能够以延迟的方式进行侦测;服务器可以设置"retry:"。有"id:"和标题"Last-Event-ID"可以从正确的位置恢复。
- 将传出的per客户端消息配额。
- 限制未排队的事件;溢出-丢弃低优先级/聚合。
- 对于WS:在应用程序级别使用"滑动窗口"和ACK模式。
4)连接可靠性
4.1检测和keepalive
WS:每N秒发送"ping";Taymout断裂-带有指数倒数+摇杆的重新连接。
SSE:schlet"注释"服务器':\n'作为心跳以防止连接被视为idle;客户会重新连线。
4.2流恢复
WS:保持留言/序列,并在重新连接后请求三角洲。
SSE:对于每个事件使用'id:'和查询中的'Last-Event-ID'-服务器跳过事件。
5)认证和授权
请求URL (WS)中的JWT Bearer不安全(日志泄漏)。使用"Secure"、"HttpOnly"和"SameSite"标志的标题(通过主HTTP handshake)或cookie。
mTLS(尤其是B2B)以及签名(HMAC)在初始请求之上是可能的。
对于具有cookie的SSE,请记住有关CORS("访问控制-Allow-Origin","Allow-Credentials")的信息。
令牌轮换:不要打断流。传输"即将到期"→客户端将重新连接新令牌。
6)数据格式和压缩
WebSocket:小心打开永久退款(CPU);避免压缩已经压缩的格式(Proto、Avro)。二进制付费比JSON更经济。
SSE:这是文本;对于大型数据,将链接发送到REST/gRPC资源或chunk文件;SSE传输gzip-适当,但要注意代理/CDN中的缓冲。
7)缩放和粉丝out
7.1水平缩放
让应用程序在重新启动时保持静止。连接的状态在前层;数据来自经纪人。
如果有本地队列,则需要使用sticky(会议/用户哈希)。
理想是无状态的前面:节点仅复用订阅;事件来自一般的pub/sub。
7.2 Pub/Sub和经纪人
对于广泛的粉丝外出,请使用Kafka/NATS/Redis Streams。
"Fanout Gateway"层订阅拓扑并通过WS/SSE向客户喷枪。
应用路由密钥(例如"userId","matchId")来平衡指节之间的负载。
8)生产合金
8.1 NGINX — WebSocket
nginx map $http_upgrade $connection_upgrade { default upgrade; '' close; }
server {
listen 443 ssl http2;
server_name ws. example. com;
location /ws {
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_http_version 1. 1;
proxy_read_timeout 75s; # increase for long sessions proxy_send_timeout 15s;
proxy_pass http://ws-backend;
}
}
8.2 NGINX-SSE(必须禁用缓冲)
nginx location /events {
proxy_http_version 1. 1;
proxy_set_header Connection "";
proxy_buffering off; # is critical for proxy_cache off threads;
chunked_transfer_encoding on;
proxy_read_timeout 60m;
proxy_pass http://sse-backend;
}
8.3 Kubernetes (Ingress Annotations, NGINX Ingress)
yaml metadata:
annotations:
nginx. ingress. kubernetes. io/proxy-read-timeout: "3600"
nginx. ingress. kubernetes. io/proxy-send-timeout: "3600"
nginx. ingress. kubernetes. io/enable-websocket: "true"
nginx. ingress. kubernetes. io/proxy-buffering: "off" # для SSE
9)代码示例
9.1 SSE客户端(浏览器)
js const es = new EventSource("/events? channel=odds", { withCredentials: true });
es. addEventListener("message", (e) => {
const data = JSON. parse(e. data);
renderOdds(data);
});
es. addEventListener("error", () => {
//EventSource will reconnect itself; can be shown spinner
});
9.2 SSE服务器(代码。js/Express)
js app. get('/events', (req, res) => {
res. writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
res. write ('retry: 3000\n\n') ;//3s backoff to client
const sub = subscribe(req. query. channel, (event) => {
res. write(`id: ${event. id}\n`);
res. write(`event: message\n`);
res. write(`data: ${JSON. stringify(event. payload)}\n\n`);
});
req. on('close', () => sub. unsubscribe());
});
9.3 WebSocket客户端(浏览器)
js const ws = new WebSocket("wss://ws. example. com/ws");
ws. onopen = () => ws. send(JSON. stringify({ type: "join", room: "chat-1" }));
ws. onmessage = (m) => handle(JSON. parse(m. data));
ws. onclose = () => scheduleReconnect();
10)安全和限制
按连接和按用户限制:限制传入消息的频率(WS)和传出流速度(WS/SSE)。
按连接寿命和总流量计算的Quota。
Message size limit и max messages/sec.
Handshake阶段的WAF/机器人过滤器;防连接引导(许多短连接)。
tenant/namespace隔离:单独的资源池。
11)可观察性
度量标准:- `connections_active`, `connections_new_total`, `bytes_in/out`,
- `messages_in/out_total`, `dropped_messages_total`,
- `reconnects_total`, `latency_delivery_ms{p50,p95,p99}`.
- Logs:IP/UA,userId/tenantId,关闭原因("close_code"),持续时间。
- 跟踪:将事件链接到源命令(相关ID);对于WS,在蹦床上使用"虚拟"间谍。
12)操作细微差别
TLS终端更接近客户端(CDN/edge)。
在脱落时主动旋转化合物(graceful):给标记"重新连接"。
在钥匙上缝合以均匀分配"热"通道。
已故订户的状态快照(snapshot+delta)。
最后一个值缓存(SSE特别)-对于冷客户端很有用。
13)反模式
通过SSE或JSON通过WS流式传输大型二元件-使用HTTP下载和消息中的链接。
仅在连接时授权,在长时间内不进行笔试。
不需要的全球僵局→失衡和"热"脚趾。
断开的Taymauts/Lands →悬停的连接器会吞噬池。
SSE 代理/CDN缓冲→"实时"变为延迟分钟。
缺少sequence/offset →客户机在重新连接后会失去数据完整性。
14)实施支票
- 选择了以下模型:WS(双向)或SSE(单向)。
- 定制taymauts、keepalive和reconnect (backoff+jitter)。
- 由sequence/offset和(用于SSE)"id"/"Last-Event-ID"设计。
- 定义了限制:尺寸/速度/连接/配额,国防部保护。
- Config代理/收据:升级,proxy_buffering关(SSE), read/send timeouts。
- 缩放:pub/sub-broker, fan out-gateway, sticky只在需要时。
- 身份验证:令牌安全传输,无悬崖旋转。
- 可观察性:度量,标志,轨迹;dashbords和alertes。
- 发布计划:graceful-drein连接,向客户端发出有关串联的信号。
- Game Days:网络悬崖,脚趾下降,经纪人过热,RTT长。
15) FAQ
可以通过CDN缓存SSE吗?
通常不是:它是个性化的线程。对于公共频道-可以使用短的TTL和chunked传送,但很容易倒入"真实时间"。
WebSocket是否在HTTP/2/3之上运行?
浏览器WS从HTTP/1开始。1-upgrade;有RFC 8441 for h2,在代理/服务器中需要单独支持。H3-运动;对于h3流媒体,有WebTransport,但它不同的API。
gRPC vs WS浏览器?
纯浏览器不说gRPC;需要通过Envoy的gRPC-Web。对于交互式UI,WS+REST通常更简单。
16)结果
WebSocket-当需要实时对话框和紧凑的双向通道时。
SSE-当需要从服务器到客户端的简单可靠的推送时,最小的复杂性和自动重新连接。
销售成功是正确的计时和限制,从offset恢复,pub/sub-fan out,正确的代理设置和清晰的可观察性。