【问题标题】:Can't seem to keep a redis counter correct似乎无法保持 redis 计数器正确
【发布时间】:2016-03-30 23:11:34
【问题描述】:

我有一些计数器存储在 REDIS 中,这些计数器会根据 customer.rb 中的状态更改进行更新。我需要存储的东西是:

1) 与用户关联的客户数量(用户 has_many customers) 2)具有(使用aasm_state)“开放”或“已声明”状态的客户计数 3)状态为(使用aasm_state)'open

的客户计数

每当客户的状态发生变化时,我都会相应地增加/减少 redis 计数器。但是,无论我尝试过什么,计数似乎总是在一段时间后关闭。

我正在使用 Sidekiq,但我不认为这是一个并发问题,因为 REDIS 不应该受到并发问题的影响,对吧?

这是我的计数更新方法:

  def reset_stats
    if aasm_state_was == 'open' && aasm_state == 'claimed' # open => assigned
      # update company and user
      user.redis_increment_my_customers_length
      company.redis_decrement_open_customers_length

    elsif user_id_changed? && aasm_state_was == 'claimed' && aasm_state == 'claimed' # assigned => assigned
      # update users (assigner and assignee)
      user_was = User.find(user_id_was)
      user.redis_increment_my_customers_length
      user_was.redis_decrement_my_customers_length

    elsif aasm_state_was == 'claimed' && aasm_state == 'closed' # assigned => closed
      # update company and user
      user_was = User.find(user_id_was)
      user_was.redis_decrement_my_customers_length
      company.redis_decrement_all_customers_length

    elsif aasm_state_was == 'closed' && aasm_state == 'claimed' # closed => assigned
      # update company and user
      user.redis_increment_my_customers_length
      company.redis_increment_all_customers_length

    elsif aasm_state_was == 'closed' && aasm_state == 'open' # closed => open
      # update company
      company.redis_increment_all_customers_length
      company.redis_increment_open_customers_length

    elsif aasm_state_was == 'open' && aasm_state == 'closed' # open => closed
      # update company
      company.redis_decrement_all_customers_length
      company.redis_decrement_open_customers_length

    end

在 user.rb 中:

def redis_length_key
    "my_customers_length_for_#{id}"
  end

  def set_my_customers_length(l)
    RED.set(redis_length_key, l)
    l.to_i
  end

  def redis_increment_my_customers_length
    RED.get(redis_length_key) ? RED.incr(redis_length_key) : my_customers_length
  end

  def redis_decrement_my_customers_length
    RED.get(redis_length_key) ? RED.decr(redis_length_key) : my_customers_length
  end

  def my_customers_length
    if l = RED.get(redis_length_key)
      l.to_i
    else
      set_my_customers_length(my_customers.length)
    end
  end

在 company.rb 中:

def open_customers
    customers.open
  end

  def redis_open_length_key
    "open_customers_length_for_#{id}"
  end

  def set_open_customers_length(l)
    RED.set(redis_open_length_key, l)
    l.to_i
  end

  def redis_increment_open_customers_length
    RED.get(redis_open_length_key) ? RED.incr(redis_open_length_key) : open_customers_length
  end

  def redis_decrement_open_customers_length
    RED.get(redis_open_length_key) ? RED.decr(redis_open_length_key) : open_customers_length
  end

  def open_customers_length
    if l = RED.get(redis_open_length_key)
      return l.to_i
    else
      set_open_customers_length(open_customers.length)
    end
  end

  def redis_all_length_key
    "all_customers_length_for_#{id}"
  end

  def set_all_customers_length(l)
    RED.set(redis_all_length_key, l)
    l
  end

  def redis_increment_all_customers_length
    RED.get(redis_all_length_key) ? RED.incr(redis_all_length_key) : all_customers_length
  end

  def redis_decrement_all_customers_length
    RED.get(redis_all_length_key) ? RED.decr(redis_all_length_key) : all_customers_length
  end

  def all_customers_length
    if l = RED.get(redis_all_length_key)
      l.to_i
    else
      set_all_customers_length(open_or_claimed_customers.length)
    end
  end

  def open_or_claimed_customers
    customers.open_or_claimed
  end

对于我想要完成的工作,是否有更好的模式?这非常令人沮丧,因为计数似乎总是在一段时间后变得不正确。请帮忙!

【问题讨论】:

    标签: ruby-on-rails redis sidekiq


    【解决方案1】:

    在调用 set_my_customers_length(my_customers_length + 1) 和调用 RED.set(redis_open_length_key, l) 之间存在竞争条件。

    1. 两个进程启动。
    2. 当对两个进程进行第一次调用时,my_customers_length 为 5。
    3. 第一个进程进行第二次调用并将 Redis 设置为 6。
    4. 第二个进程进行第二次调用并再次将 Redis 设置为 6。
    5. Redis 值实际上应该是 7。

    考虑使用 Redis 的 INCR 和 DECR 函数以原子方式更新值。

    【讨论】:

    • 这是一个很好的建议,谢谢。我修复了我的代码并部署了一个小时后......计数再次减少了 1。我正在使用我在编辑中提供的方法随机检查计数。还有其他可能发生的事情吗?可能在我部署时计数已关闭,因此我将它们重置并很快再次检查。 (用你的更新更新了我原来的问题)
    • 另外,如果有帮助,最新的重置显示 redis 计数比实际计数大 +1
    • 我的猜测是你在其他地方有竞争条件,或者 incr/decr 代码被多次调用。您必须追踪所有路径才能看到。
    【解决方案2】:

    这里有一个竞争条件:

    RED.get(redis_all_length_key) ? RED.incr(redis_all_length_key) : all_customers_length
    

    你不能在读取和写入 Redis 之间做任何逻辑。

    【讨论】:

      猜你喜欢
      • 2017-03-06
      • 2014-05-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-10
      • 2017-09-10
      相关资源
      最近更新 更多