使用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>() {@Overridepublic 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"