Ruby on Rails中的$redis全局变量

38

我正在使用Redis作为读取缓存。我已经创建了一个初始化程序

config/initializer/redis.rb

$redis = Redis.new(:host => ENV["REDIS_HOST"], :port => ENV["REDIS_PORT"])

我正在unicorn.rb中使用此全局变量,以便在每次创建新工作者时创建一个新的连接。

before_fork do |server, worker|
  # clear redis connection
  $redis.quit unless $redis.blank?
end

# Give each child process its own Redis connection
after_fork do |server, worker|
  $redis = Redis.new(:host => ENV["REDIS_HOST"], :port => ENV["REDIS_PORT"])
end

每当我需要访问我的 Redis 服务器时,我也使用这个全局变量。但是我不太喜欢使用全局变量。有没有比使用全局变量更好的选择?


9
在这个问题中,https://dev59.com/7Wgv5IYBdhLWcg3wCcSZ#16474679 的答案比下面的任何答案都要好。 - tomtaylor
6个回答

99

有一个Redis.current可以用来存储你唯一的Redis实例。

因此,你可以按照以下方式分配实例,而不是使用$redis

Redis.current = Redis.new(:host => ENV["REDIS_HOST"], :port => ENV["REDIS_PORT"])

Redis.current引入到redis-rb中,作为一种标准方式来获取redis连接,所以我很惊讶没有其他答案提到它。

更新:从版本4.6.0开始,Redis.current已经被弃用。作者指出,在典型的多线程应用程序中,会发现在共享的redis客户端周围有很多锁定。他们建议定义一个自己的位置来获取redis客户端,但也建议使用连接池。

因此,接受的答案是实现类似于Redis.current最简单的解决方案,但在多线程环境中可能不会表现出最佳性能。


2
令人烦恼的是,这正是我在寻找的答案,但由于它位于被接受的答案之下,我浪费了时间没有采纳这个答案。 - Paul Danelli
在我看来,这应该是被接受的答案。 - Saman Hamidi
1
现在它已经被弃用了。真是惊喜,多有趣啊。 - courtsimas
2
我提交了一个问题,请求“官方”的建议:https://github.com/redis/redis-rb/issues/1064 - Henrik N
2
@courtsimas 感谢您注意到这一点。我已在相应的提交中提出了问题,并相应更新了答案。 - NobodysNightmare
@NobodysNightmare 我印象深刻,谢谢。这是一个快速而全面的更新。 - courtsimas

32

进一步扩展mestachs的建议,将模块命名空间化到您的初始化程序中,如下所示

config/initializers/redis.rb

module ReadCache
  class << self
    def redis
      @redis ||= Redis.new(:url => (ENV["REDIS_URL"] || 'redis://127.0.0.1:6379'))
    end
  end
end

接着在 unicorn.rb 中进行操作。

 before_fork do |server, worker|
    ...
   if defined?(ReadCache.redis)
    ReadCache.redis.quit
   end
    ...
 end

 after_fork do |server, worker|
    ...
   if defined?(ReadCache.redis)
    ReadCache.redis.client.reconnect
   end
    ...
 end

那么,如果我有一千个独特的请求同时发生,我也需要一千个连接到 Redis 服务器,对吗? - yeyo
@Kira,您能再解释一下吗?我认为它不需要成千上万的连接。如果我理解正确,这个模块将是整个应用程序共用的。 - Anirudhan J
1
这篇文章 https://devcenter.heroku.com/articles/concurrency-and-database-connections 对于理解连接池非常有帮助,无论你是否使用Heroku。通过对此的一些了解,你可以调整你的sidekiq并发性,请参考:https://github.com/mperham/sidekiq/wiki/Advanced-Options#wiki-concurrency。 - blotto
@blotto,非常感谢你的分享,读起来很不错。不过,你的示例代码好像没有使用ActiveRecord(如果我说错了,请纠正我),所以连接池技术并没有被使用,每个客户端/工作者仍然有一个Redis连接。 - yeyo
1
请按照@NobodysNightmare的说明使用Redis.current - Paul Danelli
显示剩余2条评论

0
根据 this Heroku,您无需将$redis添加到Unicorn中:

使用Redis Cloud和Unicorn服务器时不需要进行特殊设置。在Unicorn上运行Rails应用程序的用户应该遵循从Rails配置Redis部分中的说明,而用户...

这里是所有“从Rails配置Redis”部分在Rails 4之前的内容(除了Gemfile和一些其他Rails 3之前的东西):
# config/initalizers/redis.rb

if ENV["REDISCLOUD_URL"]
  uri = URI.parse(ENV["REDISCLOUD_URL"])
  $redis = Redis.new(:host => uri.host, :port => uri.port, :password => uri.password)
end

这并没有真正解释为什么“不需要特殊设置”。


1
我知道你需要做一些特殊的事情。我已经在生产Heroku服务器上使用独角兽(maxed out) 512个连接Redis云实例。 - Aeramor

0

0

一个更加命名空间的选项,用于替代全局变量,你可以在模块中创建一个方法

module Caching
  def self.redis
    ... 在这里初始化/缓存/重新连接 ...
  end
end

然后你可以这样调用它:

Caching.redis


0

试试这个:

你可以使用常量代替全局变量。例如在config/initializer/redis.rb中。

REDIS = Redis.new(:host => ENV["REDIS_HOST"], :port => ENV["REDIS_PORT"])

而在 unicorn.rb 文件中

before_fork do |server, worker|
  # clear redis connection
  REDIS.quit if defined?(REDIS)
end

# 为每个子进程提供独立的 Redis 连接

after_fork do |server, worker|
  REDIS ||= Redis.new(:host => ENV["REDIS_HOST"], :port => ENV["REDIS_PORT"])
end

1
听起来很有趣,这个常量的作用是什么?我的意思是,它不是全局变量,但它的行为类似于全局变量。 - yeyo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接