文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Java开发利器之Guava Cache

2024-12-01 14:50

关注

在企业Web应用中,通过缓存技术能够提高请求的响应速度;减少系统IO开销;降低系统数据读写压力...

缓存的意义

首先我们要知道,在我们开发过程中,为什么要使用缓存,缓存能够为我们带来哪些好处!

优点

缺点

总的来说,缓存主要是针对高频访问但低频更新的数据,从而加快服务器响应与原资源访问压力。

Guava Cache是一个相对比较简单并且容易理解的本地缓存框架,今天主要以此为开端来认识并学习如何使用缓存。

Guava Cache特色

本地缓存我们可以简单的理解为Map,将数据保存到Map(内存)中,下次使用该数据时,通过key直接从Map中取即可。但是使用Map会有一些几个问题需要考虑:

当然以上问题我们通过我们对Map包装下即可实现,当然Guava Cache也就是基于这种思想,底层原理则是基于Map实现,我们看下其有哪些特色:

通过设置Key的过期时间,包括访问过期和创建过期;设置缓存容量大小,采用LRU的方式,选择最近最久的缓存进行删除。

Cache主要基于CurrentHashMap实现线程安全;通过对key的计算,基于分段锁,提高缓存读写效率,降低锁的粒度,提升并发能力。

在缓存中查询某个key,如果不存在,则查源数据,并回填缓存。在高并发下会出现,多次查询元数据并重复回填缓存,可能会造成系统故障,最明显的DB服务器宕机,性能下降等。GuavaCache通过在CacheLoader调用load方法时,对同一个key同一时刻只会有一个请求去读源数据并回填缓存,后面的请求则直接继续从缓存读取,有效阻断并发请求对资源服务的影响。

一般我们在业务中操作缓存,都会操作缓存和数据源两部分GuavaCache的get可以集成数据源,在从缓存中读取不到时可以从数据源中读取数据并回填缓存。

监控缓存加载次数、命中率、失误率以及数据加载时长等。

API介绍

ManualCache此时Cache相当于一个Map,对数据进行CRUD操作时,需要同步操作缓存Map; 高并发情况时,可以使用get(k,loader)读缓存,通过Cache锁机制,防止对系统资源(DB)的并发访问 通过put方法实现缓存的存入与更新;

此时构建的是一个实现了Cache接口的LoadingCache,相比ManualCache,提供了缓存回填机制,即当缓存不存在时,会基于CacheLoader查询数据并将结果回填到缓存, 在高并发时,可以有效地基于缓存锁减少对系统资源的调用。此时仅需要关注缓存的使用,缓存的更新与存入都是基于CacheLoader实现;

get(k)

根据key查询,没有则触发load;如果load为空则抛出异常。

getUnchecked(k)

缓存不存在或返回为null会抛出检查异常。

get(k,loader)

根据key查询,没有则调用loader方法,且对结果缓存;如果loader返回null则抛出异常,此时不会调用默认的load方法。

getIfPresent(k)

有缓存则返回,否则返回null,不会触发load。

put(k,v)

如果缓存已经存在,则会先进行一次删除。

invalidate(k)

根据key使缓存失效。

过期

通过配置的过期参数,比如expireAfterAccess、expireAfterWrite、refreshAfterWrite。

过载

当缓存数据量超过设置的最大值时,根据LRU算法进行删除。

引用

构建缓存时将键值设置为弱引用、软引用,基于GC机制来清理缓存。

hitRate()

缓存命中率;

hitMiss()

缓存失误率;

loadCount() 加载次数;

averageLoadPenalty()

加载新值的平均时间,单位为纳秒;

evictionCount() 缓存项被回收的总数,不包括显式清除。

Builder配置

配置

描述

expireAfterAccess

多久没有读写则过期

expireAfterWrite

写入后多久没更新自动过期,先删除,后load

refreshAfterWrite

上一次更新后多久自动刷新,先reload后删除,并发时会取到老的数据

removalListener

设置缓存删除监听

initialCapacity

缓存初始化大小

concurrencyLevel

最大的并发数,可以理解为并发线程数量

maximumSize

最大缓存数量,超过时会根据策略清除

maximumWeight

最大权重容量数,仅用于确定缓存是否超过容量

recordStats

缓存命中统计

简单示例

下面以用户服务为例,我们看下如何在增删改查方法中使用缓存:

private Cache<String, User> cache = CacheBuilder.newBuilder()
.expireAfterWrite(3, TimeUnit.SECONDS)//写入多久没更新自动过期,先删除,后load
.removalListener(new RemovalListener<Object, Object>() {
@Override
public void onRemoval(RemovalNotification<Object, Object> notification) {
LOGGER.info("{} remove {}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),notification.getKey());
}
})
.initialCapacity(20) //初始化容量
.concurrencyLevel(10) // 并发
.maximumSize(100) //最多缓存数量
.recordStats() // 开启统计
.build();

@Override
public User getUser(String id){
// 缓存不存在时,通过LocalCache锁机制,防止对数据库的高频访问
User user;
try {
user = cache.get(id,()-> {
LOGGER.info("缓存不存在,从loader加载数据");
return userDao.get(id);
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
return user;
}

@Override
public User saveOrUpdateUser(User user){
userDao.saveOrUpdate(user);
cache.put(user.getId(),user);
return user;
}

@Override
public void removeUser(String id){
userDao.remove(id);
cache.invalidate(id);
}
private LoadingCache<String, User> cache = CacheBuilder.newBuilder()
// 省略
.build(new CacheLoader<String, User>() {
@Override
public User load(String key) throws Exception {
LOGGER.info("{} load {}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),key);
return userDao.get(key);
}

@Override
public ListenableFuture<User> reload(String key, User oldUser) throws Exception {
LOGGER.info("{} reload {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),key);
ListenableFutureTask<User> listenableFutureTask = ListenableFutureTask.create(() -> userDao.get(key));
CompletableFuture.runAsync(listenableFutureTask);
return listenableFutureTask;
}
});

@SneakyThrows
@Override
public User getUser(String id){
// 缓存不存在或返回为null会抛出异常
try {
return cache.getUnchecked(id);
} catch (Exception e) {
return null;
}
}

@Override
public User saveOrUpdateUser(User user){
cache.invalidate(user.getId());
return userDao.saveOrUpdate(user);
}

@Override
public void removeUser(String id){
cache.invalidate(id);
userDao.remove(id);
}

总结:第一种写法更像是前面说到的Map,在对数据进行CRUD操作时,需要用户手动对缓存进行同步的更新或删除操作,所以叫ManualCache(手动),当然Guava Cache对Map的加强依然有效,比如过期清除,缓存容量限制。第二种方式写法差不多,主要是引入了CacheLoader接口,在读数据时缓存数据不存在时,通过CacheLoader的load方法先写缓存后返回数据。

注意

在refreshAfterWrite导致缓存失效时,并不会因为更新缓存而阻塞缓存数据的返回,只不过是返回老的数据。

有时候为了将值为null的数据统一缓存,这样就不会因为没有缓存数据而访问数据库造成压力。

Guava Cache的缓存数据删除是在更新或写入时才会触发,没有单独的调度服务完成这一工作。

类似的本地缓存还有,有兴趣的可以自己尝试,其实实现思想应该也差不多。

来源:Java技术指北内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