redispressure

Will redis incr command can be limitation to specific number?


In our project, we want to use redis's incr command to do limitation to the storage. For example, for the specific goods A, We want to sell only ten of them during the promotion. So we planned to use incr command to add the storage value from 0 to 10. In theory, this plan is no problem with this scenario.

Before start, we did a performance test on the incr command, the result appeared that using incr to do limitation to the goods storage may be unrealiable.

The testing code below:

static int count = 0;

public void performanceToken() throws RedisAccessException {

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

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

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

The incrRedisEx was the wrapper of incr command and we used jedis as the driver to drive our redis in our java project.

Then We started our tomcat and we could invoke above interface directly from our browser. The result was good, no mistake, no error.

And then we switched to jmeter, the performance testing tool, who could simulate great pressure from users to invoke the interface. we created 1500 requests to invoke the interface in 1 seconds. But the result is below:

At the first, the code worked ok and we can see that the goods storage was limited to 10. enter image description here enter image description here

After a while, we can see that the count number broke the limitation, it growssss!!! enter image description here

Don't know why, redis incr can't do limitation to storage under great user requests. Anyone can give me some lights on it?

Based on redis incr mechanism, it will return new value after doing incr command , if so, why it can't stop the growth of the number, it's a strange thing, really.

For me, I think, maybe at the same time, there are two operations send to redis to do incr operation, but one does the incr command and returns directly, another one does incr so slow and when it returns, it is found that the number it increases is not 2, but 11, because other requests do the incr command already and the good storage has been increased to 10, maybe? But this explaination is not correct because redis is single thread model.

--------EDIT----------------

I changed my code to avoid thread safe problem:

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

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

    Struts2Utils.renderJson(result );
    return;

Just found that the log will be write more than ten times.


Solution

  • You could use a little Lua script to do the increment within Redis itself so that it is essentially single-threaded:

    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
    

    Rather than typing in the script, you can also put the Lua code into a file called IncWithCap.lua like this:

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

    Then you can load it into Redis with:

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

    Sample Output

    "6e6ad88c9a2b7dfdade9c5763467aaab2358d4e1"
    

    Then you can call/execute it with:

    127.0.0.1:6379> evalsha 6e6ad88c9a2b7dfdade9c5763467aaab2358d4e1 1 CappedInt get