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

目录
  • SSE 实时行情与汇率推送 — 亮点难点与百问百答
  • 亮点与难点总览
  • 问题
  • 方案
  • 面试话术
  • 一、协议与原理(Q1–Q10)
  • Q1:SSE 是什么?和普通 HTTP 请求有什么区别?
  • Q2:SSE 的数据格式是什么?为什么是 data: xxx\n\n?
  • Q3:SSE 基于 HTTP/1.1 还是 HTTP/2?多路复用的影响?
  • Q4:Content-Type: text/event-stream 必须带哪些头?Cache-Control 为什么重要?
  • Q5:SSE 的 event、id、retry 字段分别有什么用?
  • Q6:SSE 是「长连接」还是「短连接 + 轮询」?
  • Q7:SSE 能跨域吗?和 CORS 的关系?
  • Q8:EventSource 能带自定义 Header(如 Authorization)吗?
  • Q9:SSE 和 WebSocket 在协议层有什么本质区别?
  • Q10:为什么说 SSE 是「文本」的?能推二进制吗?
  • 二、SSE 与 WebSocket 选型(Q11–Q20)
  • Q11:实时汇率/行情为什么我们选 SSE 而不是 WebSocket?
  • Q12:SSE 和轮询比,有什么优缺点?
  • Q13:一条 SSE 连接能推多个 symbol(多对汇率)吗?
  • Q14:SSE 连接数有限制吗?浏览器端、服务端分别怎么考虑?
  • Q15:SSE 适合「请求当前汇率」这种语义吗?和 REST「一次请求一次响应」的区别?
  • Q16:WebSocket 在交易所里通常用在什么场景?
  • Q17:SSE 能实现「客户端发一个请求,服务端持续推多条」吗?
  • Q18:若上游是 WebSocket,前端想要 SSE,怎么做的?
  • Q19:SSE 和 HTTP/2 Server Push 有什么区别?
  • Q20:选型时「实时汇率」和「实时订单簿」为什么可能用不同方案?
  • 三、前端 EventSource 与重连(Q21–Q30)
  • Q21:EventSource 的 onopen、onmessage、onerror 分别何时触发?
  • Q22:为什么需要「指数退避」重连?固定间隔不行吗?
  • Q23:EventSource 有没有「自动重连」?我们为什么还自己写?
  • Q24:在 React 里用 SSE,为什么要在 useEffect 里 subscribe、return 里 cleanup?
  • Q25:SSE 的「连接状态」怎么设计?idle / connecting / connected / reconnecting / disconnected 分别表示什么?
  • Q26:重连时如何避免「重复订阅」或「状态错乱」?
  • Q27:EventSource 的 readyState 有哪几种?各表示什么?
  • Q28:前端如何解析 SSE 的「多行 data」?
  • Q29:useSSEQuotes 的 symbol 参数当前有没有真正传给服务端?如何扩展「按 symbol 订阅」?
  • Q30:在 Next.js 里 EventSource 的 URL 用相对路径还是绝对路径?为什么?
  • 四、服务端实现(Q31–Q40)
  • Q31:Next.js App Router 里 SSE 用 Route Handler 的 ReadableStream 怎么实现?
  • Q32:为什么 Route Handler 要设 export const dynamic = 'force-dynamic'?
  • Q33:SSE 服务端为什么要立即 flush?不 flush 会怎样?
  • Q34:Node 里除了 ReadableStream,还能用什么写 SSE?
  • Q35:SSE 连接在服务端什么时候会断开?超时怎么设?
  • Q36:一条 SSE 连接能同时推「行情」和「订单状态」吗?
  • Q37:Next.js 的 SSE Route 在 serverless 上能跑吗?有什么限制?
  • Q38:服务端如何从「上游」拿实时汇率再推给前端?
  • Q39:SSE 服务端怎么做「广播」(多个客户端收同一条消息)?
  • Q40:为什么说 SSE 是「拉模型」?和「推模型」矛盾吗?
  • 五、首屏解耦与性能(Q41–Q50)
  • Q41:为什么要把「首屏」和「实时行情」解耦?
  • Q42:首屏「不依赖 SSE」具体指什么?代码上怎么体现?
  • Q43:SSE 和 React Query 怎么配合?谁做主数据源?
  • Q44:SSE 连接建立时机:一进页面就建还是等用户操作?
  • Q45:大量组件都消费「当前汇率」时,怎么避免重复建多条 SSE?
  • Q46:SSE 消息频率很高时,前端如何避免渲染过多?
  • Q47:首屏 LCP 和 SSE 有关系吗?怎么保证 LCP 不受 SSE 影响?
  • Q48:SSE 的 JS 要不要懒加载?对首屏体积的影响?
  • Q49:React 18 的 useSyncExternalStore 和 SSE 有关系吗?
  • Q50:首屏「可交互」时间(TTI)和 SSE 的关系?
  • 六、安全与鉴权(Q51–Q60)
  • Q51:SSE 连接需要鉴权吗?为什么?
  • Q52:用 query 传 token 有什么风险?怎么缓解?
  • Q53:SSE 如何防重放?需要吗?
  • Q54:同源 SSE 和跨域 SSE 在鉴权上有什么不同?
  • Q55:如何限制「单用户多开 SSE 连接」?
  • Q56:SSE 内容需要加密吗?HTTPS 足够吗?
  • Q57:EventSource 能否带 Cookie?跨域呢?
  • Q58:如何防止 SSE 被滥用(爬虫、刷接口)?
  • Q59:BFF 代理 SSE 时,BFF 和上游之间的鉴权怎么处理?
  • Q60:SSE 和 CORS 预检(preflight)有关系吗?
  • 七、汇率/行情业务场景(Q61–Q70)
  • Q61:用 SSE「实时请求当前汇率」在业务上指什么?
  • Q62:汇率/行情的数据源一般从哪里来?
  • Q63:多币对(BTC/USDT、ETH/USDT…)怎么推?一条连接还是多条?
  • Q64:行情推送频率多少合适?前端如何节流?
  • Q65:「当前价」和「盘口深度」都用 SSE 吗?
  • Q66:汇率保留几位小数?前端展示和服务端推送是否一致?
  • Q67:SSE 推送的 timestamp(ts)用来做什么?
  • Q68:行情断线期间,前端展示什么?「最后一次价格」是否还可用?
  • Q69:交易所「休市」时 SSE 怎么处理?
  • Q70:实时汇率用于「下单预填总价」时,如何避免脏读?
  • 八、故障与弱网(Q71–Q80)
  • Q71:SSE 断线有哪些常见原因?
  • Q72:如何检测 SSE 已经断开?
  • Q73:指数退避的「上限」为什么要有?设多少合适?
  • Q74:重连失败多次后要不要提示用户?怎么提示?
  • Q75:弱网下 SSE 消息会不会乱序或重复?
  • Q76:移动端或后台 Tab 时,要不要主动断开 SSE 以省电省流量?
  • Q77:Nginx 反向代理 SSE 要配什么?
  • Q78:服务端重启时,前端如何快速恢复?
  • Q79:如何监控 SSE 连接健康度?
  • Q80:SSE 和「离线优先」或 PWA 怎么结合?
  • 九、扩展与多路复用(Q81–Q90)
  • Q81:如何实现「按 symbol 订阅」?服务端怎么区分?
  • Q82:一条 SSE 里能混推「行情」和「系统通知」吗?
  • Q83:SSE 能否和 WebSocket 共存在同一页面?连接数怎么控?
  • Q84:服务端用 Redis Pub/Sub 做多实例广播时,SSE 流程是什么?
  • Q85:SSE 的 Last-Event-ID 机制是什么?我们项目用了吗?
  • Q86:如何做 SSE 的「心跳」?服务端还是客户端?
  • Q87:前端用 fetch + ReadableStream 模拟 SSE 有什么优缺点?
  • Q88:SSE 和 GraphQL Subscription 的区别?
  • Q89:如何做 SSE 的「多路复用」(一条连接多个逻辑流)?
  • Q90:Edge Runtime(如 Vercel Edge)能跑 SSE 吗?
  • 十、面试话术与总结(Q91–Q100)
  • Q91:用一句话说清楚项目里 SSE 的用途和实现。
  • Q92:SSE 和 WebSocket 选型时,你会怎么回答?
  • Q93:首屏和实时数据「解耦」具体怎么做的?
  • Q94:断线重连为什么用指数退避?
  • Q95:EventSource 不能带 Header,鉴权怎么做?
  • Q96:如何向非技术面试官解释 SSE?
  • Q97:若让你从零设计「实时汇率」方案,你会考虑哪些点?
  • Q98:SSE 的 100 问里,你认为最常被问的是哪几道?
  • Q99:本项目 SSE 可以继续做哪些优化?
  • Q100:总结:SSE 在金融交易所前端的价值是什么?

