相关文章推荐
近视的茶壶  ·  点云 - 知乎·  1 年前    · 
温柔的紫菜汤  ·  Hibernate ...·  1 年前    · 

前面的文章 缓存篇(一)- Guava 有讲到Guava Cache有区别于ConcurrentHashMap的使用,就是因为其自带有自动刷新和自动失效的功能,避免我们去自己编写刷新和失效的后台线程程序。Guava Cache提供了简单便捷的api给我们使用,但是研究源码发现这里的自动刷新缓存和自动失效原理,并非是Guava Cache帮我们去添加了类似后台线程自动刷新或失效逻辑的代码,而是用另外一种巧妙的方式进行。

expireAfterWrite 与 refreshAfterWrite区别

expireAfterWrite

在缓存更新后某个时间失效缓存,这里Guava内部会对某个时间点失效的缓存做统一失效,只要有get访问任一key,就会失效当前时间失效的缓存,会移除当前key。所以这里也希望我们创建的缓存数据量不宜过大,使用guavaCache最好是设置一下maximumSize,避免出现内存溢出的情况。失效后需要获取新值才可会返回。

refreshAfterWrite

是指在创建缓存后,如果经过一定时间没有更新或覆盖,则会在下一次获取该值的时候,会在后台异步去刷新缓存,如果新的缓存值还没有load到时,则会先返回旧值。这里跟上面的expireAfterWrite不同的是,及时到了该刷新的时间,不会失效旧值和移除对应key。在后台异步刷新的过程中,如果当前是刷新状态,及时有其他线程访问到旧值,依然只有一个线程在更新,不会出现多个线程同时刷新同一个key的缓存。

是否需要编写缓存刷新代码

不需要。上面讲到,使用了refreshAfterWrite后台会异步去刷新。这里后台刷新是使用线程池去完成异步刷新过程,即ListeningExecutorService sameThreadExecutor = MoreExecutors.sameThreadExecutor();

这里是模拟1000个线程在3秒内10次从guavaCacha拿数据,通过loadTimes判断刷新了多少次,输出结果可以看到拿到的结果正确性。

代码已上传至 https://github.com/zhuzhenke/common-caches

