You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails部署于Heroku:如何可靠管理外部服务长连接WebSocket?

在Heroku上的Rails应用中可靠维护Binance WebSocket连接的方案

一、应用启动时的可靠连接初始化

Heroku上的Rails web进程主打处理HTTP请求,别把WebSocket监听逻辑塞进去——会阻塞正常请求。建议用独立worker进程跑WebSocket监听:

  1. 在项目根目录的Procfile里新增worker配置:
    web: bundle exec puma -C config/puma.rb
    worker: bundle exec rails runner "BinanceApi.start_all_listeners"
    
  2. 初始化时要避免重复创建连接:在模块里维护线程安全的连接缓存,启动前先检查对应symbol是否已有活跃连接。

二、连接的状态监控、停止与重启

要实现全生命周期管理,核心是线程安全地跟踪连接实例:

  • 用类变量存连接映射(比如@@connections),搭配Mutex保证多线程操作安全
  • 状态检查:遍历连接缓存,判断每个连接的state是否为:open
  • 停止连接:调用连接的close方法,并从缓存中移除
  • 重启连接:先停止目标连接,再重新初始化

三、可靠重连机制(含24小时超时处理)

Binance的WebSocket会在24小时后主动断开,直接在close回调里递归重连容易栈溢出,建议用指数退避重连:

  1. 断开后不要立刻重连,先延迟一段时间(初始1秒,每次失败翻倍,上限30秒)
  2. 连接成功时重置退避计数器
  3. 主动处理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部署注意事项

  1. 部署后启动worker进程:heroku ps:scale worker=1
  2. 通过日志查看连接状态:heroku logs --tail -p worker
  3. 若需动态调整监听symbol,可结合Sidekiq等后台任务调用listen_for_depth或stop_listening方法

内容的提问来源于stack exchange,提问作者turkeyman84

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.18 01:40:25