检查并增加 Redis 中的计数器

问题描述 投票:0回答:3

我有一项手术需要完成 N 次,不能再多了。该操作是由许多接收请求的可能并行的进程完成的。进程必须检查计数器是否超过 N,如果没有,则递增计数器并执行操作。我想我可以使用 Redis 计数器来实现这一点。

但是,如果我只是

GET
然后
INCR
一个值,我可能会遇到竞争条件,这将导致操作执行超过 N 次。如何对 Redis 执行某种
test-and-incr
操作?

我知道我可以使用

WATCH
,但这是一个乐观锁。我预计每秒都会发生很多次碰撞。这会导致很多失败。也许我可以用某种外部互斥体包装简单的
GET
INCR
。但我不确定它的性能是否足够好。

redis counter increment race-condition
3个回答
9
投票
  • 您可以在这里安全地使用Redis
    INCR
  • INCR 返回增量后的值。

  • 您首先检查 INCR 返回的值(看到不需要执行 GET ) &继续根据该值进行操作。

  • 唯一的问题是,您必须将 INCR 返回值阈值设置为 N+1,以限制 N 次操作,即比 N 次额外的 redis 操作。 例如,如果我们想限制操作只发生3次,如果INCR在增量后返回4,那么 你停止做进一步的操作,因为它已经发生了 3 次。


0
投票

试试这个

增量

SET my_counter 10
INCR my_counter   // Result: 11
INCR my_counter   // Result: 12

增加并返回新值

// INCR command increments the value of the key and returns the new value
const incrementedValue = await redis.incr(key);
console.log("Incremented value:", incrementedValue);

0
投票

这里有一个示例,说明如何使用单个 LUA 脚本执行此操作,因为 Redis 是单线程的,因此您不需要在此处添加任何锁或其他任何内容。

-- This script increments a counter and returns a boolean indicating whether the counter is below a limit. --
-- If the counter is below the limit, the counter is incremented and the new value is returned. --
-- If the counter is above the limit, the counter is not incremented and the current value is returned. --
-- The counter is set to expire after 60 seconds. --
-- ARGV[1] - The key of the counter in Redis --
-- ARGV[2] - The command INCR or DECR --
-- ARGV[3] - Limit to check against --

local counter
local counter_key = ARGV[1]
local command = ARGV[2]
local limit = tonumber(ARGV[3])
local expiration_in_seconds = 60
local success = false

counter = redis.call("GET", counter_key)
if counter == false then
  counter = 0
end

if tonumber(counter) < limit and command == "INCR" then
  success = true
  counter = redis.call("INCR", counter_key)
elseif tonumber(counter) > limit and command == "DECR" then
  success = true
  counter = redis.call("DECR", counter_key)
end

redis.call("EXPIRE", counter_key, expiration_in_seconds)

return { success, tonumber(counter) }

-- Example of call --
-- EVAL ${LUA_SCRIPT_ABOVE_AS_STRING} 0 test_key INCR 10 --

Ruby 中的示例或客户端(您可以用您选择的语言重新实现):

module RedisHelpers
  module CounterWithLimit
    LUA_SCRIPT = File.read("#{__dir__}/counter_with_limit.lua").freeze
    # NOTE: Redis returns 1 for true and nil for false when LUA script is executed
    SUCCESS_VALUE = 1

    module_function

    def try_incr(key:, limit:, redis: Redis.new)
      # NOTE: https://redis.io/commands/eval/
      success, counter = redis.call('EVAL', LUA_SCRIPT, 0, key, 'INCR', limit)

      { success: success == SUCCESS_VALUE, counter: }
    end

    def try_decr(key:, limit: 0, redis: Redis.new)
      # NOTE: https://redis.io/commands/eval/
      success, counter = redis.call('EVAL', LUA_SCRIPT, 0, key, 'DECR', limit)

      { success: success == SUCCESS_VALUE, counter: }
    end
  end
end
© www.soinside.com 2019 - 2024. All rights reserved.