SSE 实时行情与汇率推送 — 亮点难点与百问百答

针对 SSE(Server-Sent Events) 在金融交易所场景下「实时请求当前汇率/行情」的亮点、难点,以及 100 道深度面试题,含具体答案与深度分析。可配合本仓库 apps/web/app/api/sse/quotes/route.ts、apps/web/hooks/useSSEQuotes.ts 使用。

目录:一、协议与原理(Q1–Q10)|二、SSE 与 WebSocket 选型(Q11–Q20)|三、前端 EventSource 与重连(Q21–Q30)|四、服务端实现(Q31–Q40)|五、首屏解耦与性能(Q41–Q50)|六、安全与鉴权(Q51–Q60)|七、汇率/行情业务场景(Q61–Q70)|八、故障与弱网(Q71–Q80)|九、扩展与多路复用(Q81–Q90)|十、面试话术与总结(Q91–Q100)


亮点与难点总览

问题

  • 交易所需要实时汇率/行情,若用轮询会浪费带宽、延迟高;若与首屏强耦合会导致首屏慢、水合压力大。
  • 需要服务端单向推送、自动重连、断线可感知,且与现有 HTTP 体系兼容(网关、鉴权、CDN)。

方案

  1. 用 SSE 做实时汇率/行情推送

    • 服务端通过 Content-Type: text/event-stream 维持长连接,按行推送 data: {...}\n\n。
    • 前端用 EventSource 订阅,onmessage 收数据;本项目推送 type/quote、symbol、price、ts,用于当前汇率/行情展示。
  2. 首屏与实时流解耦

    • 首屏用 BFF/React Query 静态或低频数据渲染,不依赖 SSE。
    • 页面加载后再建立 SSE 连接,增量更新行情;重连期间继续用 React Query 缓存或上次快照展示。
  3. 断线重连与连接状态

    • 监听 EventSource 的 error/close,断线后按指数退避重连(如 1s、2s、4s… 上限 30s)。
    • UI 展示连接状态(连接中 / 已连接 / 重连中 / 已断开),弱网时用户有预期。

面试话术

「我们通过 SSE 实时推送当前汇率/行情,服务端用 text/event-stream 长连接按行推送;前端用 EventSource 订阅,并做了断线指数退避重连和连接状态展示。首屏与实时流解耦:首屏不依赖 SSE,页面加载后再建连,兼顾首屏性能与交易实时性。」


一、协议与原理(Q1–Q10)

Q1:SSE 是什么?和普通 HTTP 请求有什么区别?

答案:SSE(Server-Sent Events)是基于 HTTP 的单向推送机制。客户端发一次 GET,服务端不立即结束响应,而是保持连接,持续写入 Content-Type: text/event-stream 的 body,按「事件行 + 数据行 + 空行」格式推送。与普通 HTTP 的区别:普通请求是「一发一收」;SSE 是「一发多收」,服务端可多次写数据,客户端通过 EventSource API 持续接收。

深度分析:SSE 本质是「长连接 + 流式响应」,利用 HTTP/1.1 的 keep-alive 与 chunked 传输,不升级协议,因此易过网关、易配合现有鉴权(如 Cookie、Header)。


Q2:SSE 的数据格式是什么?为什么是 data: xxx\n\n?

答案:规范格式为「事件类型(可选)+ 数据行 + 空行」。数据行以 data: 开头,多行用多个 data:;空行(\n\n)表示一条消息结束。例如:data: {"price":"40000"}\n\n。这样解析简单、可扩展(可加 event:、id:、retry:)。\n\n 作为分隔符便于流式解析,服务端每写一段就 flush,客户端按行读取即可。

深度分析:若只有 \n 没有空行,无法区分「多行 data」与「下一条消息」;\n\n 是规范约定的消息边界,与 HTTP header 的「空行分隔 header 与 body」一致。


Q3:SSE 基于 HTTP/1.1 还是 HTTP/2?多路复用的影响?

答案:SSE 传统上基于 HTTP/1.1,一个连接一个流。在 HTTP/2 下,一个 TCP 连接可多路复用多条流,但每条 SSE 连接仍是一个流(服务端持续写该流)。HTTP/2 不会「自动」把多条 SSE 合并成一条连接;若要多条 SSE 共用一个 TCP 连接,需要服务端或网关支持多路复用 SSE(如通过 stream id 区分),或前端开多条 EventSource(通常每条一个 TCP,在 H2 下会复用到同一连接)。

深度分析:浏览器对同一域名的 HTTP/1.1 连接数有限制(通常 6),多条 SSE 会占满;HTTP/2 下多条 SSE 可共享一个 TCP 连接,连接数压力小。


Q4:Content-Type: text/event-stream 必须带哪些头?Cache-Control 为什么重要?

答案:必须 Content-Type: text/event-stream;建议 Cache-Control: no-cache, no-transform、Connection: keep-alive。no-cache 避免中间缓存缓存响应体,导致客户端收不到实时数据;no-transform 禁止代理做 gzip 等转换,避免破坏流式解析。部分场景还会加 X-Accel-Buffering: no(Nginx)关闭缓冲,让数据立即推到客户端。

深度分析:代理和 CDN 默认可能缓冲响应,SSE 需要「有数据就下发」,所以必须禁止缓存与转换。


Q5:SSE 的 event、id、retry 字段分别有什么用?

答案:event:自定义事件类型,客户端可用 addEventListener('xxx', handler) 监听,不设则走默认 message。id:消息 ID,断线重连后客户端可带 Last-Event-ID 请求,服务端可从该 ID 之后补发,实现「断点续传」。retry:建议重连间隔(毫秒),浏览器可据此自动重连。例如 retry: 3000\n 表示建议 3 秒后重连。

深度分析:本项目未用 id/retry,重连逻辑在前端自己实现(指数退避);若要做「不丢消息」,需服务端支持 Last-Event-ID 并维护一段消息历史。


Q6:SSE 是「长连接」还是「短连接 + 轮询」?

答案:长连接。客户端发一次 GET,TCP 连接保持打开,服务端在该连接上持续写数据;只有连接断开(超时、错误、主动关闭)才会结束。不是「短连接 + 轮询」:轮询是多次短请求反复拉取,SSE 是一次请求、服务端主动推。

深度分析:长连接会占用服务端 fd 和内存,高并发时需要控制单机连接数或做水平扩展;轮询则请求次数多、延迟高,实时场景优先 SSE/WebSocket。


Q7:SSE 能跨域吗?和 CORS 的关系?

答案:能跨域,但受 CORS 约束。EventSource 的 URL 若为跨域,服务端必须返回 Access-Control-Allow-Origin(或 *)等 CORS 头,否则浏览器会拒绝。与 fetch 类似:浏览器会发请求,但若响应头不允许当前源,则 JS 拿不到数据(EventSource 会触发 error)。同源则无需 CORS。

深度分析:SSE 不支持自定义 Header(如 Authorization),跨域且需鉴权时,通常用 query 参数 或 Cookie(需 Credentials: include 等价能力;EventSource 同源会带 Cookie,跨域需服务端允许 credentials)。


Q8:EventSource 能带自定义 Header(如 Authorization)吗?

答案:不能。标准 EventSource(url) 只接受 URL,不支持配置 Header。因此带 token 的鉴权只能通过 URL query(如 ?token=xxx)或 Cookie(同源自动带)。若必须用 Header 鉴权,可用 fetch + ReadableStream 自己读流,或通过同源 BFF 代理 SSE(BFF 带 Header 请求上游,再转推给前端)。

深度分析:本项目 SSE 走同源 Next.js API Route,鉴权可放在 Cookie 或由 BFF 代理时加 Header;若 SSE 直连第三方行情源,多用 query 或 Cookie。


