提供了基于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=xxxxspring.redis.password=spring.redis.port=6379spring.redis.database=0spring.redis.timeout=30sspring.redis.lettuce.pool.max-active=800spring.redis.lettuce.pool.max-idle=800spring.redis.lettuce.pool.max-wait=10sspring.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@Overridepublic Boolean tryLock(String key) {long threadId = Thread.currentThread().getId();return this.tryLock(key, 30, String.valueOf(threadId));}//默认锁的key:业务输入。time:30秒,value:业务输入@Overridepublic Boolean tryLock(String key, String value) {return this.tryLock(key, 30, value);}//默认锁的key:业务输入。time:业务输入(单位秒),value:当前线程id@Overridepublic Boolean tryLock(String key, int seconds) {long threadId = Thread.currentThread().getId();return this.tryLock(key, seconds, String.valueOf(threadId));}//默认锁的key:业务输入。time:业务输入(单位秒),value:业务输入@Overridepublic 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)的加锁方法@Overridepublic 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)的加锁方法@Overridepublic 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;@Overridepublic Boolean tryLock(String key) {RLock lock = redissonClient.getLock(key);boolean tryLock = lock.tryLock();log.info("get distributed lock,key:{},result:{}", key, tryLock);return tryLock;}@Overridepublic 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;}@Overridepublic 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;}}@Overridepublic 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;}}@Overridepublic Boolean releaseLock(String key) {RLock lock = redissonClient.getLock(key);lock.unlock();return true;}@Overridepublic 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();//缓存keyString[] 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*/@Configurationpublic class RedisConfig {/*** redis监听器** @param connectionFactory RedisConnectionFactory* @return RedisMessageListenerContainer*/@Beanpublic 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*/@Componentpublic class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {super(listenerContainer);}/*** 针对redis数据失效事件,进行数据处理** @param message expiredKey* @param pattern*/@Overridepublic void onMessage(Message message, byte[] pattern) {// 用户做自己的业务处理即可,注意message.toString()可以获取失效的keyString expiredKey = message.toString();if (expiredKey.startsWith("Order:")) {//如果是Order:开头的key,进行处理}}}
7cat打点
对于常见的RedisTemplate中的方法,进行了二次包装,对每一个操作,进行了cat transaction和event的打点,
使用方法
private final XyRedisTemplate redisTemplate;
�使用XyCatRedisTemplate替换之前的RedisTemplate方法
