Sidekiq和Puma中的Redis变量,线程安全吗?

时间:2017-08-23 06:44:26

标签: ruby-on-rails ruby redis thread-safety sidekiq

我目前正在制作仪表板应用,但我不确定我的解决方案是否正确。

目前我有一个每30秒运行一次的Sidekiq作业。作业将数据保存到数据库,并将新结果流式传输到每个仪表板。

我必须解决的问题是从这项工作中将数据(使用ActionCable)仅流式传输到' online'仪表板。 (如果仪表板在浏览器选项卡中打开)

解决方案是从我的DashboardChannel中保存Redis,例如:

class DashboardChannel < ApplicationCable::Channel
 def subscribed
   stream_from "dashboard:#{params['dashboard_id']}"

   tabs_number = $redis.get("dashboard_#{params['dashboard_id']}_online").to_i
   $redis.set("dashboard_#{params['dashboard_id']}_online", tabs_number+=1)
 end

 def unsubscribed
   tabs_number = $redis.get("dashboard_#{params['dashboard_id']}_online").to_i
   tabs_number-=1

   if tabs_number == 0
     $redis.del("dashboard_#{params['dashboard_id']}_online")
   else
     $redis.set("dashboard_#{params['dashboard_id']}_online", tabs_number)
   end
 end
end

我保存了按键&#34;仪表板_#{DASHBOARD_ID} _online&#34;中打开的标签数量。保存标签数量非常重要,因为如果只保存true / false(1/0),当仪表板在2个标签中打开,而其中一个标签关闭时,它将被标记为离线。

在我的Sidekiq工作中我有类似的东西:

      if Dashboard.online?(widget.dashboard_id) # returns true / false
        ActionCable.server.broadcast "dashboard:#{widget.dashboard_id}", 
          { widget_id: widget.id, 
            dashboard_id: widget.dashboard_id, 
            value: data_value.value, 
            recorded_at: data_value.recorded_at.strftime("%I:%M%p"), 
            in_bounds: data_value.in_bounds
          }
     end

在线方法app / models / dashboard.rb

  def self.online?(dashboard_id)
    !$redis.get("dashboard_#{dashboard_id}_online").nil?
  end

在app / initializers / sidekiq.rb

url = ENV["REDISTOGO_URL"] || "redis://localhost:6379/0"
uri = URI.parse(ENV["REDISTOGO_URL"] || "redis://localhost:6379/")


Sidekiq.configure_server do |config|
  config.redis = { url: url, size: 4 }
end

Sidekiq.configure_client do |config|
  config.redis = { url: url, size: 4 }
end

$redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)

在app / initializers / redis.rb

uri = URI.parse(ENV["REDISTOGO_URL"] || "redis://localhost:6379/")
$redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)

# Remove all 'online dashboard' keys
keys = $redis.keys("dashboard_")
$redis.del(*keys) unless keys.empty?

我的问题:是否可以使用&#39; $ redis&#39;在&#39; app / initializers / sidekiq.rb&#39;中声明的全局变量在Sidekiq工作?是线程保存吗?

目前我没有发现这种代码方法存在工作流程问题,但我担心“redis&#39;全局变量,不应该接近&#39;一个Sidekiq工作。

随意添加代码建议。谢谢你,祝你有愉快的一天!

1 个答案:

答案 0 :(得分:0)

实际上,线程安全与全局变量$ redis没有任何关系。 $ redis只是保持与redis服务器的连接。 redis服务器是一个关于进程客户端请求的单线程模型,因此它将处理请求命令作为命令队列的顺序。

但请注意Sidekiq是多线程的。如果你的sidekip持有很多线程,每个线程都会向redis服务器发送命令。会出现线程安全问题。请阅读transaction of redis了解详情。

关于你的代码,我不太了解。但有一点,如果您的DashboardChannel的订阅和取消订阅在多线程或多处理中运行,您应该注意到会发生坏事。

e.g。如果行动按以下顺序发生,

number1 = client-1 get key

number2 = client-2 get key

client-1设置密钥编号1 + 1

client-2设置密钥number2 + 1

然后您的密钥将保留与实数相对应的错误值。如果是这样,请尝试使用redis手表。