☰
选型纠结与架构落地
2026/9/30 20:14:39 网站建设 项目流程

我花了两周把客户旧系统的定时轮询改成了WebSocket长连接,光部署就踩了三个大坑。

最近帮一个做供应链监控的客户做系统升级。他们之前用Spring Boot + 定时任务,每10秒拉一次数据库里的订单状态变更。业务量小的时候还行,上个月并发涨到5000+,数据库连接池直接打满了,客服后台刷新页面要转圈等8秒。客户老板急得直拍桌子,说再不动手就要换供应商了。

说实话,当时我觉得加个Redis缓存就能糊弄过去,试了一圈发现治标不治本。每次轮询都要查全量数据,哪怕只有一条变化也要走网络传输,前端还得自己diff。这方案在QPS破万的场景下纯属浪费带宽。最后不得不咬牙做WebSocket迁移,把“拉”改成“推”。

选型纠结与架构落地

最开始我纠结是用Spring框架自带的SimpMessagingTemplate,还是引入Netty原生。前者开发快,注解一下就行;后者性能好,但得自己管连接生命周期。

我选了Spring Native方案,因为团队全是Java背景,没人熟悉Netty的ChannelHandler管道模型。但坑就出在这儿——默认实现用的是内存Map存Session,一断线重连就容易内存泄漏。

```java
@Configuration
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompWebSocketHandlerRegistry registry) {
registry.addEndpoint("/ws").setAllowedOrigins("*").withSockJS();
}

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
}
```

关键改动在消息发布端。原来定时任务扫描完直接log.info("更新成功"),现在改成往/topic/order发JSON。但这里有个隐蔽问题:Stomp默认心跳间隔是10秒,网络抖动超过这个时间连接就断了。我把heartbeat-atm调到5秒,同时前端加了重连逻辑。

有意思的是,测试环境一切正常,上线后凌晨三点告警炸了。排查发现Nginx反向代理默认proxy_read_timeout是60秒,长连接空闲超过这个时间就被强制断开。改完proxy_read_timeout 3600s才稳住。

生产环境的血泪教训

最惨的是压测阶段。用JMeter模拟3000个并发用户,发现服务端GC频率异常高。用JVisualVM一看,老年代占用率90%,Full GC耗时2.3秒。

当时我觉得这样就行,结果发现错了——不是GC策略问题,是消息序列化用的Jackson默认配置太蠢。每条消息都带着完整ClassLoader反射信息,内存碎片化严重。

我做了两个调整:一是改用ObjectMapper并配置PropertyNamingStrategy,减少不必要的getter调用;二是把消息体瘦身,只推orderId和status两个字段,详情让前端按需查API。

```java
public class OrderStatusDTO {
private String orderId;
private Integer status;
// 只保留必要字段,避免序列化膨胀
}
```

还有个没写进文档的坑:浏览器同一域名限制6个连接。如果用户开了多个监控页面,第7个WebSocket就会pending。我在网关层加了LimitRequestRate,限制单IP最多3个活跃连接,超出就返回429。

最后交付时,P99延迟从8.2秒降到300ms以内,数据库QPS降了70%。客户技术总监拍我肩膀说:“早知道早改了,白扛了三个月。”

本文基于实际项目经验整理,欢迎在评论区交流技术问题。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询