Q9:SSE 和 WebSocket 在协议层有什么本质区别?

答案:SSE:基于 HTTP,单向(服务端→客户端),文本为主,自动重连与 Last-Event-ID 有规范。WebSocket:基于独立协议(握手后升级),全双工、可二进制,无内置重连。SSE 更简单、易过网关、易配合现有 HTTP 鉴权;WebSocket 适合双向、低延迟、二进制场景(如订单簿、聊天)。

深度分析:选型看业务:只需「服务端推行情/汇率」用 SSE;若要「客户端高频上行 + 服务端下行」用 WebSocket。两者可并存(如 SSE 推行情,WS 推订单状态)。


Q10:为什么说 SSE 是「文本」的?能推二进制吗?

答案:规范里 SSE 数据是 UTF-8 文本,格式是 data: ...\n,所以天然是文本。若要推二进制,需要 Base64 编码 后放在 data 里,客户端解码;或改用 WebSocket。交易所行情/汇率多为数字和字符串,用 SSE 文本即可,无需二进制。

深度分析:二进制可省带宽,但 SSE 的解析与重连逻辑都按行来,引入 Base64 会增加编解码与体积,一般只在确有大量二进制时考虑 WebSocket。


二、SSE 与 WebSocket 选型(Q11–Q20)

Q11:实时汇率/行情为什么我们选 SSE 而不是 WebSocket?

答案:单向推送即可:服务端推价格,客户端只收、不需频繁上行。SSE 基于 HTTP,部署简单、易过网关和 CDN、可用 Cookie/query 鉴权;EventSource 自带重连与解析。WebSocket 需要单独协议与连接管理,对「只推不拉」的场景优势不大,反而增加复杂度。

深度分析:若后续有「客户端频繁下单、撤单」等强双向需求,可再上 WebSocket;当前「行情/汇率推送」SSE 足够。


Q12:SSE 和轮询比,有什么优缺点?

答案:SSE:一次连接、服务端有数据就推,延迟低、请求次数少、服务端可控推送节奏。轮询:定时发请求拉数据,实现简单,但间隔短则请求多、间隔长则延迟大,且很多请求可能无新数据(浪费)。实时性要求高时选 SSE;无长连接能力或仅低频更新时可轮询。

深度分析:交易所行情若用轮询,要「准实时」通常需 1–2 秒一次,请求量和延迟都不如 SSE 一次长连接。


Q13:一条 SSE 连接能推多个 symbol(多对汇率)吗?

答案:能。服务端在同一流里按行推送不同 data 即可,例如 data: {"symbol":"BTC/USDT","price":"40000"}\n\n 和 data: {"symbol":"ETH/USDT","price":"2500"}\n\n。客户端根据 symbol 分发到对应 state 或 UI。若 symbol 很多、推送频率很高,可考虑按 symbol 分连接或分 channel,避免单连接带宽与解析压力过大。

深度分析:本项目当前单连接推 BTC/USDT;扩展多 symbol 只需服务端循环或按订阅推送多条 data,协议无需改。


Q14:SSE 连接数有限制吗?浏览器端、服务端分别怎么考虑?

答案:浏览器:同源 HTTP/1.1 通常约 6 个连接,多开 SSE 会占满,影响其他请求。服务端:单机 fd 与内存有限,每连接一个 fd、一块缓冲,需设 max connections、超时关闭。高并发时用多机 + 负载均衡,或网关限制单 IP 连接数。

深度分析:前端尽量「按页/按模块」合并订阅(如一个页面一个 EventSource 推当前页需要的 symbol),避免开很多连接。


Q15:SSE 适合「请求当前汇率」这种语义吗?和 REST「一次请求一次响应」的区别?

答案:「请求当前汇率」可以有两种实现:REST 一次 GET 返回当前快照;SSE 是「订阅」,连接建立后持续收到后续更新。所以 SSE 适合「持续获得最新汇率」,而不是「只取一次」。若产品是「打开页面看到当前价并持续更新」,用 SSE;若只是「点一下查一次」,用 REST 即可。

深度分析:本项目是「交易页持续展示实时行情」,所以用 SSE 订阅;同时首屏用 BFF 的 REST 拉一次快照,再交给 SSE 增量更新,两者结合。


Q16:WebSocket 在交易所里通常用在什么场景?

答案:订单簿深度、逐笔成交、订单状态推送 等需要低延迟、双向或高频的场景。客户端可能频繁发「下单/撤单」,服务端实时回「成交流」;或服务端推送深度档位变化,数据量大、需二进制时用 WS 更合适。行情/汇率这种以读为主、更新频率中等,SSE 即可。

深度分析:很多交易所后端同时提供 REST(查快照)、SSE/WS(推流),前端按场景选:列表/首屏用 REST,实时牌价用 SSE,订单簿/成交流用 WS。


Q17:SSE 能实现「客户端发一个请求,服务端持续推多条」吗?

答案:能。这就是 SSE 的典型用法:客户端发一次 GET,服务端不关闭连接,持续写多条 data: ...\n\n。例如「订阅 BTC 行情」后,服务端每 2 秒写一条最新价。语义上就是「请求当前并持续更新」。

深度分析:与「请求一次响应一次」的 REST 不同,SSE 的「请求」是「建立订阅」,响应是「流式多条消息」。


Q18:若上游是 WebSocket,前端想要 SSE,怎么做的?

答案:在 BFF 或 API 层做协议转换:BFF 用 WebSocket 连上游行情源,收到消息后通过 HTTP 响应流以 SSE 格式推给前端。即「上游 WS → BFF → 前端 SSE」。这样前端只需 EventSource,鉴权、限流都在 BFF 统一做。

深度分析:本项目当前是 Next.js API Route 直接模拟推送;若真实行情是 WS,可在此 Route 里连上游 WS,把收到的数据转成 data: ...\n\n 写回前端。


Q19:SSE 和 HTTP/2 Server Push 有什么区别?

答案:HTTP/2 Server Push 是服务端在客户端请求一个资源时,主动再推其它资源(如 CSS/JS),用于优化首屏;一次推完即结束,不是长连接流。SSE 是长连接流式推送,持续写数据。两者不同:Server Push 不替代 SSE,SSE 仍是「订阅 + 持续推送」的标准做法。

深度分析:Server Push 在前端用得不多(浏览器支持与缓存行为复杂);实时数据推送仍以 SSE/WebSocket 为主。


Q20:选型时「实时汇率」和「实时订单簿」为什么可能用不同方案?

答案:实时汇率:数据量小、单向、更新频率中等(如秒级),SSE 足够,实现简单。实时订单簿:数据量大、档位多、可能需增量/二进制,延迟要求更高,且可能有「下单/撤单」双向交互,更适合 WebSocket。所以同一项目里可以「汇率用 SSE、订单簿用 WS」。

深度分析:按「数据量、方向、延迟、协议与运维成本」综合选型,不必一刀切。


三、前端 EventSource 与重连(Q21–Q30)

Q21:EventSource 的 onopen、onmessage、onerror 分别何时触发?

答案:onopen:连接建立成功(收到 200 且 Content-Type 正确)时触发。onmessage:每收到一条 SSE 消息(以 \n\n 结尾的一段)触发,event.data 为 data 行内容。onerror:连接出错、断开、或响应非 200/非 event-stream 时触发;具体错误类型浏览器不一定暴露,一般用 onerror 做「断线重连」。

深度分析:本项目在 onerror 里关闭当前连接、设状态为 reconnecting、用 setTimeout 做指数退避重连。


Q22:为什么需要「指数退避」重连?固定间隔不行吗?

答案:固定间隔在服务端故障或过载时,大量客户端同时重连会形成「惊群」,加重压力。指数退避(如 1s、2s、4s、8s… 上限 30s)让重连时间分散,避免同时撞上去;同时给服务端恢复时间。成功连接后应重置退避时间(如回到 1s),否则下次断线会等很久。

深度分析:本项目 MIN_BACKOFF=1000、MAX_BACKOFF=30000、BACKOFF_MULTIPLIER=2,onopen 时重置 backoffRef。


Q23:EventSource 有没有「自动重连」?我们为什么还自己写?

