web123456

Springboot series: Lockless inventory deduction through redis+lua script

Preface:

In all marketing activities, inventory processing is involved, such as the issuance of red envelopes and the distribution of energy, it is necessary to ensure that neither too much deduction nor less.  It is usually achieved by locking the deduction interface, but when large-scale concurrency occurs, locking the active inventory will affect the concurrency efficiency, because it is only after each thread is processed before the next thread will be the turn.  Redis provides the form of a lua script to implement atomic operations of commands. By placing all the logic that needs to be deducted in the script, it ensures concurrency without affecting the amount of inventory deductions.
  • 1

maven dependency

<dependency>
		<groupId></groupId>
		<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • 1
  • 2
  • 3
  • 4

Used herespringboot versionIt is above 2.0.
springboot AboutredisThe connection configuration will not be described here. Let's take a look at the configuration content of the lua script directly.

luaConfig configuration

@Configuration
public class LuaConfig {

    @Bean
    public DefaultRedisScript<Long> stockLua() {
        DefaultRedisScript<Long> defaultRedisScript = new DefaultRedisScript<>();
        defaultRedisScript.setScriptSource(new ResourceScriptSource(
                new ClassPathResource("/lua/")));
        defaultRedisScript.setResultType(Long.class);
        return defaultRedisScript;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
stock- Script File
--Return successfully1,Failed to return0
if (redis.call('exists', KEYS[1]) == 1)
then
    local cc = redis.call('get', KEYS[1]);
    local stock = tonumber(cc);
    local num = tonumber(ARGV[1]);
    if (stock < num) then
        return -1;
    end ;
    if (stock >= num) then
        return redis.call('INCRBYFLOAT', KEYS[1], 0 - num);
    end ;
end ;
return -1;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

How to use

    @Autowired
    private DefaultRedisScript stockLua;

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
  • 1
  • 2
  • 3
  • 4
  • 5

Inject the script's bean and the template that operates the redis server into the service class.

        if (SystemConstant.MONEY_REWARDS.equals(reqVO.getRightType())) {
            redisKeyList.add(activityStockKey);
            List<String> redisAvgList = new ArrayList<>();
            redisAvgList.add(reqVO.getQuota().toString());
            Object aa = stringRedisTemplate.execute(stockLua, redisKeyList, redisAvgList.toArray());
            BigDecimal leftStock = new BigDecimal(aa.toString());
            if (leftStock.compareTo(new BigDecimal("-1")) <= 0) {
                throw new BusinessException(ExceptionEnum.ACTIVITY_ACCOUNT_RIGHT_NOT_ENOUGH);
            }
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

By calling the execute method of Template, execute the script and deduct the corresponding inventory.
No locking operation is required.