SpringBoot2.3.4整合Redisson和SpringCache实现分布式锁和分布式缓存

导读:本篇文章讲解 SpringBoot2.3.4整合Redisson和SpringCache实现分布式锁和分布式缓存,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

概述

为了提升访问效率,适应高并发访问,在程序中引入了缓存,常见的缓存有本地缓存和分布式缓存,分布式缓存中常使用Redis中间件实现。在高并发下缓存会存在失效问题,常见的缓存失效有以下三种:

缓存穿透

查询一个一定不存在的数据,由于缓存是不命中,将去查询数据库,但是数据库也无此记录,我们没有将这次查询的null写入缓存,这将导致这个不存在的数据每次请求都要到存储层去查询,失去了缓存的意义
风险:利用不存在的数据进行攻击,数据库瞬时压力增大,最终导致崩溃
解决:null结果缓存,并加入短暂过期时间

缓存雪崩

在我们设置缓存时key采用了相同的过期时间,导致缓存在某一时刻同时失效,请求全部转发到数据库,数据库瞬时压力过重雪崩。
解决:原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这
样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件

缓存穿透

对于一些设置了过期时间的key,如果这个key在大量请求同时进来前正好失效,那么所有对这个key的数据查询都落到数据库
解决:加锁,大量并发只让一个去查,其他人等待,查到以后释放锁,其他人获取到锁,先查缓存,就会有数据,不用去数据库

引入依赖

核心依赖如下:

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--分布式锁redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.13.5</version>
</dependency>
<!--Spring Cache缓存操作-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

配置文件

主配置文件核心配置如下:

spring:
  cache:
    type: redis #缓存类型
    redis:
      time-to-live: 3600000 #存活时间(毫秒)
      #key-prefix: CACHE_ #key前缀,默认使用缓存的名字作为前缀
      #use-key-prefix: true #是否使用前缀
      cache-null-values: true #是否缓存空值,防止缓存穿透

Redisson的相关属性需要配置在单独的配置文件中,核心配置如下:

singleServerConfig:
  idleConnectionTimeout: 10000 #连接空闲超时(毫秒),默认10000
  connectTimeout: 10000 #连接空闲超时(毫秒),默认10000
  timeout: 3000 #命令等待超时(毫秒),默认3000
  retryAttempts: 3 #命令失败重试次数
  retryInterval: 1500 #命令重试发送时间间隔(毫秒),默认1500
  password: null
  subscriptionsPerConnection: 5 #单个连接最大订阅数量,默认5
  clientName: null #客户端名称
  address: "redis://127.0.0.1:6379"
  subscriptionConnectionMinimumIdleSize: 1 #发布和订阅连接的最小空闲连接数,默认1
  subscriptionConnectionPoolSize: 50 #发布和订阅连接池大小,默认50
  connectionMinimumIdleSize: 24 #最小空闲连接数,默认32
  connectionPoolSize: 64 #连接池大小,默认64
  database: 0
  dnsMonitoringInterval: 5000 #DNS监测时间间隔(毫秒),默认5000
threads: 16
nettyThreads: 32
codec: !<org.redisson.codec.JsonJacksonCodec> {}
"transportMode": "NIO"

加载Redisson配置文件实例化RedissonClient和配置Redis序列化,核心代码如下:

@Bean
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
    RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
    config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
    config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
    CacheProperties.Redis redisProperties = cacheProperties.getRedis();
    if (redisProperties.getTimeToLive() != null) {
        config = config.entryTtl(redisProperties.getTimeToLive());
    }
    if (redisProperties.getKeyPrefix() != null) {
        config = config.prefixKeysWith(redisProperties.getKeyPrefix());
    }
    if (!redisProperties.isCacheNullValues()) {
        config = config.disableCachingNullValues();
    }
    if (!redisProperties.isUseKeyPrefix()) {
        config = config.disableKeyPrefix();
    }
    return config;
}
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
    Config config = Config.fromYAML(new ClassPathResource("application-single.yml").getInputStream());
    RedissonClient redisson = Redisson.create(config);
    return redisson;
}

核心代码

@Cacheable(value = "user", key = "#root.methodName", sync = true)
public AjaxResult selectUserById(@PathVariable Long id) {
    String key = "selectUserById";
    RLock readLock = cacheService.getReadLock(key);
    readLock.lock(20, TimeUnit.SECONDS);
    SysUser user = new SysUser();
    try {
        logger.info("加锁成功,开始执行业务......");
        user = userService.getById(id);
        logger.info("从数据库中查询用户信息.....");
    } catch (Exception e) {
        e.printStackTrace();
        logger.error("查询用户数据异常", e.getMessage());
    } finally {
        logger.info("释放锁,业务执行结束......");
        readLock.unlock();
    }
    return AjaxResult.ok().data("user", user);
}

SpringCache常用注解

@Cacheable:触发将数据保存到缓存,在方法上标注,表示当前方法的结果需要缓存,如果缓存中有,方法不用调用,如果缓存中没有,会调用方法,最后将方法的结果放入缓存。
@CacheEvict:触发将数据从缓存中删除
@CachePut:不影响方法执行更新缓存
@Caching:组合以上多个操作
@CacheConfig:在类级别共享缓存的相同配置

使用缓存会存在缓存数据一致性问题,常见的解决办法是双写模式和失效模式,但也不能绝对的解决数据一致性问题。在使用缓存时应该是实时性和一致性要求并不是很高的数据,在缓存中加上过期时间,可以保证一定时间后能拿到最新的数据,对于实时性和一致性要求特高的数据,可以使用canal订阅binlog的方式解决数据一致性问题。
代码详见码云地址

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/76838.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!