一、简介
缓存是存储在内存中的KV数据结构,分为分布式缓存和本地缓存。
分布式缓存:一般应用进程和缓存进程不在同一台服务器,通过远程调用进行通信,可以实现应用服务和缓存的完全解耦,支持大量的数据存储,
分布式缓存常见有redis,memcache等。
本地缓存:应用进程和缓存进程在同一个进程,没有网络开销,访问速度快,但受限于内存,不适合存储大量数据。本地缓存主要有Guava cache,Caffeine,Encache等。
二、压测对比(实测)
1、10W 级别 (put耗时,10线程)
a、caffeine,put,costTime=81ms,cpu=14.6% ,内存=15.4%
b、guava,put,costTime=194ms,cpu=20.7% ,内存=14.8%
c、ehcache,put,costTime=171ms, cpu=39.9% ,内存= 14.8%
2、10W 级别 (get耗时,10线程)
a、caffeine,get,costTime=17ms,cpu=10% ,内存=14.8%
b、guava,get,costTime=34ms,cpu=10% ,内存= 14.8%
c、ehcache,get,costTime=80ms,cpu=14.3% ,内存= 14.8%
3、100W 级别 (put耗时,10线程)
a、caffeine,put,costTime=354ms,cpu=21.6% ,内存=16.8%
b、guava,put,costTime=1396ms,cpu=47.7% ,内存=16.8%
c、ehcache,put,costTime=673ms,cpu=41.9% ,内存= 16.8%
4、100W 级别 (get耗时,10线程)
a、caffeine,get,costTime=154ms,cpu=12.3% ,内存=16.8%
b、guava,get,costTime=250ms,cpu=16.3% ,内存= 16.8%
c、ehcache,get,costTime=241ms,cpu=14.4% ,内存= 16.8%
5、1000W 级别 (put耗时,10线程)
a、caffeine,put,costTime=15618ms,cpu=189.0% ,内存= 31.6%
b、guava,put,costTime=25607ms,cpu=190.1% ,内存= 32.2%
c、ehcache,put,costTime=21425ms,cpu=193.1% ,内存= 32.2%
6、1000W 级别 (get耗时,10线程)
a、caffeine,get,costTime=1939ms,cpu=73.3% ,内存= 32.2%
b、guava,get,costTime=2811ms,cpu=98.7% ,内存= 32.2%
c、ehcache,get,costTime=10027ms,cpu=98.7% ,内存= 32.2%
三、Guava Cache
1、maven引入
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</dependency>
2、使用介绍
@Component
public class MyGuavaCache {
private LoadingCache<String, Field> fieldCache = CacheBuilder.newBuilder()
.maximumSize(1000) // 设置最大容量
.refreshAfterWrite(24, TimeUnit.HOURS) //设置过期刷新间隔
.build(
new CacheLoader<String, Field>() { // 这是自动刷新
@Override
public Field load(String key) throws Exception {
return getFromDb(key);
}
}
);
/** * 从db取数 */
private Field getFromDb(String key) {
return dbRepositiory.getByKey(key);
}
/** * 提供给外部调用获取结果 */
public Map<String, Field> get(String key) {
try {
return fieldCache.get(key); // 过期会自动执行load获取数据
} catch (ExecutionException e) {
throw new UncheckedExecutionException(e);
}
}
/** *
手动强制缓存失效
* @param key
*/
public void invalidate(String key) {
fieldCache.invalidate(key);
}
}
四、Caffeine
1、maven引入
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
2、使用介绍
1、手动加载
Cache<String, String> cache = Caffeine.newBuilder()
//5分钟没有读写自动删除
.expireAfterAccess(5, TimeUnit.MINUTES)
//最大容量1024个,超过会自动清理空间
.maximumSize(1024)
.build();
String key = "key";
// 使用getIfPresent方法从缓存中获取值。如果缓存中不存指定的值,则方法将返回 null:
System.out.println(cache.getIfPresent(key));
// 也可以使用 get 方法获取值,该方法将一个参数为 key 的 Function 作为参数传入。如果缓存中不存在该 key, 则该函数将用于提供默认值,该值在计算后插入缓存中:
System.out.println(cache.get(key, new Function<Integer, Integer>() {
@Override
public Integer apply(Integer integer) {
return 2;
}
}));
2、同步加载
LoadingCache<String, String> cache = Caffeine.newBuilder()
//5分钟没有读写自动删除
.expireAfterAccess(5, TimeUnit.MINUTES)
//最大容量1024个,超过会自动清理空间
.maximumSize(1024)
.build((newCacheLoader() {
//默认的数据加载实现,当调用get取值的时候,
//如果key没有对应的值,就调用这个方法进行加载
@Override
public String load(String key) {
return"";
}
}
);
3、异步加载
public static void main(String[] args) {
AsyncLoadingCache<String, String> asyncLoadingCache = Caffeine
.newBuilder() .maximumSize(1000) .
buildAsync(key -> slowMethod(key));
CompletableFuture<String> g = loadingCache.get("test");
String value = g.get();
}
static String slowMethod(String key) throws Exception {
Thread.sleep(1000); return key + ".result";
}
4、过期时间设置
LoadingCache<String,String> cache = Caffeine.newBuilder()
//限制最大数量
.maximumSize(1000)
//基于权重
.maximumWeight(1000)
//指定计算权重的方式
.weigher(this::caculateWeight)
//缓存在写入多久后失效
.expireAfterWrite(1000,TimeUnit.SECONDS)
//缓存在访问多久后失效
.expireAfterAccess(1000,TimeUnit.SECONDS)
//多种时间过期策略组合使用
.expireAfter(new Expiry<String, String>() {
public long expireAfterCreate(Key key, Graph graph, long currentTime) {
//首次存入缓存后,过期时间为 4 秒
return 4000000000L;
}
public long expireAfterUpdate(Key key,
Graph graph,long currentTime,
long currentDuration) {
//更新key后,过期时间为 4 秒
return 4000000000L;
}
public long expireAfterRead(Key key,
Graph graph,
long currentTime,
long currentDuration) {
//每次被读取后,过期时间为 4 秒
return 4000000000L;
}
})
.build(this::load);
5、延迟刷新
refreshAfterWrite(long duration, TimeUnit unit)
写操作完成后多久才将数据刷新进缓存中,两个参数只是用于设置时间长短的。
只适用于 LoadingCache 和 AsyncLoadingCache,如果刷新操作没有完成,读取的数据只是旧数据。
6、异步清除、更新监听
当缓存中的数据发送更新,或者被清除时,就会触发监听器,在监听器里可以自定义一些处理手段,比如打印出哪个数据被清除,原因是什么。这个触发和监听的过程是异步的,就是说可能数据都被删除一小会儿了,监听器才监听到。
例子
MyStatsCounter myStatsCounter = new MyStatsCounter();
Caffeine caffeine = Caffeine.newBuilder()
.maximumWeight(30)
.removalListener((String key, Person value, RemovalCause cause)->{
System.out.println("被清除人的年龄:" + value.getAge() + "; 清除的原因是:" + cause);
})
.weigher((String key, Person value)-> value.getAge());
Cache cache = caffeine.build();
cache.put("one", new Person(12, "one"));
cache.put("two", new Person(18, "two"));
cache.put("one", new Person(14, "one"));
cache.invalidate("one");
cache.put("three", new Person(31, "three"));
Thread.sleep(2000);
运行结果
被清除人的年龄:12; 清除的原因是:REPLACED
被清除人的年龄:14; 清除的原因是:EXPLICIT
被清除人的年龄:18; 清除的原因是:SIZE
EXPLICIT : 表示显式地调用删除操作,直接将某个数据删除。
REPLACED:表示某个数据被更新。
EXPIRED:表示因为生命周期结束(过期时间到了),而被清除。
SIZE:表示因为缓存空间大小受限,总权重受限,而被清除。
7、同步监听器
之前的 removalListener 是异步监听,此处的 writer 方法可以设置同步监听器,同步监听器一个实现了接口 CacheWriter 的实例化对象,我们需要自定义接口的实现类
关键是要实现 CacheWriter 接口的两个方法,当新增,更新某个数据时,会同步触发 write 方法的执行。当删除某个数据时,会触发 delete 方法的执行。
举个例子:
public class MyCacheWriter implements CacheWriter<String, Application.Person> {
@Override
public void write(String s, Application.Person person) {
System.out.println("新增/更新了一个新数据:" + person.getName());
}
@Override
public void delete(String s, Application.Person person,
RemovalCause removalCause) {
System.out.println("删除了一个数据:" + person.getName());
}
}
Caffeine caffeine = Caffeine.newBuilder()
.maximumWeight(30)
.writer(new MyCacheWriter())
.weigher((String key, Person value)-> value.getAge());
Cache cache = caffeine.build();
cache.put("one", new Person(12, "one"));
cache.put("two", new Person(18, "two"));
cache.invalidate("two");
运行结果:
新增/更新了一个新数据:one
新增/更新了一个新数据:two
删除了一个数据:two
8、常用的方法
V getIfPresent(K key) :如果缓存中 key 存在,则获取 value,否则返回 null。
void put( K key, V value):存入一对数据 。
Map getAllPresent(Iterable var1) :参数是一个迭代器,表示可以批量查询缓存。
void putAll( Map var1): 批量存入缓存。
void invalidate(K var1):删除某个 key 对应的数据。
void invalidateAll(Iterable var1):批量删除数据。
void invalidateAll():清空缓存。
long estimatedSize():返回缓存中数据的个数。
CacheStats stats():返回缓存当前的状态指标集。
ConcurrentMap asMap():将缓存中所有的数据构成一个 map。
void cleanUp():会对缓存进行整体的清理,比如有一些数据过期了,但是并不会立马被清除,所以执行一次 cleanUp 方法,会对缓存进行一次检查,清除那些应该清除的数据。
V get( K var1, Function var2):第一个参数是想要获取的 key,第二个参数是函数
五、Encache
1、maven引入
<dependency>
<groupId>org.ehcache</groupId>
<artifactId>ehcache</artifactId>
</dependency>
2、使用介绍
static longsize=10000;
// 声明一个cacheBuilder
static CacheManagerehcacheManager = CacheManagerBuilder
.newCacheManagerBuilder()
.withCache("encacheInstance", CacheConfigurationBuilder
//声明一个容量为100000000的堆内缓存
.newCacheConfigurationBuilder(String.class,String.class,
ResourcePoolsBuilder.heap(100000000)))
.build(true);
static org.ehcache.Cacheehcache =
ehcacheManager.getCache("encacheInstance", String.class, String.class);
for (int j = 0; j <size; j++) {
ehcache.put(i+j+"key",i+j+"value");
}
for (int j = 0; j <size; j++) {
ehcache.get(i+j+"key");
}
六、Spring boot 集成Caffeine
1、maven
<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、配置文件
spring.cache.cache-names=people
spring.cache.caffeine.spec= initialCapacity=50,maximumSize=500,expireAfterWrite=20s,refreshAfterWrite=5s
3、启动类
package com.linlong;
import com.github.benmanes.caffeine.cache.CacheLoader;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
/**
* 启动类
* @author yehan
*/
@SpringBootApplication
@Slf4j
@EnableCaching// 开启缓存,需要显示的指定
public class Application implements CommandLineRunner {
@Value("${spring.application.name}")
private String applicationName;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... strings) throws Exception {
log.info("-------------------- [{}]初始化完成 --------------------", applicationName);
}
/**
* 必须要指定这个Bean,refreshAfterWrite这个配置属性才生效
*
* @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;
}
}
4、代码
package com.linlong.logic.service.impl;
import com.linlong.domain.vo.PeopleVO;
import com.linlong.logic.service.CaffeineService;
import com.linlong.repository.cache.PeopleMap;
import lombok.extern.log4j.Log4j2;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Map;
/**
*
* @author yehan
*/
@Service
@Log4j2
public class CaffeineServiceImpl implements CaffeineService {
@Resource
private PeopleMap peopleMap;
@Cacheable(value = "people", key = "#id", sync = true)
@Override
public PeopleVO get(String id) {
return peopleMap.get(id);
}
@Cacheable(value = "people")
@Override
public Map<String, PeopleVO> getAll() {
return peopleMap.getAll();
}
@CachePut(value = "people", key = "#value.id")
@Override
public void put(PeopleVO value) {
peopleMap.put(value.getId(),value);
}
@CacheEvict(value = "people",key = "#id")
@Override
public void delete(String id) {
peopleMap.delete(id);
}
}
6、配置参数
initialCapacity=[integer]: 初始的缓存空间大小
maximumSize=[long]: 缓存的最大条数
maximumWeight=[long]: 缓存的最大权重
weigher(Function函数):缓存权重
expireAfterAccess=[duration]: 最后一次写入或访问后经过固定时间过期
expireAfterWrite=[duration]: 最后一次写入后经过固定时间过期
refreshAfterWrite=[duration]: 创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
weakKeys: 打开key的弱引用
weakValues:打开value的弱引用
softValues:打开value的软引用
recordStats:开发统计功能
7、注意事项
expireAfterWrite和expireAfterAccess同时存在时,以expireAfterWrite为准。
maximumSize和maximumWeight不可以同时使用
weakValues和softValues不可以同时使用
8、DEMO
暂时无法在文档外展示此内容