Tony
Tony

Reputation: 473

Handling race conditions with Redis Cache

I am looking at a problem, where I have an object that contains a string key, and a counter. Every time the object is accessed the counter is decremented. This brings in the problem of race conditions. To handle this I have coded the following, using the synchronized keyword

try {
            option = cartRepository.findById(url);
            Rate rate = option.get();
            synchronized (this) {
                if(rate.getRate() > 0) {
                    rate.decRate(1);
                    allow = true;
                    cartRepository.save(rate);
                } 
            }
        } catch(NoSuchElementException e) {
            cartRepository.save(new Rate(url, 5));
            allow = true;
        } 

I was wondering, if redis itself has functionality that increments or in this case decrements a counter every time you access on that key. The docs are a tad confusing. it does talk about auto increment on a key. But I am guessing, its not creating a two part key, with the key and counter. But autoincrements a key every time you create and save a new object.

Upvotes: 0

Views: 1944

Answers (2)

Tony
Tony

Reputation: 473

With thanks to Sergie, this is the working code

public boolean isAllowed(String url, String maxRate) {
        logger.info("URL is "+url);
        boolean allow = false;
        ValueOperations<String, String> valueOps = redisTemplate.opsForValue();
        String rate = valueOps.get(url);
        logger.info("RATE "+rate);
        if(rate == null) {
            valueOps.set(url, maxRate, 5, TimeUnit.SECONDS);
            allow = true;
        } else {
            valueOps.decrement(url, 1);
            if(Integer.parseInt(rate) > 0) {
                allow = true;
            }
        }
        return allow;
    }

Upvotes: 0

Sergei
Sergei

Reputation: 623

You can have a key to hold the counter and use INCR command to change counter value. This command returns the counter value after operation is performed.

You can use ValueOperations in Spring Data Redis to issue this command. Here's sample code:

StringRedisTemplate rt = new StringRedisTemplate(factory);

ValueOperations<String, String> valueOps = rt.opsForValue();

valueOps.set(KEY, "10");
System.out.println(valueOps.get(key));
System.out.println(valueOps.increment(KEY, 1));
System.out.println(valueOps.increment(KEY, 1));
System.out.println(valueOps.increment(KEY, -1));

The output will be:

10
11
12
11

In case you have many counters to maintain you can store them in a hashmap and use HINCRBY command. With Spring Data Redis you can use HashOperations to manipulate values in the hash. See this page for more detailed explanation.

Upvotes: 1

Related Questions