本地缓存Guava Cache-Guava

概述

缓存的目的是使用空间来换时间,比较常见的使用场景是,将经过复杂计算(或者跨网络socket调用)的结果缓存到本地,以便下次使用时直接利用上次结果,对于读多写少的数据,缓存可以有效的减少请求处理的端到端延迟,以及对后端的基础资源有效的保护。
分布式缓存,一般选择redis等分布式缓存组件来实现,而本地内存缓存涉及到线程安全、缓存失效时间控制等问题。对于本地缓存可以使用Guava Cache,Guava Cache是一款优秀的内存缓存组件。

maven依赖

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>18.0</version>
</dependency>

使用示例

创建缓存

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

具体示例:

private LoadingCache<String, CResp> deviceCache = CacheBuilder.newBuilder().expireAfterWrite(dTimeout, TimeUnit.MINUTES).maximumSize(5000)
        .build(
                new CacheLoader<String, CResp>() {
                    @Override
                    public CResp load(String key) throws Exception {
                        CResp cResp = cService.getInfo(key);
                        return cResp;
                    }
                });

初始化大小、最大个数

个数设置

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .initialCapacity(10)//初始化个数
                .maximumSize(55)//设置最大个数
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

注:初始化如何太小,会导致扩容,比较浪费时间,太大浪费内存.
最大数太大,可能导致内存溢出。

重量设置

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .maximumWeight(1000) //设置重量,配合weigher使用
                .weigher(new Weigher<Object, Object>() {
                    @Override
                    public int weigh(Object key, Object value) {
                        return 100;
                    }
                })
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

weigher相当一杆秤,称每个元数多重,maximumWeight相当总重量.一般用的较少。

时间设置

过期时间

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(10, TimeUnit.SECONDS) //多长时间未读写后过期
                .expireAfterWrite(10, TimeUnit.SECONDS)  //多长时间未写后过期
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return key.hashCode();
                    }
                });

注意:元素过期,guava并不会自动回收他们,它会在写操作时顺带做少量的维护工作,或者偶尔在读操作时做(如果写操作实在太少的话)。 这样做的原因在于:如果要自动地持续清理缓存,就必须有一个线程,这个线程会和用户操作竞争共享锁。此外,某些环境下线程创建可能受限制,这样CacheBuilder就不可用了。
如果非要及时清理缓存,可以自己实现清理策略。如果你的缓存是高吞吐的,那就无需担心缓存的维护和清理等工作。如果你的 缓存只会偶尔有写操作,而你又不想清理工作阻碍了读操作,那么可以创建自己的维护线程,以固定的时间间隔调用Cache.cleanUp()。ScheduledExecutorService可以帮助你很好地实现这样的定时调度。

回收策略

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读、写访问,则回收。
expireAfterWrite(long, TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。该种方式主要适用于无法保证本地缓存和依赖的数据的一致性的情况下,强制只缓存一段时间。
我们也可以显式地清除缓存项,而不是等到它被回收:
个别清除:invalidate(key)
批量清除:invalidateAll(keys)
清除所有缓存项:invalidateAll()

刷新时间

public static void main(String[] args) throws ExecutionException, InterruptedException {
        // guava线程池,用来产生ListenableFuture
        ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
        LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                // 指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
                .refreshAfterWrite(2, TimeUnit.SECONDS)
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
						
				// 重写reload,使其异步刷新数据
                    @Override
                    public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
                        System.out.println("......后台线程池异步刷新:" + key);
                        return service.submit(new Callable<Object>() { // 模拟一个需要耗时2s的数据库查询任务
                            @Override
                            public Object call() throws Exception {
                                System.out.println("begin to mock query db...");
                                Thread.sleep(2000);
                                System.out.println("success to mock query db...");
                                return UUID.randomUUID().toString() + key;
                            }
                        });
                    }
                });
    }

刷新策略比较

  • 使用expireAfterAccess或expireAfterWrite时,当缓存过期后,恰好有N个客户端发起请求,需要读取值。使用Guava Cache可以保证只让一个线程去加载数据(比如从数据库中),而其他线程则等待这个线程的返回结果.这样就能避免大量用户请求穿透缓存,但同时也降低了吞吐量.
  • refreshAfterWrite: 当缓存数据过期的时候,真正去加载数据的线程会阻塞一段时间,其余线程立马返回过期的值,然这种处理方式更符合实际的使用场景。
  • 真正加载数据的那个线程一定会阻塞,我们希望这个加载过程是异步的。这样就可以让所有线程立马返回旧值,在后台刷新缓存数据。refreshAfterWrite默认的刷新是同步的,会在调用者的线程中执行。我们可以改造成异步的,实现CacheLoader.reload()。上面的代码就将其实现.

基于引用的回收–强(strong)、软(soft)、弱(weak)

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .softValues()
                .weakKeys()
                .weakValues()
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });

强(strong)、软(soft)、弱(weak)请参考:Guava—缓存之Reference

监听器

LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .removalListener(new RemovalListener<Object, Object>() {
                    @Override
                    public void onRemoval(RemovalNotification<Object, Object> notification) {
                        /**
                         *RemovalCause 枚举
                         * 标明是什么情况下 被移除的
                         */
                        RemovalCause cause = notification.getCause();
                        if (notification.wasEvicted()) { //是否被移除(排除主动删除,和替换)
                            System.out.println(notification.getKey());
                            System.out.println(notification.getValue());
                        }
                    }
                })
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });

