【问题标题】:Will redis incr command can be limitation to specific number?redis incr 命令可以限制特定数量吗?
【发布时间】:2017-12-20 04:41:38
【问题描述】:

在我们的项目中,我们想使用redis的incr命令来限制存储。例如,对于特定商品A,我们希望在促销期间只销售其中的十件。所以我们打算用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命令后会返回新的值,如果是这样,为什么不能阻止数字的增长,真是奇怪。

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

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

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

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

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

    Struts2Utils.renderJson(result );
    return;

刚刚发现日志会被写十多次。

【问题讨论】:

  • performanceToken 是否在多线程环境中运行?
  • @for_stack 这是一个 struts 动作,我将它公开给用户。
  • performanceToken 不是线程安全的。如果用户或您的测试代码在多个线程中调用此方法,count 可能大于 10。
  • @for_stack 我更改了代码以避免线程安全问题。请再次建议。
  • 您应该检查其他一些客户端是否减少或删除了密钥。试试Redis Keyspace Notifications

标签: redis pressure


【解决方案1】:

您可以使用一个小 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"

然后你可以调用/执行它:

127.0.0.1:6379> evalsha 6e6ad88c9a2b7dfdade9c5763467aaab2358d4e1 1 CappedInt get

【讨论】:

  • 谢谢,我们与团队成员进行了一次转换并找出了问题所在。是的,就像你说的,redis + lua 应该是最好的解决方案。
  • 非常好 - 祝你的项目好运。顺便说一句,这是我写的第一个 Lua,所以可能有更好的表达方式。
  • 没问题。我们公司其他项目使用redis+lua,我可以从他们那里得到资料。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-01-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多