讲解“实时”是怎么实现的

1.理论

"实时"在互联网技术中,是指信息从产生到被接收方看到,延迟极低(通常在毫秒到几百毫秒之间),并且具有持续、主动推送的特性,而不是接收方反复询问。

要实现这种效果,需要前端、后端、网络协议三者紧密配合。下面我从最基础的通信模型讲起,逐步深入到现代实时系统的完整架构。


一、核心思想:从"拉"到"推"

传统的 HTTP 请求是 客户端拉取(Pull) 模式:客户端主动问,服务器才回答。

实时系统需要 服务器推送(Push) 模式:服务器一有新数据,立刻主动发给客户端。

打个比方:

  • 拉取 = 你每隔几秒打电话问外卖员"到了吗?"

  • 推送 = 外卖员到了主动打电话给你。


二、实时通信的技术演进(后端 → 前端)

1. 短轮询(Short Polling)------ 最原始,不算真实时

  • 过程 :前端用 setInterval 每几秒发一次 HTTP 请求,问"有新消息吗?"

  • 缺点:大量无效请求,浪费带宽和 CPU;实时性差(延迟 = 轮询间隔)。

  • 现状:几乎被淘汰。

2. 长轮询(Long Polling)------ 伪实时,兼容性好

  • 过程

    1. 前端发起 HTTP 请求,服务器挂起这个请求,不立即返回。

    2. 当有数据可推时,服务器才返回响应。

    3. 前端收到后立即再发起下一个长轮询请求。

  • 优点:兼容所有 HTTP/1.1 环境,无需特殊协议。

  • 缺点:依然有 HTTP 头开销;服务器需维护大量挂起连接;消息顺序难保证。

  • 典型应用:早期网页版 IM(如 Facebook Chat 早期)。

3. Server-Sent Events (SSE) ------ 单向实时推送

  • 过程

    • 前端通过 EventSource API 建立连接,服务器持续输出 text/event-stream 格式的数据流。

    • 连接不断开,服务器可随时发消息,但只能服务器→客户端

  • 优点:基于 HTTP,轻量,自动重连,支持自定义事件。

  • 缺点:单向;不支持二进制数据(需 base64);浏览器连接数有限制。

  • 场景:股票行情、微博推送、实时通知。

4. WebSocket ------ 真正的全双工实时通信

  • 过程

    1. 前端发起 HTTP 升级请求(Upgrade: websocket)。

    2. 服务器同意后,协议升级为 WebSocket,连接保持。

    3. 此后双方可以随时互相发送消息(文本或二进制),帧头极小(2~14 字节)。

  • 优点

    • 真正的实时、全双工。

    • 极低延迟(可做到几十毫秒)。

    • 服务端可主动推送,客户端也可主动发。

    • 支持二进制数据。

  • 缺点:需要服务器支持;部分老旧代理可能拦截。

  • 场景:在线游戏、实时弹幕、协同编辑、交易系统。

5. WebRTC 数据通道 ------ P2P 极致实时

  • 过程:浏览器之间直接建立点对点连接,通过 UDP 传输,中间可绕开服务器(但仍需信令服务器协助建立连接)。

  • 优点:延迟极低(几十毫秒甚至更低),适合实时音视频和极速数据交换。

  • 缺点:实现复杂,NAT 穿透困难,不适合大规模广播。

  • 场景:视频会议、屏幕共享、P2P 游戏。

目前主流实时系统(弹幕、聊天、协作)的首选是 WebSocket,辅以 SSE 或长轮询做降级。


三、后端架构:如何支撑海量实时连接?

单个 WebSocket 连接很简单,但百万、千万并发时,后端面临巨大挑战。

1. 连接管理 ------ 事件驱动 + 非阻塞 I/O

传统线程/进程模型(一个连接一个线程)无法支撑大量连接。现代后端使用 事件驱动模型(如 Node.js、Netty、Nginx、EPoll),一个线程可处理数万连接。

2. 协议层 ------ WebSocket 帧处理

后端需要解析 WebSocket 帧(文本、二进制、ping/pong、关闭帧),并维护连接状态。

