缓存概述

对于访问频率高、一段时间内变动频率低的数据,可以加缓存,以缓解服务器cpu、数据库的压力。如果数据库连接池max-active设置很大但还经常报连接不够,可以考虑缓存部分结果集。

 

缓存可分为2大类

  • 本地缓存:速度快、效率高,但分布式环境下容易出现数据不一致的问题、且缓存的数据量一般不能太大,常见的比如guava、ehcache、caffeine
  • 分布式缓存:速度比本地缓存慢,常用于多个服务、应用之间的数据共享,常见的比如redis、memcached

 

本地缓存

  • 可以自己用ConcurrentLinkedHashMap实现
  • ehcache:纯java的进程内缓存框架,快速精干,是hibernate默认的缓存管理器,缓存数据的存储有内存、硬盘两级,无需担心容量问题
  • caffeine:基于java8,高性能、高命中率、低内存占用,性能极高,在spring 2.x中取代guava成为默认的本地缓存组件,只在内存中存储缓存的数据

 

分布式缓存

  • redis、memcached的相同点:都是把全量数据存储在内存中,高性能,支持海量数据存储
  • redis:数据类型丰富,支持数据持久化,可备份数据至硬盘,容灾能力强,可用于存储缓存之外的其他数据,比如可当做队列使用
  • memcached:只支持String类型;缺乏认证、安全管制机制,使用时需要将memcached服务器放在防火墙后;不支持持久化,宕机全部数据丢失,一般只用于存储缓存
  • redis只使用单核,IO多路复用+减少线程间切换,memcached可以使用多核,存储少量数据时redis单核性能高于memcached,存储大量数据时memcached整体性能高于redis。

 

如何选择

  • 本地缓存极大地提升了效率,如果只有一台服务器(一个服务节点),可以使用本地缓存
  • 在分布式环境下,本地缓存容易出现节点之间数据不一致的问题,尽量不要使用本地缓存,使用分布式缓存代替

 

springboot整合caffeine

依赖
创建项目时勾选 I/O -> Spring cache abstraction,并加上caffeine的依赖

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

<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

 

引导类
加spring的缓存注解 @EnableCaching 开启缓存

 

yml

spring:
  cache:
    type: caffeine
    cache-names: caffeineCache
    caffeine:
      spec: initialCapacity=100,maximumSize=1000,expireAfterAccess=1800s

也可以用bean进行配置

/**
  * 配置caffeine缓存管理器
  */
 @Bean(name = "caffeineCache")  //指定缓存管理器名称
 public CacheManager cacheManager() {
     CaffeineCacheManager cacheManager = new CaffeineCacheManager();
     cacheManager.setCaffeine(Caffeine.newBuilder()
             //初始容量
             .initialCapacity(100)
             //最大个数
             .maximumSize(1000)
             //写入缓存或最后一次访问该缓存对象后,多长时间内没访问就过期
             .expireAfterAccess(30, TimeUnit.SECONDS));
     return cacheManager;
 }

caffeine提供三类驱逐策略:基于size(size-based)、基于time(time-based)、基于引用(reference-based),此处是size、time搭配使用。

 

实体类

@Data
public class Goods implements Serializable {

    private Integer goodsId;

    private String goodsName;

    private BigDecimal goodsPrice;

}

 

spring缓存注解的使用

缓存注解一般加在controller或service的方法上,controller常常要记录请求日志(请求参数、返回给前端的数据),建议加在service中的方法上。

@Service
public class GoodsService {

    @Cacheable(cacheNames = "caffeineCache", key = "#goodsId")
    public Goods findGoodsById(Integer goodsId) {
        Goods goods = new Goods();
        return goods;
    }


    @CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
    public Goods updateGoods(Goods goods) {
        return goods;
    }


    @CachePut(cacheNames = "caffeineCache", key = "#goods.goodsId")
    public Goods addGoods(Goods goods) {
        return goods;
    }


    @CacheEvict(cacheNames = "caffeineCache", key = "#goodsId")
    public void deleteGoods(Integer goodsId) {

    }

}

spring提供了通用的缓存注解,由具体的缓存框架提供实现,spring常用的缓存注解如下

  • @Cacheable:和查询搭配使用,先查本地缓存,本地缓存中有就直接返回结果,不再执行方法;本地缓存中没有就执行方法,执行成功后将结果写入本地缓存
  • @CacheEvict:和删除搭配使用,如果本地有对应的缓存,执行成功后清除缓存
  • @CachePut:执行成功后将返回值写入缓存,如果本地没有对应的缓存则为写入缓存,如果本地已有相应的缓存,则会更新缓存。insert、update更新缓存需要返回最新的实体对象,也可以使用@CacheEvict清除缓存,后续查询时@Cacheable写入缓存。

