草庐IT

Guava缓存(一)基础

雪孤城 2024-04-19 原文

1. 概述

1.1 简介

Guava缓存,谷歌开源的一种本地缓存,使用本节点的内存来存储的,实现原理类似于ConcurrentHashMap,使用多个segments方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求,同时支持多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等。

1.2 本地缓存&分布式缓存

  • 本地缓存:指的是在应用中的缓存组件,其最大的优点是应用和cache在同一个进程内部,请求缓存非常快速,没有过多的网络开销等,在单应用不需要集群支持或者集群情况下各节点无需互相通知的场景下使用本地缓存较为合适;同时,它的缺点也是因为缓存跟应用程序耦合,多个应用程序无法直接共享缓存,各应用或集群的各节点都需要维护自己的单独缓存,对内存是一种浪费。
  • 分布式缓存:指的是与应用分离的缓存组件或服务,其最大的优点是自身就是一个独立的应用,与本地应用隔离,多个应用可直接共享缓存。

2. 创建方式

2.1 CacheLoader的方式

CacheLoader可以理解为一个固定的加载器,在创建Cache时指定,重写V load(K key) 方法后,当检索不存在的时会自动的加载数据。

package com.example.cache;


import com.google.common.cache.*;

import java.util.HashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 描述:
 * 本地缓存Ddmo
 *
 * @author XueGuCheng
 * @create 2022-11-01 23:03
 */
public class GuavaCacheDemo {

    // 模拟DB
    private static final HashMap<Integer, String> map = new HashMap<>();

    public static LoadingCache<Integer, String> createGuavaCache(){

        return CacheBuilder.newBuilder()
                // 设置并发级别为5,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(5)
                // 设置写缓存后10秒钟后过期
                .expireAfterWrite(10, TimeUnit.SECONDS)
                // 设置缓存容器的初始容量为8
                .initialCapacity(8)
                // 设置缓存最大容量为10,超过10之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(10)
                // 设置统计缓存的各种统计信息(生产坏境关闭)
                .recordStats()
                // 设置缓存的移除通知
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                // 指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(new CacheLoader<Integer, String>() {
                    @Override
                    public String load(Integer key) throws Exception {
                        // 往DB中查询数据
                        System.out.println("查询key:" + key + "的数据");
                        return map.get(key);
                    }
                });
    }

    public static void main(String[] args) throws ExecutionException {
        map.put(1,"java");
        map.put(2,"天下");
        map.put(3,"第一");

        LoadingCache<Integer, String> loadingCache = createGuavaCache();

        // 第一次缓存中没有数据,所以会往DB中查询数据
        System.out.println(loadingCache.get(2));
        // 第二次缓存中有数据,CacheLoader.load方法不会加载
        System.out.println(loadingCache.get(2));

    }
}

运行结果:

2.2 callable方式

Callable在get时可以指定,效果跟CacheLoader一样,区别就是两者定义的时间点不一样,Callable更加灵活。

   public static void main(String[] args) throws ExecutionException {
        map.put(1,"java");
        map.put(2,"天下");
        map.put(3,"第一");

        LoadingCache<Integer, String> loadingCache = createGuavaCache();

        // 第一次缓存中没有数据,所以会往DB中查询数据
//        System.out.println(loadingCache.get(2));
//        // 第二次缓存中有数据,CacheLoader.load方法不会加载
//        System.out.println(loadingCache.get(2));
        int i = 3;
        String s = loadingCache.get(i, new Callable<String>() {
            @Override
            public String call() throws Exception {
                // 往DB中查询数据
                System.out.println("(callable) 查询key:" + i + "的数据");
                return map.get(i);
            }
        });
        System.out.println(s);
    }

运行结果:

3. 缓存清除策略

3.1 基于容量的清除策略

清除策略:超过最大容量之后就会按照LRU最近虽少使用算法来移除缓存项。

    public static void main(String[] args) throws ExecutionException {
        map.put(1,"java");
        map.put(2,"天下");
        map.put(3,"第一");
        map.put(4,"j");
        map.put(5,"a");
        map.put(6,"v");

        LoadingCache<Integer, String> loadingCache = createGuavaCache();

        // 此时缓存的最大容量为4,存放6个数据
        for(int i = 1; i <= 6; i++){
            System.out.println(loadingCache.get(i));
        }
    }

运行结果:

3.2 基于权重的清除策略

使用CacheBuilder.weigher(Weigher)指定一个权重函数,并且用CacheBuilder.maximumWeight(long)指定最大总重。

LoadingCache<Key, Graph> graphs = CacheBuilder.newBuilder()
       .maximumWeight(1000)
       .weigher(new Weigher<Key, Graph>() {
          public int weigh(Key k, Graph g) {
            return g.vertices().size();
          }
        })
       .build(
           new CacheLoader<Key, Graph>() {
             public Graph load(Key key) { 
               return createExpensiveGraph(key);;
             }
           });

