Spring 中处理 Redis 的并发导致的 expire 失效问题

原文连接:使用redis incr处理并发,存在死锁问题

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 */
public boolean getLock(String key) {
     try {
         long count = redisTemplate.opsForValue().increment(key, 1);
         //此段代码出现异常则会出现死锁问题,key一直都存在
         if(count == 1){
             //设置有效期2秒
             redisTemplate.expire(key, 2, TimeUnit.SECONDS);
             return true;
         }
         //如果存在表示重复
         return false;
     } catch (Exception e) {
         logger.error("redis加锁异常", e);
         redisTemplate.delete(key);     //出现异常删除锁
         return true;
     }
}

当正常情况没有问题,但是当计数器设置成功后,服务出现异常,那么这个key会永远存在,这样肯定不行。

Redis Incr 命令将 key 中储存的数字值增一,如果 key 不存在,那么 key 的值会先被初始化为 0 ,然后再执行 INCR 操作,且将key的有效时间设置为长期有效。

修改后的代码

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 */
public boolean getLock(String key) {
     try {
         long count = redisTemplate.opsForValue().increment(key, 1);
         if(count == 1){
             //设置有效期2秒
             redisTemplate.expire(key, 2, TimeUnit.SECONDS);
             return true;
         }else{
             long time = redisTemplate.getExpire(key,TimeUnit.SECONDS);
             if(time == -1){
                 //设置失败重新设置过期时间
                 redisTemplate.expire(key, 2, TimeUnit.SECONDS);
                 return true;
             }
         }
         //如果存在表示重复
         return false;
     } catch (Exception e) {
         logger.error("redis加锁异常", e);
         redisTemplate.delete(key);     //出现异常删除锁
         return true;
     }
}

更新:

将redis版本升级到2.1以上,可以直接使用setIfAbsent设置过期时间,使用 4 个参数的重载方法是原子性的。

redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);

说明:当key不存在,将key的值设为value,并设置过期时间,返回true;若给定的key已经存在,则不做任何动作,并返回false。此方法是原子性的。

img

使用 setIfAbsent

@Autowired
private RedisTemplate redisTemplate;

/**
 * 加锁
 * @param key key
 * @param value value
 * @param timeout 过期时间单位秒
 * @param true表示key不存在
 */
public boolean getLock(String key, String value, long timeout) {
     try {
         //原子性操作
         boolean flag = redisTemplate.opsForValue().setIfAbsent(key, value, timeout, TimeUnit.SECONDS);
         //如果存在false表示重复
         return flag;
     } catch (Exception e) {
         logger.error("redis加锁异常", e);
         redisTemplate.delete(key);     //出现异常删除锁
         return true;
     }
}

发表评论