Java开发技术,java应用开发技术
前言缓存技术被认为是降低服务器负载、减少网络拥塞、增强Web可扩展性的有效方法之一。它的基本思想是使用客户访问的临时局部性原则在缓存中存储客户访问内容的副本。下次访问内容时,不需要连接常驻网站,而是由缓存中预留的副本提供。
在企业Web应用中,可以通过缓存技术提高请求的响应速度;减少系统IO开销;减轻系统数据读写压力。
首先我们要知道我们在开发过程中为什么要使用缓存,它能给我们带来什么好处!
优点:可以通过缓存系统压力来减少访问系统或网络资源带来的性能消耗,在流量较大时可以减少系统拥塞。一般使用访问速度非常快的组件来实现缓存,可以快速响应客户端请求,从而减少客户端访问的延迟,提高系统的响应速度。在配备负载均衡的应用架构中,缓存静态资源可以有效降低服务器负载压力。当下游应用出现故障时,通过返回缓存的数据,可以在一定程度上增强应用的容错能力。缺点缓存数据与实际数据不一致。高并发场景下存在缓存崩溃、缓存穿透和缓存雪崩等问题。一般来说,缓存主要是针对访问频率高但更新频率低的数据,从而加快服务器的响应速度和访问原有资源的压力。
Guava Cache是一个相对简单且易于理解的本地缓存框架。今天我们主要从它入手,学习如何使用缓存。
番石榴缓存的特点是本地缓存,可以简单理解为Map。它将数据保存到地图(内存)中,下次使用这些数据时,可以通过键直接从地图中获取。但是使用Map时需要考虑一些问题:
缓存的容量。不可能无限期地缓存数据。数据量大的时候,占用系统资源会影响主营业务。清理缓存。有些缓存很少使用,如果一直占用资源就是浪费。并发访问的效率。缓存更新期间对系统和网络资源的即时访问会导致失败。评估缓存使用情况。当然,以上问题可以通过包装图来实现。当然,番石榴缓存就是基于这个思路,底层原理是基于地图的。让我们来看看它的特点:
缓存过期和失效机制通过设置密钥的过期时间,包括访问过期和创建过期;设置缓存容量的大小,使用LRU,并选择要删除的最新和最长的缓存。
并发缓存主要基于CurrentHashMap实现线程安全;通过key的计算,基于分段锁,提高了缓存读写效率,降低了锁的粒度,提高了并发性。
更新并查询缓存中的一个键,如果它不存在,检查源数据并回填缓存。在高并发下,元数据的多次查询和缓存的反复回填可能会造成系统故障,最明显的DB服务器宕机,性能下降等。当GuavaCache调用CacheLoader中的load方法时,只有一个对同一个键的请求会同时读取源数据并回填缓存,后续的请求会继续直接从缓存中读取,有效阻断了并发请求对资源服务的影响。
数据源的集成一般我们在业务中操作缓存的时候,既可以操作缓存,也可以操作数据源。GuavaCache的get可以整合数据源,当无法从缓存中读取时,可以从数据源中读取数据,回填缓存。
统计数据监控缓存加载时间、命中率、错误率和数据加载时间。
引入API来构建ManualCache。这时,Cache就相当于一张地图。CRUD数据时,缓存映射需要同步操作。在高并发的情况下,可以使用get(k,loader)读取缓存,通过缓存锁机制阻止对系统资源(DB)的并发访问,通过put方法存储和更新缓存;
加载缓存此时构建的是一个实现了躲藏接口的正在加载缓存,相比手动缓存,提供了缓存回填机制,即当缓存不存在时,会基于缓存加载器查询数据并将结果回填到缓存,在高并发时,可以有效地基于缓存锁减少对系统资源的调用。此时仅需要关注缓存的使用,缓存的更新与存入都是基于缓存加载器实现;
缓存获取得到(k)
根据键查询,没有则触发负载;如果负荷为空则抛出异常。
getUnchecked(k)
缓存不存在或返回为空会抛出检查异常。
get(k,loader)
根据键查询,没有则调用装货设备方法,且对结果缓存;如果装货设备返回空则抛出异常,此时不会调用默认的负荷方法。
getIfPresent(k)
有缓存则返回,否则返回空,不会触发装载。
缓存更新put(k,v)
如果缓存已经存在,则会先进行一次删除。
缓存删除无效(k)
根据键使缓存失效。
过期
通过配置的过期参数,比如expireAfterAccess、expireAfterWrite、refreshAfterWrite。
过载
当缓存数据量超过设置的最大值时,根据最近最少使用算法算法进行删除。
引用
构建缓存时将键值设置为弱引用、软引用,基于乔治勋章机制来清理缓存。
统计命中率()
缓存命中率;
命中失误()
缓存失误率;
负载计数()加载次数;
averageLoadPenalty()
加载新值的平均时间,单位为纳秒;
驱逐计数()缓存项被回收的总数,不包括显式清除。
建设者配置
简单示例手动缓存模式下面以用户服务为例,我们看下如何在增删改查方法中使用缓存:
私有缓存字符串,用户缓存=CacheBuilder.newBuilder()。expireAfterWrite(3,时间单位。秒)//写入多久没更新自动过期,先删除,后负荷。移除监听器(新的移除监听器对象,Object () {
@覆盖
删除时公共无效(删除通知对象,对象通知){
伐木工。info({ }删除{ } ,LocalDateTime.now().格式(日期时间格式化程序。模式( yyyy-MM-DD HH:MM:ss )、通知。getkey());
}
})。初始容量(20) //初始化容量。并发级别(10) //并发。maximumSize(100) //最多缓存数量。recordStats() //开启统计。build();
@覆盖
公共用户getUser(字符串id){
//缓存不存在时,通过本地缓存锁机制,防止对数据库的高频访问
用户用户;
尝试{
user=cache.get(id,()- {
LOGGER.info(缓存不存在,从装货设备加载数据);
返回userdao。get(id);
});
} catch (ExecutionException e) {
抛出新的运行时异常(e);
}
返回用户;
}
@覆盖
公共用户saveOrUpdateUser(用户用户){
userDao.saveOrUpdate(用户);
cache.put(user.getId(),用户);
返回用户;
}
@覆盖
公共void removeUser(字符串id){
userdao。删除(id);
缓存。无效(id);
}加载缓存模式
私有加载缓存字符串,用户缓存=CacheBuilder.newBuilder()
//省略。构建(新的缓存加载器字符串,用户(){
@覆盖
公共用户加载(字符串键)引发异常{
伐木工。信息({ }加载{ } ,LocalDateTime.now().格式(日期时间格式化程序。of模式( yyyy-MM-DD HH:MM:ss )),key);
返回userdao。get(键);
}
@覆盖
公共列表启用未来未来用户重新加载(字符串键,用户旧用户)引发异常{
logger . info({ } reload { } ,LocalDateTime.now().格式(日期时间格式化程序。of模式( yyyy-MM-DD HH:MM:ss )),key);
ListenableFutureTask用户ListenableFutureTask=ListenableFutureTask。创建(()-userdao。get(key));
completablefuture。运行异步(listenableFutureTask);
返回listenableFutureTask
}
});
@ SneakyThrows
@覆盖
公共用户getUser(字符串id){
//缓存不存在或返回为空会抛出异常
尝试{
返回缓存。未选中(id);
} catch(异常e) {
返回空
}
}
@覆盖
公共用户saveOrUpdateUser(用户用户){
缓存。无效(用户。getid());
返回userDao.saveOrUpdate(用户);
}
@覆盖
公共void removeUser(字符串id){
缓存。无效(id);
userdao。删除(id);
}总结:第一种写法更像前面说的地图。当对数据进行CRUD操作时,用户需要手动更新或删除缓存,所以称为ManualCache。当然,Guava Cache对Map的增强还是有效的,比如逾期清理、缓存容量限制等。第二种方法编写类似,主要介绍CacheLoader接口。当读取数据时缓存数据不存在时,CacheLoader的load方法先写缓存,再返回数据。
注意expireAfterWrite和refreshAfterWrite的区别。当refreshAfterWrite导致缓存失效时,不会因为更新缓存而阻塞缓存数据的返回,只是返回旧的数据。
您不能缓存null。有的时候,空值的数据是统一缓存的,因为没有缓存的数据,所以访问数据库没有压力。
读写时删除。Guava Cache的缓存数据删除是在更新或者写的时候触发的,没有单独的调度服务来完成这个任务。
本地缓存类似于本地缓存。有兴趣的话可以自己试试。其实实现思路应该差不多。
版权归作者所有:原创作品来自博主小二上九8,转载请联系作者取得转载授权,否则将追究法律责任。
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。