转载

拿什么拯救你的性能(2) - 更换Cache实现

在上一集中,我们使用了默认的ConcurrentMapCacheManager作为Spring Cache的默认实现。在小型的应用中,这已经足够了,但在实际的项目中,我们可能需要引入JCache,Redis,MemCache等更加成熟的缓存技术.下面我们就来看看吧.

JCache

JCache通常也叫JSR-107,JCache是一套规范.
JCACHE规范承诺为Java提供一套标准API,通过这套API,编程人员可以透明地操作数据,不用关心数据放在哪里.
我们来看看,它的初衷和Spring Cache的目的是一样的,都是一套规范.
同时,JCache提供了一套类似于Spring Cache的annotation来标注方法和类.包括@CacheResult,@CachePut,@CacheRemove等,这些注解都位于javax.cache.annotation包下.
在使用Spring Cache的时候,我们也可以使用JCache的注解,Spring 能正确的根据注解实现相应的缓存逻辑.
JCache有各种实现,比较典型的是EhCache3,Hazelcast等,Spring Cache可以直接使用JCache的各种实现来作为自己的Cache实现. 下面我们以Spring Boot使用EhCache为例。改造上一次的代码

首先,导入必要依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-cache</artifactId>
 </dependency>
<dependency>
  <groupId>javax.cache</groupId>
  <artifactId>cache-api</artifactId>
</dependency>
<dependency>
  <groupId>org.ehcache</groupId>
  <artifactId>ehcache</artifactId>
</dependency>

这里的cache-api就是JSR-107所定义的相关的Cache的接口.
ehcache就是ehcache3.

启用Spring Cache

@SpringBootApplication
@EnableCaching
public class EhCacheApplication {
  public static void main(String[] args) throws Exception {
    SpringApplication.run(EhCacheApplication.class, args);
  }
}

我们所有的代码没有变化,在controller中加入一个接口,用来获取系统中实际使用的cacheManager

/**
 * 注入Spring Boot生成的CacheManager
 */
@Autowired
private CacheManager cacheManager;

/**
 * 获取并显示实际使用的CacheManager
 */
@GetMapping("manager")
public String getManager() {
  return cacheManager.toString();
}

在浏览器中输入相关URL,可以获得结果
org.springframework.cache.jcache.JCacheCacheManager@xxxx
我们可以注释掉相关的依赖,即pom.xml中的cache-api和ehcache两项,再运行相关的接口,接口会返回相应的数据
org.springframework.cache.concurrent.ConcurrentMapCacheManager@xxxx.

Spring Boot会按照如下优先级来自动装配一个CacheManager
Generic
JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, etc)
EhCache 2.x
Hazelcast
Infinispan
Couchbase
Redis
Caffeine
Guava (deprecated)
Simple
参见Spring Boot文档.
当Spring Boot按照以上顺序检测到某一个缓存实现存在的话,会自动构建一个使用相应实现的CacheManager.并跳过后续检测。

配置JCache的配置文件

我们使用ehcache3作为缓存实现的时候,可以对ehcache进行配置,以实现缓存过期等策略。具体的包括

  • 简单配置
    我们的代码,到目前为止,当我们启用了ehcache3,并进行了相关的接口调用的时候,会抛出异常java.lang.IllegalArgumentException,Cannot find cache named 'test' for Builder.这是因为ehcahce需要我们显示的声明Cache.
    我们可以在Spring Boot的配置文件 src\main\resources\application.yml中声明Cache
spring:
  cache:
    cache-names:
    - test

cache-names是一个列表,声明了在项目中用到的cache的名称。

  • 完整的Ehcache配置
    在yml/properties文件中,我们只能进行一些简单的配置。如果要进行复杂的配置,我们需要指定一个配置文件所在的路径。
    修改后的yml如下
spring:
  cache:
    jcache:
      config: classpath:cache.xml

并且,我们在resources目录下面建立cache.xml文件。进行cache的配置

<?xml version="1.0" encoding="UTF-8"?>
<config xmlns='http://www.ehcache.org/v3' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance'
    xsi:schemaLocation="http://www.ehcache.org/v3
        http://www.ehcache.org/schema/ehcache-core-3.0.xsd">

    <cache-template name="default">
        <heap unit="MB">1</heap>
    </cache-template>

    <cache alias="test" uses-template="default">
        <expiry>
            <ttl unit="seconds">10</ttl>
        </expiry>
    </cache>
</config>

在这里,我们通过alias值定了cache的名称,通过ttl指定了cache过期时间,这里指定为10秒。我们重新访问接口时,当距离上次缓存的时间大于10秒钟的时候,都会重新执行一次方法。
更多ehcache的配置文件,可以参考Ehcache官方网站,不在这里展开。

  • 缓存并发穿透
    并发穿透,指的是当缓存过期失效后。如果瞬间有大量的请求进来,这些请求在执行的时候,均会查询缓存,这些查询都会导致缓存没有命中,进而执行实际的代码。如果这些代码是持久层的操作,或者是比较耗时的操作的,会导致计算压力瞬间倍增。
    通常要解决这些场景的问题,需要一些线程同步的能力,但在Spring Cache 4.3以后的版本中,这种情况大为改善。我们只需要在Cacheable的注解中,指定sync=true即可。示例代码如下。
@Override
@Cacheable(cacheNames = "test", sync = true)
 public String get(String id) {
    // 记录数据产生的时间,用于测试对比
    long time = new Date().getTime();
    // 打印使用到的cacheManager
    logger.info("The cacheManager is" + cacheManager);
    // 当数据不是从cache里面获取时,打印日志
    logger.info("Get value by id=" + id + ", The time is " + time);
    return "Get value by id=" + id + ",the value is " + enties.get(id);
}

当缓存失效之后,有请求并发访问到这里的时,只会有一个线程实际执行方法体,其它的请求等待之前的线程执行并缓存结果。这大大简化了并发的处理逻辑。

Redis

redis是一个常用的集中式缓存服务。Spring对Redis也进行了集成,我们可以方法的使用RedisTemplate进行Redis的读写操作。当然,我们也可以非常方便的将Spring的缓存实现更改为Redis实现,你只需要加入Redis相关的依赖。

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

然后在配置文件中配置相关的redis连接信息。以上的代码不用更改,即可实现使用Redis作为缓存的实现。

指定缓存类型

当项目中同时存在多种技术时,Spring会按照一定的顺序去寻找缓存的实现。但有时候我们需要显示的指定缓存的实现,比如当比如Jcache和redis共存时,Spring会使用JCache作为缓存的实现,而事实上我们可能需要的是Redis.这时,就需要我们手工显示的指定实现。在yml/properties文件中指定即可

spring:
  cache:
    type: redis

可供选择的类型在org.springframework.boot.autoconfigure.cache.CacheType枚举中。

点击这里下载相关代码

小结:缓存在互联网时代是非常重要的技术,也不是一两篇文章就能讲完的,大家一起研究,一起学习。
距离上一篇已经过去了一个月了,码字的速度好慢。



作者:虾游于海
链接:https://www.jianshu.com/p/3fbd5af2d789
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
正文到此结束
Loading...