Spring Boot中如何基于WebSocket事件正确配置@Scheduled任务
问题诊断
你当前的实现逻辑是错误的,核心问题是对该币安SDK的WebSocket API行为理解有误:
BinanceApiWebSocketClient的onCandlestickEvent方法不是单次拉取事件的方法,每调用一次就会新建一条独立的WebSocket长连接,向币安服务器发起一次K线流订阅。只要连接不中断,后续新的K线事件产生时,服务器会主动推送到你注册的回调函数,完全不需要重复调用该方法。- 你把订阅逻辑放在了每秒执行一次的
@Scheduled定时任务里,相当于每秒都会新增一条重复的K线订阅连接,旧连接从未被关闭。每一条连接都会推送订阅生效后的所有K线事件,运行时间越长,冗余连接越多,自然会出现事件大量堆积、重复打印的问题,还会产生无意义的带宽和内存损耗。
正确实现方案
WebSocket本身就是事件驱动的长连接协议,不需要任何定时轮询逻辑,只需要在应用启动时初始化一次订阅,事件实际产生时会自动触发回调,不会有堆积问题。
修正后的监听器代码
你可以直接删除原来带@Scheduled的监听类,换成下面的实现:
import lombok.extern.slf4j.Slf4j; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; @Component @Slf4j public class BinanceCandlestickListener { private final BinanceApiWebSocketClient webSocketClient; // 构造器注入,比字段@Autowired更符合Spring最佳实践 public BinanceCandlestickListener(BinanceApiWebSocketClient webSocketClient) { this.webSocketClient = webSocketClient; } /** * 应用启动完成后仅执行一次,初始化K线订阅 */ @EventListener(ApplicationReadyEvent.class) public void initKlineSubscription() { // 仅调用一次订阅方法,建立长连接 webSocketClient.onCandlestickEvent( "ethbtc", CandlestickInterval.ONE_MINUTE, event -> { // 此处编写K线事件的业务处理逻辑,事件到达时会自动触发 log.info("收到ETH/BTC 1分钟K线推送: {}", event); // 如果只需要处理收盘完成的K线,加这个判断即可过滤K线形成过程中的中间推送 if (event.getBarFinal()) { log.info("K线收盘,触发交易逻辑: {}", event); } } ); log.info("币安K线WebSocket订阅初始化完成"); } }
关键注意事项
- 订阅逻辑全局只需要执行一次,不要放在定时任务、HTTP接口、重复触发的业务方法里,避免产生冗余连接
- 你之前定义的
WebSocketConfig配置类不需要修改,可以正常用来实例化客户端Bean - 如果需要订阅多个交易对、多时间周期的K线,在
initKlineSubscription方法内依次调用onCandlestickEvent传入不同参数即可,不要重复订阅相同参数的流 - 如果需要实现断线重连,可以在订阅时传入连接异常的回调,在连接意外断开时重新执行订阅逻辑即可,不需要靠定时任务轮询重建
补充:你之前观察到的定时节点收到堆积事件的现象,本质是每新建一个订阅,币安服务器会首先推送当前未收盘K线的最新状态,每秒新建一个连接就会多收到一份推送,运行越久冗余连接越多,延迟和堆积就越明显,调整为启动时单次订阅后该问题会完全消失。
内容的提问来源于stack exchange,提问作者LeoAlmDiniz
相关产品推荐
相关产品推荐

