提供了基于redis的组件包,其包含两大功能。

  • 分布式锁
  • 缓存注解

1.Lettuce的bug

springboot2.0以后默认使用Lettuce作为redis的操作客户端,它使用netty进行网络通信。
Lettuce的bug导致netty堆外内存溢出,-Xmx300M;netty如果没有指定堆外内存,就会默认使用-Xmx300M
可以通过-Dio.netty.maxDirectMemory只去调大堆外内存
解决方案:

1.升级Lettuce客户端
2.使用Jedis客户端(需要排除Lettuce)

这个已经在当前版本得到解决。

2.引入依赖文件

  1. <dependency>
  2. <groupId>com.xy</groupId>
  3. <artifactId>xy-core-redis-boot-starter-monitor</artifactId>
  4. <version>${xy-core-redis-boot-starter-version}</version>
  5. </dependency>

�3.添加配置

  1. spring.redis.host=xxxx
  2. spring.redis.password=
  3. spring.redis.port=6379
  4. spring.redis.database=0
  5. spring.redis.timeout=30s
  6. spring.redis.lettuce.pool.max-active=800
  7. spring.redis.lettuce.pool.max-idle=800
  8. spring.redis.lettuce.pool.max-wait=10s
  9. spring.redis.lettuce.pool.min-idle=0
  10. #设置在自己的项目中
  11. spring.redis.distributed.lock.enable=true
  12. #提供了基于redission的lock,根据实际需要选择配置(会额外新增32个线程)
  13. spring.redis.distributed.lock.client=redis_template

4.分布式锁使用

4.1.注解方式

  1. @DistributedLock(businessType = "testRedisLOck", keys = {"#lockDto.name", "#lockDto.age", "#num"})
  2. public void test(LockDto lockDto, Integer num) {
  3. log.info("lockDto is {}", JSON.toJSONString(lockDto));
  4. }
参数说明 >1.businessType:分组
>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,可通过配置项设置具体选用哪一种。

  1. spring.redis.distributed.lock.client=redis_template

基于redisTemplate实现

  1. /**
  2. * <p>基于redis实现分布式锁</p >
  3. *
  4. * @author li
  5. * @version 1.0
  6. * @date 2020/04/29 21:07f
  7. */
  8. @Slf4j
  9. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  10. public class RedisDistributedLockImpl implements RedisLockService {
  11. private final RedisTemplate<String, String> redisTemplate;
  12. //默认锁的key:业务输入。time:30秒,value:当前线程id
  13. @Override
  14. public Boolean tryLock(String key) {
  15. long threadId = Thread.currentThread().getId();
  16. return this.tryLock(key, 30, String.valueOf(threadId));
  17. }
  18. //默认锁的key:业务输入。time:30秒,value:业务输入
  19. @Override
  20. public Boolean tryLock(String key, String value) {
  21. return this.tryLock(key, 30, value);
  22. }
  23. //默认锁的key:业务输入。time:业务输入(单位秒),value:当前线程id
  24. @Override
  25. public Boolean tryLock(String key, int seconds) {
  26. long threadId = Thread.currentThread().getId();
  27. return this.tryLock(key, seconds, String.valueOf(threadId));
  28. }
  29. //默认锁的key:业务输入。time:业务输入(单位秒),value:业务输入
  30. @Override
  31. public Boolean tryLock(String key, int seconds, String value) {
  32. Boolean result = redisTemplate.opsForValue().setIfAbsent(key, value, seconds, TimeUnit.SECONDS);
  33. log.info("get distributed lock,key:{},result:{}", key, result);
  34. return result;
  35. }
  36. //对应释放由tryLock(String key)或tryLock(String key, int seconds)的加锁方法
  37. @Override
  38. public Boolean releaseLock(String key) {
  39. long threadId = Thread.currentThread().getId();
  40. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  41. DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
  42. defaultRedisScript.setResultType(Boolean.class);
  43. defaultRedisScript.setScriptText(script);
  44. defaultRedisScript.afterPropertiesSet();
  45. return redisTemplate.execute(defaultRedisScript, Collections.singletonList(key), String.valueOf(threadId));
  46. }
  47. //对应释放由tryLock(String key, String value)和tryLock(String key, int seconds, String value)的加锁方法
  48. @Override
  49. public Boolean releaseLock(String key, String value) {
  50. String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
  51. DefaultRedisScript<Boolean> defaultRedisScript = new DefaultRedisScript<>();
  52. defaultRedisScript.setResultType(Boolean.class);
  53. defaultRedisScript.setScriptText(script);
  54. defaultRedisScript.afterPropertiesSet();
  55. return redisTemplate.execute(defaultRedisScript, Collections.singletonList(key), value);
  56. }
  57. }

