似乎无法保持 redis 计数器正确

Can't seem to keep a redis counter correct

我有一些计数器存储在 REDIS 中,这些计数器在 customer.rb 中的状态变化时得到更新。我需要存储的东西是:

1) 与用户关联的客户数(用户 has_many 客户) 2) 状态为(使用 aasm_state)'open' 或 'claimed' 的客户数 3) 状态为(using aasm_state)'open

的客户数

每当客户的状态发生变化时,我都会 increment/decrement 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

是否有更好的模式来实现我想要完成的目标?这非常令人沮丧,因为一段时间后计数似乎总是不正确。请帮忙!

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

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

考虑使用 Redis 的 INCR 和 DECR 函数自动更新值。

你这里有一个竞争条件:

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

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