3. 消息路由 ------ 房间/频道(Room/Topic)

实时系统通常有"房间"概念(如直播间、聊天群)。

  • 当一条消息到达服务器,服务器需根据消息的目标房间,找到该房间内所有在线连接,然后逐个发送。

  • 高效的数据结构:哈希表 key = roomId,value = Set<ConnectionId>。

4. 水平扩展 ------ 多节点同步问题

当后端有多个服务器节点时,用户 A 连接到节点 1,用户 B 连接到节点 2。如果 A 发消息给 B,节点 1 必须知道 B 在节点 2 上。

解决方案:

  • 消息代理(Message Broker):如 Redis Pub/Sub、Kafka、RabbitMQ。节点 1 将消息发布到 Redis 的某个频道,节点 2 订阅该频道,收到后再推给用户 B。

  • 全量广播:节点 1 向所有其他节点广播该消息(效率低)。

  • 一致性哈希:根据 roomId 或 userId 将连接固定到某个节点,减少跨节点通信。

5. 状态存储 ------ 在线状态、消息历史

  • 在线状态:通常存储在 Redis(内存数据库),支持快速读写和过期设置。

  • 消息历史:对于离线用户,需要存储消息,等用户上线后拉取。常用 Kafka + 持久化数据库(Cassandra、TiDB)。


四、前端实现:从接收到渲染的实时管道

后端推来数据后,前端要高效地接收并更新界面。

1. 接收数据 ------ WebSocket / SSE API

javascript

复制代码
const ws = new WebSocket('wss://example.com/room/123');
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  // 交给渲染引擎
};

2. 数据处理 ------ 防抖、节流、缓冲

如果消息来得太快(如每秒几千条弹幕),直接每条都触发 DOM 更新会导致浏览器卡死。
策略

  • 缓冲队列 :将消息放入数组,用 requestAnimationFrame 批量取出并渲染。

  • 节流:限制每秒最多更新 UI 的次数(例如 60 次)。

3. 渲染 ------ 高性能动画

以弹幕为例:

  • 使用 Canvas 代替 DOM 元素,在每一帧中重绘所有弹幕。

  • 使用 requestAnimationFrame 驱动动画循环,保证与屏幕刷新率同步。

  • 对于文本聊天,使用虚拟滚动技术,只渲染可见区域的消息。

4. 重连与状态管理

  • WebSocket 可能断开(网络波动)。前端需实现指数退避重连

  • 重连后可能需要发送 sync 消息,从服务器拉取离线期间遗漏的消息。


五、完整实时数据流示例(直播间弹幕)

  1. 连接建立

    • 用户进入直播间,前端发起 WebSocket 连接,并携带 roomId、token。
  2. 后端接入

    • 网关节点接收连接,将连接信息注册到 Redis(roomId → connectionId)。

    • 该节点加入 Redis Pub/Sub 频道 room:123

  3. 用户 A 发送弹幕

    • 前端通过 WebSocket 发送 {type:'danmaku', content:'666', roomId:123}

    • 节点 1 收到后,将消息存储到消息队列(如 Kafka)用于持久化。

    • 同时,节点 1 将消息发布到 Redis 频道 room:123

  4. 广播给房间内所有用户

    • 所有订阅了 room:123 的节点(包括节点 1、节点 2、节点 3...)都会收到这条消息。

    • 每个节点查询本地维护的房间连接表,找到所有连接(用户 B、C...),通过各自的 WebSocket 连接推送消息。

  5. 前端渲染

    • 用户 B 的前端 WebSocket onmessage 触发,解析 JSON。

    • 将弹幕数据加入渲染队列。

    • requestAnimationFrame 循环读取队列,绘制到 Canvas 上。

  6. 延迟分析

    • A 发送 → 节点 1 处理(微秒级)→ Redis 广播(毫秒级)→ 节点 2 收到(毫秒级)→ 推送至 B(毫秒级)→ 浏览器渲染(几毫秒)。

    • 端到端延迟通常 20~100ms


六、不同实时场景的技术选型