�基于redisson的实现

  1. @Slf4j
  2. @RequiredArgsConstructor(onConstructor = @__(@Autowired))
  3. public class RedisSonDistributedLockImpl implements RedisLockService {
  4. private final RedissonClient redissonClient;
  5. @Override
  6. public Boolean tryLock(String key) {
  7. RLock lock = redissonClient.getLock(key);
  8. boolean tryLock = lock.tryLock();
  9. log.info("get distributed lock,key:{},result:{}", key, tryLock);
  10. return tryLock;
  11. }
  12. @Override
  13. public Boolean tryLock(String key, String value) {
  14. RLock lock = redissonClient.getLock(key);
  15. boolean tryLock = lock.tryLock();
  16. log.info("get distributed lock,key:{},result:{}", key, tryLock);
  17. return tryLock;
  18. }
  19. @Override
  20. public Boolean tryLock(String key, int seconds) {
  21. RLock lock = redissonClient.getLock(key);
  22. try {
  23. boolean tryLock = lock.tryLock(Long.valueOf(seconds), TimeUnit.SECONDS);
  24. log.info("get distributed lock,key:{},result:{}", key, tryLock);
  25. return tryLock;
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. log.error("Failed to acquire distributed lock,key:{}", key);
  29. return false;
  30. }
  31. }
  32. @Override
  33. public Boolean tryLock(String key, int seconds, String value) {
  34. RLock lock = redissonClient.getLock(key);
  35. try {
  36. boolean tryLock = lock.tryLock(Long.valueOf(seconds), TimeUnit.SECONDS);
  37. log.info("get distributed lock,key:{},result:{}", key, tryLock);
  38. return tryLock;
  39. } catch (InterruptedException e) {
  40. e.printStackTrace();
  41. return false;
  42. }
  43. }
  44. @Override
  45. public Boolean releaseLock(String key) {
  46. RLock lock = redissonClient.getLock(key);
  47. lock.unlock();
  48. return true;
  49. }
  50. @Override
  51. public Boolean releaseLock(String key, String value) {
  52. RLock lock = redissonClient.getLock(key);
  53. lock.unlock();
  54. return true;
  55. }
  56. }

因为实现相同锁接口,对于传入value的方法,value的值并没有使用,实际锁的所有value值都是当前线程的id

4.2.2RedisLockerTemplate

加锁结果返回为null问题?

5.缓存使用

  1. @CacheCreate(group = "taskNotify", keys = {"#taskNotify.notifyId"}, timeout = "120000")
  2. public TaskNotify createTaskNotify(TaskNotify taskNotify) {}
  3. @CacheSearch(group = "taskNotify", keys = {"#notifyId"}, emptyCached = true, timeout = "100")
  4. @CacheDelete(group = "taskNotify", keys = {"#notifyId"},cacheTiming=CacheTimingEnum.OPERATE_AFTER_DB)
  5. @CacheUpdate(group = "taskNotify", keys = {"#notifyId"})

采用springEL表达式获取缓存key的部分信息

建议只使用@CacheDelete和@CacheSearch两个注解。

