1. 程式人生 > >Redis分布式鎖,基於StringRedisTemplate和基於Lettuce實現setNx

Redis分布式鎖,基於StringRedisTemplate和基於Lettuce實現setNx

timeout out light 代碼 efault enum img 時間 comm

使用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"

Redis分布式鎖,基於StringRedisTemplate和基於Lettuce實現setNx