提供了基于redis的组件包,其包含两大功能。
- 分布式锁
- 缓存注解
1.Lettuce的bug
springboot2.0以后默认使用Lettuce作为redis的操作客户端,它使用netty进行网络通信。
Lettuce的bug导致netty堆外内存溢出,-Xmx300M;netty如果没有指定堆外内存,就会默认使用-Xmx300M
可以通过-Dio.netty.maxDirectMemory只去调大堆外内存
解决方案:
2.使用Jedis客户端(需要排除Lettuce)
这个已经在当前版本得到解决。
2.引入依赖文件
<dependency>
<groupId>com.xy</groupId>
<artifactId>xy-core-redis-boot-starter-monitor</artifactId>
<version>${xy-core-redis-boot-starter-version}</version>
</dependency>
�3.添加配置
spring.redis.host=xxxx
spring.redis.password=
spring.redis.port=6379
spring.redis.database=0
spring.redis.timeout=30s
spring.redis.lettuce.pool.max-active=800
spring.redis.lettuce.pool.max-idle=800
spring.redis.lettuce.pool.max-wait=10s
spring.redis.lettuce.pool.min-idle=0
#设置在自己的项目中
spring.redis.distributed.lock.enable=true
#提供了基于redission的lock,根据实际需要选择配置(会额外新增32个线程)
spring.redis.distributed.lock.client=redis_template
4.分布式锁使用
4.1.注解方式
参数说明 >1.businessType:分组
@DistributedLock(businessType = "testRedisLOck", keys = {"#lockDto.name", "#lockDto.age", "#num"})
public void test(LockDto lockDto, Integer num) {
log.info("lockDto is {}", JSON.toJSONString(lockDto));
}
>2.keys:方法参数,采用springEl表达式进行配置
>3.timeout:锁有效时间,单位为秒
cacheKey格式:【applicationId:cache:group:value】
lockKey格式:【**applicationId:lock:group:value】**
userTokenKey:【access_token:userId】
4.2.编程方式
不使用注解怎么使用?不使用注解时提供了两种方式
1.直接注入RedisLockService
2.直接使用RedisLockerTemplate
4.2.1RedisLockService
RedisLockService是一个分布式锁接口定义,具体实现有两种:
基于redisTemplate和redisson,可通过配置项设置具体选用哪一种。
spring.redis.distributed.lock.client=redis_template
基于redisTemplate实现
/**
* <p>基于redis实现分布式锁</p >
*
* @author li
* @version 1.0
* @date 2020/04/29 21:07f
*/
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RedisDistributedLockImpl implements RedisLockService {
private final RedisTemplate<String, String> redisTemplate;
//默认锁的key:业务输入。time:30秒,value:当前线程id
@Override
public Boolean tryLock(String key) {
long threadId = Thread.currentThread().getId();
return this.tryLock(key, 30, String.valueOf(threadId));
}
//默认锁的key:业务输入。time:30秒,value:业务输入
@Override
public Boolean tryLock(String key, String value) {
return this.tryLock(key, 30, value);
}
//默认锁的key:业务输入。time:业务输入(单位秒),value:当前线程id
@Override
public Boolean tryLock(String key, int seconds) {
long threadId = Thread.currentThread().getId();
return this.tryLock(key, seconds, String.valueOf(threadId));
}
//默认锁的key:业务输入。time:业务输入(单位秒),value:业务输入
@Override
public Boolean tryLock(String key, int seconds, String value) {
Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, seconds, TimeUnit.SECONDS);
log.info("get distributed lock,key:{},result:{}", key, result);
return result;
}
//对应释放由tryLock(String key)或tryLock(String key, int seconds)的加锁方法
@Override
public Boolean releaseLock(String key) {
long threadId = Thread.currentThread().getId();
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Boolean.class);
defaultRedisScript.setScriptText(script);
defaultRedisScript.afterPropertiesSet();
return redisTemplate.execute(defaultRedisScript, Collections.singletonList(key), String.valueOf(threadId));
}
//对应释放由tryLock(String key, String value)和tryLock(String key, int seconds, String value)的加锁方法
@Override
public Boolean releaseLock(String key, String value) {
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(Boolean.class);
defaultRedisScript.setScriptText(script);
defaultRedisScript.afterPropertiesSet();
return redisTemplate.execute(defaultRedisScript, Collections.singletonList(key), value);
}
}
�基于redisson的实现
@Slf4j
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class RedisSonDistributedLockImpl implements RedisLockService {
private final RedissonClient redissonClient;
@Override
public Boolean tryLock(String key) {
RLock lock = redissonClient.getLock(key);
boolean tryLock = lock.tryLock();
log.info("get distributed lock,key:{},result:{}", key, tryLock);
return tryLock;
}
@Override
public Boolean tryLock(String key, String value) {
RLock lock = redissonClient.getLock(key);
boolean tryLock = lock.tryLock();
log.info("get distributed lock,key:{},result:{}", key, tryLock);
return tryLock;
}
@Override
public Boolean tryLock(String key, int seconds) {
RLock lock = redissonClient.getLock(key);
try {
boolean tryLock = lock.tryLock(Long.valueOf(seconds), TimeUnit.SECONDS);
log.info("get distributed lock,key:{},result:{}", key, tryLock);
return tryLock;
} catch (InterruptedException e) {
e.printStackTrace();
log.error("Failed to acquire distributed lock,key:{}", key);
return false;
}
}
@Override
public Boolean tryLock(String key, int seconds, String value) {
RLock lock = redissonClient.getLock(key);
try {
boolean tryLock = lock.tryLock(Long.valueOf(seconds), TimeUnit.SECONDS);
log.info("get distributed lock,key:{},result:{}", key, tryLock);
return tryLock;
} catch (InterruptedException e) {
e.printStackTrace();
return false;
}
}
@Override
public Boolean releaseLock(String key) {
RLock lock = redissonClient.getLock(key);
lock.unlock();
return true;
}
@Override
public Boolean releaseLock(String key, String value) {
RLock lock = redissonClient.getLock(key);
lock.unlock();
return true;
}
}
因为实现相同锁接口,对于传入value的方法,value的值并没有使用,实际锁的所有value值都是当前线程的id
4.2.2RedisLockerTemplate
加锁结果返回为null问题?
5.缓存使用
@CacheCreate(group = "taskNotify", keys = {"#taskNotify.notifyId"}, timeout = "120000")
public TaskNotify createTaskNotify(TaskNotify taskNotify) {}
@CacheSearch(group = "taskNotify", keys = {"#notifyId"}, emptyCached = true, timeout = "100")
@CacheDelete(group = "taskNotify", keys = {"#notifyId"},cacheTiming=CacheTimingEnum.OPERATE_AFTER_DB)
@CacheUpdate(group = "taskNotify", keys = {"#notifyId"})
采用springEL表达式获取缓存key的部分信息
建议只使用@CacheDelete和@CacheSearch两个注解。
@CacheSearch
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
public @interface CacheSearch {
//业务组
String group();
//缓存key
String[] keys();
//数据不存在,是否缓存空
boolean emptyCached() default false;
//缓存过期时间
String timeout() default "6000";
/**
* 是否开启本地缓存
* 若开启 request->localCache->redisCache
* 若未开启 request -> redisCache
*
* @return
*/
boolean localCache() default false;
}
使用了本地缓存,本地缓存默认实现为GuavaCache:
/**
* <p>description</p >
*
* @author jack.li
* @version 1.0
* @date 2022/7/16 下午10:47
*/
public class BaseAspect {
private Cache<Object, Object> loadingCache = CacheBuilder.newBuilder()
.maximumSize(1000)
.softValues()
.expireAfterWrite(1L, TimeUnit.MINUTES)
.recordStats()
.build();
protected void deleteLocalKey(String key) {
loadingCache.invalidate(key);
}
protected Object getLocalKey(String key) {
return loadingCache.getIfPresent(key);
}
protected void putLocalKey(String key, Object object) {
loadingCache.put(key, object);
}
}
若默认实现不能满足业务需求,可覆盖默认实现,即在项目中,建立完全相同的类路径,覆盖默认实现。
备注:暂未实现分布式本地缓存的同步,local只适用于对数据一致性要求不高的场景。
6.redis监听器�
此功能依赖服务商配置,不是确认可用项
1.注册redis监听器容器
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
/**
* <p>redis config</p >
*
* @author iu
* @version 1.0
* @date 2020/05/22 17:04
*/
@Configuration
public class RedisConfig {
/**
* redis监听器
*
* @param connectionFactory RedisConnectionFactory
* @return RedisMessageListenerContainer
*/
@Bean
public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
RedisMessageListenerContainer container = new RedisMessageListenerContainer();
container.setConnectionFactory(connectionFactory);
return container;
}
}
2.对监听的过期key进行业务处理
import org.springframework.data.redis.connection.Message;
import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.stereotype.Component;
/**
* <p>监听所有db的过期事件__keyevent@*__:expired"</p >
*
* @author iu
* @version 1.0
* @date 2020/05/21 12:00
*/
@Component
public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
super(listenerContainer);
}
/**
* 针对redis数据失效事件,进行数据处理
*
* @param message expiredKey
* @param pattern
*/
@Override
public void onMessage(Message message, byte[] pattern) {
// 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
String expiredKey = message.toString();
if (expiredKey.startsWith("Order:")) {
//如果是Order:开头的key,进行处理
}
}
}
7cat打点
对于常见的RedisTemplate中的方法,进行了二次包装,对每一个操作,进行了cat transaction和event的打点,
使用方法
private final XyRedisTemplate redisTemplate;
�使用XyCatRedisTemplate替换之前的RedisTemplate方法