Redis分布式鎖,基於StringRedisTemplate和基於Lettuce實現setNx
阿新 • • 發佈:2019-02-28
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; privateStringRedisTemplate 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