3.3 基于存活时间的清除策略

  1. expireAfterWrite :写缓存后多久过期
  2. expireAfterAccess :读写缓存后多久过期
  3. refreshAfterWrite :写入数据后多久过期,只阻塞当前数据加载线程,其他线程返回旧值

expireAfterWrite与refreshAfterWrite混合使用情况:

  • 当没有数据的时候,其他线程在加载数据的时候,当前线程会一直阻塞等待其他线程加载数据完成; 如果有数据的情况下其他线程正在加载数据,已经超过refreshAfterWrite设置时间但是没有超过expireAfterWrite设置的时间时当前线程返回旧数据。
  • 如果有数据的情况下其他线程正在加载数据,已经超过expireAfterWrite设置的时间时当前线程阻塞等待其他线程加载数据完成. 这种情况适合与设置一个加载缓冲区的情况,既能保证过期后加载数据,又能保证长时间没访问多个线程并发时获取到过期旧数据的情况。

例:

public static void main(String[] args) throws ExecutionException, InterruptedException {
        map.put(1,"java");
        map.put(2,"天下");
        map.put(3,"第一");
        map.put(4,"j");
        map.put(5,"a");
        map.put(6,"v");

        LoadingCache<Integer, String> loadingCache = createGuavaCache();

        // 此时缓存的过期时间为3s
        for(int i = 1; i <= 6; i++){
            System.out.println(loadingCache.get(i));
            if (i>=3){
                Thread.sleep(1000);
            }
        }
    }

运行结果:

问题: 如果对缓存设置过期时间,在高并发下同时执行get操作,而此时缓存值已过期了,如果没有保护措施,则会导致大量线程同时调用生成缓存值的方法,比如从数据库读取,对数据库造成压力,这也就是我们常说的“缓存击穿”。

refreshAfterWrite: 当大量线程用相同的key获取缓存值时,只会有一个线程进入load方法,而其他线程则等待,直到缓存值被生成。这样也就避免了缓存击穿的危险。这两个配置的区别前者记录写入时间,后者记录写入或访问时间,内部分别用writeQueue和accessQueue维护。

3.4 显式清除

  1. 清除单个key:Cache.invalidate(key)
  2. 批量清除key:Cache.invalidateAll(keys)
  3. 清除所有缓存项:Cache.invalidateAll()

3.5 基于引用的清除策略

  1. CacheBuilder.weakKeys():使用弱引用存储键。当键没有其它(强或软)引用时,缓存项可以被垃圾回收
  2. CacheBuilder.weakValues():使用弱引用存储值。当值没有其它(强或软)引用时,缓存项可以被垃圾回收
  3. CacheBuilder.softValues():使用软引用存储值。软引用只有在响应内存需要时,才按照全局最近最少使用的顺序回收。考虑到使用软引用的性能影响,我们通常建议使用更有性能预测性的缓存大小限定。

4. 监听

默认情况下,监听器方法是在移除缓存时同步调用的。因为缓存的维护和请求响应通常是同时进行的,代价高昂的监听器方法在同步模式下会拖慢正常的缓存请求。假如在同步监听模式下,监听方法中的逻辑特别复杂,执行效率慢,那此时如果有大量的key进行清理,会使整个缓存性能变得很低下,所以此时适合用异步监听RemovalListeners.asynchronous(RemovalListener, Executor)把监听器装饰为异步操作,移除key与监听key的移除分属2个线程。

 				// 设置缓存的移除通知(同步)
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        System.out.println(notification.getKey() + " was removed, cause is " + notification.getCause());
                    }
                })
                // 设置缓存的移除通知(异步)
                .removalListener(RemovalListeners.asynchronous(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {

                    }
                },Executors.newSingleThreadExecutor()))

5. 统计

  • hitRate():缓存命中率
  • hitMiss():缓存失误率
  • loadcount() : 加载次数
  • averageLoadPenalty():加载新值的平均时间,单位为纳秒
  • evictionCount():缓存项被回收的总数,不包括显式清除

6. 常用API

  • V getIfPresent(Object key): 获取缓存中key对应的value,如果缓存没命中,返回null
  • V get(K key) :获取key对应的value,若缓存中没有,则调用LocalCache的load方法,从数据源中加载,并缓存
  • void put(K key, V value) :如果缓存有值,覆盖,否则,新增
  • void putAll(Map m):循环调用单个的方法
  • void invalidate(Object key): 删除缓存
  • void invalidateAll():清楚所有的缓存,相当远map的clear操作
  • long size():获取缓存中元素的大概个数。注:元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素
  • CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等
  • asMap():获得缓存数据的ConcurrentMap快照
  • cleanUp():清空缓存
  • refresh(Key) :刷新缓存,即重新取缓存数据,更新缓存
  • ImmutableMap getAllPresent(Iterable keys) :一次获得多个键的缓存值

