当前位置: 首页 > news >正文

Spring Boot 与 WebSocket:长连接掉线、心跳与消息广播的问题

Spring Boot 与 WebSocket:长连接掉线、心跳与消息广播的问题

文章目录

  • Spring Boot 与 WebSocket:长连接掉线、心跳与消息广播的问题
  • 一、前言
  • 二、为什么选择 WebSocket?
  • 三、Spring Boot 集成 WebSocket 的基础写法
    • 1、添加依赖
    • 2、 编写配置与端点
  • 四、连接为什么总掉?
  • 五、正确的心跳机制:前后端都要配合
  • 六、集群部署下的“广播失效”问题
  • 七、消息泛滥与内存泄漏
  • 八、Spring Boot + STOMP 简化方案(可选)
  • 九、总结:稳定的 WebSocket 要点

一、前言

WebSocket 是前后端实时通信的利器,但凡你写过在线聊天、系统通知、看板监控,十有八九都用过它。 然而实际开发中,掉线、卡顿、消息延迟、内存暴涨等问题层出不穷。

很多人以为是“服务器太烂”,其实更多时候,是 WebSocket 的连接机制和 Spring Boot 默认实现没搞懂。

本文我们就从底层原理出发,系统讲清楚——为什么 WebSocket 会掉线?心跳机制怎么做才靠谱?消息广播如何不炸内存?

二、为什么选择 WebSocket?

HTTP 是“请求-响应”模型,前端必须主动发起请求,服务端被动回应。 而 WebSocket 属于“全双工通信”,一旦建立连接,客户端和服务端就能相互推消息。

这让很多实时场景成为可能:

  • 聊天室消息推送
  • 系统状态监控
  • 股票/交易数据实时刷新
  • 即时通知提醒
    但别忘了:连接一旦长期存在,就要面对断线重连、心跳检测、资源回收等麻烦事。

三、Spring Boot 集成 WebSocket 的基础写法

最基本的 WebSocket 服务端实现,通常只需两步:

1、添加依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、 编写配置与端点

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {registry.addHandler(new ChatHandler(), "/chat").setAllowedOrigins("*");}
}

接着写个简单的处理器:

@Component
public class ChatHandler extends TextWebSocketHandler {private final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();@Overridepublic void afterConnectionEstablished(WebSocketSession session) {sessions.put(session.getId(), session);System.out.println("连接建立:" + session.getId());}@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {for (WebSocketSession s : sessions.values()) {if (s.isOpen()) {s.sendMessage(message);}}}@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) {sessions.remove(session.getId());System.out.println("连接关闭:" + session.getId());}
}

运行后前端连接:

const socket = new WebSocket("ws://localhost:8080/chat");
socket.onmessage = (e) => console.log(e.data);
socket.send("hello");

一切似乎完美——直到你上线。

四、连接为什么总掉?

WebSocket 本身的协议很稳定,问题往往出在 网络环境和中间层 上。

常见掉线原因包括:

  1. 反向代理或负载均衡超时比如 Nginx 默认 proxy_read_timeout 只有 60s,超过这个时间没数据传输就断。
  2. 客户端断线未检测到浏览器或移动端断网后,TCP 连接其实已死,但服务端 session 仍“以为”它活着。
  3. 心跳机制缺失WebSocket 没有内置心跳,需要手动实现。
  4. 服务器重启或集群切换session 保存在内存中,节点切换就断。

五、正确的心跳机制:前后端都要配合

心跳包本质是“定期发送一条确认连接活性的消息”,防止连接假死。

前端实现(示例)

let ws = new WebSocket("ws://localhost:8080/chat");
let heartbeatInterval;ws.onopen = () => {heartbeatInterval = setInterval(() => {ws.send(JSON.stringify({type: "ping"}));}, 30000);
};

服务端处理心跳

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {String payload = message.getPayload();if ("ping".equals(payload)) {session.sendMessage(new TextMessage("pong"));return;}// 正常消息广播for (WebSocketSession s : sessions.values()) {if (s.isOpen()) s.sendMessage(message);}
}

这样浏览器每 30 秒发一次 ping,服务端回复 pong,既能维持连接,也能检测超时。

六、集群部署下的“广播失效”问题

当你把服务从单机部署改成集群,就会发现: 用户 A 连到节点 1 发消息,用户 B 连到节点 2,消息却收不到。

这是因为每个节点各自维护独立的 sessions,它们之间不共享。

✅ 解决方案:引入消息中间件

最常见的方式是使用 Redis Pub/Sub:

@Component
public class WebSocketRedisListener implements MessageListener {private final Map<String, WebSocketSession> sessions;public WebSocketRedisListener(Map<String, WebSocketSession> sessions) {this.sessions = sessions;}@Overridepublic void onMessage(Message message, byte[] pattern) {String msg = new String(message.getBody());for (WebSocketSession session : sessions.values()) {try {session.sendMessage(new TextMessage(msg));} catch (IOException ignored) {}}}
}

