guava缓存

google guava-cache

redis做缓存很快,但是还有网络IO,在并发量大要求耗时低的情况下还是不满足需求,所以想试试本地缓存。

发现guava缓存不错,想试试

在本地demo测试的时候发现更新缓存时有毛刺出现

package cn.wkq.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.junit.Test;

import java.util.concurrent.*;

/**
 * LocalCache
 *
 * @author: weikeqin.cn@gmail.com
 * @date: 2020-05-10 17:08
 **/
public class LocalCache {
    /**
     *
     */
    private transient static final Logger log = LoggerFactory.getLogger(LocalCache.class);
    /**
     *
     */
    private static LoadingCache<String, Object> cache = null;


    static {

        StringBuilder sb = new StringBuilder(1 << 20);
        for (int i = 0; i < 102400; i++) {
            sb.append(i);
        }
        String testString = sb.toString();
        log.info("{}", testString.length());

        cache = CacheBuilder.newBuilder()
                //设置并发级别为8,并发级别是指可以同时写缓存的线程数
                .concurrencyLevel(8)
                //设置缓存容器的初始容量为10
                .initialCapacity(10)
                //设置缓存最大容量为100,超过100之后就会按照LRU最近虽少使用算法来移除缓存项
                .maximumSize(100)
                //是否需要统计缓存情况,该操作消耗一定的性能,生产环境应该去除
                .recordStats()
                //设置写缓存后n秒钟过期
                .expireAfterWrite(1, TimeUnit.SECONDS)
                //设置读写缓存后n秒钟过期,实际很少用到,类似于expireAfterWrite
                //.expireAfterAccess(17, TimeUnit.SECONDS)
                //只阻塞当前数据加载线程,其他线程返回旧值
                .refreshAfterWrite(1, TimeUnit.SECONDS)
                //设置缓存的移除通知
                .removalListener(notification -> {
                    log.info(" 本地缓存key:{} 被移除,原因:{}  ", notification.getKey(), notification.getCause());
                    //log.info(" 本地缓存key:{} 被移除,原因:{}   对应值:{}", notification.getKey(), notification.getCause(), notification.getValue());
                })
                //build方法中可以指定CacheLoader,在缓存不存在时通过CacheLoader的实现自动加载缓存
                .build(new CacheLoader<String, Object>() {
                    @Override
                    public Object load(String key) throws Exception {
                        log.info("重新查库");
                        return "获取的缓存的值" + testString;
                    }
                });
    }


    /**
     *
     */
    @Test
    public void cacheTest() {

        ThreadPoolExecutor executor = new ThreadPoolExecutor(4, 8, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());

        String key = "1";
        int count = 100000;
        for (int i = 0; i < count; i++) {

            int finalI = i;
            executor.execute(() -> {

                long t1 = System.currentTimeMillis();
                Object value = null;
                try {
                    value = cache.get(key);
                } catch (ExecutionException e) {
                    log.error("", e);
                }
                long t2 = System.currentTimeMillis();

                log.info(" {}  cost {} ms ", finalI, t2 - t1);
                //log.info("{} 根据 {} 获取到的值 {} ", Thread.currentThread().getName(), key, ((String) value).length());

            });

//            try {
//                Thread.sleep(1);
//            } catch (InterruptedException e) {
//                log.error("", e);
//            }

        }

        executor.shutdown();
        log.info("线程池关闭。");

        while (!executor.isTerminated()) {
            try {
                TimeUnit.SECONDS.sleep(1);
                log.info("线程池中线程数目:{},队列中等待执行的任务数目:{},已执行玩别的任务数目:{}", executor.getPoolSize(), executor.getQueue().size(), executor.getCompletedTaskCount());
            } catch (InterruptedException e) {
                log.error("", e);
            }
            log.info("还有线程未执行完成");
        }

        //缓存状态查看
        log.info(cache.stats().toString());


    }

}

少了网络IO,整体 TP99 比redis好了不少
但是在压测时发现毛刺更严重

References

[1] google/guava/wiki/CachesExplained
[2] [Google Guava] 3-缓存 并发编程网 - ifeve.com
[3] 缓存篇 : Guava cache 之全面剖析