如何实现本地缓存?


一、为什么要用本地缓存?

正常情况下,我们不使用本地缓存而直接使用分布式缓存(如Redis)的话,就会涉及网络调用,会出现一定的性能消耗,所以在大型项目中,一般使用多级缓存也就是本地缓存+分布式缓存来实现缓存查询,进一步提高系统性能。(具体可参考《如何实现多级缓存?》

二、实现本地缓存要考虑什么问题?

想要实现本地缓存,要有以下几个考虑点:

  • 数据结构:为了提高查询性能,我们一般可以用Key-Value数据结构来进行数据存储(如HashMap)
  • 线程安全:一般情况下,本地缓存的使用场景和使用频率会比较多,所以要考虑到线程安全问题
  • 清除策略:为了防止创建了大量对象而导致OOM的问题,还需要加上清除策略进行定期清除(如LRU算法、LFU算法等)
  • 过期时间:为了防止某些数据一直没有被清除到,我们还需要加上过期时间,保证对应Key一定会被清除回收

三、如何实现本地缓存?

一般最简单的方式就是直接使用HashMap来实现,因为它本身就是一个Key-Value结构,如果要考虑线程安全问题,也可以使用ConcurrentHashMap。但不足的是需要自己加上清除策略和过期时间,防止内存占用过大。

当前市面上已经有了成熟的本地缓存框架可以直接使用,比如:

  • Guava Cache
  • Caffeine

1、Guava Cache

添加依赖:

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

使用示例:

public class TestGuavaCacheClass {
    LoadingCache<String, String> cache = CacheBuilder.newBuilder()
            // 设置缓存最大容量为100(超过100会使用LRU算法清除)
            .maximumSize(100)
            // 设置缓存初始容量为10,默认为16
            .initialCapacity(16)
            // 设置并发级别(即可以同时写缓存的线程数,默认为4)
            .concurrencyLevel(8)
            // 设置写缓存后n秒后过期
            .expireAfterWrite(30, TimeUnit.SECONDS)
            // 设置读写缓存后n秒后过期(跟expireAfterWrite类似)
            .expireAfterAccess(30, TimeUnit.SECONDS)
            // 设置写缓存后n秒后刷新,只会阻塞第一个访问的线程,其他线程访问返回旧值
            .refreshAfterWrite(30, TimeUnit.SECONDS)
            .build(new CacheLoader<String, String>() {
                @Override
                public String load(String key) throws Exception {
                    return loadCache(key);
                }
            });

    public String getCache(String key) throws Exception {
        return cache.get(key);
    }
    
    private String loadCache(String key) {
        return "xxx";
    }
}

2、Caffeine

//TODO


文章作者: GaryLee
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 GaryLee !
  目录