然后在发送消息时:

redisTemplate.convertAndSend("chat-channel", jsonMessage);

这样,无论消息在哪个节点产生,所有节点都能广播出去。

Redis 的轻量特性非常适合这种场景,不推荐直接上 Kafka 或 RabbitMQ,延迟太高。

七、消息泛滥与内存泄漏

WebSocket 消息广播虽然方便,但如果你不控制消息频率、缓存清理和 session 生命周期,很容易出现:

  • 内存逐步升高;
  • GC 频繁;
  • CPU 飙高;
  • “Ghost session”(僵尸连接)堆积。

优化思路:

  1. 使用 ConcurrentHashMap 存储 session,并在 afterConnectionClosed 时及时清理;
  2. 定期检测 session.isOpen(),关闭无效连接;
  3. 对消息频率较高的场景(如行情推送)增加队列缓冲;
  4. 不建议在内存中维护大量 session,可考虑使用 Redis + Channel 分布式方案。

八、Spring Boot + STOMP 简化方案(可选)

Spring Boot 其实提供了更“高级”的封装:STOMP + SockJS + MessageBroker。

简单来说:

  • STOMP 是一种基于 WebSocket 的消息协议;
  • SockJS 兼容性更强,支持自动降级;
  • Spring Boot 自动帮你做 session 管理、广播、订阅等操作。

只需配置:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {@Overridepublic void registerStompEndpoints(StompEndpointRegistry registry) {registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();}@Overridepublic void configureMessageBroker(MessageBrokerRegistry registry) {registry.enableSimpleBroker("/topic");registry.setApplicationDestinationPrefixes("/app");}
}

前端直接:

const socket = new SockJS('/ws');
const client = Stomp.over(socket);client.connect({}, () => {client.subscribe('/topic/message', (msg) => {console.log(msg.body);});client.send('/app/chat', {}, JSON.stringify({content: 'hello'}));
});

Spring Boot 自动处理订阅、广播、心跳等问题,极大减少手写代码。

九、总结:稳定的 WebSocket 要点

问题原因解决方案
长连接掉线反向代理超时、无心跳定时心跳 + Nginx proxy_read_timeout
假死连接客户端断网未检测定期 ping/pong
集群广播失败Session 不共享Redis Pub/Sub
内存泄漏Session 未清理定期检测 + 主动关闭
兼容性差浏览器/代理不支持使用 SockJS/STOMP

WebSocket 并不是“配置一下就能用”的黑盒技术。 一旦连接长期存在,它就和数据库连接池一样,需要监控、清理、心跳、限流。

在 Spring Boot 项目中,用好心跳机制、合理维护 session、配合 Redis 实现广播,才能真正让你的实时通信系统稳如老狗。

http://www.dtcms.com/a/464982.html

相关文章:

  • 数琨创享:新能源行业标杆企业QMS质量管理平台案例
  • 如何用easyui做网站网站设计说明书5000字
  • 从MySQL到ClickHouse超大规模数据分析的架构迁移实践与性能对比
  • 架构图在什么网站可以做wordpress-saas
  • echarts不根据传入参数,自定义 legend 的内容(视觉映射)
  • H3C IRF
  • 【C++】继承深度解析:继承方式和菱形虚拟继承的详解
  • 徐州 网站 备案 哪个公司做的好phpcms 中英文网站
  • WebSocket | 一点简单了解
  • 算法题基础 : Java : BufferedReader、BufferedWriter 和 StringTokenizer 详解
  • 企业微信 自建应用审批流程引擎功能开发【报错分析】
  • Slf4j 接口文档左侧菜单有显示,但是点击后空白
  • 【AES加密专题】4.Sbox的解析和生成
  • 考完HCIE数通,能转云计算 / 安全 / AI方向吗?
  • 重庆企业网站建设推荐怎么申请域名和备案
  • 松江 网站建设公司拼多多推广联盟
  • 中国极端气象干旱事件(1951-2022)
  • 一文详解Go 语言内存逃逸(Escape Analysis)
  • 学习threejs,实现粒子化交互文字
  • 密码学基础:RSA与AES算法的实现与对比
  • RAG:生成与检索的完美结合
  • 一款由网易出品的免费、低延迟、专业的远程控制软件,支持手机、平板、Mac 、PC、TV 与掌机等多设备远控电脑!
  • [C# starter-kit] Blazor EntityTable 组件 | 预构建
  • 深入浅出 AI Agent:从概念本质到技术基石
  • 宁波网站制作服务wordpress搭建淘客网站
  • 第五章:Go的“面向对象”编程
  • 【实用工具】mac电脑计算文件的md5、sha1、sha256
  • 数据结构算法学习:LeetCode热题100-矩阵篇(矩阵置零、螺旋矩阵、旋转图像、搜索二维矩阵 II)
  • CAD文件处理控件Aspose.CAD教程:在 Python 中将 SVG 转换为 PDF
  • Go语言游戏后端开发9:Go语言中的结构体