美文网首页
本地缓存Google Guava

本地缓存Google Guava

作者: 欢乐时光欢乐你我 | 来源:发表于2019-03-26 16:02 被阅读0次

Cache与ConcurrentMap很相似,但也不完全一样。
最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。

Guava 开发和使用介绍

基本容量的回收 设置了初始值和最大容量上限,如果逼近容量上限,就会触发回收机制。

.maximumSize(200000)// 最大缓存数据量
.initialCapacity(100000)// 初始容量

定时回收

expireAfterAccess(long, TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。请注意这种缓存的回收顺序和基于大小回收一样。

expireAfterWrite(long,TimeUnit):缓存项在给定时间内没有被写访问(创建或覆盖),则回收。

如果认为缓存数据总是在固定时候后变得陈旧不可用,这种回收方式是可取的。

pom文件:

 <!-- Google Guava  如果服务上要使用本地缓存,可以考虑使用guava框架。Guava Cache与ConcurrentMap很相似 -->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>

demo代码展示

package com.yaoex.contract.common;

import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import org.apache.commons.lang.RandomStringUtils;

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;

/**
 * @author xushuai
 * @date 2019年3月26日
 * @note 可以参考  https://www.jianshu.com/p/f4b99b70bd76
 */
public class GuavaCacheUtil {

    /*
     *
     * 相对地,Guava Cache为了限制内存占用,通常都设定为自动回收元素。在某些场景下,尽管LoadingCache
     * 不回收元素,它也是很有用的,因为它会自动加载缓存。
     * 
     * 通常来说,Guava Cache适用于:
     * 
     * 你愿意消耗一些内存空间来提升速度。 
     * 你预料到某些键会被查询一次以上。 
     * 缓存中存放的数据总量不会超出内存容量。
     * (GuavaCache是单个应用运行时的本地缓存。它不把数据存放到文件或外部服务器。如果这不符合你的需求,请尝试Memcached这类工具)
     * memcached是一套分布式的高速缓存系统
     * 如果你的场景符合上述的每一条,Guava Cache就适合你。
     * 注:如果你不需要Cache中的特性,使用ConcurrentHashMap有更好的内存效率——但Cache的大多数特性都很难基于旧有的ConcurrentMap复制,甚至根本不可能做到。
     *
     */

    // 新建了一个线程池,用来执行缓存刷新任务。并且重写了CacheLoader的reload方法,在该方法中建立缓存刷新的任务并提交到线程池。
    private static ListeningExecutorService backgroundRefreshPools = MoreExecutors
            .listeningDecorator(Executors.newFixedThreadPool(20));

    private static Cache<String, String> localCache = CacheBuilder.newBuilder()
            .maximumSize(200000)// 最大缓存数据量
            .initialCapacity(100000)// 初始容量
            .recordStats()// 用来开启Guava Cache的统计功能
            .expireAfterWrite(5, TimeUnit.SECONDS)// 过期清除
            /*
             * 1.缓存值定时刷新:更新线程调用load方法更新该缓存,其他请求线程返回该缓存的旧值。这里的定时并不是真正意义上的定时。
             * 2.Guava
             * cache的刷新需要依靠用户请求线程,让该线程去进行load方法的调用,所以如果一直没有用户尝试获取该缓存值,则该缓存也并不会刷新
             * 3.这样对于某个key的缓存来说,只会有一个线程被阻塞,用来生成缓存值,而其他的线程都返回旧的缓存值,不会被阻塞。
             */
            .refreshAfterWrite(50, TimeUnit.SECONDS)// 每隔十s缓存值则会被刷新。防止缓存穿透
            .removalListener(new RemovalListener<String, String>() { // 设置监听事件,就是在
                                                                        // 删除key的时候触发这个事件
                @Override
                public void onRemoval(
                        RemovalNotification<String, String> notification) {
                    String email = notification.getValue();
                    String key = notification.getKey();
                    RemovalCause cause = notification.getCause();
                    System.out.println("结果====" + email);
                    System.out.println("key====" + key);
                    System.out.println("cause====" + cause);
                }
            }).build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return generateValueByKey(key);
                }
                /*
                 * Guava Cache异步刷新:
                 * 
                 * 如上的使用方法,解决了同一个key的缓存过期时会让多个线程阻塞的问题,只会让用来执行刷新缓存操作的一个用户线程会被阻塞。
                 * 由此可以想到另一个问题,当缓存的key很多时,高并发条件下大量线程同时获取不同key对应的缓存,此时依然会造成大量线程阻塞
                 * ,并且给数据库带来很大压力。
                 * 这个问题的解决办法就是将刷新缓存值的任务交给后台线程,所有的用户请求线程均返回旧的缓存值,这样就不会有用户线程被阻塞了。
                 * 详细做法如下:
                 */

                // 注意此时缓存的刷新依然需要靠用户线程来驱动,只不过和上面不同之处在于该用户线程触发刷新操作之后,会立马返回旧的缓存值。
                @Override
                public ListenableFuture<String> reload(final String key,
                        String oldValue) throws Exception {
                    return backgroundRefreshPools
                            .submit(new Callable<String>() {

                                @Override
                                public String call() throws Exception {
                                    return generateValueByKey(key);
                                }
                            });
                }

            });

    public static void main(String[] args) {
        add();
        asMap();
        
        try {
            Thread.sleep(10000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tongji();
        System.out.println("2>>>>>" + localCache.getIfPresent("username"));

        /*
         * 结果: 1>>>>>xushuai 2>>>>>null
         */

        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        get();

        try {
            Thread.sleep(15000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        tongji();
        del();
    }

    /**
     * 
     */
    private static void asMap() {
        ConcurrentMap<String, String> currentMap = localCache.asMap();
        Set<String> set = currentMap.keySet();
        for (String s : set) {
            System.out.println(localCache.getIfPresent(s));
        }
    }

    /**
     * 获取
     */
    private static void get() {
        for (int i = 0; i <= 100; i++) {
            if (i % 20 == 0) {
                System.out.println(
                        i + ">>>>>" + localCache.getIfPresent("username"));
            }
        }
    }

    /**
     * 清除
     */
    private static void del() {
        // 个别清除:Cache.invalidate(key) 批量清除:Cache.invalidateAll(keys)
        // 清除所有缓存项:Cache.invalidateAll()

        localCache.invalidate("username2");
    }

    /**
     * 
     */
    private static void add() {
        localCache.put("username", "xushuai");

        localCache.put("username2", "xushuai");
        System.out.println("1>>>>>" + localCache.getIfPresent("username"));
    }

    /**
     * Guava Cache统计
     */
    private static void tongji() {
        CacheStats stats = localCache.stats();
        // 缓存命中率;
        System.out.println("缓存命中率:" + stats.hitRate());
        // 缓存项被回收的总数,不包括显式清除。
        System.out.println("缓存项被回收的总数,不包括显式清除:" + stats.evictionCount());
    }

    public static String generateValueByKey(String key) {
        return key + ":缓存值定时刷新 防止有缓存穿透"
                + RandomStringUtils.randomAlphabetic(10);
    }
}


相关文章

  • 本地缓存Google Guava

    Cache与ConcurrentMap很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所...

  • Java内存缓存-通过Google Guava创建缓存

    谷歌Guava缓存 Guava介绍 Guava是Google guava中的一个内存缓存模块,用于将数据缓存到JV...

  • Google,Guava本地高效缓存

    Guva是google开源的一个公共java库,类似于Apache Commons,它提供了集合,反射,缓存,科学...

  • Google Guava Cache 全解析

    Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于...

  • 本地缓存之神-guava cache

    背景 guava cache是google工具包中提供的关于本地缓存解决方案,提供了基于容量,时间和引用的缓存回收...

  • Google Guava Cache 详解

    Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于...

  • Guava Cache

    Google Guava Cache是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。 基...

  • guava cache vs caffeine

    说起Guava Cache,很多人都不会陌生,它是Google Guava工具包中的一个非常方便易用的本地化缓存实...

  • guavaCache本地缓存失效方案expireAfterWri

    Guava实现本地缓存,为了保证缓存一致性,本地缓存需要被动的失效(即设置失效时间)。Guava Cache有两种...

  • google Guava Cache使用(本地缓存)

    1.在多线程并发场景中往往是离不开cache的,需要根据不同的场景来选择不同的cache, cache就是为了提升...

网友评论

      本文标题:本地缓存Google Guava

      本文链接:https://www.haomeiwen.com/subject/roxnvqtx.html