当前位置: 首页 > 科技观察

Java开发工具GuavaCache

时间:2023-03-17 22:20:39 科技观察

前言缓存技术被认为是降低服务器负载、减少网络拥塞、增强Web可扩展性的有效途径之一。基本思想是利用客户端访问的时间局部性原理,在Cache中保存一份客户访问过的内容,下次访问该内容时不需要连接常驻网站,而是由缓存中保留的副本提供。在企业Web应用中,缓存技术可以提高请求的响应速度;减少系统IO开销;降低系统数据读写压力...缓存的意义首先我们要知道我们在开发过程中为什么要使用缓存,缓存能给我们带来什么好处!优点通过缓存系统的负载,减少访问系统或网络资源带来的性能消耗,在流量较大时可以很好的减少系统拥塞。缓存通常使用访问速度非常快的组件来实现。通过缓存,可以快速响应客户端请求,从而减少客户端访问延迟,提高系统响应速度。在具备负载均衡的应用架构中,缓存静态资源可以有效降低服务器负载压力。当下游应用出现故障时,通过返回缓存数据可以在一定程度上增强应用的容错能力。缺点缓存数据和实际数据不一致。在高并发场景下,存在缓存击穿、缓存穿透、缓存雪崩等问题。一般情况下,缓存主要针对频繁访问但不经常更新的数据,从而加快服务器响应速度,减轻原有资源访问压力。GuavaCache是??一个比较简单易懂的本地缓存框架。今天,我们主要以此为起点,来了解和学习缓存的使用方法。GuavaCache特性本地缓存可以简单理解为一个Map,将数据存储在Map(内存)中,下次使用数据时,可以直接通过key从Map中取出。但是使用Map有几个问题需要考虑:缓存的容量。没有限制地缓存数据是不可能的。当数据量大时,会占用系统资源,影响主要业务。缓存清理。有些缓存使用频率很低,一直占用资源是一种浪费。并发访问时的效率问题。更新缓存时对系统和网络资源的瞬时访问会导致失败。缓存使用评估当然,我们可以通过包装Map来实现上面的问题。当然GuavaCache就是基于这个思路,底层原理是基于Map实现的。我们来看看它的特点:缓存过期和淘汰机制,通过设置Key的过期时间,包括访问过期和创建过期;设置缓存容量的大小,使用LRU方法选择最新最旧的缓存进行删除。并发处理能力Cache主要基于CurrentHashMap实现线程安全;通过key的计算,基于分段锁,提高缓存读写效率,降低锁的粒度,提高并发能力。updatelock查询缓存中的某个key,如果不存在则检查源数据,回填缓存。高并发下会出现,多次查询元数据,反复回填缓存可能导致系统故障,最明显的DB服务器宕机,性能下降等。同一个key同时读取源数据并回填缓存,后续请求会继续直接从缓存中读取,有效阻断并发请求对资源服务的影响。整合数据源一般我们在业务中对缓存进行操作时,都会对缓存和数据源两部分进行操作。GuavaCache的get可以整合数据源。当无法从缓存中读取时,可以从数据源中读取数据回填缓存。监控统计监控缓存加载时间、命中率、错误率和数据加载时间。API引入缓存,构建ManualCache。这时候Cache就相当于一个Map。对数据进行CRUD操作时,需要同步操作缓存Map;在高并发的情况下,可以使用get(k,loader)读取缓存,并使用缓存锁机制来防止系统损坏。并发访问资源(DB)通过put方法实现缓存存储和更新;LoadingCache此时构建一个实现了Cache接口的LoadingCache。与ManualCache相比,它提供了缓存回填机制,即当缓存不存在时,会根据CacheLoader查询数据,并将结果回填到缓存中。在高并发下,可以有效减少基于缓存锁对系统资源的调用。此时只需要注意缓存的使用即可。缓存的更新和存储是基于CacheLoader实现的;缓存获取get(k)根据key查询,没有key则触发加载;如果负载为空,则抛出异常。如果缓存不存在或返回null,getUnchecked(k)将抛出一个已检查的异常。get(k,loader)根据key查询,如果没有key,则调用loader方法,并缓存结果;如果loader返回null,则抛出异常,此时不会调用默认的load方法。getIfPresent(k)如果有缓存就会返回,否则返回null,不会触发加载。Cacheupdateput(k,v)如果缓存已经存在,则先删除。缓存删除invalidate(k)根据key使缓存失效。通过配置的过期参数进行过期,如expireAfterAccess、expireAfterWrite、refreshAfterWrite。过载当缓存数据量超过设置的最大值时,将按照LRU算法删除。引用构建缓存时,将key值设置为弱引用或软引用,基于GC机制清理缓存。hitRate()缓存命中率统计;hitMiss()缓存未命中率;loadCount()加载次数;averageLoadPenalty()加载新值的平均时间,以纳秒为单位;evictionCount()回收的缓存项总数,不包括显式清除。Builderconfiguration配置说明expireAfterAccess无读写后过期,expireAfterWrite写入后自动过期,先删除,再loadrefreshAfterWrite上次更新后自动刷新,先重载后删除,并发时会取旧数据。removalListener设置缓存删除监听initialCapacity缓存初始化sizeconcurrencyLevel最大并发数,可以理解为并发线程数maximumSize最大缓存数,超过则按照策略清除maximumWeight最大权重容量数仅用于判断缓存是否超过容量recordStats缓存命中统计简单示例ManualCache下面以用户服务为例。看看在增删改查方法中如何使用缓存:privateCachecache=CacheBuilder.newBuilder().expireAfterWrite(3,TimeUnit.SECONDS)//写了多久?更新自动过期,先删除再加载LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss")),notification.getKey());}}).initialCapacity(20)//初始化capacity.concurrencyLevel(10)//Concurrency.maximumSize(100)//最大缓冲区存储数量.recordStats()//开启statistics.build();@OverridepublicUsergetUser(Stringid){//当缓存不存在时,使用LocalCache锁机制,防止频繁访问数据库User用户;try{user=cache.get(id,()->{LOGGER.info("缓存不存在,从loader加载数据");returnuserDao.get(id);});}catch(ExecutionExceptione){thrownewRuntimeException(e);}返回用户;}@OverridepublicUsersaveOrUpdateUser(Useruser){userDao.saveOrUpdate(user);cache.put(user.getId(),user);返回用户;}@OverridepublicvoidremoveUser(Stringid){userDao.remove(id);缓存无效(id);}LoadingCache模式privateLoadingCachecache=CacheBuilder.newBuilder()//omit.build(newCacheLoader(){@OverridepublicUserload(Stringkey)throwsException{LOGGER.info("{}加载{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss")),key);返回userDao.get(key);}@OverridepublicListenableFuturereload(Stringkey,UseroldUser)throwsException{LOGGER.info("{}reload{}",LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-ddHH:mm:ss")),键);ListenableFutureTask<用户>listenableFutureTask=ListenableFutureTask.create(()->userDao.get(key));CompletableFuture.runAsync(listenableFutureTask);返回listenableFutureTask;}});@SneakyThrows@OverridepublicUsergetUser(Stringid){//绑定不存在或返回为null会抛出异常try{returncache.getUnchecked(id);}catch(Exceptione){返回空值;}}@Override公共使用rsaveOrUpdateUser(Useruser){cache.invalidate(user.getId());返回userDao.saveOrUpdate(用户);}@OverridepublicvoidremoveUser(Stringid){cache.invalidate(id);userDao.remove(id);}总结:第一种写法比较像上面说的Map。用户在对数据进行CRUD操作时,需要手动同步更新或删除缓存,因此称为ManualCache(手动)。增强功能仍然有效,例如过期清除和缓存容量限制。第二种方法在写作上类似。主要原因是引入了CacheLoader接口。当读取数据时缓存的数据不存在时,会使用CacheLoader的load方法先写入缓存再返回数据。注意expireAfterWrite和refreshAfterWrite之间的区别。当refreshAfterWrite导致缓存失效时,不会因为更新缓存而阻塞缓存数据的返回,只会返回旧数据。不能缓存null有时候为了统一缓存值为null的数据,这样会因为没有缓存数据而访问数据库没有压力。删除是在读写时进行的GuavaCache的缓存数据删除只有在更新或写入时才会触发,并没有单独的调度服务来完成这项工作。有类似于本地缓存的本地缓存。如果你有兴趣,你可以自己尝试一下。其实实现思路应该是差不多的。