Rails部署于Heroku:如何可靠管理外部服务长连接WebSocket?
在Heroku上的Rails应用中可靠维护Binance WebSocket连接的方案
一、应用启动时的可靠连接初始化
Heroku上的Rails web进程主打处理HTTP请求,别把WebSocket监听逻辑塞进去——会阻塞正常请求。建议用独立worker进程跑WebSocket监听:
- 在项目根目录的
Procfile里新增worker配置:web: bundle exec puma -C config/puma.rb worker: bundle exec rails runner "BinanceApi.start_all_listeners" - 初始化时要避免重复创建连接:在模块里维护线程安全的连接缓存,启动前先检查对应symbol是否已有活跃连接。
二、连接的状态监控、停止与重启
要实现全生命周期管理,核心是线程安全地跟踪连接实例:
- 用类变量存连接映射(比如
@@connections),搭配Mutex保证多线程操作安全 - 状态检查:遍历连接缓存,判断每个连接的
state是否为:open - 停止连接:调用连接的
close方法,并从缓存中移除 - 重启连接:先停止目标连接,再重新初始化
三、可靠重连机制(含24小时超时处理)
Binance的WebSocket会在24小时后主动断开,直接在close回调里递归重连容易栈溢出,建议用指数退避重连:
- 断开后不要立刻重连,先延迟一段时间(初始1秒,每次失败翻倍,上限30秒)
- 连接成功时重置退避计数器
- 主动处理24小时超时:连接建立后设定时器,提前5分钟主动断开重连,避免被动断开的不确定性
四、优化后的代码实现
module BinanceApi require 'uri' require "openssl" require 'websocket-client-simple' include HTTParty # 线程安全的连接缓存与锁 @@connections = {} @@mutex = Mutex.new # 重连退避配置 @@retry_delay = 1 @@max_retry_delay = 30 def self.start_all_listeners # 可从数据库/配置文件读取需要监听的symbol列表 symbols = ['BTCUSDT', 'ETHUSDT'] symbols.each { |symbol| listen_for_depth(symbol) } end def self.listen_for_depth(symbol='BTCUSDT') symbol_key = symbol.downcase @@mutex.synchronize do # 避免重复创建活跃连接 return if @@connections[symbol_key]&.state == :open end path = "/ws/#{symbol_key}@depth" ws = WebSocket::Client::Simple.connect "#{ENV['BINANCE_WS_URL']}#{path}" do |conn| conn.on :open do puts "[#{symbol}] 已连接到Binance WebSocket" # 重置重连延迟 @@retry_delay = 1 # 24小时超时处理:提前5分钟主动断开重连 Thread.new do sleep(24*3600 - 300) conn.close if conn.state == :open end # 将连接存入缓存 @@mutex.synchronize { @@connections[symbol_key] = conn } end conn.on :message do |msg| # 替换为实际业务逻辑,比如写入数据库、触发事件 puts "[#{symbol}] 收到消息: #{msg.data}" end conn.on :close do |e| puts "[#{symbol}] 连接断开,原因: #{e&.reason || '未知'}" # 移除失效连接 @@mutex.synchronize { @@connections.delete(symbol_key) } # 指数退避重连 sleep(@@retry_delay) @@retry_delay = [@@retry_delay * 2, @@max_retry_delay].min listen_for_depth(symbol) end conn.on :error do |e| puts "[#{symbol}] 连接出错: #{e.message}" end end end # 检查指定symbol的连接状态 def self.connection_status(symbol='BTCUSDT') @@mutex.synchronize do conn = @@connections[symbol.downcase] conn ? conn.state : :not_exists end end # 停止指定symbol的连接 def self.stop_listening(symbol='BTCUSDT') symbol_key = symbol.downcase @@mutex.synchronize do conn = @@connections[symbol_key] if conn && conn.state == :open conn.close @@connections.delete(symbol_key) puts "[#{symbol}] 已停止监听" end end end # 重启指定symbol的连接 def self.restart_listening(symbol='BTCUSDT') stop_listening(symbol) listen_for_depth(symbol) puts "[#{symbol}] 已重启监听" end end
五、Heroku部署注意事项
- 部署后启动worker进程:
heroku ps:scale worker=1 - 通过日志查看连接状态:
heroku logs --tail -p worker - 若需动态调整监听symbol,可结合Sidekiq等后台任务调用
listen_for_depth或stop_listening方法
内容的提问来源于stack exchange,提问作者turkeyman84
相关产品推荐
相关产品推荐