答案:规范里浏览器在连接断开后可以自动重连(且可带 Last-Event-ID),但行为与间隔不可控,且不同浏览器实现不一。自己写可以:控制退避时间、上限、重连前清理、与 React 生命周期绑定(unmount 时不再重连),并展示「重连中」状态,体验更好。

深度分析:生产环境通常不用浏览器默认重连,而是自己管理连接与状态,便于监控与降级。


Q24:在 React 里用 SSE,为什么要在 useEffect 里 subscribe、return 里 cleanup?

答案:避免重复连接与内存泄漏。useEffect 在 mount 时建立 EventSource,在 unmount(或 deps 变)时 return 的 cleanup 里 close 并置空 ref,这样组件销毁时连接会关闭。若在 useEffect 外建连接,组件多次挂载或路由切换会留下悬空连接;且 React 严格模式会 double mount,必须保证 cleanup 能正确关闭。

深度分析:本项目用 mounted 标志位 + cleanup 里 eventSourceRef.current?.close(),保证 unmount 后不再 setState 和重连。


Q25:SSE 的「连接状态」怎么设计?idle / connecting / connected / reconnecting / disconnected 分别表示什么?

答案:idle:未开始连接(如 enabled=false)。connecting:正在建立连接(new EventSource 到 onopen 之前)。connected:已连接,正常收数据。reconnecting:断线后等待退避并准备重连。disconnected:主动关闭(如 unmount)或不再重连。前端根据状态展示不同 UI(如绿点/黄点/灰点、文案「实时连接中/重连中/已断开」)。

深度分析:本项目 useSSEQuotes 返回 status、lastQuote、error,交易页用 status 驱动连接状态展示。


Q26:重连时如何避免「重复订阅」或「状态错乱」?

答案:单连接:用 ref 持有当前 EventSource,重连前先 close 再 new;不要同时存在多个 EventSource 指向同一 URL。mounted 标志:cleanup 里设 mounted=false,重连回调里若 !mounted 则不再 setState 和继续重连。deps:useEffect 的 deps 只放 enabled、symbol 等,避免无谓重建;symbol 变化时 cleanup 会先关旧连接再建新连接。

深度分析:本项目 eventSourceRef 保证同一时刻只有一个连接;mounted 保证 unmount 后不再更新 state。


Q27:EventSource 的 readyState 有哪几种?各表示什么?

答案:CONNECTING (0):正在连接。OPEN (1):已连接,可收数据。CLOSED (2):已关闭。只读,不能主动设为 OPEN;连接断开或 close() 后变为 CLOSED。可用于在 onerror 里判断是否已关闭再决定是否重连。

深度分析:本项目未直接读 readyState,而是用 onerror 统一处理「断线」并重连。


Q28:前端如何解析 SSE 的「多行 data」?

答案:规范允许多行 data:,合并时用 \n 连接成一条 message 的 data。例如 data: line1\ndata: line2\n\n,则 event.data 为 "line1\nline2"。若用 JSON,可在一行内写完,如 data: {"a":1}\n\n,客户端 JSON.parse(e.data) 即可。本项目采用单行 JSON,解析简单。

深度分析:多行 data 常用于大块文本或兼容旧格式;行情类小 payload 单行 JSON 足够。


Q29:useSSEQuotes 的 symbol 参数当前有没有真正传给服务端?如何扩展「按 symbol 订阅」?

答案:当前前端把 symbol 作为 hook 参数,服务端 Next.js API Route 未读 query,是写死推 BTC/USDT。要按 symbol 订阅:前端把 symbol 放在 URL query,如 /api/sse/quotes?symbol=BTC/USDT;服务端 GET 时读 searchParams.get('symbol'),只推该 symbol,或多 symbol 时循环推送。EventSource 不支持自定义 header,所以 symbol 只能放 URL。

深度分析:多 symbol 也可一条连接推多种 symbol,客户端根据 data.symbol 分发;或每 symbol 一条连接(注意浏览器连接数限制)。


Q30:在 Next.js 里 EventSource 的 URL 用相对路径还是绝对路径?为什么?

答案:用 window.location.origin + '/api/sse/quotes' 或相对路径 /api/sse/quotes(相对当前页 origin)。同源时两者等价。用 origin 拼接可避免在 SSR 或 baseURL 异常时出错;相对路径在 SPA 内通常也没问题。本项目用 window.location.origin 保证在客户端且与当前页同源。

深度分析:SSR 阶段没有 window,所以 EventSource 必须在客户端(useEffect 内)创建,此时 origin 正确。


四、服务端实现(Q31–Q40)

Q31:Next.js App Router 里 SSE 用 Route Handler 的 ReadableStream 怎么实现?

答案:在 app/api/sse/quotes/route.ts 里 export async function GET(),返回 new NextResponse(stream, { headers: { 'Content-Type': 'text/event-stream', ... } }),其中 stream 是 new ReadableStream({ start(controller) { ... } })。在 start 里用 setInterval 或从上游拉数据,每次 controller.enqueue(encoder.encode('data: ' + JSON.stringify(obj) + '\n\n')),需要关闭时 controller.close()。

深度分析:本项目用 setInterval 每 2 秒推一条模拟行情,5 分钟后 cleanup 关流;真实场景可改为从 Redis/WS 上游读数据再推。


Q32:为什么 Route Handler 要设 export const dynamic = 'force-dynamic'?

答案:Next.js 默认会静态化或缓存 GET;SSE 是动态、长连接,不能缓存、每次请求都要新开流。dynamic = 'force-dynamic' 表示该路由不参与静态优化,每次 GET 都执行 handler,保证 SSE 行为正确。

深度分析:不设的话可能被缓成静态响应或只执行一次,后续请求拿不到新流。


Q33:SSE 服务端为什么要立即 flush?不 flush 会怎样?

答案:不 flush 时数据可能留在服务端缓冲区,等缓冲区满或连接关闭才发到客户端,导致延迟很大甚至收不到。立即 flush(如 Node 里每次 write 后调用 flush、或使用不缓冲的 API)让「有数据就下发」,满足实时性。Next.js 的 ReadableStream 每次 enqueue 一般会尽快下发,具体依赖运行环境。

深度分析:若前面有 Nginx,需加 X-Accel-Buffering: no 关缓冲,否则 Nginx 可能缓冲响应。


Q34:Node 里除了 ReadableStream,还能用什么写 SSE?

答案:Express:res.writeHead(200, { 'Content-Type': 'text/event-stream', ... }); setInterval(() => res.write('data: ...\n\n'), 2000),注意不要调用 res.end() 直到要关连接。http 模块:同样 res.write 多次,最后 res.end()。ReadableStream 是 Web API,在 Node 和 Edge 都可用,适合 Next.js Route Handler 等统一写法。

深度分析:核心都是「保持连接 + 按格式写 data 行 + 必要时 flush」。


Q35:SSE 连接在服务端什么时候会断开?超时怎么设?

答案:客户端关页面/EventSource.close()、网络中断、服务端主动 close、代理/负载均衡超时。服务端可设空闲超时(如 5 分钟无写则 close)和最大存活时间(如 30 分钟强制关),避免长时间占用 fd。本项目 Route Handler 里 5 分钟后主动 cleanup,即最大存活时间。

深度分析:Nginx 等代理常有 proxy_read_timeout,需设得足够大(如 3600s)否则会主动断 SSE。


Q36:一条 SSE 连接能同时推「行情」和「订单状态」吗?

答案:能。在同一流里写不同 data 即可,用 event: 或 data 里的 type 区分。例如 event: quote\ndata: {...}\n\n 和 event: order\ndata: {...}\n\n,前端用 addEventListener('quote', ...) 和 addEventListener('order', ...)。若业务上希望分离(如权限、频率不同),也可以开两条 SSE 或一条 SSE + 一条 WebSocket。

深度分析:合并可省连接数,分离则职责清晰;按业务与运维需求选。


Q37:Next.js 的 SSE Route 在 serverless 上能跑吗?有什么限制?

答案:有限制。传统 serverless(如 Lambda)是「请求-响应」模型,响应必须在一定时间内结束,且不支持长连接。所以 Vercel 等 serverless 上长时间 SSE 可能超时或被终止。长连 SSE 更适合常驻进程(Node 服务器、Docker、K8s Pod)。若必须 serverless,可考虑「短 SSE + 轮询」或把 SSE 放到单独的长连服务。

