本文我们先介绍一些缓存的背景知识,以及内存缓存的流行开源库类实现,最后利用一些例子重点介绍下 Guava Cache 的缓存功能。
背景
什么是缓存
在计算中,缓存是一个高速数据存储层,其中存储了数据子集,且通常是短暂性存储,这样日后再次请求该数据时,速度要比访问数据的主存储位置快。通过缓存,可以高效地重用之前检索或计算的数据。
本文中所提及的缓存主要是指内存缓存,跟硬件没什么关系(比如三级缓存什么的),主要是应用代码层面和内存交互的这部分。
缓存的特点
第一个特点:贼快(操作内存读写当然快了)
你可能会问了,贼快是多快?嗯,没有对比就没有伤害,我们来看一下不同介质访问数据的时间情况
看到了吧,RAM 的速度大概是 10-100 纳秒,什么概念? 1 秒钟等于 10 亿纳秒,这速度快到你根本感觉不到。
第二个特点:说没就没
- 断电立即丢失
- 超过缓存失效时间
解决什么问题
一般来说,我们利用本地的内存缓存主要可以达到减轻数据库压力、提高系统响应速度和吞吐量的目的。
总之,如果对某些值的计算或检索成本很高,并且多次需要使用该值时,应该考虑使用缓存。
内存缓存库类
在 Java 中一提到缓存,我们首先想到的可以用 ConcurrentHashMap
做缓存。
static ConcurrentHashMap<String,Object> localCache = new ConcurrentHashMap<>();
为什么要用 ConcurrentHashMap
呢?
因为首先它是个 Map,这种 K,V 的数据结构很适合用来读写缓存对象,其次它还是线程安全的,多线程并发不会有线程安全问题。
Java 虽然为我们提供了ConcurrentHashMap
这样合适做缓存的数据结构,但他在功能上却有很多的不足,比如没有 回收、驱逐、监听、刷新等功能。一般来说,我们设计一套完整的缓存方案虽然这些功能,用 ConcurrentHashMap
意味着这些功能你要自己开发了。
在 Java 的生态中有许多库可以帮助我们省去自己开发的麻烦,人家都封装好了,开箱即用,这里我们列举几个知名和常用的,后面我们重点介绍 Guava 的 cache 模块:
Guava Cache
Spring Cache
Spring 提供的一整套的缓存解决方案。虽然它本身并没有提供缓存的实现,但是它提供了一整套的接口和代码规范、配置、注解等,这样它就可以整合各种缓存方案了,比如 Redis、Ehcache,我们也就不用关心操作缓存的细节。Caffeine
(以 GuavaCache 为原型而开发的一个本地缓存框架,相对 GuavaCache, 它有更高的性能与命中率,更强大的功能,更灵活的配置方式)J2Cache
(OSChina 开源的一个两级缓存框架,采用固定的 一级 + 二级缓存 的模式,从一开始就是为了解决两级缓存一致性的问题)JetCache
(是阿里开源的通用缓存访问框架,它统一了多级缓存的访问方式,封装了类似于 SpringCache 的注解,以及 GuavaCache 类似的 Builder, 来简化项目中使用缓存的难度)
这里多说两句:
Caffeine
是当前最优秀的内存缓存框架,不论读还是写的效率都远高于其他缓存,而且在Spring5
开始的默认缓存实现就将 Caffeine 代替原来的 Guava。
在项目中,比如你用 SpringBoot 想加本地缓存,我们通常会引入 SpringCache+Caffeine
的依赖。使用 SpringCache
注解方法实现缓存。SpringCache 帮我们封装了 Caffeine,通过这种方式集成 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>
有朋友说了,你这是一级缓存,我们一般会使用二级缓存,即一级缓存用 caffeine
二级缓存用 Redis
(强强联合,很常用的方案),一级缓存找不到去二级缓存找。
没错,如果你想用 SpringBoot 集成 Caffeine
和Redis
实现二级缓存,有两种方式:
第一种,直接集成,引入的依赖有变化:
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
这里顺便说一下 spring-boot-starter-data-redis
,spring-data-redis 和 Redis 的关系如下图,延续了 Spring 的一贯思想,对上层仍然是一层封装,对底层支持各种 Redis 客户端的实现。
第一种方式的集成比较简单,但请注意 spring cache (caffeine) 和 spring-data-redis(redis),是各管各的(如前面括号里写的),不好意思,一二级缓存之间的逻辑关系需要你自己处理 具体来说比如你可以实现 cache 拦截器 CacheInterceptor
这里有一个比较容易混乱的点, spring cache 是支持多个 Provider 的:
- Generic
- JCache (JSR-107) (EhCache 3, Hazelcast, Infinispan, and others)
- EhCache 2.x
- Hazelcast
- Infinispan
- Couchbase
- Redis