chat

主要实现方式

在实现即时通讯(IM)系统的前后端交互时,长轮询(Long Polling)、定时轮询(Polling)、SSE(Server-Sent Events)和 WebSocket(WS) 是常见的通信模式,但它们各有适用场景和优缺点。

以下是对比分析及现代 IM 系统的典型技术选型:


1. 通信模式对比

| 技术 | 协议 | 方向性 | 实时性 | 适用场景 | 缺点 | |——————–|———–|————–|————–|———————————-|———————————–| | 定时轮询 | HTTP | 客户端主动拉取 | 低(延迟高) | 简单场景,兼容性要求高 | 资源浪费(频繁请求)、延迟不可控 | | 长轮询 | HTTP | 客户端半主动 | 中(依赖超时) | 兼容老旧设备,短时实时需求 | 连接频繁重建,服务器并发压力大 | | SSE | HTTP | 服务端单向推送 | 高(单向流) | 服务端主动通知(如新闻推送) | 仅支持单向通信,需浏览器兼容性 | | WebSocket | WS/WSS | 全双工双向通信 | 高(实时) | 即时聊天、实时协作、游戏 | 需协议升级,部分网络设备可能拦截 |

详细介绍一下 IM 的实现策略之 定时轮询(Polling)

定时轮询(Polling)是即时通讯(IM)系统中常见的一种消息推送策略,它的实现机制是客户端定期向服务器请求新消息。

以下是定时轮询的实现策略及其优缺点的详细介绍:

1. 基本概念

定时轮询是一种客户端主动请求消息的方式,客户端定期向服务器发送请求,询问是否有新的消息。

如果有,服务器将消息返回给客户端;如果没有,服务器通常返回一个空响应,客户端会在下一个周期继续请求。定时轮询的核心思想是客户端通过定时请求服务器来检查是否有新数据(如消息)到达。

2. 定时轮询的工作流程

  1. 客户端请求: 客户端在固定的时间间隔内向服务器发送请求(通常是HTTP请求)。

  2. 服务器响应: 服务器接收到请求后,检查是否有新的消息。如果有新的消息,服务器将消息返回给客户端;如果没有,返回一个空响应。

  3. 客户端等待下一轮请求: 客户端会在接收到响应后,等待下一个轮询周期继续发起请求。

3. 实现步骤

  • 客户端部分:
    1. 设置一个定时器,定期发起HTTP请求(例如每隔1秒、5秒、10秒等)。
    2. 每次请求时,客户端向服务器发送一个请求,询问是否有新消息。
    3. 接收到响应后,更新客户端UI界面,展示新消息或进行其他处理。
  • 服务器部分:
    1. 接收到请求时,服务器会检查数据库或消息队列中是否有新的消息。
    2. 如果有新消息,服务器将消息返回给客户端。
    3. 如果没有新消息,服务器将返回一个空响应,客户端等待下一轮轮询。

4. 定时轮询的优缺点

优点:

  1. 简单易实现:
    • 定时轮询实现简单,客户端和服务器只需要实现基本的请求和响应机制,无需复杂的技术栈。
    • 适合低并发场景,尤其是消息量不大时。
  2. 兼容性强:
    • 不依赖于WebSocket或其他长连接技术,适合在网络环境不稳定或老旧的设备上使用。
    • 兼容性较好,可以与HTTP协议兼容,易于与现有的HTTP服务结合。
  3. 控制简单:
    • 客户端可以自由控制请求的频率,灵活性较高。
    • 可以根据业务需求调整请求的时间间隔,避免服务器压力过大。

缺点:

  1. 延迟较高:
    • 定时轮询有时间间隔,消息传递的延迟较高。即使消息已经到达服务器,客户端也需要等待下一个请求周期才能收到消息。
    • 实时性差,特别是在消息量大的场景下,可能会造成明显的延迟。
  2. 资源浪费:
    • 每次请求都需要消耗网络带宽和计算资源。如果客户端频繁请求,甚至当没有新消息时,也会造成不必要的资源浪费。
    • 对服务器和网络带宽的负担较重,尤其是大量客户端同时进行轮询时,可能导致服务器负载过高。
  3. 服务器压力大:
    • 如果客户端数量非常庞大,定时轮询会导致服务器频繁处理大量的请求,增加了服务器的负载。
    • 如果轮询间隔过短,可能导致服务器无法处理所有请求,甚至出现性能瓶颈。
  4. 不适合高并发场景:
    • 当客户端数目较多,且每个客户端请求频繁时,服务器可能难以应对高并发请求,尤其是在没有采用合适的负载均衡机制时。

