服务端实时推送技术之SSE(Server-Send Events)
文章目录
- 前言
- 一、解决方案:
- 1、传统实时处理方案:
- 2、HTML5 标准引入的实时处理方案:
- 3、第三方推送:
- 二、SSE
- 1.引入库
- 1、客户端:
- 2.服务端:
- 三、业务实践:
- 能否做到精准投递?
- 总结
前言
服务端推送,也称为消息推送或通知推送,是一种允许应用服务器主动将信息发送到客户端的能力,为客户端提供了实时的信息更新和通知,增强了用户体验。
服务端推送的背景与需求主要基于以下几个诉求:
-
实时通知:在很多情况下,用户期望实时接收到应用的通知,如新消息提醒、商品活动提醒等。
-
节省资源:如果没有服务端推送,客户端需要通过轮询的方式来获取新信息,会造成客户端、服务端的资源损耗。通过服务端推送,客户端只需要在收到通知时做出响应,大大减少了资源的消耗。
-
增强用户体验:通过服务端推送,应用可以针对特定用户或用户群发送有针对性的内容,如优惠活动、个性化推荐等。这有助于提高用户对应用的满意度和黏性。
常见推送场景有:微信消息通知栏、新闻推送、外卖状态 等等,我们自身的推送场景有:下载、连线请求、直播提醒 …
一、解决方案:
1、传统实时处理方案:
轮询:这是一种较为传统的方式,客户端会定时地向服务端发送请求,询问是否有新数据。服务端只需要检查数据状态,然后将结果返回给客户端。轮询的优点是实现简单,兼容性好;缺点是可能产生较大的延迟,且对服务端资源消耗较高。
长轮询(Long Polling):轮询的改进版。客户端向服务器发送请求,服务器收到请求后,如果有新的数据,立即返回给客户端;如果没有新数据,服务器会等待一定时间(比如30秒超时时间),在这段时间内,如果有新数据,就返回给客户端,否则返回空数据。客户端处理完服务器返回的响应后,再次发起新的请求,如此反复。长轮询相较于传统的轮询方式减少了请求次数,但仍然存在一定的延迟。
2、HTML5 标准引入的实时处理方案:
WebSocket:一种双向通信协议,同时支持服务端和客户端之间的实时交互。WebSocket 是基于 TCP 的长连接,和HTTP 协议相比,它能实现轻量级的、低延迟的数据传输,非常适合实时通信场景,主要用于交互性强的双向通信。
SSE:SSE(Server-Sent Events)是一种基于 HTTP 协议的推送技术。服务端可以使用 SSE 来向客户端推送数据,但客户端不能通过SSE向服务端发送数据。相较于 WebSocket,SSE 更简单、更轻量级,但只能实现单向通信。
两者的主要区别:
SSE | WebSocket | |
---|---|---|
通信 | 单向通信 | 双向通信 |
协议 | HTTP | WebSocket |
自动重连 | 支持 | 不支持,需要客户端自行支持 |
数据格式 | 文本格式,如果需要二进制数据,需要自行编码 | 默认二进制数据,支持文本格式 |
浏览器支持 | 大部分支持,早期Edge浏览器,Internet Explorer不支持 | 主流浏览器(包括移动端)的支持较好 |
3、第三方推送:
常见的有操作系统提供相应的推送服务,如苹果的APNs(Apple Push Notification service)、谷歌的FCM(Firebase Cloud Messaging)等。同时,也有一些跨平台的推送服务,如个推、极光推送、友盟推送等,帮助开发者在不同平台上实现统一的推送功能。
这种推送方式在生活中十分常见,一般你打开手机就能看到各种信息推送,基本就是利用第三方推送来实现。
二、SSE
1.引入库
接下来我们重点讲讲 SSE 服务端推送,它基于 HTTP 协议,易于实现和部署,特别适合那些需要服务器主动推送信息、客户端只需接收数据的场景:
1、客户端:
Server-Sent Events(SSE)是 HTML5 的一部分,用于从服务器实时接收更新,目前大部分主流浏览器都提供了支持:
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>SSE Client</title>
</head>
<body><h1>Receive: <span id="sse"></span></h1><script>const numberElement = document.getElementById("sse");const source = new EventSource('http://localhost:8080/sse');source.onmessage = (event) => {numberElement.innerText = event.data;};source.onerror = (error) => {console.error("SSE error:", error);};</script>
</body>
</html>
自动重连:一旦连接断开,浏览器会自动尝试重新建立连接。当然,每个浏览器都有自己的重连策略和措施。因此,重连时间和尝试次数可能因浏览器而异。
2.服务端:
我们目前服务端主要使用 Spring,其对 SSE 主要提供了两种支持:
- Spring WebMVC:传统的基于 Servlet 的同步阻塞编程模型,即 同步模型Web框架。
- Spring WebFlux:异步非阻塞的响应式编程模型,即 异步模型Web框架。
1)Spring WebFlux 中的 SSE 支持(支持版本Spring5.0):
Spring WebFlux 框架提供了一套基于响应式编程的非阻塞异步IO模型,能高效支持 SSE。在 Spring WebFlux 中,我们可以结合 Flux 和 MediaType.TEXT_EVENT_STREAM_VALUE 来实现 SSE。
以下示例展示如何在 Spring WebFlux 中创建 SSE 流:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.time.Duration;@RestController
public class SseController {@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> getSseStream() {// 使用Flux生成每秒一个递增的数据流,用于模拟实时数据推送return Flux.interval(Duration.ofSeconds(1)).map(sequence -> "Data: " + sequence);}
}
Spring WebFlux 底层依赖于两个非阻塞的异步框架: Reactor 和 Netty。其中,Reactor 库主要是提供响应式式编程的支持,Netty 是一个高性能的非阻塞网络框架,主要负责处理 HTTP 输入输出。
- Reactor:Reactor 是一个基于 Java 8 的响应式流库,它实现了 Reactive Streams 规范。Reactor 提供了两个核心的响应式类型 - Mono 和 Flux,相当于 findOne 和 findList 的区别。这两个类型提供了丰富的操作符,允许你以声明式和函数式的方式来处理你的业务逻辑。
- Netty:Netty 是一个高性能、异步的事件驱动的网络框架,虽然 Netty 重点在网络通信层,但仍然提供了 Web 服务器的能力。
Reactor 对 Netty 进行了集成,提供了子模块 Reactor Netty,在 Spring WebFlux 中,Reactor Netty 主要用作默认的服务器运行时环境,负责处理 HTTP 请求和响应。
这两个框架共同为 Spring WebFlux 提供了底层的支持,使得我们能够使用响应式编程编写高性能、可扩展的Web应用程序。另外,虽然 Spring WebFlux 在底层默认使用 Reactor 和 Netty,但它也有很好的灵活性和可替换性,我们可以根据需要更换其他非阻塞的异步框架。
2)Spring WebMVC 中的 SSE 支持(支持版本Spring4.2):
在 Spring WebMVC 中,可以通过SseEmitter对象来处理 SSE 请求,它允许将数据通过 SSE 连接发送给客户端。
下面是一个使用SseEmitter提供 SSE 的简单示例:
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;@RestController
public class SseController {private ExecutorService nonBlockingService = Executors.newCachedThreadPool();@GetMapping("/sse")public SseEmitter getSseStream() {SseEmitter emitter = new SseEmitter();nonBlockingService.execute(() -> {// 这里模拟数据发送给客户端的逻辑try {for (int i = 0; i < 10; i++) {emitter.send("Data: " + i);Thread.sleep(1000);}emitter.complete();} catch (Exception ex) {emitter.completeWithError(ex);}});return emitter;}
}
如果你的项目使用 SpringMVC 模型,不想再引入 Spring WebFlux,能否利用 Reactor 响应式库呢?
答案也是可以的,虽然 SpringMVC 主要提供的是同步阻塞能力,但也不妨碍它提供一定的异步支持,比如这里,我们可以直接引入 Reactor 库,也同样可以实现 SSE:
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;import java.time.Duration;@RestController
public class SseController {@GetMapping(value = "/sse", produces = MediaType.TEXT_EVENT_STREAM_VALUE)public Flux<String> getSseStream() {// 使用Flux生成每秒一个递增的数据流,用于模拟实时数据推送return Flux.interval(Duration.ofSeconds(1)).map(sequence -> "Data: " + sequence);}
}
基于 Spring WebMVC 或 Spring WebFlux,我们可以方便地在 Spring 框架中实现 SSE 的支持。这两种方法根据具体需求和场景,可以灵活选择。
三、业务实践:
我们以「文件下载」功能进行说明,一般情况下,大文件的下载,服务端压力比较大、处理时间也比较长,为了有更好的交互体验,我们可以使用异步处理,服务端处理完了之后主动通知 客户端,效果如下:
1)SSE 连接:
先建立 SSE 连接,确保服务端有主动推送消息的能力。
2)异步下载:
长耗时下载任务我们通过异步的方式处理,避免用户在下载页面长时间等待。
3)广播并推送:
下载完成后,我们需要将完成事件推送给客户端。需要注意的是,由于服务是集群部署、SSE 连接在节点本地 Map 维护,这就有可能导致当前客户端的 SSE连接所在节点 与 事件推送节点 是两个独立的节点。
因此,我们这里借助于 Redis 的发布/订阅能力,将消息广播出去,能匹配连接的节点负责将消息推送至客户端、其他节点直接丢弃即可。效果图如下:
能否做到精准投递?
答案也是可以的,我们可以这样来做:
借助 Redis 做中心存储,存储Map<用户,节点IP> 这样的映射关系。
在推送消息之前,先通过映射关系找到该用户的SSE连接所在节点
然后在通过 RPC调用 直接将消息投递到对应的服务节点,最后由该节点进行事件推送。
一般情况下,我们可以用「广播」这种简单粗暴的方式应对大部分场景,毕竟「精准投递」需要中心化的维护节点关系、应对节点变更等,处理起来稍显麻烦。具体视业务场景来做选择即可。
总结
SSE 技术是一种轻量级的实时推送技术,具有支持跨域、使用简单、支持自动重连等特点,使得其在实时消息推送、股票交易等场景下广泛使用。
另外,SSE 相对于 WebSocket 更加轻量级,如果需求场景不需要交互式动作,那么 SSE 是一个不错的选择。
相关文章:

服务端实时推送技术之SSE(Server-Send Events)
文章目录 前言一、解决方案:1、传统实时处理方案:2、HTML5 标准引入的实时处理方案:3、第三方推送: 二、SSE1.引入库1、客户端: 2.服务端:三、业务实践:能否做到精准投递? 总结 前言…...

使用IntelliJ IDEA查看接口的全部实现方法
在大型Java项目中,经常会使用接口和抽象类进行代码设计。为了更好地了解代码结构和功能,我们需要快速查看一个接口的所有实现类。IntelliJ IDEA提供了一些方便的方法来实现这一目标。 1. 点击查看接口的实现子类 在IDEA中,你可以轻松地查看…...
阿里云幻兽帕鲁服务器操作系统类型怎么选择?
使用阿里云服务器搭建幻兽帕鲁操作系统类型选Windows还是Linux?如果对Linux熟悉就选择Linux,相对于windows,Linux更少占用系统资源;如果对Linux不熟悉,首选Windows。事实上,阿里云提供的幻兽帕鲁服务器通过…...

Codeforces Round 927 (Div. 3) LR-remainders的题解
原题描述: C.LR-remains 每次测试时限:2 秒 每次测试的内存限制:256 兆字节 输入:标准输入 输出:标准输出 样例1输入: 4 4 6 3 1 4 2 LRRL 5 1 1 1 1 1 1 LLLLL 6 8 1 2 3 4 5 6 RLLLRR 1 10000 1000…...

HarmonyOS—@Observed装饰器和@ObjectLink嵌套类对象属性变化
Observed装饰器和ObjectLink装饰器:嵌套类对象属性变化 概述 ObjectLink和Observed类装饰器用于在涉及嵌套对象或数组的场景中进行双向数据同步: 被Observed装饰的类,可以被观察到属性的变化;子组件中ObjectLink装饰器装饰的状…...

The method toList() is undefined for the type Stream
The method toList() is undefined for the type Stream (JDK16) default List<T> toList() { return (List<T>) Collections.unmodifiableList(new ArrayList<>(Arrays.asList(this.toArray()))); }...

vue+element (el-progress)标签 隐藏百分比(%) ,反向显示 ,自定义颜色, demo 复制粘贴拿去用
1 效果: 2 页面代码: <el-row :gutter"10" ><el-col :span"12"><el-card ><div class"fourqu"><div><span slot"title">{{推送任务TOP5}}</span></div></div><div class&…...

Android轻量级进程间通信Messenger源码分析
一. 概述 Android中比较有代表性的两大通信机制:1. 线程间Handler通信 2. 进程间Binder通信,本篇文章中我们在理解AIDL原理的基础上来解读一下Messenger的源代码, 并结合示例Demo加深理解。 在看本篇文章前,建议先查阅一下笔者的…...

C#开发AGV地图编辑软件
C#自己开发AGV地图编辑软件: 1、自由添加和删除站点、停车位、小车、运行路径。 2、编辑得地图以XML文件保存。 3、导入编辑好地图的XML文件。 4、程序都是源码,可以直接在此基础上进行二次开发。 下载链接:https://download.csdn.net/d…...
嵌入式学习day22 Linux
文件IO: 1. lseek off_t lseek(int fd, off_t offset, int whence); 功能: 重新设定文件描述符的偏移量 参数: fd:文件描述符 offset:偏移量 whence: SEEK_SET 文件开头 …...
不确定性问题的论文笔记
Statistics starting from 01/2024, 仅列出了优秀工作中的一部分 每一年的排列顺序: CVPR, ICLR, ECCV, ICCV, ICML, AAAI, TPAMI,TIP,Arxiv 等 每周更新 2024 论文信息速览笔记是 否 已精读精读笔记Shao W, Xu Y, Peng L, et al. Failure Detection fo…...

C语言推荐书籍
本书详细讲解了C语言的基本概念和编程技巧。全书共17章。第1章、第2章介绍了C语言编程的预备知识。第3章~第15章详细讲解了C语言的相关知识,包括数据类型、格式化输入/输出、运算符、表达式、语句、循环、字符输入和输出、函数、数组和指针、字符和字符串…...

基于uniapp微信小程序的汽车租赁预约系统
随着现代汽车租赁管理的快速发展,可以说汽车租赁管理已经逐渐成为现代汽车租赁管理过程中最为重要的部分之一。但是一直以来我国传统的汽车租赁管理并没有建立一套完善的行之有效的汽车租赁管理系统,传统的汽车租赁管理已经无法适应高速发展,…...

ClickHouse 基础(一)
官网 ClickHouse release 24.1, 2024-01-30 以毫秒为单位查询数十亿行 ClickHouse是用于实时应用和分析的最快、资源效率最高的开源数据库。 安装ClickHouse 使用ClickHouse,你有三个选择: ClickHouse云:官方ClickHouse作为一项服务,-由ClickHouse的创…...

07-k8s中secret资源02-玩转secret
一、回顾secret资源的简单实用 第一步:将想要的数据信息【key:value】中的value值,使用base64编码后,写入secret资源清单中; 第二步:创建secret资源; 第三步:pod资源引用secret资源&…...

HTTP特性
大家好我是苏麟 , 今天说说HTTP特性. 资料来源 : 小林coding 小林官方网站 : 小林coding (xiaolincoding.com) 到目前为止,HTTP 常见到版本有 HTTP/1.1,HTTP/2.0,HTTP/3.0,不同版本的 HTTP 特性是不一样的。 这里先用 HTTP/1.1 版本给大家介…...

ARM 之十六 详解 CMSIS 版本变迁、各组件使用示例
目前,CMSIS 已经发展到了第六版,其目录结构也发生了重大的变化。在不断发展中,很多原来 CMSIS 的组件被不断独立出去,并因此成立了很多开源社区,今天就来学习一下! 由于 CMSIS 已经包含了相当丰富的文档,因此,本文重点学习版本之间的变化以及一些实际使用示例。 什么是…...

【北京游戏业:出海竞争实力全面】
本文将深入分析北京的游戏行业发展。在上海、广州、北京、深圳、成都、杭州、福建七大游戏产业中心城市中,北京无疑是出海竞争力最强的游戏产业集群。本文将全面剖析北京游戏行业的发展现状。 北京是中国游戏产业的发源地。拥有从游戏引擎到美术设计等完整的产业链…...

课程大纲:图像处理中的矩阵计算
课程名称:《图像处理中的矩阵计算》 课程简介: 图像处理中的矩阵计算是图像分析与处理的核心部分。本课程旨在教授学员如何应用线性代数中的矩阵计算,以实现各种图像处理技术。我们将通过强调实际应用和实践活动来确保学员能够理解和掌握这些…...

【Go语言】Go语言的数据类型
GO 语言的数据类型 Go 语言内置对以下这些基本数据类型的支持: 布尔类型:bool 整型:int8、byte、int16、int、uint、uintptr 等 浮点类型:float32、float64 复数类型:complex64、complex128 字符串:st…...
[特殊字符] 智能合约中的数据是如何在区块链中保持一致的?
🧠 智能合约中的数据是如何在区块链中保持一致的? 为什么所有区块链节点都能得出相同结果?合约调用这么复杂,状态真能保持一致吗?本篇带你从底层视角理解“状态一致性”的真相。 一、智能合约的数据存储在哪里…...
进程地址空间(比特课总结)
一、进程地址空间 1. 环境变量 1 )⽤户级环境变量与系统级环境变量 全局属性:环境变量具有全局属性,会被⼦进程继承。例如当bash启动⼦进程时,环 境变量会⾃动传递给⼦进程。 本地变量限制:本地变量只在当前进程(ba…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)
上一章用到了V2 的概念,其实 Fiori当中还有 V4,咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务),代理中间件(ui5-middleware-simpleproxy)-CSDN博客…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
4. TypeScript 类型推断与类型组合
一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式,自动确定它们的类型。 这一特性减少了显式类型注解的需要,在保持类型安全的同时简化了代码。通过分析上下文和初始值,TypeSc…...
【Elasticsearch】Elasticsearch 在大数据生态圈的地位 实践经验
Elasticsearch 在大数据生态圈的地位 & 实践经验 1.Elasticsearch 的优势1.1 Elasticsearch 解决的核心问题1.1.1 传统方案的短板1.1.2 Elasticsearch 的解决方案 1.2 与大数据组件的对比优势1.3 关键优势技术支撑1.4 Elasticsearch 的竞品1.4.1 全文搜索领域1.4.2 日志分析…...
uniapp 实现腾讯云IM群文件上传下载功能
UniApp 集成腾讯云IM实现群文件上传下载功能全攻略 一、功能背景与技术选型 在团队协作场景中,群文件共享是核心需求之一。本文将介绍如何基于腾讯云IMCOS,在uniapp中实现: 群内文件上传/下载文件元数据管理下载进度追踪跨平台文件预览 二…...
上位机开发过程中的设计模式体会(1):工厂方法模式、单例模式和生成器模式
简介 在我的 QT/C 开发工作中,合理运用设计模式极大地提高了代码的可维护性和可扩展性。本文将分享我在实际项目中应用的三种创造型模式:工厂方法模式、单例模式和生成器模式。 1. 工厂模式 (Factory Pattern) 应用场景 在我的 QT 项目中曾经有一个需…...

【Linux】Linux安装并配置RabbitMQ
目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的,需要先安…...