0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

如何使用缓存

科技绿洲 来源:Java技术指北 作者:Java技术指北 2023-10-08 14:07 次阅读

缓存技术被认为是减轻服务器负载、降低网络拥塞、增强Web可扩展性的有效途径之一,其基本思想是利用客户访问的时间局部性(Temproral Locality)原理, 将客户访问过的内容在Cache中存放一个副本,当该内容下次被访问时,不必连接到驻留网站,而是由Cache中保留的副本提供。

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

缓存的意义

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

优点

  • 通过缓存承载系统压力,减少对系统或网络资源访问而引起的性能消耗,在流量较大时能够很好地减少系统拥塞
  • 缓存一般都是使用存取非常快的组件实现,通过缓存能够快速响应客户端请求,从而降低客户访问延迟,提审系统响应速度
  • 在配备负载均衡的应用架构中,通过缓存静态资源能够有效减少服务器负载压力
  • 当下游应用故障时,通过返回缓存数据能够在一定程度上增强应用容错性

缺点

  • 缓存数据与实际数据不一致问题问题
  • 高并发场景时存在缓存击穿、缓存穿透、缓存雪崩等问题

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

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

Guava Cache特色

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

  1. 缓存的容量。不可能无限制的对数据进行缓存,当数据较大时占用系统资源会导致主业务受影响
  2. 缓存的清理。有些缓存使用频率很低,如果一直占用资源也是一种浪费
  3. 并发访问时的效率问题。缓存更新时瞬时对系统、网络资源的访问导致故障
  4. 缓存使用情况评估

当然以上问题我们通过我们对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方法实现缓存的存入与更新;
    • LoadingCache
      此时构建的是一个实现了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缓存命中统计

简单示例

  • ManualCache模式
    下面以用户服务为例,我们看下如何在增删改查方法中使用缓存:
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);
    }
  • LoadingCache模式
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方法先写缓存后返回数据

注意

  • expireAfterWrite、refreshAfterWrite的区别

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

  • 不能缓存null

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

  • 读写时才进行删除

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

本地缓存

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数据
    +关注

    关注

    8

    文章

    6511

    浏览量

    87600
  • 服务器
    +关注

    关注

    12

    文章

    8116

    浏览量

    82509
  • 网络
    +关注

    关注

    14

    文章

    7251

    浏览量

    87440
  • 缓存
    +关注

    关注

    1

    文章

    220

    浏览量

    26441
  • key
    key
    +关注

    关注

    0

    文章

    44

    浏览量

    12744
收藏 人收藏

    评论

    相关推荐

    157.157、缓存 缓存使用 本地锁在分布式下的问题

    缓存
    充八万
    发布于 :2023年07月18日 04:44:59

    什么是追踪缓存/转接卡?

    什么是追踪缓存/转接卡?  追踪缓存  在奔腾IV一级缓存中,一般一级缓存中的指令缓存都是即时解码:而追踪
    发表于 02-04 10:40 429次阅读

    什么是CPU一级缓存/二级缓存

    什么是CPU一级缓存/二级缓存?  即L1 Cache。集成在CPU内部中,用于CPU在处理数据过程中数据的暂时保存。由于缓存指令和数据与CPU
    发表于 02-04 10:43 1025次阅读

    Mybatis缓存之一级缓存

    本文主要讲mybatis的一级缓存,一级缓存是SqlSession级别的缓存。mybatis提供查询缓存,用于减轻数据压力,提高数据库性能。mybaits提供一级
    发表于 11-27 20:44 1081次阅读
    Mybatis<b class='flag-5'>缓存</b>之一级<b class='flag-5'>缓存</b>

    基于节点热度与缓存替换率的ICN协作缓存

    信息中心网络默认的LCE缓存策略在数据包返回路径上的每个节点缓存内容,会产生大量冗余副本,无法充分利用缓存资源。针对该问题,提出基于节点热度与缓存替换率的
    发表于 03-29 15:17 1次下载

    渲染中的帧缓存和深度缓存

    渲染涉及大量的缓存,这里缓存只是一个简单的存有像素数据的矩形内存块,最重要缓存是帧缓存和深度缓存
    的头像 发表于 05-14 11:44 5632次阅读
    渲染中的帧<b class='flag-5'>缓存</b>和深度<b class='flag-5'>缓存</b>

    什么是Web缓存,HTTP缓存和浏览器缓存的区别

    前端缓存主要是分为HTTP缓存和浏览器缓存。其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器
    发表于 09-13 04:17 9171次阅读
    什么是Web<b class='flag-5'>缓存</b>,HTTP<b class='flag-5'>缓存</b>和浏览器<b class='flag-5'>缓存</b>的区别

    缓存是什么 为什么需要缓存

    缓存是软件开发中一个非常有用的概念,数据库缓存更是在项目中必然会遇到的场景。
    的头像 发表于 09-28 02:48 1.1w次阅读
    <b class='flag-5'>缓存</b>是什么 为什么需要<b class='flag-5'>缓存</b>

    缓存的基本原理 缓存的分类

    缓存的主要手段有:浏览器缓存、CDN、反向代理、本地缓存、分布式缓存、数据库缓存
    发表于 06-13 12:04 4360次阅读

    缓存如何工作,如何设计CPU缓存

    20世纪80年代,CPU性能有了显著提升,但这受到板载内存访问速度缓慢增长的阻碍。随着这种差异的恶化,工程师们发现了一种通过新的设计技术缓存来解决问题的方法。本文将帮助你进一步了解什么是缓存,它如何工作以及如何设计CPU缓存
    的头像 发表于 11-19 17:23 2442次阅读

    如何设计一个缓存系统?

    设计一个缓存系统,不得不要考虑的问题就是:缓存穿透、缓存击穿与失效时的雪崩效应。 缓存穿透 缓存穿透是指查询一个一定不存在的数据,由于
    的头像 发表于 02-08 11:40 2662次阅读

    到底是更新缓存还是删缓存

    如何保证缓存和数据库一致性,这是一个老生常谈的话题了。 但很多人对这个问题,依旧有很多疑惑: 到底是更新缓存还是删缓存? 到底选择先更新数据库,再删除缓存,还是先删除
    的头像 发表于 10-22 17:05 4688次阅读
    到底是更新<b class='flag-5'>缓存</b>还是删<b class='flag-5'>缓存</b>

    聊聊本地缓存和分布式缓存

    本地缓存 :应用中的缓存组件,缓存组件和应用在同一进程中,缓存的读写非常快,没有网络开销。但各应用或集群的各节点都需要维护自己的单独缓存,无
    发表于 06-11 15:12 581次阅读
    聊聊本地<b class='flag-5'>缓存</b>和分布式<b class='flag-5'>缓存</b>

    mybatis一级缓存和二级缓存的原理

    MyBatis是一种轻量级的持久化框架,它提供了一级缓存和二级缓存的机制来优化数据库操作性能。一级缓存是默认开启的,而二级缓存需要手动配置启用。 一、一级
    的头像 发表于 12-03 11:55 516次阅读

    Redis缓存预热+缓存雪崩+缓存击穿+缓存穿透要点简析

    缓存预热就是系统上线后,提前将相关的缓存数据直接加载到缓存系统。
    的头像 发表于 12-25 09:41 300次阅读
    Redis<b class='flag-5'>缓存</b>预热+<b class='flag-5'>缓存</b>雪崩+<b class='flag-5'>缓存</b>击穿+<b class='flag-5'>缓存</b>穿透要点简析