缓存命中统计

 LoadingCache<Object, Object> cache = CacheBuilder.newBuilder()
                .recordStats()//统计
                .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                    @Override
                    public Object load(Object key) throws Exception {
                        return System.currentTimeMillis();
                    }
                });


        CacheStats stats = cache.stats(); //不可变对象
        stats.hitCount(); //命中次数
        stats.hitRate();  //命中概率
        stats.missCount();
        stats.missRate();

完整参数配置

ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10));
    LoadingCache<String, Object> cache = CacheBuilder.newBuilder()
            .initialCapacity(10)//初始化个数
            .maximumSize(55)//设置最大个数
            .maximumWeight(1000) //设置重量,配合weigher使用
            .weigher(new Weigher<String, Object>() { //weigher相当一杆秤,称每个元数多重
                @Override
                public int weigh(String key, Object value) {
                    return 100;
                }
            })
            .expireAfterAccess(10, TimeUnit.SECONDS) //多长时间未读写后过期
            .expireAfterWrite(10, TimeUnit.SECONDS)  //多长时间未写后过期
            //指定时间内没有被创建/覆盖,则指定时间过后,再次访问时,会去刷新该缓存,在新值没有到来之前,始终返回旧值
            .refreshAfterWrite(2, TimeUnit.SECONDS)
            .concurrencyLevel(1) //写的并发数
            .softValues() //软引用
            .weakKeys() //弱引用
            .weakValues() //弱引用
            .recordStats() //统计的
            .removalListener(new RemovalListener<String, Object>() {
                @Override
                public void onRemoval(RemovalNotification<String, Object> notification) {
                    /**
                     *RemovalCause 枚举
                     * 标明是什么情况下 被移除的
                     */
                    RemovalCause cause = notification.getCause();
                    if (notification.wasEvicted()) { //是否被移除(排除主动删除,和替换)
                        System.out.println(notification.getKey() + notification.getValue());
                    }
                }
            })
            .build(new CacheLoader<Object, Object>() { //若没有元素,则创建并且放入缓存
                @Override
                public Object load(Object key) throws Exception {
                    return System.currentTimeMillis();
                }

                @Override
                public ListenableFuture<Object> reload(Object key, Object oldValue) throws Exception {
                    System.out.println("......后台线程池异步刷新:" + key);
                    return service.submit(new Callable<Object>() { //模拟一个需要耗时2s的数据库查询任务
                        @Override
                        public Object call() throws Exception {
                            System.out.println("begin to mock query db...");
                            Thread.sleep(2000);
                            System.out.println("success to mock query db...");
                            return UUID.randomUUID().toString() + key;
                        }
                    });
                }
            });

上述算是完整的LoadingCache, 按照实际业务自行配置参数。

参考

Guava—最全缓存cache讲解
Guava Cache简介、应用场景分析、代码实现以及核心的原理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Guava Cache是Google Guava库中提供的一种本地缓存解决方案。它是一个基于内存的缓存,可以在应用程序内部存储数据,提高应用程序性能。 Guava Cache提供了以下特性: 1. 自动加载:当缓存中不存在某个键的值时,可以自动加载生成该值。 2. 自动移除:缓存中的某些条目可以在一定时间内自动过期,或者可以使用大小限制来限制缓存中的条目数。 3. 针对不同的缓存数据设置不同的过期时间、存活时间、最大值、最小值等。 4. 支持同步和异步缓存。 使用Guava Cache非常简单,只需要按以下步骤操作: 1. 引入Guava库。 2. 创建一个CacheBuilder对象,用于配置缓存。 3. 调用build()方法创建一个Cache对象。 4. 使用put()方法向缓存中添加数据。 5. 使用get()方法从缓存中读取数据,如果缓存中不存在该键对应的值,则可以自动加载。 6. 使用invalidate()方法从缓存中移除数据。 下面是一个简单的示例: ```java import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; public class GuavaCacheExample { public static void main(String[] args) throws ExecutionException { // 创建一个CacheBuilder对象 CacheBuilder<Object, Object> cacheBuilder = CacheBuilder.newBuilder() .maximumSize(100) // 设置缓存最大条目数 .expireAfterWrite(10, TimeUnit.MINUTES); // 设置缓存过期时间 // 创建一个Cache对象 LoadingCache<String, String> cache = cacheBuilder.build(new CacheLoader<String, String>() { @Override public String load(String key) throws Exception { System.out.println("loading " + key); // 自动加载数据 return "value-" + key; } }); // 添加数据到缓存cache.put("key1", "value1"); cache.put("key2", "value2"); // 从缓存中读取数据 System.out.println(cache.get("key1")); // 输出"value1" System.out.println(cache.get("key3")); // 输出"loading key3"和"value-key3" // 移除缓存中的数据 cache.invalidate("key1"); System.out.println(cache.get("key1", () -> "default")); // 输出"default" } } ``` 在这个示例中,我们使用CacheBuilder对象配置了缓存的最大条目数和过期时间。我们还使用CacheLoader对象创建了一个自动加载的缓存,当缓存中不存在某个键的值时,可以自动加载生成该值。我们使用put()方法向缓存中添加了两个数据,使用get()方法从缓存中读取了两个数据,并使用invalidate()方法从缓存中移除了一个数据。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

融极

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值