场景 推荐技术 原因
股票/币价行情 SSE 或 WebSocket 单向推送,数据量大,要求轻量。
在线聊天(群聊/私聊) WebSocket + Redis Pub/Sub 全双工,需要发送消息,低延迟。
实时弹幕 WebSocket + Canvas 高吞吐,双向(弹幕可发),低延迟。
协同编辑(如 Google Docs) WebSocket + OT/CRDT 需要顺序保证和冲突解决,双向通信。
实时音视频 WebRTC 极低延迟,P2P,需要媒体传输。
服务器监控仪表盘 SSE 简单,单向推送指标数据。
游戏同步(非帧同步) WebSocket / UDP (自定义) 低延迟,可靠/不可靠混合。

七、总结:"实时"的本质

  1. 通信模型:从客户端拉取 → 服务器推送。

  2. 协议选择:WebSocket 是目前双向实时的主流;SSE 适合单向推送;WebRTC 用于极致 P2P。

  3. 后端挑战:连接管理、水平扩展、跨节点消息路由、状态同步。

  4. 前端挑战:高性能接收、缓冲、渲染、重连。

  5. 延迟权衡 :真正的实时并不意味着零延迟,而是低且可预测的延迟,用户体验平滑。

一句话:实时 = 持久化全双工通道 + 事件驱动推送 + 高效端到端渲染

2.代码

用vue+springboot代码示例讲解

下面通过一个实时聊天室 的完整代码示例,讲解如何使用 Vue 3 + Spring Boot 2/3 + WebSocket 实现真正的实时通信。


一、整体架构

  • 后端:Spring Boot 提供 WebSocket 端点,支持消息收发与广播。

  • 前端:Vue 3 通过原生 WebSocket API 连接后端,发送/接收消息。

  • 实时核心:WebSocket 全双工通道,服务器可主动推送消息到所有客户端。


二、后端实现(Spring Boot + WebSocket)

1. 添加依赖(pom.xml)

xml

复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2. WebSocket 配置类

java

复制代码
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册 WebSocket 处理器,并允许跨域(方便前端调试)
        registry.addHandler(new ChatWebSocketHandler(), "/chat")
                .setAllowedOrigins("*");
    }
}

3. 核心处理器:处理 WebSocket 消息

java

复制代码
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.util.concurrent.CopyOnWriteArraySet;

public class ChatWebSocketHandler extends TextWebSocketHandler {