public class GuavaCacheRefreshTest {
    @Data
    public class SkuCache {
        private String skuId;
        private String skuCode;
        private Long realQuantity;
    AtomicInteger loadTimes = new AtomicInteger(0);
    AtomicInteger count = new AtomicInteger(0);
    @Test
    public void testCacheUse() throws Exception {
        LoadingCache<String, SkuCache> loadingCache = CacheBuilder.newBuilder()
                .refreshAfterWrite(1000, TimeUnit.MILLISECONDS)
                //Prevent data reloading from failing, but the value of memory remains the same
                .expireAfterWrite(1500, TimeUnit.MILLISECONDS)
                .build(new CacheLoader<String, SkuCache>() {
                    @Override
                    public SkuCache load(String key) {
                        SkuCache skuCache = new SkuCache();
                        skuCache.setSkuCode(key + "---" + (loadTimes.incrementAndGet()));
                        skuCache.setSkuId(key);
                        skuCache.setRealQuantity(100L);
                        System.out.println("load..." + key);
                        return skuCache;
                    @Override
                    public ListenableFuture<SkuCache> reload(String key, SkuCache oldValue) throws Exception {
                        checkNotNull(key);
                        checkNotNull(oldValue);
                        System.out.println("reload...");
                        //Simulate time consuming operation
//                        Thread.sleep(1000);
                        return Futures.immediateFuture(load(key));
                });
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                try {
                    getValue(loadingCache);
                } catch (Exception e) {
                    e.printStackTrace();
            }).start();
        System.in.read();
        System.out.println("finish");
    private void getValue(LoadingCache<String, SkuCache> loadingCache) throws Exception {
        for (int i = 0; i < 10; i++) {
            Thread.sleep(300l);
            System.out.println(loadingCache.get("sku").toString() + " - " + count.incrementAndGet());
                    前面的文章缓存篇(一)- Guava有讲到Guava Cache有区别于ConcurrentHashMap的使用,就是因为其自带有自动刷新和自动失效的功能,避免我们去自己编写刷新和失效的后台线程程序。Guava Cache提供了简单便捷的api给我们使用,但是研究源码发现这里的自动刷新缓存和自动失效原理,并非是Guava Cache帮我们去添加了类似后台线程自动刷新或失效逻辑的代码,而是用另外一种...
转载:https://www.cnblogs.com/liuxiaochong/p/13613071.html
总览参考:【Guava】Google Guava本地高效缓存
2.思考和猜想
首先看一下三种基于时间的清理或刷新缓存数据的方式:
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。
expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收。
refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。 
1、expireAfterWrite
每次缓存失效LoadingCache都会去调用我们实现的load方法去重新加载缓存,在加载期间,所有线程对该缓存key的访问都将被block。所以如果实际加载缓存需要较长时间的话,这种方式不太适用。从代码中还可以看到,即使在CacheLoader实现了reload方法,也不会被调用,因为reload只有当设置了refreshAfterWrites时才会被调用。
2、refreshAfte
Guava Cache特性:对于同一个key,只让一个请求回源load数据,其他线程阻塞等待结果这种情况:如果缓存过期,恰好有多个线程读取同一个key的值,那么guava只允许一个线程去加载数据,其余线程阻塞。这虽然可以防止大量请求穿透缓存,但是效率低下。使用refreshAfterWrite可以做到:只阻塞加载数据的线程,其余线程返回旧数据。
2:...
				
Guava Cache是没有定时的,不会去主动失效key。除非是超过最大的容量,LUA算法才会去移除key。 refreshAfterWrite是指创建指定时间后,没有get过此key, 没有被LUA淘汰删除key,那么此时缓存里面是有旧值的。get时候会进行同步更新旧值的内容,其他线程等待。 LUA淘汰删除key,那么此时缓存里面没有值的。get时候会进行同步新增值内容,其他线程等待。 expireAfterWrite是指创建指定时间后,没有get过此key, 没有被LUA淘汰删除key,那么此时
expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。 expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收(移除key),需要等待获取新值才会返回。 refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。第一个请求进来,执行load把数据加载到内存中(同步过程),指定的过期时间内比如10秒,都是从cache里读取数据。过了10秒后,没有请求进来,不会移除key。再有请求过来,才则执行 某个缓存key和对应的value被缓存guava后,一段时间后该key对应的记录在数据库中被删除; 当这个key再次被获取的时候,到达refreshAfterWrite设置时间触发refresh方法, 然后委托给我们自己实现的reload方法,我们的reload方法又调用了load方法来重新刷新该key对应的最新值。那么这个时候问题来了。因为我们知道,如果一个key曾经没有存在过,触发load方法的时候,我们return null,最终guava不会去增加一个key来保存这一对无效键值对。
.expireAfterWrite(20, TimeUnit.MINUTES) 在20分钟内没有创建/覆盖时,会移除该key,下次取的时候从loading中取【重点:失效、移除Key、失效后需要获取新值】 .refreshAfterWrite(10, TimeUnit.MINUTES) 在10分钟内没有被创建/覆盖,那么访问时,会去拿(新值or旧值)刷新该缓存【重点:不会失效、旧值刷新、不会移除Key】 注意:refreshAfterWrite后台异步刷新,其他线程访问旧值,有一个线程在执行刷新,但不会
三种基于时间清理或刷新缓存数据的方式: expireAfterAccess: 当缓存项在指定的时间段内没有被读或写就会被回收。 expireAfterWrite:当缓存项在指定的时间段内没有更新就会被回收(移除key),需要等待获取新值才会返回。 refreshAfterWrite:当缓存项上一次更新操作之后的多久会被刷新。第一个请求进来,执行load把数据加载到内存中(同步过程),指定的过期时间内比如10秒,都是从cache里读取数据。过了10秒后,没有请求进来,不会移除key。再有请求过来,才则执行re
上一篇文章"Guava Cache特性:对于同一个key,只让一个请求回源load数据,其他线程阻塞等待结果"提到:如果缓存过期,恰好有多个线程读取同一个key的值,那么guava只允许一个线程去加载数据,其余线程阻塞。这虽然可以防止大量请求穿透缓存,但是效率低下。使用refreshAfterWrite可以做到:只阻塞加载数据的线程,其余线程返回旧数据。 public class GuavaCache4TestRefresh { private static CountDownLatch latch
本地缓存因为少了网络传输环节,所以读取速度比分布式缓存要快一些,但是在分布式环境下可能会出现多机不一致问题。 ---------------------吹水分割线-------------------- 这里引申下,怎么解决分布式环境下多机本地缓存不一致的问题?提供两个思路,不知道好不好: (1)使用kafka消息队列: 生产者:每一台机器都是一个生产...