服务端推送,也称为消息推送或通知推送,是一种允许应用服务器主动将信息发送到客户端的能力,为客户端提供了实时的信息更新和通知,增强了用户体验。服务端推送的背景与需求主要基于以下几个诉求:实时通知、节省资源、增强用户体验。常见推送场景有微信消息通知栏、新闻推送、外卖状态等。我们自身的推送场景有下载、连线请求、直播提醒等。一、解决方案:1、传统实时处理方案:轮询、长轮询。2、HTML5 标准引入的实时处理方案:WebSocket、SSE。两者的主要区别在于通信方式和协议不同。

不支持,需要客户端自行支持

数据格式:文本格式,如果需要二进制数据,需要自行编码。默认情况下,使用二进制数据,支持文本格式。

浏览器支持:大部分浏览器都支持,但是早期的Edge浏览器和Internet Explorer不支持。主流浏览器(包括移动端)的支持较好。

3、第三方推送:

常见的有操作系统提供相应的推送服务,如苹果的APNs(Apple Push Notification service)、谷歌的FCM(Firebase Cloud Messaging)等。同时,也有一些跨平台的推送服务,如个推、极光推送、友盟推送等,帮助开发者在不同平台上实现统一的推送功能。这种推送方式在生活中十分常见,一般你打开手机就能看到各种信息推送,基本就是利用第三方推送来实现。

二、SSE

1.引入库

接下来我们重点讲讲 SSE 服务端推送,它基于 HTTP 协议,易于实现和部署,特别适合那些需要服务器主动推送信息、客户端只需接收数据的场景:

1、客户端:

Server-Sent Events(SSE)是 HTML5 的一部分,用于从服务器实时接收更新。目前大部分主流浏览器都提供了支持。

SSE Client

Receive:

Spring WebFlux 中的 SSE 支持(支持版本Spring5.0):

Spring WebFlux 框架提供了一套基于响应式编程的非阻塞异步IO模型,能高效支持 SSE。在 Spring WebFlux 中,我们可以结合 Flux 和 MediaType.TEXT_EVENT_STREAM_VALUE 来实现 SSE。以下示例展示如何在 Spring WebFlux 中创建 SSE 流:

```java

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 getSseStream() {

// 使用Flux生成每秒一个递增的数据流,用于模拟实时数据推送

return Flux.interval(Duration.ofSeconds(1))

.map(sequence -> "Data: " + sequence);

}

}

```

Spring WebFlux 底层依赖于两个非阻塞的异步框架:Reactor 和 Netty。其中,Reactor 库主要是提供响应式式编程的支持,Netty 是一个高性能的非阻塞网络框架,主要负责处理 HTTP 输入输出。

Reactor 是一个基于 Java 8 的响应式流库,它实现了 Reactive Streams 规范。Reactor 提供了两个核心的响应式类型 - Mono 和 Flux,相当于 findOne 和 findList 的区别。这两个类型提供了丰富的操作符,允许你以声明式和函数式的方式来处理你的业务逻辑。

Netty 是一个高性能、异步的事件驱动的网络框架,虽然 Netty 重点在网络通信层,但仍然提供了 Web 服务器的能力。Reactor 对 Netty 进行了集成,提供了子模块 Reactor Netty,在 Spring WebFlux 中,Reactor Netty 主要用作默认的服务器运行时环境,负责处理 HTTP 请求和响应。

这两个框架共同为 Spring WebFlux 提供了底层的支持,使得我们能够使用响应式编程编写高性能、可扩展的Web应用程序。另外,虽然 Spring WebFlux 在底层默认使用 Reactor 和 Netty,但它也有很好的灵活性和可替换性,我们可以根据需要更换其他非阻塞的异步框架。

在 Spring WebMVC 中,可以通过SseEmitter对象来处理 SSE 请求,它允许将数据通过 SSE 连接发送给客户端。下面是一个使用SseEmitter提供 SSE 的简单示例:

```java

@GetMapping("/sse")

public SseEmitter handleSse() {

SseEmitter emitter = new SseEmitter();

emitter.onCompletion(() -> {

// 当连接关闭时执行的操作

System.out.println("Connection closed");

});

emitter.onTimeout(() -> {

// 当超时时执行的操作

System.out.println("Timeout");

emitter.complete();

});

emitter.send("Hello, world!"); // 发送消息到客户端

emitter.complete(); // 完成响应

return emitter;

}

```

```java

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(Server-Sent Events)。

以下是一个基于 Spring WebMVC 实现 SSE 的示例代码:

```java

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 getSseStream() {

// 使用Flux生成每秒一个递增的数据流,用于模拟实时数据推送

return Flux.interval(Duration.ofSeconds(1))

.map(sequence -> "Data: " + sequence);

}

}

```

基于 Spring WebMVC 或 Spring WebFlux,我们可以方便地在 Spring 框架中实现 SSE 的支持。这两种方法根据具体需求和场景,可以灵活选择。

SSE(Server-Sent Events)技术是一种轻量级的实时推送技术,具有支持跨域、使用简单、支持自动重连等特点,使得其在实时消息推送、股票交易等场景下广泛使用。在实际应用中,我们可以通过以下几种方式进行广播和推送:

1. 广播并推送:

下载完成后,我们需要将完成事件推送给客户端。需要注意的是,由于服务是集群部署、SSE连接在节点本地Map维护,这就有可能导致当前客户端的SSE连接所在节点与事件推送节点是两个独立的节点。因此,我们这里借助于Redis的发布/订阅能力,将消息广播出去,能匹配连接的节点负责将消息推送至客户端,其他节点直接丢弃即可。效果图如下:

2. 精准投递:

借助Redis做中心存储,存储Map<用户,节点IP>这样的映射关系。在推送消息之前,先通过映射关系找到该用户的SSE连接所在节点,然后再通过RPC调用直接将消息投递到对应的服务节点,最后由该节点进行事件推送。一般情况下,我们可以用「广播」这种简单粗暴的方式应对大部分场景,毕竟「精准投递」需要中心化的维护节点关系、应对节点变更等,处理起来稍显麻烦。具体视业务场景来做选择即可。

总结:SSE技术是一种轻量级的实时推送技术,具有支持跨域、使用简单、支持自动重连等特点,使得其在实时消息推送、股票交易等场景下广泛使用。另外,SSE相对于WebSocket更加轻量级,如果需求场景不需要交互式动作,那么SSE是一个不错的选择。