Java WebSocket导致的OOM(内存溢出)问题排查

文章讲述了线上工程因WebSocket协议引发的内存溢出问题,通过MAT工具分析定位到HeapCharBuffer和messageBufferText。解决思路包括临时增加内存、产品层面限制ws链接数、超时管理以及运维层面的健康检查和自动重启服务。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.问题背景

线上某个工程总是会出现OOM导致系统宕机

2.排查追踪

拿到dump文件使用MAT工具分析
Overview

在dominator_tree中,AbstractProtocol类占据了绝大部分堆内存
看到AbstractProtocol类猜测可能和协议有关,HTTP、TCP、SMTP、WebSocket等
在这里插入图片描述
继续展开,可以发现WsFrameServer的类占据了大部分堆内存,定位为WebSocket协议导致的内存溢出。
再继续展开,可以发现HeapCharBuffer占据了大部分的内存。
如果项目中只有一个ws功能则就可以定义到具体业务了,如果有多个功能模块涉及ws则需要根据ws地址确认功能。
在这里插入图片描述
通过requestUri可以查看到ws地址和请求ip
在这里插入图片描述
通过上面排查是messageBufferText过大且链接过多导致的内存溢出,查看messageBufferText大小约20MB,则需要搞明白下面的问题:

  • 20MB是否是初始化大小?
  • 如果不是初始化大小,在哪里赋值/配置修改?
  • 如果是初始化大小,在哪里可以赋值/配置修改?

那就需要查看源码分析messageBufferText是怎么初始化大小的。

首先找到了WsFrameServer类,发现messageBufferText是WsFrameServer父类WsFrameBase的成员变量
图片描述
WsFrameBase类
在这里插入图片描述
查看messageBufferText初始化:
CharBuffer.allocate静态方法创建CharBuffer的对象,其中会初始化一个大小为getMaxTextMessageBufferSize的Char类型的数组。
下面主要看wsSession.getMaxTextMessageBufferSize()方法
在这里插入图片描述
wsSession.getMaxTextMessageBufferSize()可以看到获取的是maxTextMessageBufferSize的数值
在这里插入图片描述
查看maxTextMessageBufferSize赋值
有一个默认值是8K,显然与想要的20MB差距比较大,继续看哪里有赋值了
在这里插入图片描述
wsSession.setMaxTextMessageBufferSize()
在这里插入图片描述
查看setMaxTextMessageBufferSize方法的调用
在这里插入图片描述
在代码实现里增加了这个配置,由于char类型占两个字节,1010241024*2 约 20MB,所以这个就是原因
在这里插入图片描述
不过我们继续看另外一个调用PojoMessageHandlerWholeText
在这里插入图片描述
往下追踪发现默认是-1,注解OnMessage的maxMessageSize字段获取值。
在这里插入图片描述

3. 解决思路

  1. 临时解决: 现场临时增加内存分配空间
    ps:默认情况下,-Xms是物理内存的1/64,-Xmx是物理内存的1/4
  2. message初始值大小: 统计ws的maxTextMessageBufferSize大小,设置合理的数值,或考虑压缩message大小。
  3. 产品层面: 问题同步至产品,从产品层面优化。
    如:从根据可用内存对ws链接数做最大限制
  4. 服务优化:
    如:由于在项目中ws是长链接,及时释放不再使用的WebSocket链接(超时)、同一个ip和ws地址使用同一个链接
    超时功能:项目中使用nginx,可以在nginx中增加超时配置proxy_read_timeout
  5. 运维层面: 增加健康检查对于意外崩溃自动重启服务
