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

如何在多台Ruby on Rails生产实例执行脚本并获取公网IP?

解决多Rails实例类变量同步问题

类变量@@banners是进程内存储的,每个Rails服务器实例都是独立进程,负载均衡只会把请求转发到其中一台,所以直接调用Banner.reload_banners只能更新当前实例的变量。以下是几种可行的解决方案:

方案1:改用分布式缓存存储(推荐)

把@@banners的内容迁移到Redis、Memcached这类共享缓存中,所有实例统一从缓存读取数据,更新时直接修改缓存,确保所有实例拿到的都是最新值。

修改Banner类示例:

class Banner
  # 从缓存获取 banners
  def self.banners
    Rails.cache.fetch('banners', expires_in: 24.hours) { load_default_banners }
  end

  # 重载并更新缓存
  def self.reload_banners
    new_banners = fetch_latest_banners_from_source # 原有的 banners 获取逻辑
    Rails.cache.write('banners', new_banners)
  end

  private

  def self.load_default_banners
    # 初始加载默认 banners 的逻辑
  end

  def self.fetch_latest_banners_from_source
    # 从数据源(比如数据库、API)拉取最新 banners 的逻辑
  end
end

之后所有实例调用Banner.banners都会读取共享缓存,任意实例触发reload_banners都会更新全局缓存。

方案2:用消息队列触发全实例更新

通过Redis Pub/Sub或RabbitMQ等消息中间件,当一台实例执行重载时,发送广播消息,其他所有实例订阅该消息并执行自身的reload_banners。

1. 修改重载方法发送消息

class Banner
  def self.reload_banners
    new_banners = fetch_latest_banners_from_source
    @@banners = new_banners # 更新当前实例
    # 发送重载广播
    Redis.current.publish('banner_reload_topic', 'trigger_reload')
  end
end

2. 实例启动时订阅消息

在config/initializers/banner_subscriber.rb中添加订阅逻辑:

# 单独起线程订阅消息,避免阻塞Rails启动
Thread.new do
  redis = Redis.current
  redis.subscribe('banner_reload_topic') do |on|
    on.message do |_channel, message|
      if message == 'trigger_reload'
        Banner.reload_banners
      end
    end
  end
rescue Redis::BaseConnectionError => e
  Rails.logger.error("Banner subscriber connection failed: #{e.message}")
end

生产环境需确保订阅线程稳定,可配合进程管理工具(如systemd、Sidekiq)维护。

方案3:彻底放弃进程内类变量

类变量本身不适合分布式部署场景,建议将可变状态完全迁移到共享存储(数据库、缓存),从根源避免进程间状态不一致问题。


获取Rails生产服务器公网IP

以下是几种常用方法:

方法1:调用外部IP查询服务

通过HTTP请求外部服务获取公网IP,这是最直接的方式:

def get_public_ip
  require 'net/http'
  response = Net::HTTP.get(URI('https://icanhazip.com'))
  response.strip
rescue StandardError => e
  Rails.logger.error("Failed to fetch public IP: #{e.message}")
  # 降级方案:尝试获取本地非私网IP(仅当服务器公网IP直接绑定在网卡时有效)
  Socket.ip_address_list.detect do |addr|
    addr.ipv4? && !addr.ipv4_private?
  end&.ip_address
end

方法2:从环境变量读取(更可靠)

部署服务器时,将公网IP写入环境变量(如PUBLIC_IP),直接读取即可:

def get_public_ip
  ENV['PUBLIC_IP'] || fallback_local_ip
end

def fallback_local_ip
  Socket.ip_address_list.detect { |addr| addr.ipv4? && !addr.ipv4_private? }&.ip_address
end

这种方式不依赖外部服务,稳定性更高,适合云服务器部署时提前配置。

方法3:查看服务器网络接口(仅适用于公网IP绑定本地网卡的场景)

如果服务器的公网IP直接绑定在网卡上,可通过读取网络接口信息获取:

def get_public_ip
  `curl -s ifconfig.me`.strip # 或用`ip addr`解析,不同系统命令可能不同
end

注意:部分云服务器采用NAT网络,本地网卡只有内网IP,此方法无效。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 13:02:21