@CacheSearch

  1. @Documented
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Target({ElementType.METHOD, ElementType.TYPE})
  4. public @interface CacheSearch {
  5. //业务组
  6. String group();
  7. //缓存key
  8. String[] keys();
  9. //数据不存在,是否缓存空
  10. boolean emptyCached() default false;
  11. //缓存过期时间
  12. String timeout() default "6000";
  13. /**
  14. * 是否开启本地缓存
  15. * 若开启 request->localCache->redisCache
  16. * 若未开启 request -> redisCache
  17. *
  18. * @return
  19. */
  20. boolean localCache() default false;
  21. }

使用了本地缓存,本地缓存默认实现为GuavaCache:

  1. /**
  2. * <p>description</p >
  3. *
  4. * @author jack.li
  5. * @version 1.0
  6. * @date 2022/7/16 下午10:47
  7. */
  8. public class BaseAspect {
  9. private Cache<Object, Object> loadingCache = CacheBuilder.newBuilder()
  10. .maximumSize(1000)
  11. .softValues()
  12. .expireAfterWrite(1L, TimeUnit.MINUTES)
  13. .recordStats()
  14. .build();
  15. protected void deleteLocalKey(String key) {
  16. loadingCache.invalidate(key);
  17. }
  18. protected Object getLocalKey(String key) {
  19. return loadingCache.getIfPresent(key);
  20. }
  21. protected void putLocalKey(String key, Object object) {
  22. loadingCache.put(key, object);
  23. }
  24. }

若默认实现不能满足业务需求,可覆盖默认实现,即在项目中,建立完全相同的类路径,覆盖默认实现。

xy-redis-starter - 图1

备注:暂未实现分布式本地缓存的同步,local只适用于对数据一致性要求不高的场景。

6.redis监听器�

此功能依赖服务商配置,不是确认可用项

1.注册redis监听器容器

  1. import org.springframework.context.annotation.Bean;
  2. import org.springframework.context.annotation.Configuration;
  3. import org.springframework.data.redis.connection.RedisConnectionFactory;
  4. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  5. /**
  6. * <p>redis config</p >
  7. *
  8. * @author iu
  9. * @version 1.0
  10. * @date 2020/05/22 17:04
  11. */
  12. @Configuration
  13. public class RedisConfig {
  14. /**
  15. * redis监听器
  16. *
  17. * @param connectionFactory RedisConnectionFactory
  18. * @return RedisMessageListenerContainer
  19. */
  20. @Bean
  21. public RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
  22. RedisMessageListenerContainer container = new RedisMessageListenerContainer();
  23. container.setConnectionFactory(connectionFactory);
  24. return container;
  25. }
  26. }

2.对监听的过期key进行业务处理

  1. import org.springframework.data.redis.connection.Message;
  2. import org.springframework.data.redis.listener.KeyExpirationEventMessageListener;
  3. import org.springframework.data.redis.listener.RedisMessageListenerContainer;
  4. import org.springframework.stereotype.Component;
  5. /**
  6. * <p>监听所有db的过期事件__keyevent@*__:expired"</p >
  7. *
  8. * @author iu
  9. * @version 1.0
  10. * @date 2020/05/21 12:00
  11. */
  12. @Component
  13. public class RedisKeyExpirationListener extends KeyExpirationEventMessageListener {
  14. public RedisKeyExpirationListener(RedisMessageListenerContainer listenerContainer) {
  15. super(listenerContainer);
  16. }
  17. /**
  18. * 针对redis数据失效事件,进行数据处理
  19. *
  20. * @param message expiredKey
  21. * @param pattern
  22. */
  23. @Override
  24. public void onMessage(Message message, byte[] pattern) {
  25. // 用户做自己的业务处理即可,注意message.toString()可以获取失效的key
  26. String expiredKey = message.toString();
  27. if (expiredKey.startsWith("Order:")) {
  28. //如果是Order:开头的key,进行处理
  29. }
  30. }
  31. }

7cat打点

对于常见的RedisTemplate中的方法,进行了二次包装,对每一个操作,进行了cat transaction和event的打点,

使用方法

  1. private final XyRedisTemplate redisTemplate;

�使用XyCatRedisTemplate替换之前的RedisTemplate方法