深度分析:本项目若部署到 Vercel,SSE 可能在 60s 等限制后被断;生产建议 SSE 走独立服务或同机 Node 服务。


Q38:服务端如何从「上游」拿实时汇率再推给前端?

答案:BFF/API 层连上游(HTTP 轮询、WebSocket、或上游的 SSE),收到数据后转成 SSE 格式推给前端。例如:BFF 用 WS 连行情源,on('message') 里把价格写入前端 SSE 的 ReadableStream;或 BFF 定时请求第三方汇率 API,把结果 enqueue 到前端流。这样前端只连 BFF,鉴权、限流、聚合都在 BFF 完成。

深度分析:本项目当前是模拟数据;真实场景可在同一 Route 里加「请求第三方 API 或订阅 WS」,再推到 controller。


Q39:SSE 服务端怎么做「广播」(多个客户端收同一条消息)?

答案:服务端维护当前所有 SSE 连接的列表(如 Set),每次有数据时遍历列表,对每个连接 write 同一条 data: ...\n\n。Next.js 无状态,需在外部维护连接集(如全局变量、Redis Pub/Sub)。用 Redis Pub/Sub 时:一个进程订阅 channel,收到后推给本机所有 SSE 连接;多机则每机一个 subscriber,各自推给本机连接。

深度分析:单机用内存 Set 即可;多机必须用 Redis 等做进程间广播。


Q40:为什么说 SSE 是「拉模型」?和「推模型」矛盾吗?

答案:拉指连接是由客户端发起的(客户端 GET,服务端响应并保持)。推指数据是服务端主动写的,客户端被动收。所以「拉」指建立连接的方式,「推」指数据方向;不矛盾。SSE 是「客户端拉建立连接 + 服务端推数据」。对比:轮询是「客户端反复拉数据」,没有服务端推。

深度分析:面试时说「SSE 是服务端推送、基于 HTTP 长连接」即可;若问「谁发起」,答「客户端发起 GET,服务端保持连接并推送」。


五、首屏解耦与性能(Q41–Q50)

Q41:为什么要把「首屏」和「实时行情」解耦?

答案:首屏若依赖 SSE,需要等连接建立 + 第一条数据才能渲染,延迟大且弱网时易白屏;SSE 还可能增加首屏 JS 与网络竞争。解耦:首屏用 BFF/React Query 拉一次快照或静态数据先渲染,SSE 在页面加载后单独建连、增量更新,这样首屏快、实时性由 SSE 保证。

深度分析:本项目交易页首屏用 React Query 拉 /api/markets,SSE 在 useSSEQuotes 里单独订阅,两者独立。


Q42:首屏「不依赖 SSE」具体指什么?代码上怎么体现?

答案:指首屏渲染不等待 SSE 连接或 SSE 数据。即:不用「等 EventSource.onopen 或 onmessage 才 setState 渲染列表」;而是用 BFF 或 React Query 的初次 fetch 结果渲染,SSE 只负责后续更新同一块数据(如 lastQuote)。代码上:列表/价格初始值来自 useQuery;useSSEQuotes 的 lastQuote 只做「更新」,不参与首屏是否展示的逻辑。

深度分析:若首屏就展示「Live: xxx」,可接受「连接中」时先不显示或显示上次缓存;不阻塞首屏渲染。


Q43:SSE 和 React Query 怎么配合?谁做主数据源?

答案:React Query 做首屏与缓存:初次拉 BFF 快照,staleTime 内用缓存。SSE 做增量更新:收到新 price 后更新组件 state(如 setLastQuote),或通过 React Query 的 setQueryData 把 SSE 数据写回 cache,这样同一份数据既被 SSE 更新又被其它组件消费。主数据源可以是「React Query cache + SSE 增量写入」。

深度分析:本项目交易页同时用 useQuery(markets) 和 useSSEQuotes(lastQuote),SSE 的 lastQuote 用于「Live: BTC/USDT xxx」展示,与 markets 列表可合并展示逻辑。


Q44:SSE 连接建立时机:一进页面就建还是等用户操作?

答案:一进页面就建更常见:用户打开交易页就期望看到实时价,早建连早收数据。也可「懒建连」:如 Tab 切到「行情」再建,减少非活跃 Tab 的连接。本项目是进入交易页就 useSSEQuotes(true),即立即建连。

深度分析:若连接数紧张,可按可见性(visibilitychange)或 Tab 维度做「不可见时断开、可见时重连」。


Q45:大量组件都消费「当前汇率」时,怎么避免重复建多条 SSE?

答案:单例订阅:在全局或 Context 里只建一条 EventSource,多组件通过 Context 或状态管理(如 Zustand)消费同一份 lastQuote。即「一个 EventSource + 多组件订阅同一 state」。不要在每个组件里各写一个 useSSEQuotes,否则会多连接。

深度分析:本项目目前是交易页一个 useSSEQuotes,若多个页面都要行情,可把 useSSEQuotes 提到 Layout 或 Provider,或封装成 Context。


Q46:SSE 消息频率很高时,前端如何避免渲染过多?

答案:节流:如每 100ms 最多 setState 一次,中间收到的数据取最新一条。requestAnimationFrame:在 rAF 里批量更新,避免同一帧多次渲染。分离「接收」与「展示」:接收层持续更新 ref,展示层用 setInterval 或 rAF 从 ref 读并 setState,降低 React 更新频率。

深度分析:行情若每秒多条,可节流到每秒 2–4 次更新,视觉上足够流畅且省 CPU。


Q47:首屏 LCP 和 SSE 有关系吗?怎么保证 LCP 不受 SSE 影响?

答案:有间接关系:若首屏关键内容等 SSE 才渲染,LCP 会受 SSE 延迟影响。保证 LCP 不受影响:首屏关键内容(标题、首屏列表、核心 CTA)都用同步数据或 BFF 请求渲染,不依赖 SSE;SSE 只更新「实时价格」等非首屏关键或可延迟的区块。这样 LCP 不依赖 SSE 连接速度。

深度分析:Web Vitals 里 LCP 取最大可见元素;若最大元素是「价格数字」且来自 SSE,需改为首屏用快照、SSE 只做替换。


Q48:SSE 的 JS 要不要懒加载?对首屏体积的影响?

答案:EventSource 是浏览器原生 API,不需要额外 JS;但封装 SSE 的 React hook 和业务逻辑会打进 bundle。若交易页是懒加载的,SSE 相关代码会随交易页 chunk 一起加载,不会占首屏主 chunk。若首屏就引了 useSSEQuotes,则 SSE 逻辑在主 chunk,体积通常很小(几十行),影响有限。

深度分析:SSE 逻辑简单,一般不单独拆 chunk;关键是「首屏渲染不依赖 SSE 数据」。


Q49:React 18 的 useSyncExternalStore 和 SSE 有关系吗?

答案:有。若把 SSE 的「最新一条消息」存到外部 store(如全局变量 + 订阅列表),可用 useSyncExternalStore 让组件订阅该 store,这样 React 18 的并发渲染与 SSR 能正确与外部数据同步。本项目目前用 useState,若改为外部 store + useSyncExternalStore,可更好地支持 SSR 与并发。

深度分析:useSyncExternalStore 适合「外部数据源(如 SSE、WebSocket)驱动 React 状态」的场景,避免 tear 和闪烁。


Q50:首屏「可交互」时间(TTI)和 SSE 的关系?

答案:SSE 在 useEffect 里建连,不阻塞主线程;但若 SSE 回调里做重计算或频繁 setState,可能拖慢 TTI。保证 TTI 不受影响:SSE 回调尽量轻量(只解析 + setState),重逻辑放 worker 或节流;且首屏可交互不依赖「已收到 SSE 数据」,即「没收到也能点、能操作」。

深度分析:首屏可交互的定义是「主线程空闲、可响应用户输入」;SSE 建连与收包是异步的,一般不直接阻塞 TTI,除非在收包回调里做了大量同步计算。


六、安全与鉴权(Q51–Q60)

Q51:SSE 连接需要鉴权吗?为什么?