这3个注解的写入|清除缓存都是方法执行成功后才写入|清除缓存,如果方法执行失败|抛出异常,则不会写入|清除缓存。
 

这3个注解都具有以下属性

  • cacheNames:指定要使用的缓存管理器名称,也可以使用cacheNames属性的别名value指定
  • key:指定缓存的key
  • condition:指定注解生效的条件,当方法执行成功且condition的值为true时才会清除缓存
     

key、condition可以使用SpEL取值,也可以使用内置对象root

  • #root.methodName:当前方法名
  • #root.method.name:当前方法
  • #root.target:当前方法所属的对象
  • #root.targetClass:当前方法所属的对象的Class对象
  • #root.args:实参表, #root.args[0]表示第一个参数

 

springboot整合ehcache

依赖
创建项目时勾选 I/O -> Spring cache abstraction,并加上ehcache的依赖

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

<!-- ehcache3.x -->
<dependency>
    <groupId>org.ehcache</groupId>
    <artifactId>ehcache</artifactId>
    <version>3.9.0</version>
</dependency>

<!-- ehcahce 2.x,已不再维护 -->
<!--<dependency>-->
<!--    <groupId>net.sf.ehcache</groupId>-->
<!--    <artifactId>ehcache</artifactId>-->
<!--    <version>2.10.6</version>-->
<!--</dependency>-->

 

引导类
加@EnableCaching
 

resources下新建ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>

<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">

    <!-- 磁盘缓存位置。java.io.tmpdir是系统默认的临时文件目录,也可以使用自定义的路径 -->
    <diskStore path="java.io.tmpdir" />

    <!-- 默认缓存配置 -->
    <defaultCache
            overflowToDisk="true"
            maxEntriesLocalDisk="5000"
            memoryStoreEvictionPolicy="LRU"
            eternal="false"
            timeToLiveSeconds="3600"
            timeToIdleSeconds="600" />

    <!--自定义的缓存配置,name指定缓存管理器名称,可以有多个自定义的缓存配置 -->
    <cache name="ehcacheCache"
           maxElementsInMemory="2000"
           overflowToDisk="true"
           memoryStoreEvictionPolicy="LRU"
           eternal="false"
           timeToLiveSeconds="3600"
           timeToIdleSeconds="600" />

</ehcache>
  • maxElementsInMemory:内存中的最大缓存个数
  • overflowToDisk:内存最大缓存个数达到上限时是否缓存到硬盘
  • maxEntriesLocalDisk:硬盘最大缓存个数
  • memoryStoreEvictionPolicy:缓存个数达到上限时的缓存清除策略,FIFO:类似队列,先进先出,优先清除先放入缓存的;LRU:Least Recently Used,优先清除最近未使用的;LFU:Least Frequently Used,优先清除使用频率低的。
  • eternal:缓存是否永久有效
  • timeToLiveSeconds:缓存的有效期,到期自动清除
  • timeToIdleSeconds:在有效期内缓存的闲置时间,如果在指定时间范围内缓存从未被访问,会自动清除

这依然是ehcache2.x的配置文件,3.x依然兼容2.x的方式

 

yml

spring:
    cache:
    	#指定ehcache配置文件位置
        jcache:
            config: classpath:ehcache.xml
#        ehcache:
#            config: classpath:ehcache.xml

3.x用jcache.config,2.x使用ehcache.config。3.x向下兼容,也可以使用ehcache.config。

 

实体类

@Data  //要实现序列化接口,缓存至硬盘时要用到
public class Goods implements Serializable{

    private Integer goodsId;

    private String goodsName;

    private BigDecimal goodsPrice;

}

 

spring缓存注解的使用

@Service
public class GoodsService {

    @Cacheable(cacheNames = "ehcacheCache", key = "#goodsId")
    public Goods findGoodsById(Integer goodsId) {
        Goods goods = new Goods();
        return goods;
    }


    @CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
    public Goods updateGoods(Goods goods) {
        return goods;
    }


    @CachePut(cacheNames = "ehcacheCache", key = "#goods.goodsId")
    public Goods addGoods(Goods goods) {
        return goods;
    }


    @CacheEvict(cacheNames = "ehcacheCache", key = "#goodsId")
    public void deleteGoods(Integer goodsId) {

    }

}