什么是缓存

缓存就是将程序或系统频繁使用的对象保存在内存中,便于使用时可以快速调用,不必再去创建新的重复的实例或者重复调用接口。可以显著减少系统开销,提高系统效率。

缓存分类和应用场景

缓存有各类特征,在目前的应用服务框架中,比较常见根据缓存与应用的耦合度,分为local cache(本地缓存)和remote cache(分布式缓存)

本地缓存:指的是在应用中的缓存组件,其最大的优点是应用和cache是在同一个应用内部,请求缓存非常快速,没有额外网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较合适;同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接的共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。

分布式缓存:指的是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接的共享缓存。缺点,也是因为独立,与应用分离,存在第三方依赖风险.

目前各种类型的缓存活跃在成千上万的服务中,还没有一种缓存方案可以解决一切的业务场景或数据类型,需要根据自身的特殊场景和背景,选择最适合的缓存方案。准确判断使用何种类型的缓存,以最小的成本最快的效率达到最优的目的。

为什么要有本地缓存

在系统中有些数据,数据量小,但是访问十分频繁(例如城市/bu/等一些数据字典),针对这种场景,需要将数据放到应用的本地缓存中,以提升系统的访问效率,减少无谓的数据库访问(数据库访问占用数据库连接,同时网络消耗比较大),但是有一点需要注意,就是缓存的占用空间以及缓存的失效策略。

很多情况下的数据,大多是业务无关的,且分布式缓存的构建/集群维护成本比较高,使用本地缓存足矣。

Caffeine介绍

Caffeine是一种高性能,近似最优命中的本地缓存,简单点说类似于自己实现的ConcurrentHashMap,是使用## Java8基于Guava和ConcurrentLinkedHashMap的本地缓存 ##,既然是重写,性能必须吊打Guava(对比参考<<你应该知道的Java缓存进化史>>),且在Spring Boot 2.0(spring 5)中已取代Guava,那么我们就没有理由怀疑Caffeine的性能了。Guava Cache 的功能的确是很强大,满足了绝大多数人的需求,但是其本质上还是 LRU 的一层封装,所以在众多其他较为优良的淘汰算法中就相形见绌了。而 Caffeine Cache 实现了 W-TinyLFU(LFU+LRU 算法的变种)

具体原理可以参考官网,github地址https://github.com/ben-manes/caffeine

Spring Boot自动配置Caffeine

spring boot的自动配置无比强大,能不写的代码一点也不用写.

Caffeine在Spring Boot 2.0中取代Guava。如果出现Caffeine,CaffeineCacheManager(由spring-boot-starter-cache提供)将会自动配置。使用spring.cache.cache-names属性可以在启动时创建缓存,并可以通过以下配置进行自定义(按顺序):

spring.cache.caffeine.spec: ##缓存的自定义参数
com.github.benmanes.caffeine.cache.CaffeineSpec: ##bean定义
com.github.benmanes.caffeine.cache.Caffeine: ##bean定义

例如,以下配置创建一个cache1和cache2缓存,最大数量为500,存活时间为10分钟:

spring.cache.cache-names=cache1,cache2
spring.cache.caffeine.spec=maximumSize=500,expireAfterAccess=600s

如果定义了com.github.benmanes.caffeine.cache.CacheLoader,它会自动关联到CaffeineCacheManager。由于该CacheLoader将关联被该缓存管理器管理的所有缓存,所以必须定义为CacheLoader<Object, Object>,自动配置将忽略所有泛型类型。

Spring boot中如何使用

1.引入依赖包

无需指定版本,starter已包含版本定义

<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>

2.添加注解,开启缓存支持

启动类上添加@EnableCaching注解

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

3.配置文件

用配置文件可以对所有缓存进行配置,一般情况已足够,这种方法配置简单,但是灵活性不高,对设置的cache(cache name)所有的规则都是一样的,如对单个缓存有配置需求,需自定义配置类Caffeine或者CaffeineSpec

properties文件

spring.cache.cache-names=cache1
spring.cache.caffeine.spec=initialCapacity=50,maximumSize=500,expireAfterWrite=10s

或Yaml文件

spring:
  cache:
    type: caffeine
    cache-names:
    - city
    caffeine:
      spec: maximumSize=1024,expireAfterWrite=2h

如果使用refreshAfterWrite配置,必须指定一个CacheLoader.不用该配置则无需这个bean,如上所述,该CacheLoader将关联被该缓存管理器管理的所有缓存,所以必须定义为CacheLoader<Object, Object>,自动配置将忽略所有泛型类型。

/**
 * 必须要指定这个Bean,refreshAfterWrite=2h属性才生效
 * @return
 */