答案:需要。若行情/汇率是非公开或按用户/权限区分,未鉴权则可能越权或滥用。鉴权方式:Cookie(同源自动带)、URL query(如 ?token=xxx,注意 token 进日志)、BFF 代理(前端只连同源 BFF,BFF 带 Header 连上游)。EventSource 不能带自定义 Header,所以 Cookie 或 query 二选一或 BFF 代理。

深度分析:本项目 SSE 走同源 Next.js API Route,可依赖 Cookie 或 Next 的 session;若 Route 内调上游,由服务端带 token。


Q52:用 query 传 token 有什么风险?怎么缓解?

答案:风险:URL 可能进日志、Referer、浏览器历史,token 泄露面大。缓解:用短期 token(如 5 分钟有效)、一次性(用一次即废)、或只用于 SSE 的只读 token,限制权限;生产尽量用 Cookie(HttpOnly)或 BFF 代理,不在 URL 暴露 token。

深度分析:若必须 query,可让服务端签发「仅 SSE 用、范围最小」的 token,并短过期。


Q53:SSE 如何防重放?需要吗?

答案:行情/汇率一般是只读推送,不涉及「操作」,通常不需要防重放。若 SSE 通道里携带「敏感指令」或「一次性数据」,可给每条消息带 nonce 或序号,服务端校验不重复;或连接级 token 一次性。本项目只推价格,无防重放需求。

深度分析:防重放多用于「写操作」或「敏感指令」;读流一般不要求。


Q54:同源 SSE 和跨域 SSE 在鉴权上有什么不同?

答案:同源:Cookie 自动带,可用 session/Cookie 鉴权;无需 CORS。跨域:Cookie 默认不带(需 credentials),且需服务端 CORS;鉴权多用 query token 或服务端允许的 Cookie 配置(如 SameSite、Secure)。前端若跨域连第三方 SSE,通常只能 query 带 token。

深度分析:本项目 SSE 同源,鉴权与主站一致即可;若将来 SSE 独立域名,需设计 token 或 Cookie 策略。


Q55:如何限制「单用户多开 SSE 连接」?

答案:服务端:按 userId(从 Cookie/token 解析)维护「当前连接数」,超过 N 则新连接返回 429 或关闭旧连接。网关:按 IP 或 userId 限连接数。前端:单例封装,确保同一用户同一页只建一条(Context/全局单例)。本项目前端单页单连接;服务端未做限连接数,可加。

深度分析:防止恶意或 bug 导致单用户开很多连接,拖垮服务端。


Q56:SSE 内容需要加密吗?HTTPS 足够吗?

答案:HTTPS 已保证传输层加密,中间人看不到 body。应用层再加密(如对 data 做 AES)一般不需要,除非有「端到端不信任网关」等需求。行情/汇率若为公开或内部,HTTPS 足够。

深度分析:金融场景通常 HTTPS + 鉴权即可;合规若有「传输加密」要求,HTTPS 已满足。


Q57:EventSource 能否带 Cookie?跨域呢?

答案:同源:自动带 Cookie,与 fetch 同源行为一致。跨域:默认不带 Cookie;若要带,需服务端 Access-Control-Allow-Credentials: true 且 Access-Control-Allow-Origin 不能为 *,且前端无法「配置」EventSource 的 credentials(标准未提供),所以跨域 SSE 带 Cookie 依赖浏览器默认策略,部分环境可能不带。稳妥做法是跨域用 query token。

深度分析:本项目同源,Cookie 自动带;若 SSE 迁到子域,需确认 Cookie 的 domain 与 SameSite。


Q58:如何防止 SSE 被滥用(爬虫、刷接口)?

答案:鉴权:必须登录或有效 token 才建连。限连接数:单 IP 或单用户最多 N 条。限流:按连接或按用户限制推送频率(服务端控制)。风控:异常连接(如短时间大量建连)可封禁。本项目可加「按 Cookie/token 限连接数」和「限流」。

深度分析:公开行情可适度限流限连接;付费或敏感数据必须鉴权+限流。


Q59:BFF 代理 SSE 时,BFF 和上游之间的鉴权怎么处理?

答案:BFF 到上游:用 BFF 自己的服务端 token(如 API Key、OAuth client credentials)放在 Header 里请求上游,不暴露给前端。前端到 BFF:用 Cookie 或前端 token 鉴权,BFF 校验后决定是否代理。这样前端不知道上游 token,上游只认 BFF。

深度分析:BFF 作为「鉴权与协议转换」层,前端 SSE 连 BFF,BFF 用服务端身份连上游。


Q60:SSE 和 CORS 预检(preflight)有关系吗?

答案:简单请求(GET、简单 Header)不会发 OPTIONS preflight;EventSource 的 GET 不带自定义 Header,所以一般不会触发 preflight。若 SSE 是跨域,浏览器会发 GET,服务端只需返回 CORS 头(如 Access-Control-Allow-Origin)即可,无需单独处理 OPTIONS。若将来用 fetch + ReadableStream 模拟 SSE 并带自定义 Header,则会 preflight。

深度分析:标准 EventSource 跨域时只需 CORS 响应头,不需要 OPTIONS 逻辑。


七、汇率/行情业务场景(Q61–Q70)

Q61:用 SSE「实时请求当前汇率」在业务上指什么?

答案:指建立订阅后,持续收到最新汇率/行情,而不是「请求一次返回一次」。即「打开交易页 → 建立 SSE → 服务端按一定频率(如每 2 秒)推送当前价」,前端展示「当前价」并持续更新。语义是「订阅当前并跟踪变化」。

深度分析:与 REST「GET 一次当前价」的区别是持续性和服务端主动推。


Q62:汇率/行情的数据源一般从哪里来?

答案:交易所 WebSocket/API、第三方行情供应商、自建撮合引擎。BFF 或中间层订阅这些源,做聚合、校验、限频后,再以 SSE 或 WebSocket 推给前端。前端不直连数据源,便于鉴权、限流与统一协议。

深度分析:本项目是模拟数据;真实项目在 Route 或 BFF 里接上游 WS/HTTP 再转 SSE。


Q63:多币对(BTC/USDT、ETH/USDT…)怎么推?一条连接还是多条?

答案:一条连接:服务端在同一流里轮询或按订阅推送多币对,每条消息带 symbol,客户端按 symbol 分发。多条连接:每币对一条 EventSource,浏览器连接数有限(如 6),币对多时不可取。推荐:一条连接推多 symbol,或按「当前页需要的 symbol」订阅一条连接。

深度分析:本项目当前单 symbol;扩展多 symbol 只需服务端多推几种 data,客户端用 symbol 区分。


Q64:行情推送频率多少合适?前端如何节流?

答案:服务端:交易所级可能毫秒级;一般展示用 1–2 秒一条即可,兼顾实时与带宽。前端:若推送超过刷新率(如 60fps),可节流到 100–200ms 更新一次 UI,或按 requestAnimationFrame 批量更新,避免过度渲染。

深度分析:本项目 2 秒一条,对「牌价展示」足够;订单簿等需更高频可单独用 WebSocket。


Q65:「当前价」和「盘口深度」都用 SSE 吗?

答案:当前价/最新价用 SSE 足够。盘口深度数据量大、更新频繁,常用 WebSocket 且可能用二进制或增量,SSE 也可但不如 WS 灵活。同一项目可「最新价用 SSE、深度用 WS」,或都用 WS 统一管理。

深度分析:按数据量与延迟需求选型;本项目只做「当前价」SSE。


Q66:汇率保留几位小数?前端展示和服务端推送是否一致?

答案:服务端按交易对规则推送(如 BTC 8 位、USDT 2 位);前端用 trading-core 的精度与 formatCurrency 展示,一致。计算用 decimal.js,展示用 Intl.NumberFormat,避免前端二次四舍五入导致与后端不一致。

深度分析:本项目 trading-core 有 CURRENCY_PRECISION,SSE 推送的 price 为字符串,前端展示时按币种精度格式化。


Q67:SSE 推送的 timestamp(ts)用来做什么?

答案:客户端可判断数据新旧(如只渲染比当前更新的)、做延迟监控(收到时间 - ts 为网络延迟)、去重(相同 ts 不重复渲染)。本项目 data 里带 ts,便于扩展延迟统计与去重。

深度分析:生产可上报「推送延迟」到监控,便于发现网络或上游问题。


Q68:行情断线期间,前端展示什么?「最后一次价格」是否还可用?

