RedisTemplate
分布式锁
Redis是一个基于内存的数据库,提供了字符串(String)、散列(Hash)、列表(list)、集合(sets) 和 有序集合(sorted sets)几种数据类型(具体参见Redis简介)。
RedisTemplate
spring-redis中使用了RedisTemplate进行redis的操作,通过泛型的K和V设置键值对的类型;并对五种类型提供对应的操作类:
-
ValueOperations:字符串类型数据操作,通过opsForValue()获取;
-
HashOperations:散列类型的数据操作,通过opsForHash()获取;
-
ListOperations:链表类型的数据操作,通过opsForList()获取;
-
SetOperations:无序集合类型的数据操作,通过opsForSet()获取;
-
ZSetOperations:有序集合类型的数据操作,通过opsForZSet()获取;
Tempate的初始化
以K、V都是String类型为例(复杂Value存储对应对象的Json)。
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// //使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
// Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(String.class);
// ObjectMapper om = new ObjectMapper();
// // 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
// om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// // 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
// om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
// jacksonSeial.setStringMapper(om);
// // 值采用json序列化
// template.setValueSerializer(jacksonSeial);
// template.setHashValueSerializer(jacksonSeial);
// StringRedisSerializer
StringRedisSerializer defSerial = new StringRedisSerializer();
template.setKeySerializer(defSerial);
template.setValueSerializer(defSerial);
// 设置hash key 和value序列化模式
template.setHashKeySerializer(defSerial);
template.setHashValueSerializer(defSerial);
template.afterPropertiesSet();
return template;
}
键值有效期
redis中的键可以设定有效期;但是有效期只能针对整个键值,不能设定到子项:如一个Hash键,要么全部失效,要么全部有效,不能针对某个子项失效
-
template.expire:设定键值的有效期;
-
template.opsForValue().set:可以在设定值的同时,设定有效期。
只有String类型可以在设定时同时设定有效期,其他类型只能设定之后再通过expire设定。
分布式锁
利用redis键值的性质,可以实现分布式锁:
-
通过setIfAbsent保证只有一个申请者获得锁;
-
在设定时设定有效期,避免申请者死掉后,一直占据着锁;
-
通过UUID保证值的唯一性,在删除前判断,避免意外删除;
public class JRedisLocker{
static final String LockerPath = "XLocker/";
private RedisTemplate<String,String> redisService;
private String lockerName;
private String lockerValue;
private int lockSeconds;
public JRedisLocker(RedisTemplate<String,String> redis, String key, int expireSeconds){
redisService = redis;
lockerName = LockerPath + key;
lockSeconds = expireSeconds;
lockerValue = "";
}
public boolean lock(){
String strValue = UUID.randomUUID().toString();
if(redisService.opsForValue().setIfAbsent(lockerName, strValue, lockSeconds, TimeUnit.SECONDS)){
lockerValue = strValue;
return true;
}
return false;
}
public boolean lock(int waitSeconds){
String strValue = UUID.randomUUID().toString();
long expire = System.currentTimeMillis() + waitSeconds*1000;
while(!redisService.opsForValue().setIfAbsent(lockerName, strValue, lockSeconds, TimeUnit.SECONDS)){
if(expire<System.currentTimeMillis())
return false;
Thread.sleep(10);
}
lockerValue = strValue;
return true;
}
public boolean unlock(){
if(lockerValue.equals(redisService.opsForValue().get(lockerName)){
redisService.delete(lockerName);
return true;
}
return false;
}
// 延长锁定时间(在执行任务时间过长时使用)
public boolean delayLock(){
if(lockerValue.equals(redisService.opsForValue().get(lockerName)){
redisService.expire(lockerName, lockSeconds, TimeUnit.SECONDS);
return true;
}
return false;
}
}