    // 线程安全的在线会话集合
    private static final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
        System.out.println("新连接:" + session.getId());
        broadcast("系统消息:有新人加入", "system");
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("收到消息:" + payload);
        // 广播给所有客户端(包括自己,前端可做去重或乐观更新)
        broadcast(payload, session.getId());
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
        System.out.println("连接关闭:" + session.getId());
        broadcast("系统消息:有人离开了", "system");
    }

    // 广播消息给所有在线客户端
    private void broadcast(String msg, String excludeSessionId) {
        for (WebSocketSession session : sessions) {
            if (session.isOpen() && !session.getId().equals(excludeSessionId)) {
                try {
                    session.sendMessage(new TextMessage(msg));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

说明:这里简化了消息格式,实际项目中可将消息包装成 JSON(包含用户名、时间、内容等)。

4. 启动类(无特殊配置)

java

复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RealTimeApplication {
    public static void main(String[] args) {
        SpringApplication.run(RealTimeApplication.class, args);
    }
}

三、前端实现(Vue 3 + WebSocket API)

1. 创建 Vue 项目(略)

安装 axios 或直接使用 fetch,这里只需 WebSocket。

2. 聊天组件 ChatRoom.vue

vue

复制代码
<template>
  <div class="chat-container">
    <div class="header">💬 实时聊天室 (Vue + Spring Boot WebSocket)</div>
    <div class="messages" ref="messagesContainer">
      <div v-for="(msg, idx) in messages" :key="idx" class="message" :class="msg.isOwn ? 'own' : 'other'">
        <strong>{{ msg.sender }}</strong> {{ msg.text }}
        <span class="time">{{ msg.time }}</span>
      </div>
    </div>
    <div class="input-area">
      <input v-model="inputText" @keyup.enter="sendMessage" placeholder="输入消息..." />
      <button @click="sendMessage">发送</button>
    </div>
    <div class="status">{{ statusText }}</div>
  </div>
</template>

<script>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'

export default {
  name: 'ChatRoom',
  setup() {
    const messages = ref([])
    const inputText = ref('')
    const statusText = ref('连接中...')
    let ws = null
    const messagesContainer = ref(null)

    // 生成随机用户名
    const username = '用户_' + Math.floor(Math.random() * 1000)

    // 添加消息
    const addMessage = (sender, text, isOwn = false) => {
      messages.value.push({
        sender,
        text,
        time: new Date().toLocaleTimeString(),
        isOwn
      })
      // 自动滚动到底部
      nextTick(() => {
        if (messagesContainer.value) {
          messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
        }
      })
    }

    // 发送消息
    const sendMessage = () => {
      const text = inputText.value.trim()
      if (!text || !ws || ws.readyState !== WebSocket.OPEN) return

      const messageData = {
        type: 'chat',
        sender: username,
        text: text,
        timestamp: Date.now()
      }
      // 发送 JSON 字符串
      ws.send(JSON.stringify(messageData))
      // 乐观更新:立即显示在自己的聊天区
      addMessage('我', text, true)
      inputText.value = ''
    }

    // 初始化 WebSocket
    const initWebSocket = () => {
      // 注意:后端地址根据实际修改,Spring Boot 默认端口 8080
      const wsUrl = 'ws://localhost:8080/chat'
      ws = new WebSocket(wsUrl)

      ws.onopen = () => {
        statusText.value = '✅ 已连接 (实时推送)'
        addMessage('系统', '欢迎进入聊天室', false)
      }

      ws.onmessage = (event) => {
        // 后端广播的是纯文本,实际可解析 JSON
        const raw = event.data
        try {
          const data = JSON.parse(raw)
          // 避免重复显示自己刚发的(已经乐观更新过了)
          if (data.sender !== username) {
            addMessage(data.sender, data.text, false)
          }
        } catch (e) {
          // 纯文本系统消息
          addMessage('系统', raw, false)
        }
      }

      ws.onclose = () => {
        statusText.value = '❌ 连接断开,5秒后重连...'
        setTimeout(() => {
          initWebSocket()
        }, 5000)
      }

      ws.onerror = (error) => {
        console.error('WebSocket 错误', error)
        statusText.value = '⚠️ 连接错误'
      }
    }

    onMounted(() => {
      initWebSocket()
    })

    onUnmounted(() => {
      if (ws) {
        ws.close()
      }
    })

    return {
      messages,
      inputText,
      statusText,
      messagesContainer,
      sendMessage
    }
  }
}
</script>

<style scoped>
.chat-container {
  width: 500px;
  max-width: 90%;
  margin: 20px auto;
  border: 1px solid #ccc;
  border-radius: 12px;
  overflow: hidden;
  font-family: sans-serif;
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}
.header {
  background: #4f46e5;
  color: white;
  padding: 12px;
  text-align: center;
  font-weight: bold;
}
.messages {
  height: 400px;
  overflow-y: auto;
  padding: 12px;
  background: #f9fafb;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.message {
  max-width: 70%;
  padding: 8px 12px;
  border-radius: 18px;
  word-wrap: break-word;
}
.message.own {
  background: #4f46e5;
  color: white;
  align-self: flex-end;
}
.message.other {
  background: #e5e7eb;
  color: #1f2937;
  align-self: flex-start;
}
.time {
  font-size: 10px;
  margin-left: 8px;
  opacity: 0.7;
}
.input-area {
  display: flex;
  padding: 12px;
  gap: 8px;
  border-top: 1px solid #e2e8f0;
}
.input-area input {
  flex: 1;
  padding: 8px 12px;
  border-radius: 24px;
  border: 1px solid #cbd5e1;
  outline: none;
}
.input-area button {
  background: #4f46e5;
  color: white;
  border: none;
  border-radius: 24px;
  padding: 8px 16px;
  cursor: pointer;
}
.status {
  padding: 6px;
  text-align: center;
  font-size: 12px;
  background: #f1f5f9;
  color: #475569;
}
</style>

3. 在 App.vue 中使用该组件

vue

复制代码
<template>
  <ChatRoom />
</template>

<script>
import ChatRoom from './components/ChatRoom.vue'
export default {
  components: { ChatRoom }
}
</script>

四、运行与测试

  1. 启动 Spring Boot :运行 RealTimeApplication,后端监听 8080 端口,WebSocket 路径 ws://localhost:8080/chat

  2. 启动 Vue 前端npm run serve,通常端口 51738080(注意与后端不冲突)。

  3. 打开多个浏览器窗口 访问前端页面,每个窗口会分配随机用户名。发送消息后,所有其他窗口会立即收到消息(延迟通常 < 50ms)。


五、实时原理讲解(结合代码)

层面 关键组件 实时性如何实现
传输协议 WebSocket 前端 new WebSocket(url) 发起 HTTP 升级握手,建立持久化全双工通道,不再需要客户端轮询。
后端连接管理 CopyOnWriteArraySet<WebSocketSession> 每当有客户端连接,会话存入集合;断开时移除。集合用于后续广播。
消息接收 handleTextMessage 后端收到任何消息后,立即触发 broadcast(),遍历所有在线会话并调用 sendMessage()
主动推送 session.sendMessage() 服务器不等待请求,直接通过已建立的 WebSocket 连接将数据推送给客户端。这是"实时"的核心。
前端接收 ws.onmessage 浏览器接收到服务器推送的数据,立即解析并更新 DOM(Vue 响应式数据),用户瞬间看到消息。
断开重连 ws.onclose + 定时器 保证网络波动后自动恢复连接,重新建立实时通道。

六、扩展:生产环境增强点

  • 消息格式 :使用 JSON 统一结构,包含 typedatasendertimestamp

  • 房间/频道 :增加 roomId,广播时只发送给同一房间的会话(使用 ConcurrentHashMap 存储房间与会话的映射)。

  • 安全性 :添加 JWT 或 Token 验证(在 WebSocket 握手时通过 HandshakeInterceptor 校验)。

  • 水平扩展:使用 Redis Pub/Sub 实现多节点间的消息同步(一个节点收到消息后发布到 Redis,其他节点订阅并推送给本地客户端)。

  • 前端优化 :对高频消息(如弹幕)使用 requestAnimationFrame + Canvas 渲染,避免大量 DOM 操作。


通过这个完整的 Vue + Spring Boot 示例,你可以清晰理解"实时"的本质:WebSocket 全双工通道 + 服务器主动推送 + 前端即时渲染

相关推荐
ok_hahaha2 小时前
AI从头开始-黑马LongGraph-简单学习
人工智能·学习·langchain·lang graph
三品吉他手会点灯2 小时前
C语言学习笔记 - 4.C概述 - C的特点
c语言·笔记·学习
星幻元宇VR2 小时前
VR科普行走平台:沉浸式科普教育新趋势
学习·安全·生活·vr
小陈phd2 小时前
多模态大模型学习笔记(三十六)——模型管理平台之 Xinference 部署
笔记·学习
一楼的猫2 小时前
茄子小说AI辅助智能写作助手:10倍速创作神器
人工智能·学习·机器学习·学习方法·ai写作·迁移学习·集成学习
吃着火锅x唱着歌2 小时前
深度探索C++对象模型 学习笔记 第四章 Function语意学(1)
c++·笔记·学习
学习论之费曼学习法2 小时前
AI 入门 30 天挑战 - Day 15 费曼学习法版 - 目标检测基础
人工智能·学习·目标检测
看我眼色行事^ \/ ^3 小时前
完整操作指南
服务器·学习
mxwin3 小时前
Unity URP 下 TBN 矩阵学习 切线空间、tangent.w 与镜像 UV 的那些坑
学习·unity·矩阵·shader