答案:断线期间可继续展示最后一次收到的价格,并标注「重连中」或「延迟更新」,避免空白或报错。lastQuote 保留上次值,重连成功后覆盖;若业务要求「断线则不展示」,可清空并显示「连接断开」。

深度分析:本项目重连期间 lastQuote 保持,UI 显示「Reconnecting…」+ 上次价格,体验较好。


Q69:交易所「休市」时 SSE 怎么处理?

答案:服务端可推送「休市」事件(如 type: 'market_closed'),然后关闭连接或停止推送;或保持连接但长时间不推,由客户端超时判断。前端收到休市后可展示「休市」、停止重连或降级为轮询。本项目未实现休市逻辑,可扩展 event 类型。

深度分析:业务规则由服务端定义,前端按 type 分支处理。


Q70:实时汇率用于「下单预填总价」时,如何避免脏读?

答案:展示用 SSE 最新价;下单时以提交瞬间服务端校验的价格为准(或带「参考价」由后端再算)。即前端用 SSE 做预填与展示,真正成交价以订单接口返回或后端计算为准,避免「读到旧价却按新价展示」的脏读。金额计算统一走 trading-core,提交 body 里带 price/quantity,后端再校验。

深度分析:金融场景「展示」与「成交」分离,后端为唯一真实源。


八、故障与弱网(Q71–Q80)

Q71:SSE 断线有哪些常见原因?

答案:网络:弱网、切换 WiFi/4G、丢包。服务端:重启、崩溃、超时关闭。代理/负载均衡:超时(如 60s)、连接数限制。客户端:页面最小化/后台、Tab 关闭、用户导航走。浏览器:某些环境下长时间无数据可能关连接。

深度分析:前端能做的是「检测断线 + 重连 + 状态展示」;服务端与网关需合理超时与限连接。


Q72:如何检测 SSE 已经断开?

答案:EventSource.onerror 触发表示连接异常或关闭;readyState === CLOSED 表示已关闭。轮询:若长时间未收到消息(如 30s),可主动认为断开并重连。本项目用 onerror 作为断线信号,不依赖超时。

深度分析:部分环境 onerror 可能不触发,可加「最后一条消息时间」超时作为兜底。


Q73:指数退避的「上限」为什么要有?设多少合适?

答案:无上限时,若服务端长期不可用,退避会无限增大,用户可能永远等不到重连。设上限(如 30s)保证「最多等 30 秒就试一次」,平衡恢复速度与对服务端压力。30s 是常见取值;可依业务调整(如 15s–60s)。

深度分析:本项目 MAX_BACKOFF=30000,退避到 30s 后不再增加,每次失败后 30s 再试。


Q74:重连失败多次后要不要提示用户?怎么提示?

答案:要。例如连续失败 3–5 次后,展示「实时行情连接异常,请检查网络或稍后重试」,并提供「重试」按钮(立即重连)。可同时保留「最后一次价格」展示,避免空白。本项目可扩展「重试次数」state,超阈值后 setError 文案并展示按钮。

深度分析:避免用户一直看到「重连中」却不知道是否正常,提升可感知性。


Q75:弱网下 SSE 消息会不会乱序或重复?

答案:TCP 保证有序,同一连接内不会乱序。重复可能出现在「断线重连后」若服务端支持 Last-Event-ID 补发,可能收到断线前已收过的消息,需客户端按 id 或 ts 去重。本项目未做 Last-Event-ID,重连后从新数据开始,无补发故无重复。

深度分析:若做「不丢消息」,需要 id + 服务端补发,客户端去重。


Q76:移动端或后台 Tab 时,要不要主动断开 SSE 以省电省流量?

答案:可选。断开:页面 visibility 为 hidden 时 close EventSource,visible 时重连;省电省流量,但再切回来会有短暂「重连中」。不断开:保持连接,切回来立即有数据,但后台可能耗电。可根据产品策略选择;本项目未做 visibility 处理。

深度分析:若连接数或电量敏感,建议 visibility 隐藏时断开、显示时重连。


Q77:Nginx 反向代理 SSE 要配什么?

答案:proxy_read_timeout 调大(如 3600s),否则 Nginx 会主动断长连接。proxy_buffering off 关闭缓冲,让数据立即到客户端。X-Accel-Buffering: no(若用 Nginx 的 proxy 模块)禁止缓冲。Connection 与 Cache-Control 透传或按 SSE 要求设置。

深度分析:不关缓冲会导致 SSE 数据被缓冲,延迟很大或收不到。


Q78:服务端重启时,前端如何快速恢复?

答案:前端:onerror 后指数退避重连,成功即恢复。服务端:重启时优雅关闭(先不再接受新连接、等现有连接写完再退出),减少「连接突然断」的数量。网关:若有连接池或多实例,重启时做灰度或 drain,避免全部断。前端侧主要靠重连与状态展示。

深度分析:本项目前端重连已实现;服务端可加 graceful shutdown。


Q79:如何监控 SSE 连接健康度?

答案:前端:上报「连接建立、断开、重连次数、最后消息延迟」到 Datadog/Bugsnag 等。服务端:记录每连接建立/关闭、推送条数、错误数。指标:连接数、断线率、重连成功率、消息延迟分布。本项目可扩展 useSSEQuotes 内上报连接事件。

深度分析:监控便于发现地域/网络/上游问题,做 SLO 与告警。


Q80:SSE 和「离线优先」或 PWA 怎么结合?

答案:离线时 SSE 必然断,可展示上次缓存数据 + 「离线」标识;恢复在线后自动重连。PWA:Service Worker 不拦截 EventSource(或对 SSE URL 放行),保证后台页也能收数据;或 SW 不缓存 SSE 响应。若要做「离线可看上次行情」,用 Cache API 存 lastQuote,离线时读缓存展示。

深度分析:本项目未做 PWA;若做,SSE 与 SW 的配合需保证连接不被 SW 误拦截。


九、扩展与多路复用(Q81–Q90)

Q81:如何实现「按 symbol 订阅」?服务端怎么区分?

答案:前端:URL 带 query,如 /api/sse/quotes?symbol=BTC/USDT;或多条连接每 symbol 一个(不推荐多 symbol)。服务端:GET 时读 searchParams.get('symbol'),只推该 symbol,或维护「连接 → 订阅列表」按需推。Next.js Route 里用 request.nextUrl.searchParams.get('symbol')。

深度分析:本项目 Route 未读 symbol,扩展时在 GET 里读 query 再决定推送内容。


Q82:一条 SSE 里能混推「行情」和「系统通知」吗?

答案:能。用 event: 区分,如 event: quote\ndata: {...}\n\n 和 event: notification\ndata: {...}\n\n;前端 addEventListener('quote', ...) 和 addEventListener('notification', ...)。或 data 里统一用 type 字段,前端按 type 分支。本项目 data 里已有 type: 'quote',可扩展 type: 'notification'。

深度分析:单连接多类型可省连接数,需约定 event/type 规范。


Q83:SSE 能否和 WebSocket 共存在同一页面?连接数怎么控?

答案:能。例如 SSE 推行情、WebSocket 推订单状态,两者独立。连接数:浏览器同源 HTTP/1.1 约 6 个,SSE 占 1、WS 占 1,其余给普通请求;HTTP/2 下多路复用,压力小。同一页尽量 SSE 1 条 + WS 1 条,避免多开。

深度分析:按业务拆通道,避免单通道承载过多类型导致耦合与限流困难。


Q84:服务端用 Redis Pub/Sub 做多实例广播时,SSE 流程是什么?

答案:流程:前端连到某台 API 机 → 该机建 SSE 连接并 subscribe Redis channel(如 quotes:BTC/USDT)→ 上游或其它服务 publish 行情到 Redis → 该机收到消息后 write 到本机所有订阅该 channel 的 SSE 连接。这样多实例共享同一份行情源,每机只推给自己机上的连接。

深度分析:Redis 做「进程间广播」,每台 API 机维护本机 SSE 连接列表,收到 Redis 消息后写回前端。


Q85:SSE 的 Last-Event-ID 机制是什么?我们项目用了吗?

答案:机制:服务端每条消息可带 id: 123\n;断线重连时,浏览器(或手动)在请求头带 Last-Event-ID: 123,服务端从该 id 之后补发消息,实现「不丢消息」。本项目未用:当前是「重连后从新数据开始」,不补发历史。

