阅读对象:本文适合SpringBoot 初学者及对SpringBoot感兴趣的童鞋阅读。
背景介绍:在企业级 WEB 应用开发中,为了更好的用户体验&提升响应速度,往往会将一些耗时费力的请求 (Excel导入or导出,复杂计算, etc.) 进行***异步化***处理。 由此带来的一个重要的问题是***如何通知用户任务状态***,常见的方法大致分为2类4种:
HTTP Polling
client pullHTTP Long-Polling
client pullServer-Sent Events (SSE)
server pushWebSocket
server push
1. Polling 短轮询
是一种非常简单的实现方式。就是client通过***定时任务***不断得重复请求服务器,从而获取新消息,而server按时间顺序提供自上次请求以后发生的单个或多个消息。
短轮询的优点非常明显,就是实现简单。当两个方向上的数据都非常少,并且请求间隔不是非常密集时,这种方法就会非常有效。例如,新闻评论信息可以每半分钟更新一次,这对用户来说是可以的。
它得缺点也是非常明显,一旦我们对数据实时性要求非常高时,为了保证消息的及时送达,请求间隔必须缩短,在这种情况下,会加剧服务器资源的浪费,降低服务的可用性。另一个缺点就是在消息的数量较少时,将会有大量的 request
做无用功,进而也导致服务器资源的浪费。
2. Long-Polling 长轮询
长轮询的官方定义是:
The server attempts to “hold open” (notimmediately reply to) each HTTP request, responding only when there are events to deliver. In this way, there is always a pending request to which the server can reply for the purpose of delivering events as they occur, thereby minimizing the latency in message delivery.
如果与Polling
的方式相比,会发现Long-Polling
的优点是通过hold open HTTP request 从而减少了无用的请求。
大致步骤为:
- client向server请求并等待响应。
- 服务端将请求阻塞,并不断检查是否有新消息。如果在这个期间有新消息产生时就立即返回。否则一直等待至
请求超时
。 - 当client
获取到新消息
或请求超时
,进行消息处理并发起下一次请求。
Long-Polling
的缺点之一也是服务器资源的浪费,因为它和Polling
的一样都属于***被动获取***,都需要不断的向服务器请求。在并发很高的情况下,对服务器性能是个严峻的考验。
Note:因为以上2两种方式的实现都比较简单,所以我们这里就不做代码演示了。接下来我们重点介绍一下
Server-Sent Events
及WebSocket
。
3. Demo概要
下面我们将通过一个***下载文件***的案例进行演示SSE
和WebSocket
的消息推送,在这之前,我们先简单说一下我们项目的结构,整个项目基于SpringBoot 构建。
首先我们定义一个供前端访问的APIDownloadController
@RestController
public class DownloadController {
private static final Logger log = getLogger(DownloadController.class);
@Autowired
private MockDownloadComponent downloadComponent;
@GetMapping("/api/download/{type}")
public String download(@PathVariable String type, HttpServletRequest request) {
// (A)
HttpSession session = request.getSession();
String sessionid = session.getId();
log.info("sessionid=[{}]", sessionid);
downloadComponent.mockDownload(type, sessionid); // (B)
return "success"; // (C)
}
}
- (A)
type
参数用于区分使用哪种推送方式,这里为sse
,ws
,stomp
这三种类型。 - (B)
MockDownloadComponent
用于异步模拟下载文件的过程。 - © 因为下载过程为异步化,所以该方法不会被阻塞并立即向客户端返回
success
,用于表明下载开始。
在DownloadController
中我们调用MockDownloadComponent
的mockDownload()
的方法进行模拟真正的下载逻辑。
@Component
public class MockDownloadComponent {
private static final Logger log = LoggerFactory.getLogger(DownloadController.class);
@Async // (A)
public void mockDownload(String type, String sessionid) {
for (int i = 0; i < 100; i ) {
try {
TimeUnit.MILLISECONDS.sleep(100); // (B)
int percent = i 1;
String content = String.format("{\"username\":\"%s\",\"percent\":%d}", sessionid, percent); // (C)
log.info("username={}'s file has been finished [{}]% ", sessionid, percent);
switch (type) {
// (D)
case "sse":
SseNotificationController.usesSsePush(sessionid, content);
break;