java - redis - incr and expire lua


使用原子自增和 expire 搭配實現原子操作

 @Bean(name = "customStringRedisTemplate")
    public RedisTemplate<String, String> customStringRedisTemplate(RedisConnectionFactory factory) {
// 要求使用默認序列化,否則有可能會拋出user_script:1: ERR value is not an integer or out of range 異常
        return new StringRedisTemplate(factory);
    }

    @Autowired
    private RedisTemplate<String, String> customStringRedisTemplate;

    @Test
    public void customLock() {
        String key = "cmq_im_dispatch_user_";
//        customStringRedisTemplate.delete(key);
        String incrByAndExpireLua = "if redis.call('incrBy', KEYS[1], ARGV[1]) == tonumber(ARGV[1]) then\n" +
                "    redis.call('expire', KEYS[1], ARGV[2])\n" +
                "    return tonumber(ARGV[1])\n" +
                "else\n" +
                "    return 0\n" +
                "end";
        if (!customStringRedisTemplate.hasKey(key)) {
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(incrByAndExpireLua, Long.class);
        // 要求這里輸入的參數都需要為字符串 Long increment
= customStringRedisTemplate.execute(redisScript, Collections.singletonList(key), NumberUtils.INTEGER_ONE.toString(), "300"); // 即使在並發情況下也只允許一個線程進入 if (NumberUtils.LONG_ONE.equals(increment)) { try { log.info("{}線程進入", Thread.currentThread().getName()); } finally { customStringRedisTemplate.delete(key); } } } }

 

對於lua腳本的分析: 

  1. "redis.call('incrBy', KEYS[1], ARGV[1])" ,表示當前調用"incrBy"命令,並指定key為輸入參數的第一個參數key,value為第一個參數value
  2. 由於"incrBy"會返回增加后的結果,如果當前key不存在,則會生成新的key,且默認value為0並增加輸入的value; 因此對於判斷 " == tonumber(ARGV[1])",這里要求"ARGV[1]"需要不等於0,否則無法判斷是否是第一次操作
  3. 對於 "if option then operate else operate" ,就是簡單的if else判斷操作, 如果是第一次操作,則在調用expire命令設置過期時間,原因在於對於"incrBy"生成的新的key默認過期時間為 -1 表示永遠存活;
  4. 對於return 操作表示當前操作會返回數據,對於返回數值類型數據操作,要求java必須使用Long類型接收;也可以直接返回布爾類型數據

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM