From 0f2dee1b4222938d2cbb4593bffebe79688d5b69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AE=80=E9=9A=90?= Date: Fri, 23 Aug 2024 17:07:27 +0800 Subject: [PATCH] =?UTF-8?q?docs(=E9=80=9F=E8=AE=B0):=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=E5=89=8D=E7=AB=AF=E7=BB=BC=E5=90=88=E7=9F=A5=E8=AF=86=E7=82=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...15\347\253\257\347\273\274\345\220\210.md" | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) diff --git "a/docs/usage-interview/\351\200\237\350\256\260\346\211\213\345\206\214/\345\211\215\347\253\257\347\273\274\345\220\210.md" "b/docs/usage-interview/\351\200\237\350\256\260\346\211\213\345\206\214/\345\211\215\347\253\257\347\273\274\345\220\210.md" index 1c40a58..f4d74b9 100644 --- "a/docs/usage-interview/\351\200\237\350\256\260\346\211\213\345\206\214/\345\211\215\347\253\257\347\273\274\345\220\210.md" +++ "b/docs/usage-interview/\351\200\237\350\256\260\346\211\213\345\206\214/\345\211\215\347\253\257\347\273\274\345\220\210.md" @@ -846,6 +846,160 @@ const changeTheme = (val) => { - https://juejin.cn/post/7202036660636287034 - https://github.com/jimmywarting/StreamSaver.js +### 实时数据更新 + +**实时数据更新方式**: + +- 短轮询(polling) +- 长轮询(long polling) +- 长连接(websocket) +- 服务器事件推送(server-sent events, SSE) + +**短轮询**:客户端不停地调用服务端接口获取最新的数据,发起请求后服务端会立即响应并返回结果给客户端,客户端在接受到数据后,(可能等待几秒后)会再次发起请求,如此反复。 + +优点: + +- 实现简单 + +缺点: + +- 无用的请求多,因为客户端不知道服务端什么时候有数据更新,所以只能不停的询问服务端。这将增加服务端带宽,消耗服务端资源,同时也会加快客户端的耗电速度。 +- 数据实时性差:在获取到服务端数据后,可能会等待一些时间客户端才开始再次发起请求,这将导致客户端需要一段时间才能拿到最新的数据。对于实时性要求高的场景是致命的。 + +**长轮询**:客户端发起请求后,服务端发现当前没新的数据,这是不会立即返回请求,而是将请求挂起,直到有新的数据更新,或者等待超时(比如设置的30s)后再将内容发给客户端。客户端在收到数据后,立即发起新的请求,如此反复。 + +优点: + +- 避免客户端大量的重复请求,因为服务端在数据未更新时不会立即将结果返回给客户端 +- 客户端在收到数据后,可以立即发起新的请求,确保数据实时性 + +缺点: + +- 大量消耗服务端资源,服务端会一直hold客户端的请求,这些请求会占用服务器的内存资源,因为每个http请求都是一个独立的连接,当请求数量增多时,服务器的内存资源很快就会被耗光 +- 难以处理数据更新频繁的情况,频繁更新数据会创建和重建大量的连接,导致服务端资源消耗过多 + +**websocket**:首先客户端会给服务端发送一个http请求,这个请求的header会告诉服务端它想基于websocket协议通信,如果服务端支持升级协议,会给客户端发送一个switching protocol的响应,后续都是基于websocket协议进行通信(客户端和服务端之间建立一个持久的长连接,这个连接是全双工的,客户端和服务端都可以主动实时地发送数据给对方)。 + +打开开发者工具,选择Network,然后刷新页面,可以看到一个ws的请求,在messages这栏可以看到客户端和服务端通信的数据。 + +优点: + +- 客户端和服务端建立连接的次数减少:理想情况下客户端只需要发送一个http升级协议就可以升级到websocket连接,后续所有的消息都是通过这个通道进行通信,无需再次建立连接 +- 消息实时性高:由于连接是一直建立的,当有数据更新时可以马上推送到客户端 +- 双工通信:客户端和服务端都可以主动发送数据给对方 +- 适用于数据频繁更新的场景:随时推送,无需建立重连接 + +缺点: + +- 扩容麻烦:基于websocket的服务都是有状态的,意味着在扩容的时候麻烦,系统设计也比较复杂 +- 代理限制:某些代理服务器(比如nginx)默认配置的长连接时间是有限的(比如几十秒),这时需要自动重连,突破这种现在需要将所有的代理默认配置都进行更改。 + +**服务端事件推送**:是一种基于http协议的实时向客户端进行数据推送(单向)的技术。首先客户端向服务端发起一个持久化的http连接,服务端收到请求后,挂起客户端请求,有数据更新时,再通过这个连接将数据推送给客户端。 + +打开开发者工具,选择Network,然后刷新页面,可以看到一个http的请求,在eventStream这栏可以看到服务端推送的消息。 + +优点: + +- 连接数少:只有一个持久化的http连接 +- 数据实时性高:服务端和客户端的连接都是持久的,当有数据更新时,服务端可以立即推送给客户端 + +缺点: + +- 单向通信:客户端无法主动向服务端发送数据 +- 代理层限制:代理服务器(比如nginx)默认配置的长连接时间有限,需要自动重连 + +::: code-group + +```javascript [polling] + +const fetchLatestEvents = async (timestamp) => { + // 获取最新事件 + const body = await fetch(`http://xxx/events?timestamp=${timestamp}`) + if (body.ok) { + return await body.json() + } else { + console.error('获取最新事件失败') + } +} + +// 每3000秒获取一次最新事件 +setInterval(async () => { + const latestEvents = await fetchLatestEvents(lastTimestamp) + if (latestEvents && latestEvents.length) { + // 更新数据 + data = [...data, ...latestEvents] + } +}, 3000) +``` + +```javascript [long-polling] +const fetchLatestEvents = async (timestamp) => { + const body = await fetch(`http://xxx/events?timestamp=${timestamp}`) + if (body.ok) { + return await body.json() + } else { + console.error('获取最新事件失败') + } +} + +const fetchTask = async () => { + const latestEvents = await fetchLatestEvents(lastTimestamp) + if (latestEvents && latestEvents.length) { + // 更新数据 + data = [...data, ...latestEvents] + } +} + +fetchTask().catch(console.error).finally(() => { + // 触发下一次请求 + fetchTask() +}) +``` + +```javascript [websocket] +const ws = new WebSocket('ws://xxx/events?timestamp=1645890723') + +ws.addEventListener('open', () => { + console.log('连接成功') +}) + +ws.addEventListener('message', (event) => { + const latestEvents = JSON.parse(event.data) + if (latestEvents && latestEvents.length) { + // 更新数据 + data = [...data, ...latestEvents] + } +}) + +ws.addEventListener('close', () => { + console.log('连接关闭') +}) +``` + +```javascript [SSE] +const source = new EventSource('http://xxx/events?timestamp=1645890723') + +source.onopen = () => { + console.log('连接成功') +} + +source.onmessage = (event) => { + const latestEvents = JSON.parse(event.data) + if (latestEvents && latestEvents.length) { + // 更新数据 + data = [...data, ...latestEvents] + } +} + +source.addEventListner('error', (e) => { + consoe.log('连接失败') + // 关闭连接,或者做其他操作 + source.close() +}) +``` + +::: + ### 大屏定时刷新内存泄露问题 场景:项目使用 ECharts 绘制图形时,需要定时刷新数据,数据量太大时,页面长时间停留后会卡顿