5. 优化方法

  1. 动态调整轮询间隔:
    • 可以根据网络延迟或消息的到达频率动态调整轮询间隔。例如,系统可以在用户长时间没有新消息时逐渐延长轮询间隔,反之则缩短轮询间隔。
  2. 长轮询(Long Polling):
    • 长轮询是定时轮询的改进版本,客户端在请求时,服务器不会立即返回响应,而是保持连接直到有新消息为止。长轮询能够减少请求次数,但也会保持连接的长时间打开,从而降低延迟。
  3. 批量请求:
    • 可以批量处理消息,避免每次请求都处理单个消息的情况,减少请求的数量。
  4. 缓存和压缩:
    • 对重复的请求结果进行缓存,避免不必要的请求响应过程。
    • 使用消息压缩技术减少带宽消耗,提升系统的传输效率。

6. 应用场景

定时轮询适合于以下几种场景:

  • 低消息频率的应用: 如果消息产生的频率较低,定时轮询的延迟影响较小,适合使用定时轮询。
  • 资源有限的环境: 比如老旧的网络环境,或者客户端设备性能较低时,定时轮询比长连接更适合。
  • 不要求即时性: 如果系统对消息的实时性要求不高,定时轮询可以作为一种实现方式。

7. 总结

定时轮询作为一种简单的消息推送策略,虽然易于实现,但在高并发、实时性要求较高的系统中可能会遇到性能瓶颈和延迟问题。它更适合低频消息推送和不要求高实时性的场景。为了优化定时轮询的性能,可以考虑使用长轮询或者其他更高效的消息推送技术,如WebSocket。

后端是 java springboot 技术栈,前段主要是 js vue 技术栈。给出定时轮训的前后端代码实现

好的,下面是基于 Java Spring Boot 和 Vue.js + JavaScript 技术栈的定时轮询实现示例。

1. 后端(Spring Boot)实现

在后端,我们通过一个定时的接口返回消息(模拟消息的产生)。我们可以使用 @Scheduled 注解模拟消息的生成。这里的接口每次被调用时,会检查是否有新消息。

1.1 Spring Boot 控制器

  [java]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package com.example.demo.controller; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.concurrent.atomic.AtomicInteger; @RestController public class MessageController { // 模拟一个消息队列的计数器 private AtomicInteger messageCount = new AtomicInteger(0); // 定时生成消息,每隔5秒生成一个新消息 @Scheduled(fixedRate = 5000) public void generateNewMessage() { // 假设每次定时任务都会生成一个新消息 messageCount.incrementAndGet(); System.out.println("生成新消息,当前消息数:" + messageCount.get()); } // 轮询接口:客户端每次请求时,返回当前的消息数 @GetMapping("/poll") public String pollMessages() { int count = messageCount.get(); if (count > 0) { // 模拟返回新消息 messageCount.set(0); // 处理完消息后,清零 return "你有新消息,消息数:" + count; } else { return "没有新消息"; } } }

1.2 Spring Boot 配置

确保在 application.propertiesapplication.yml 中启用定时任务:

  [properties]
1
spring.task.scheduling.pool.size=2

2. 前端(Vue.js + JavaScript)实现

在前端,我们可以使用 setInterval 来实现定时轮询,每隔一定时间发送请求检查是否有新消息。

2.1 Vue.js 组件

假设我们有一个简单的 Vue 组件,它会每 5 秒发一次请求检查新消息:

  [vue]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
<template> <div> <h1>消息通知</h1> <p></p> </div> </template> <script> export default { data() { return { message: '正在等待新消息...', }; }, mounted() { // 设置定时器,每隔5秒轮询一次服务器 this.startPolling(); }, methods: { async startPolling() { try { setInterval(async () => { // 发送请求检查是否有新消息 const response = await this.fetchNewMessages(); this.message = response; }, 5000); // 每5秒请求一次 } catch (error) { console.error("轮询请求失败:", error); } }, async fetchNewMessages() { const response = await fetch('/poll'); // 调用后端轮询接口 const data = await response.text(); // 获取文本数据 return data; }, }, }; </script>

3. 运行与测试

  1. 后端:启动 Spring Boot 应用,确保你能访问 /poll 接口(例如 http://localhost:8080/poll)。
  2. 前端:启动 Vue.js 应用,确保它定期调用后端接口并展示消息。

4. 优化和改进

  • 长轮询:当前实现的是定时轮询,每次固定时间间隔请求。为了更好的性能和实时性,你可以使用长轮询(Long Polling),即客户端请求后,如果没有消息,服务器保持连接直到有消息为止。这样可以减少不必要的请求。

  • 错误处理:可以增加错误重试机制、网络中断时的处理等。

  • 负载均衡:如果系统访问量较大,可以考虑对轮询请求进行负载均衡,避免单个服务器压力过大。

5. 总结

这个简单的定时轮询实现展示了如何使用 Spring Boot 在后端生成消息,并通过一个轮询接口提供给前端。而前端通过 setInterval 定时向后端发送请求,获取消息并更新 UI。

总结

参考资料