深度分析:若要做不丢消息,需服务端维护一段消息历史(如环形队列)并按 id 补发;实现与存储成本较高。


Q86:如何做 SSE 的「心跳」?服务端还是客户端?

答案:服务端定期推心跳(如每 30s 一条 data: {"type":"ping"}\n\n),让连接保持活跃,避免中间代理因「长时间无数据」断连。客户端收到心跳可忽略或回写(若用 fetch 模拟 SSE 可回写,标准 EventSource 不能上行)。本项目未做心跳;若遇代理超时,可加服务端心跳。

深度分析:心跳间隔应小于代理/负载均衡的 read_timeout,如 15–30s。


Q87:前端用 fetch + ReadableStream 模拟 SSE 有什么优缺点?

答案:优点:可带 自定义 Header(如 Authorization)、可控制重连、可解析非标准格式。缺点:需自己解析流、自己实现重连与 Last-Event-ID、代码量大;且 fetch 的 body 消费后不能「重试」同一条连接。适用:需要 Header 鉴权且不能接受 query/Cookie 时。

深度分析:本项目用标准 EventSource,简单够用;若后续要带 Header,可考虑 fetch + ReadableStream。


Q88:SSE 和 GraphQL Subscription 的区别?

答案:SSE:通用 HTTP 流式推送,格式自定(如 JSON 行)。GraphQL Subscription:通常是 WebSocket 上跑 GraphQL 协议,订阅「某 query 的变更」。若后端是 GraphQL,Subscription 常用 WS;若后端是 REST/自定义,SSE 更简单。两者可并存:查询用 GraphQL query,推送用 SSE。

深度分析:选型看后端与团队技术栈;交易所前端用 SSE 或 WS 均可,不依赖 GraphQL。


Q89:如何做 SSE 的「多路复用」(一条连接多个逻辑流)?

答案:应用层多路复用:一条 SSE 里用 event: 或 data 里的 streamId/channel 区分,如 event: stream1\ndata: {...}\n\n 和 event: stream2\ndata: {...}\n\n,前端按 event 或 channel 分发。协议层:SSE 规范本身是一条流,多路复用靠应用层字段区分,不是多 TCP 流。

深度分析:本项目单 type: 'quote',扩展多 channel 时在 data 里加 channel 或 event 即可。


Q90:Edge Runtime(如 Vercel Edge)能跑 SSE 吗?

答案:能。Edge 支持 Web API 的 ReadableStream、Response,可写 SSE 流并返回。限制:Edge 有执行时间与内存限制,长时间 SSE 可能超时;且 Edge 无状态,多实例间无法共享连接列表,适合「单连接独立推」或「从上游拉一段再推」。长时间、多连接广播更适合 Node 常驻进程。

深度分析:若 SSE 在 Edge 上且需长连,需确认平台对长连与超时的支持。


十、面试话术与总结(Q91–Q100)

Q91:用一句话说清楚项目里 SSE 的用途和实现。

答案:「我们用 SSE 实时推送当前汇率/行情,服务端是 Next.js API Route 返回 text/event-stream 的 ReadableStream,按行推 data: { type, symbol, price, ts };前端用 EventSource 订阅,并做了断线指数退避重连和连接状态展示,首屏不依赖 SSE,用 BFF 快照先渲染,SSE 只做增量更新。」

深度分析:一句话里包含:用途(实时汇率/行情)、服务端形态(Route + Stream)、前端形态(EventSource + 重连 + 状态)、与首屏解耦。


Q92:SSE 和 WebSocket 选型时,你会怎么回答?

答案:「我们场景是服务端单向推行情,不需要客户端频繁上行,所以选 SSE:基于 HTTP、部署简单、易过网关、EventSource 自带解析与重连语义;WebSocket 更适合双向、低延迟、二进制场景,如订单簿或成交流,我们后续若有需要会再上 WS,和 SSE 并存。」

深度分析:突出「单向 vs 双向」「HTTP 友好 vs 独立协议」「实现成本」。


Q93:首屏和实时数据「解耦」具体怎么做的?

答案:「首屏用 BFF/React Query 拉一次行情快照或静态数据,先渲染;SSE 在页面加载后再建连,只负责后续增量更新,不参与首屏是否展示的逻辑。这样 LCP 和可交互时间不依赖 SSE 连接速度,弱网时也不会白屏。」

深度分析:解耦 = 数据源分离 + 建连时机分离 + 首屏不等待 SSE。


Q94:断线重连为什么用指数退避?

答案:「避免惊群:服务端故障时,固定间隔重连会让大量客户端同时撞上去,加重压力。指数退避让重连时间分散,并给服务端恢复时间;成功连接后重置退避,下次断线再从短间隔开始。我们设了上下限(1s–30s),兼顾恢复速度与对后端压力。」

深度分析:退避是分布式系统里通用的「避免同时重试」手段。


Q95:EventSource 不能带 Header,鉴权怎么做?

答案:「我们 SSE 走同源 Next.js API Route,鉴权用 Cookie 或 session,同源请求自动带 Cookie。若必须跨域或带 token,只能用 URL query(注意 token 进日志风险)或 BFF 代理:前端连同源 BFF,BFF 带 Header 连上游,把上游数据转成 SSE 推给前端。」

深度分析:同源 Cookie 最省事;跨域或强安全时 BFF 代理 + 服务端 token。


Q96:如何向非技术面试官解释 SSE?

答案:「就像订阅:你打开页面相当于『订阅了最新价格』,服务器会持续把最新价格推给你,不用你反复刷新。连接偶尔会断,我们做了自动重连和状态提示,断的时候你会看到『重连中』,连上后继续收最新价。」

深度分析:避免说协议细节,用「订阅、推送、自动重连」三个概念即可。


Q97:若让你从零设计「实时汇率」方案,你会考虑哪些点?

答案:「① 数据源:上游是 WS 还是 HTTP,需不需要 BFF 聚合。② 协议:单向用 SSE,双向用 WS。③ 鉴权与限流:谁能连、单用户连接数、按 IP 限流。④ 首屏:首屏不依赖推送,用快照先渲染。⑤ 断线重连:指数退避、状态展示、可选 Last-Event-ID。⑥ 监控:连接数、断线率、消息延迟。⑦ 多实例:Redis Pub/Sub 或网关做广播。」

深度分析:按「数据源 → 协议 → 安全 → 体验 → 运维」顺序答。


Q98:SSE 的 100 问里,你认为最常被问的是哪几道?

答案:「Q1/Q2(SSE 是什么、数据格式)、Q9/Q11(和 WebSocket 区别、为什么选 SSE)、Q21/Q22(EventSource 事件、指数退避)、Q41(首屏解耦)、Q51/Q55(鉴权、限连接)、Q71/Q73(断线原因、退避上限)、Q91(一句话总结)。」

深度分析:协议、选型、前端实现、安全、故障处理、总结是高频点。


Q99:本项目 SSE 可以继续做哪些优化?

答案:「① 服务端支持 query symbol,按 symbol 订阅。② 服务端 心跳,减少代理超时断连。③ 前端 节流更新,高频时每 100ms 最多渲染一次。④ 监控上报:连接建立/断开、重连次数、消息延迟。⑤ 多实例时 Redis Pub/Sub 做广播。⑥ Last-Event-ID 做断点续传(若业务需要不丢消息)。」

深度分析:按「功能、稳定性、可观测、扩展」列 3–5 条即可。


Q100:总结:SSE 在金融交易所前端的价值是什么?

答案:「在不增加首屏负担的前提下,提供实时汇率/行情,提升交易页的实时性与体验;通过 HTTP 友好的协议降低部署与网关成本,通过 断线重连与状态展示 提升弱网与故障下的可感知性。和 BFF、React Query、trading-core 一起,形成「首屏快、数据准、实时更新、资金安全」的完整链路。」

深度分析:价值 = 实时性 + 首屏解耦 + 协议与运维成本 + 可观测与体验。


本稿对应项目:apps/web/app/api/sse/quotes/route.ts、apps/web/hooks/useSSEQuotes.ts;面试时可结合代码说明「实时请求当前汇率」的实现与重连、首屏解耦策略。