<think>嗯,用户想了解如何在Java中使用WebSocket调用AI接口并实现流式返回。首先,我需要明确这两个技术点的结合方式。WebSocket是双向通信协议,适合实时数据传输,而流式返回通常指AI接口逐步生成结果,而不是一次性返回所有数据。 首先,用户可能需要一个基本的Java WebSocket客户端示例。我记得Javajavax.websocket库,可以用来创建客户端。需要确保依赖正确,比如使用Tyrus实现。然后,连接AI的WebSocket端点,处理onMessage事件来接收流式数据。 接下来要考虑AI接口的具体要求。比如,是否需要认证参数,消息格式是JSON还是其他,如何分片发送数据。可能需要设置消息处理器,将接收到的每个片段拼装或实时显示。同时,异常处理也很重要,比如网络中断或服务端错误,需要重连机制。 用户可能还关心如何集成到现有项目,比如Spring Boot中的应用。可能需要提到@ServerEndpoint注解配置服务端,但这里主要是客户端调用。此外,流式处理中的性能问题,比如缓冲区管理,避免内存溢出。 还需要注意线程安全,WebSocket客户端通常是异步的,主线程可能需要等待或使用回调。示例代码的结构是否清晰,关键部分如消息处理、会话管理是否到位。可能还需要提到测试方法,比如使用wscat命令行工具模拟服务端。 最后,确保回答步骤清晰,代码示例正确,并提醒用户替换关键参数如URL和API密钥。可能用户实际应用中会遇到的问题,比如如何处理分片数据、如何结束流式传输,这些也需要在注意事项中提到。</think>好的,我将分步骤为您介绍如何在Java中通过WebSocket调用AI接口并处理流式返回: 1. **基础原理** - WebSocket 提供全双工通信通道,适合流式数据传输 - AI接口(如ChatGPT API)流式返回时,会分多次发送token序列 - 需要保持连接持续接收数据片段直至结束 2. **实现步骤** **步骤1:添加依赖** ```xml <!-- Tyrus WebSocket 实现 --> <dependency> <groupId>org.glassfish.tyrus</groupId> <artifactId>tyrus-client</artifactId> <version>2.1.1</version> </dependency> ``` **步骤2:创建WebSocket客户端** ```java import javax.websocket.*; import java.net.URI; @ClientEndpoint public class AIWebSocketClient { private Session session; private final StringBuilder responseBuilder = new StringBuilder(); @OnOpen public void onOpen(Session session) { this.session = session; System.out.println("Connected to AI service"); } @OnMessage public void onMessage(String message) { // 处理流式返回的每个数据块 responseBuilder.append(message); System.out.println("Received chunk: " + message); } @OnClose public void onClose(CloseReason reason) { System.out.println("Connection closed: " + reason); // 处理最终完整响应 System.out.println("Final response:\n" + responseBuilder); } public void sendRequest(String prompt) throws Exception { String jsonRequest = """ { "prompt": "%s", "stream": true, "max_tokens": 1000 } """.formatted(prompt); session.getBasicRemote().sendText(jsonRequest); } public static void main(String[] args) throws Exception { WebSocketContainer container = ContainerProvider.getWebSocketContainer(); String wsUrl = "wss://api.example-ai.com/v1/chat/stream"; container.connectToServer( new AIWebSocketClient(), URI.create(wsUrl) ); } } ``` 3. **关键处理逻辑** - **流式拼接**:通过StringBuilder持续收集返回片段 - **心跳机制**:定期发送Ping消息保持连接 - **错误处理**: ```java @OnError public void onError(Throwable t) { System.err.println("Error occurred: " + t.getMessage()); // 实现重连逻辑 } ``` 4. **集成AI接口的注意事项** - 认证处理:通常在连接时添加Header ```java ClientEndpointConfig config = ClientEndpointConfig.Builder.create() .configurator(new ClientEndpointConfig.Configurator() { @Override public void beforeRequest(Map<String, List<String>> headers) { headers.put("Authorization", Collections.singletonList("Bearer YOUR_API_KEY")); } }) .build(); ``` - 流控制:根据返回速度调整接收频率 - 超时设置:建议30-60秒无响应自动断开 5. **Spring Boot集成示例** ```java @RestController public class AIController { @GetMapping("/ask") public SseEmitter askQuestion(@RequestParam String question) { SseEmitter emitter = new SseEmitter(); AIWebSocketClient client = new AIWebSocketClient(); client.setResponseHandler(chunk -> { emitter.send(chunk); }); client.sendRequest(question); return emitter; } } ``` **测试建议**: 1. 使用`wscat`命令行工具测试WebSocket连接 2. 模拟流式响应验证数据拼接逻辑 3. 压力测试连接稳定性 **典型问题处理**: - 网络中断:实现自动重连机制 - 数据乱序:添加序列号保证顺序 - 大响应处理:设置合理缓冲区大小 完整实现需要考虑: 1. 连接池管理 2. 流量控制 3. 异步回调处理 4. 与其他服务(如Redis)的集成 实际应用中建议使用成熟的WebSocket库(如Netty)处理底层协议,并根据具体AI服务的API文档调整消息格式。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值