@Bean
public CacheLoader<Object, Object> cacheLoader() {
    CacheLoader<Object, Object> cacheLoader = new CacheLoader<Object, Object>() {
        @Override
        public Object load(Object key) throws Exception {
            return null;
        }

        // 重写这个方法将oldValue值返回回去,进而刷新缓存
        @Override
        public Object reload(Object key, Object oldValue) throws Exception {
            return oldValue;
        }
    };
    return cacheLoader;
}

Caffeine常用配置说明:

initialCapacity=[integer]: 初始的缓存空间大小

maximumSize=[long]: 缓存的最大条数

maximumWeight=[long]: 缓存的最大权重

expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期

expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期

refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存

weakKeys: 打开key的弱引用

weakValues:打开value的弱引用

softValues:打开value的软引用

recordStats:开发统计功能

注意:

expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。

maximumSize和maximumWeight不可以同时使用

weakValues和softValues不可以同时使用

4.代码中注解

在需要的方法上添加注解,@Cacheable注解value为缓存的名称,需在配置文件中定义,必须指定至少一个; key:缓存的 key可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则默认按照方法的所有参数进行组合;

此处要注意,spring 用AOP方式实现缓存,因此在同一个类中调用方法,是不生效的.

@Cacheable(value = "city")
public String getCityName(int cityId) {
    String cityName = "";
    ....
    return cityName;
}

延伸

Spring注解缓存

事实上,Spring cache不是Spring boot 才有的,只是boot提供的强大的自动配置能力进一步简化了cache的使用.

Spring 3.1之后,引入了注解缓存,其本质上不是一个具体的缓存实现方案,而是一个对缓存使用的抽象,通过在既有代码中添加少量自定义的annotation注解,就能够达到使用缓存对象和缓存方法的返回对象的效果。Spring的缓存技术具备相当的灵活性,不仅能够使用SpEL(Spring Expression Language)来定义缓存的key和各种condition,还提供开箱即用的缓存临时存储方案(SIMPLE),也支持和主流的专业缓存集成(REDIS等)。其特点总结如下:

少量的配置annotation注释即可使得既有代码支持缓存;

支持开箱即用,不用安装和部署额外的第三方组件即可使用缓存;

支持Spring Express Language(SpEL),能使用对象的任何属性或者方法来定义缓存的key和使用规则条件;

支持自定义key和自定义缓存管理者,具有相当的灵活性和可扩展性。

和Spring的事务管理类似,Spring Cache的关键原理就是Spring AOP,通过Spring AOP实现了在方法调用前、调用后获取方法的入参和返回值,进而实现了缓存的逻辑。

注解驱动的Spring Cache能够极大的减少编写常见缓存的代码量,通过少量的注释标签和配置文件,即可达到使代码具备缓存的能力,且具备很好的灵活性和扩展性。但是Spring Cache由于基于Spring AOP 动态的proxy技术,导致其不能很好的支持方法的内部调用或者非public方法的缓存设置。

不同注解区别

@Cacheable/@CachePut/@CacheEvict

Spring boot支持以下缓存的自动配置

主要包含EHCACHE,REDIS,CAFFEINE,SIMPLE(也就是ConcurrentHashMap)

guava已经被CAFFEINE替代

/**
 * Supported cache types (defined in order of precedence).
 * @since 1.3.0
 */
public enum CacheType {
    /**
     * Generic caching using 'Cache' beans from the context.
     */
    GENERIC,

    /**
     * JCache (JSR-107) backed caching.
     */
    JCACHE,

    /**
     * EhCache backed caching.
     */
    EHCACHE,

    /**
     * Hazelcast backed caching.
     */
    HAZELCAST,

    /**
     * Infinispan backed caching.
     */
    INFINISPAN,

    /**
     * Couchbase backed caching.
     */
    COUCHBASE,

    /**
     * Redis backed caching.
     */

    REDIS,

    /**
     * Caffeine backed caching.
     */
    CAFFEINE,

    /**
     * Simple in-memory caching.
     */

    SIMPLE,

    /**
     * No caching.
     */
    NONE
}

Spring boot默认缓存实现

If you do not add any specific cache library, Spring Boot auto-configures a simple provider that uses concurrent maps in memory. When a cache is required (such as piDecimals in the preceding example), this provider creates it for you. The simple provider is not really recommended for production usage, but it is great for getting started and making sure that you understand the features.

如果项目中开启了缓存,但是没有指定特定的缓存库,BOOT会自动配置一个SIMPLE的本地缓存,也就是以ConcurrentHashMap实现的缓存.不过不建议在生产使用.

参考

Caffeine官网github地址https://github.com/ben-manes/caffeine

spring官网cache特性https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-caching.html

Spring Cache的抽象解释与注解 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/integration.html#cache

<<你应该知道的Java缓存进化史>> https://mp.weixin.qq.com/s/DV5eSZtShs2twGe0UwzPuA<<缓存那些事>> https://tech.meituan.com/cache_about.html

https://blog.csdn.net/xiaolyuh123/article/details/78794119

留言