Redis分布式锁,基于StringRedisTemplate和基于Lettuce实现setNx

使用redis分布式锁,来确保多个服务对共享数据操作的唯一性
一般来说有StringRedisTemplate和RedisTemplate两种redis操作模板。

根据key-value的类型决定使用哪种模板,如果k-v均是String类型,则使用StringRedisTemplate,否则使用RedisTemplate

redis加锁操作
必须遵循原子性操作,保证加锁的唯一性
核心方法
set(lockKey,value,"NXXX","EXPX",expireTime)
NXXX:只能取NX或者XX,NX-key不存在时进行保存,XX-key存在时才进行保存
EXPX:过期时间单位 (EX,PX),EX-秒,PX-毫秒

使用StringRedisTemplate实现加锁

public class StringRedisTemplateImplClient {
    // NX,XX
    //NX-key不存在则保存,XX-key存在则保存
    private static final String STNX= "NX";

    //EX,PX
    //EX-秒,PX-毫秒
    private static final String SET_EXPIRE_TIME = "PX";

    private RedisTemplate redisTemplate;
    private StringRedisTemplate stringRedisTemplate;
    

    public StringRedisTemplateImplClient(RedisTemplate redisTemplate){
        this.redisTemplate = redisTemplate;
        this.stringRedisTemplate = new StringRedisTemplate();
        this.stringRedisTemplate.setConnectionFactory(redisTemplate.getConnectionFactory());
        this.stringRedisTemplate.afterPropertiesSet();
    }

    public StringRedisTemplateImplClient(StringRedisTemplate redisTemplate){
        this.stringRedisTemplate = redisTemplate;
    }
    
    public boolean addRedisLock(String lockKey,String requestId,long expireTime){
        boolean result = stringRedisTemplate.opsForValue()
            .setIfAbsent(lockKey,lockKey,expireTime, TimeUnit.SECONDS);
        return result;
    }
}

下面简要分析下这个方法的一致性,查看setIfAbsent的源码

setIfAbsent这个方法是spring-data-redis提供的,分析其源码,实现类为org.springframework.data.redis.core.DefaultValueOperations
方法如下:
key-需要加锁的key
value-需要加锁的value
timeout-失效的时间
timeunit-常量,指定失效的时间单位

connection默认为lettuce驱动连接(springboot 2.0以后的版本)

public Boolean setIfAbsent(K key, V value, long timeout, TimeUnit unit) {
    byte[] rawKey = this.rawKey(key);
    byte[] rawValue = this.rawValue(value);
    Expiration expiration = Expiration.from(timeout, unit);
    return (Boolean)this.execute((connection) -> {
        return connection.set(rawKey, rawValue, expiration, SetOption.ifAbsent());
    }, true);
}

其中SetOption为指定NX/XX类型

public static enum SetOption {
    UPSERT,
    SET_IF_ABSENT,
    SET_IF_PRESENT;

    private SetOption() {
    }

    public static RedisStringCommands.SetOption upsert() {
        return UPSERT;
    }

    public static RedisStringCommands.SetOption ifPresent() {
        return SET_IF_PRESENT;
    }

    public static RedisStringCommands.SetOption ifAbsent() {
        return SET_IF_ABSENT;
    }
}

查询spring官网查询这三个属性

SET_IF_ABSENT--->NX
SET_IF_PRESENT--->XX

 

自定义实现SetNx

setNx+expireTime实现加锁

 核心代码

String status = stringRedisTemplate.execute(new RedisCallback<String>() {
  @Override
  public String doInRedis(RedisConnection connection) throws DataAccessException {
    Object nativeConnection = connection.getNativeConnection();
    String status = null;
    RedisSerializer<String> stringRedisSerializer = (RedisSerializer<String>) stringRedisTemplate.getKeySerializer();

    byte[] keyByte = stringRedisSerializer.serialize(key);
    //springboot 2.0以上的spring-data-redis 包默认使用 lettuce连接包

    //lettuce连接包,集群模式,ex为秒,px为毫秒
    if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
      logger.debug("lettuce Cluster:---setKey:"+setKey+"---value"+value+"---maxTimes:"+expireSeconds);
      status = ((RedisAdvancedClusterAsyncCommands) nativeConnection)
          .getStatefulConnection().sync()
          .set(keyByte,keyByte,SetArgs.Builder.nx().ex(30));
      logger.debug("lettuce Cluster:---status:"+status);
    }
    //lettuce连接包,单机模式,ex为秒,px为毫秒
    if (nativeConnection instanceof RedisAsyncCommands) {
      logger.debug("lettuce single:---setKey:"+setKey+"---value"+value+"---maxTimes:"+expireSeconds);
      status = ((RedisAsyncCommands ) nativeConnection)
      .getStatefulConnection().sync()
      .set(keyByte,keyByte, SetArgs.Builder.nx().ex(30));
      logger.debug("lettuce single:---status:"+status);
    }
    return status;
  }
});
logger.debug("getLock:---status:"+status);//执行正确status="OK"

 

posted @ 2019-02-28 10:38  辉夜  阅读(17047)  评论(0编辑  收藏  举报