请问redis incr命令可以限制特定数量吗?

Will redis incr command can be limitation to specific number?

在我们的项目中,我们想使用redis的incr命令来限制存储。例如,对于特定商品 A,我们希望在促销期间只销售其中的 10 个。所以我们打算用incr命令把存储值从0加到10,理论上这个方案在这个场景下是没有问题的。

在开始之前,我们对incr命令进行了性能测试,结果发现使用incr对货物存储进行限制可能不可靠。

测试代码如下:

static int count = 0;

public void performanceToken() throws RedisAccessException {

    long result = redisUtil.incrRedisEx("myrrrxxxrrrediskey6");

    if (result <= 10) {
        count ++;
    }

    Struts2Utils.renderJson(result +"|"+count);
    return;
}

incrRedisEx 是 incr 命令的包装器,我们在 java 项目中使用 jedis 作为驱动程序来驱动我们的 redis。

然后我们启动了tomcat,我们可以直接从浏览器调用上面的界面。结果很好,没有出错,没有错误。

然后我们改用jmeter这个性能测试工具,它可以模拟用户调用接口的巨大压力。我们在 1 秒内创建了 1500 个调用接口的请求。但结果如下:

起初,代码运行正常,我们可以看到商品存储限制为 10 个。

过了一会儿,我们看到计数突破了限制,越来越大了!!!

不知道为什么,redis incr在用户请求大的情况下无法对存储做限制。任何人都可以给我一些灯吗?

基于redis的incr机制,执行完incr命令后会return新的值,如果是这样,为什么不能阻止数字的增长,很奇怪,真的。

对我来说,我想,可能同时有两个操作发送到redis做incr操作,但是一个直接做incr命令和returns,另一个做incr好慢而当它returns的时候,发现它增加的数量不是2,而是11,因为其他请求已经做了incr命令,好的存储已经增加到10了,也许吧?但是这个解释是不正确的,因为redis是单线程模型。

--------编辑----------------

我更改了代码以避免线程安全问题:

 long result = redisUtil.incrRedisEx("axgggggggggg");

    if (result <= 10) {
        logger.error("==generate==order==result==" + result);
    }

    Struts2Utils.renderJson(result );
    return;

刚发现日志会被写十几次

您可以使用一些 Lua 脚本在 Redis 本身内执行增量,以便它本质上是单线程的:

127.0.0.1:6379> set CappedInt 7
OK
127.0.0.1:6379> eval "local c=redis.call(ARGV[1],KEYS[1])+0;if c<10 then return redis.call('INCR',KEYS[1]); else return 10; end" 1 CappedInt get
(integer) 8
127.0.0.1:6379> eval "local c=redis.call(ARGV[1],KEYS[1])+0;if c<10 then return redis.call('INCR',KEYS[1]); else return 10; end" 1 CappedInt get
(integer) 9
127.0.0.1:6379> eval "local c=redis.call(ARGV[1],KEYS[1])+0;if c<10 then return redis.call('INCR',KEYS[1]); else return 10; end" 1 CappedInt get
(integer) 10
127.0.0.1:6379> eval "local c=redis.call(ARGV[1],KEYS[1])+0;if c<10 then return redis.call('INCR',KEYS[1]); else return 10; end" 1 CappedInt get
(integer) 10

除了输入脚本,您还可以将 Lua 代码放入名为 IncWithCap.lua 的文件中,如下所示:

local cap=10
if(redis.call(ARGV[1],KEYS[1])+0 < cap) then
   return redis.call('INCR',KEYS[1])
end
return cap

然后你可以将它加载到 Redis 中:

redis-cli SCRIPT LOAD "$(cat IncWithCap.lua)"

示例输出

"6e6ad88c9a2b7dfdade9c5763467aaab2358d4e1"

然后你可以call/execute它:

127.0.0.1:6379> evalsha 6e6ad88c9a2b7dfdade9c5763467aaab2358d4e1 1 CappedInt get