配置文件:
package com.xxx.conf.cache;
import com.beautiful.util.Constants;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class RedisConfig extends CachingConfigurerSupport {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory);
// 使用Jackson2JsonRedisSerialize 替换默认序列化
Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 设置value的序列化规则和 key的序列化规则
redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
/**
* 通过cacheManager方式配置缓存区域
*
* @param redisConnectionFactory
* @return
*/
@Bean
public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
return new RedisCacheManager(
RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
// 默认策略,未配置的 key 会使用这个
this.getRedisCacheConfigurationWithTtl(4 * 60 * 60),
// 指定 key 策略
this.getRedisCacheConfigurationMap()
);
}
private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
//SsoCache和BasicDataCache进行过期时间配置
redisCacheConfigurationMap.put("xxx", this.getRedisCacheConfigurationWithTtl(30 * 60));
redisCacheConfigurationMap.put("YYY", this.getRedisCacheConfigurationWithTtl(50 * 60));
redisCacheConfigurationMap.put("ZZZ", this.getRedisCacheConfigurationWithTtl(12 * 30 * 60));
return redisCacheConfigurationMap;
}
/**
* 缓存时间
*
* @param seconds 秒
* @return
*/
private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jackson2JsonRedisSerializer.setObjectMapper(om);
RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(jackson2JsonRedisSerializer)
).entryTtl(Duration.ofSeconds(seconds));
return redisCacheConfiguration;
}
}
分布式锁文件:
package com.xxx.conf.cache;
import io.lettuce.core.RedisFuture;
import io.lettuce.core.ScriptOutputType;
import io.lettuce.core.SetArgs;
import io.lettuce.core.api.async.RedisAsyncCommands;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.nio.charset.StandardCharsets;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
@Slf4j
public class RedisLock {
private RedisTemplate<String, Object> redisTemplate;
/**
* 调用set后的返回值
*/
public static final String OK = "OK";
/**
* 默认请求锁的超时时间(ms 毫秒)
*/
private static final long TIME_OUT = 100;
/**
* 默认锁的有效时间(s)
*/
public static final int EXPIRE = 60;
/**
* 解锁的lua脚本
*/
public static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1])");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
/**
* 锁标志对应的key
*/
private String lockKey;
/**
* 锁对应的值
*/
private String lockValue;
/**
* 锁的有效时间(s)
*/
private int expireTime = EXPIRE;
/**
* 请求锁的超时时间(ms)
*/
private long timeOut = TIME_OUT;
/**
* 锁标记
*/
private volatile boolean locked = false;
private final Random random = new Random();
/**
* 使用默认的锁过期时间和请求锁的超时时间
*
* @param redisTemplate
* @param lockKey 锁的key(Redis的Key)
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
/**
* 使用默认的请求锁的超时时间,指定锁的过期时间
*
* @param redisTemplate
* @param lockKey 锁的key(Redis的Key)
* @param expireTime 锁的过期时间(单位:秒)
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int expireTime) {
this(redisTemplate, lockKey);
this.expireTime = expireTime;
}
/**
* 使用默认的锁的过期时间,指定请求锁的超时时间
*
* @param redisTemplate
* @param lockKey 锁的key(Redis的Key)
* @param timeOut 请求锁的超时时间(单位:毫秒)
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, long timeOut) {
this(redisTemplate, lockKey);
this.timeOut = timeOut;
}
/**
* 锁的过期时间和请求锁的超时时间都是用指定的值
*
* @param redisTemplate
* @param lockKey 锁的key(Redis的Key)
* @param expireTime 锁的过期时间(单位:秒)
* @param timeOut 请求锁的超时时间(单位:毫秒)
*/
public RedisLock(RedisTemplate<String, Object> redisTemplate, String lockKey, int expireTime, long timeOut) {
this(redisTemplate, lockKey, expireTime);
this.timeOut = timeOut;
}
/**
* 尝试获取锁 超时返回
*
* @return
*/
public boolean tryLock() {
// 生成随机key
lockValue = UUID.randomUUID().toString();
// 请求锁超时时间,纳秒
long timeout = timeOut * 1000000;
// 系统当前时间,纳秒
long nowTime = System.nanoTime();
while ((System.nanoTime() - nowTime) < timeout) {
if (OK.equalsIgnoreCase(this.set(lockKey, lockValue, expireTime))) {
locked = true;
// 上锁成功结束请求
return locked;
}
// 每次请求等待一段时间
sleep(10, 50000);
}
return locked;
}
/**
* 尝试获取锁 立即返回
*
* @return 是否成功获得锁
*/
public boolean lock() {
lockValue = UUID.randomUUID().toString();
//不存在则添加 且设置过期时间(单位ms)
String result = set(lockKey, lockValue, expireTime);
locked = OK.equalsIgnoreCase(result);
return locked;
}
/**
* 以阻塞方式的获取锁
*
* @return 是否成功获得锁
*/
public boolean lockBlock() {
lockValue = UUID.randomUUID().toString();
while (true) {
//不存在则添加 且设置过期时间(单位ms)
String result = set(lockKey, lockValue, expireTime);
if (OK.equalsIgnoreCase(result)) {
locked = true;
return locked;
}
// 每次请求等待一段时间
sleep(10, 50000);
}
}
/**
* 解锁
*
* @return false: 锁已不属于当前线程 或者 锁已超时
*/
public Boolean unlock() {
// 只有加锁成功并且锁还有效才去释放锁
if (locked) {
return redisTemplate.execute((RedisCallback<Boolean>) connection -> {
Object nativeConnection = connection.getNativeConnection();
Long result = 0L;
Object[] keys = new Object[]{lockKey.getBytes(StandardCharsets.UTF_8)};
if (nativeConnection instanceof RedisAsyncCommands) {
RedisAsyncCommands<Object, byte[]> commands = ((RedisAsyncCommands) nativeConnection).getStatefulConnection().async();
RedisFuture future = commands.eval(UNLOCK_LUA, ScriptOutputType.INTEGER, keys, lockValue.getBytes(StandardCharsets.UTF_8));
try {
result = (Long) future.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
if (result == 1) {
log.info("Redis分布式锁,解锁{}成功!解锁时间:{}", lockKey, System.currentTimeMillis());
locked = false;
return true;
}
return false;
});
}
return true;
}
/**
* 重写redisTemplate的set方法
* <p>
* 命令 SET resource-name anystring NX EX max-lock-time 是一种在 Redis 中实现锁的简单方法。
* <p>
* 客户端执行以上的命令:
* <p>
* 如果服务器返回 OK ,那么这个客户端获得锁。
* 如果服务器返回 NIL ,那么客户端获取锁失败,可以在稍后再重试。
*
* @param key 锁的Key
* @param value 锁里面的值
* @param seconds 过去时间(秒)
* @return
*/
private String set(final String key, final String value, final long seconds) {
Assert.isTrue(!StringUtils.isEmpty(key), "key不能为空");
return redisTemplate.execute((RedisCallback<String>) connection -> {
Object nativeConnection = connection.getNativeConnection();
String result = null;
//单机模式
if (nativeConnection instanceof RedisAsyncCommands) {
RedisAsyncCommands commands = (RedisAsyncCommands) nativeConnection;
RedisFuture future = commands
.getStatefulConnection()
.async()
.set(key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8), SetArgs.Builder.ex(seconds).nx());
try {
result = String.valueOf(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
//集群模式,ex为秒,px为毫秒
if (nativeConnection instanceof RedisAdvancedClusterAsyncCommands) {
RedisAdvancedClusterAsyncCommands commands = (RedisAdvancedClusterAsyncCommands) nativeConnection;
RedisFuture future = commands
.getStatefulConnection()
.async()
.set(key.getBytes(StandardCharsets.UTF_8), value.getBytes(StandardCharsets.UTF_8), SetArgs.Builder.nx().ex(30));
try {
result = String.valueOf(future.get());
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
if (result.equalsIgnoreCase("ok")) {
result = "ok";
log.info("获取锁{}的时间:{}", key, System.currentTimeMillis());
}
return result;
});
}
/**
* 获取锁状态
*
* @return
* @Title: isLock
*/
public boolean isLock() {
return locked;
}
/**
* 线程等待时间
*
* @param millis 毫秒
* @param nanos 纳秒
*/
private void sleep(long millis, int nanos) {
try {
Thread.sleep(millis, random.nextInt(nanos));
} catch (InterruptedException e) {
log.info("获取分布式锁休眠被中断:", e);
}
}
public int getExpireTime() {
return expireTime;
}
public void setExpireTime(int expireTime) {
this.expireTime = expireTime;
}
public long getTimeOut() {
return timeOut;
}
public void setTimeOut(long timeOut) {
this.timeOut = timeOut;
}
}
properties文件redis部分:
## redis
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.database=9
spring.redis.password=
#缓存的类型
spring.cache.type=redis
# redis lettuce配置
spring.redis.lettuce.pool.min-idle=0
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.shutdown-timeout=100ms