有关Guava缓存(一)基础的更多相关文章

  1. ruby - 如何在 Ubuntu 中清除 Ruby Phusion Passenger 的缓存? - 2

    我试过重新启动apache,缓存的页面仍然出现,所以一定有一个文件夹在某个地方。我没有“公共(public)/缓存”,那么我还应该查看哪些其他地方?是否有一个URL标志也可以触发此效果? 最佳答案 您需要触摸一个文件才能清除phusion,例如:touch/webapps/mycook/tmp/restart.txt参见docs 关于ruby-如何在Ubuntu中清除RubyPhusionPassenger的缓存?,我们在StackOverflow上找到一个类似的问题:

  2. ruby-on-rails - Ruby on Rails 计数器缓存错误 - 2

    尝试在我的RoR应用程序中实现计数器缓存列时出现错误Unknownkey(s):counter_cache。我在这个问题中实现了模型关联:Modelassociationquestion这是我的迁移:classAddVideoVotesCountToVideos0Video.reset_column_informationVideo.find(:all).eachdo|p|p.update_attributes:videos_votes_count,p.video_votes.lengthendenddefself.downremove_column:videos,:video_vot

  3. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  4. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  5. ES基础入门 - 2

    ES一、简介1、ElasticStackES技术栈:ElasticSearch:存数据+搜索;QL;Kibana:Web可视化平台,分析。LogStash:日志收集,Log4j:产生日志;log.info(xxx)。。。。使用场景:metrics:指标监控…2、基本概念Index(索引)动词:保存(插入)名词:类似MySQL数据库,给数据Type(类型)已废弃,以前类似MySQL的表现在用索引对数据分类Document(文档)真正要保存的一个JSON数据{name:"tcx"}二、入门实战{"name":"DESKTOP-1TSVGKG","cluster_name":"elasticsear

  6. 【网络】-- 网络基础 - 2

    (本文是网络的宏观的概念铺垫)目录计算机网络背景网络发展认识"协议"网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型报头以太网碰撞路由器IP地址和MAC地址IP地址与MAC地址总结IP地址MAC地址计算机网络背景网络发展        是最开始先有的计算机,计算机后来因为多项技术的水平升高,逐渐的计算机变的小型化、高效化。后来因为计算机其本身的计算能力比较的快速:独立模式:计算机之间相互独立。    如:有三个人,每个人做的不同的事物,但是是需要协作的完成。    而这三个人所做的事是需要进行协作的,然而刚开始因为每一台计算机之间都是互相独立的。所以前面的人处理完了就需要将数据

  7. ruby-on-rails - bundle 安装尝试使用缓存文件 - 2

    当我尝试进行bundle安装时,我的gem_path和gem_home指向/usr/local/rvm/gems/我没有写入权限,并且由于权限无效而失败。因此,我已将两个路径都更改为我具有写入权限的本地目录。这样做时,我进行了bundle安装,我得到:bruno@test6:~$bundleinstallFetchinggemmetadatafromhttps://rubygems.org/.........Fetchinggemmetadatafromhttps://rubygems.org/..Bundler::GemspecError:Couldnotreadgemat/afs/

  8. ruby-on-rails - Heroku Action 缓存似乎不起作用 - 2

    我一直在Heroku上尝试不同的缓存策略,并添加了他们的memcached附加组件,目的是为我的应用程序添加Action缓存。但是,当我在我当前的应用程序上查看Rails.cache.stats时(安装了memcached并使用dalligem),在执行应该缓存的操作后,我得到current和total_items为0。在Controller的顶部,我想缓存我有的Action:caches_action:show此外,我修改了我的环境配置(对于在Heroku上运行的配置)config.cache_store=:dalli_store我是否可以查看其他一些统计数据,看看它是否有效或我做错

  9. ruby-on-rails - rails expire_page 没有删除缓存的文件 - 2

    我有一个具有页面缓存的ControllerAction,我制作了一个清扫程序,它使用Controller和指定的Action调用expire_page...Controller操作呈现一个js.erb模板,所以我试图确保expire_page删除public/javascripts中的.js文件,但它没有这样做。classJavascriptsController"javascripts",:action=>"lol",:format=>'js')endend...所以,我访问javascripts/lol.js并呈现我的模板。我验证了public/javascripts/lol.js

  10. ruby-on-rails - rails 3 缓存 : expire action for named route - 2

    我的Controller有这个:caches_action:render_ticker_for_channel,:expires_in=>30.seconds在我的路由文件中我有这个:match'/render_c_t/:channel_id'=>'render#render_ticker_for_channel',:as=>:render_channel_ticker在日志文件中我看到了这个:Writefragmentviews/mcr3.dev/render_c_t/63(11.6ms)我如何手动使它过期?我需要从与渲染Controller不同的Controller使它过期,但即使

随机推荐