本地缓存的优缺点

  • 应用场景
    在单利用不须要集群反对或者集群状况下各节点无需相互告诉的场景下应用本地缓存较适合。
  • 长处
    利用和cache是在同一个过程外部,申请缓存十分疾速,没有过多的网络开销等。
  • 毛病
    缓存跟应用程序耦合,多个应用程序无奈间接的共享缓存,各利用或集群的各节点都须要保护本人的独自缓存,对内存是一种节约。

本地缓存的几种实现

间接编程实现

应用动态变量实现本地缓存。

  • 应用场景:单机场景下的小数据量缓存需要,同时对缓存数据的变更无需太敏感感知,如上个别配置管理、根底静态数据等场景。
  • 长处:通过动态变量一次获取数据而后缓存在内存中,缩小频繁的I/O读取,动态变量实现类间可共享,过程内可共享。
  • 毛病:缓存的实时性稍差,受java堆内存大小的影响,缓存数量无限。
  • 改良计划: 应用Zookeeper 的主动发现机制,更新缓存。

应用示例:

City.java

public class City {    private int id;    private String name;    public City(int i, String name) {        this.id = i;        this.name = name;    }    public void setId(int id) {        this.id = id;    }    public void setName(String name) {        this.name = name;    }    public int getId() {        return id;    }    public String getName() {        return name;    }    public static List<City> getCitysFromDB() {        List<City> cities = new ArrayList<>();        cities.add(new City(1, "江西"));        cities.add(new City(2, "上海"));        cities.add(new City(3, "北京"));        cities.add(new City(4, "天津"));        cities.add(new City(5, "河北"));        cities.add(new City(6, "湖北"));        cities.add(new City(7, "湖南"));        cities.add(new City(8, "广东"));        cities.add(new City(9, "安徽"));        cities.add(new City(10, "..."));        return cities;    }}

CityLocalCacheExample.java

public class CityLocalCacheExample {    private static Map<Integer, String> cityNameMap = new HashMap<Integer, String>();    static {        System.out.println("Init Cache");        try {            List<City> cities = City.getCitysFromDB();            for (City city : cities) {                cityNameMap.put(city.getId(), city.getName());            }        } catch (Exception e) {            throw new RuntimeException("Init City List Error!", e);        }    }    public static String getCityName(int cityId) {        String name = cityNameMap.get(cityId);        if (name == null) {            name = "未知";        }        return name;    }    public static void main(String[] args) {        String cityName = CityLocalCacheExample.getCityName(1);        System.out.println(cityName);    }}

Ehcache

Ehcache是纯Java开源缓存框架,配置简略、构造清晰、功能强大,是一个十分轻量级的缓存实现.
Ehcache的外围概念次要包含:

  • cache manager:缓存管理器,以前是只容许单例的,不过当初也能够多实例了。
  • cache:缓存管理器内能够搁置若干cache,存放数据的本质,所有cache都实现了Ehcache接口,这是一个真正应用的缓存实例;通过缓存管理器的模式,能够在单个利用中轻松隔离多个缓存实例,独立服务于不同业务场景需要,缓存数据物理隔离,同时须要时又可共享应用。
  • element:单条缓存数据的组成单位。

Ehcache的缓存介质涵盖堆内存(heap)、堆外内存(BigMemory商用版本反对)和磁盘,各介质可独立设置属性和策略。

  • 应用场景:大型高并发零碎场景,须要疾速的读取。
  • 长处:

    1. 简略,很小的jar包,简略配置就可间接应用,单机场景下无需过多的其余服务依赖。
    2. 反对多种的缓存策略,灵便。
    3. 缓存数据有两级:内存和磁盘,与个别的本地内存缓存相比,有了磁盘的存储空间,将能够反对更大量的数据缓存需要。
    4. 具备缓存和缓存管理器的侦听接口,能更简略不便的进行缓存实例的监控治理。
    5. 反对多缓存管理器实例,以及一个实例的多个缓存区域。
  • 毛病:

    1. 在应用中要留神过期生效的缓存元素无奈被GC回收,工夫越长缓存越多,内存占用也就越大,内存泄露的概率也越大。

应用示例:

引入Ehcache依赖

<dependency>  <groupId>net.sf.ehcache</groupId>  <artifactId>ehcache</artifactId>  <version>2.10.2</version></dependency>

Ehcache 配置文件

<ehcache>    <!-- 指定一个文件目录,当Ehcache把数据写到硬盘上时,将把数据写到这个文件目录下 -->    <diskStore path="java.io.tmpdir"/>    <!-- 设定缓存的默认数据过期策略 -->    <defaultCache            maxElementsInMemory="10000"            eternal="false"            overflowToDisk="true"            timeToIdleSeconds="0"            timeToLiveSeconds="0"            diskPersistent="false"            diskExpiryThreadIntervalSeconds="120"/>    <!--        设定具体的命名缓存的数据过期策略        cache元素的属性:            name:缓存名称            maxElementsInMemory:内存中最大缓存对象数            maxElementsOnDisk:硬盘中最大缓存对象数,若是0示意无穷大            eternal:true示意对象永不过期,此时会疏忽timeToIdleSeconds和timeToLiveSeconds属性,默认为false            overflowToDisk:true示意当内存缓存的对象数目达到了maxElementsInMemory界线后,会把溢出的对象写到硬盘缓存中。留神:如果缓存的对象要写入到硬盘中的话,则该对象必须实现了Serializable接口才行。            diskSpoolBufferSizeMB:磁盘缓存区大小,默认为30MB。每个Cache都应该有本人的一个缓存区。            diskPersistent:是否缓存虚拟机重启期数据            diskExpiryThreadIntervalSeconds:磁盘生效线程运行工夫距离,默认为120秒            timeToIdleSeconds: 设定容许对象处于闲暇状态的最长工夫,以秒为单位。当对象自从最近一次被拜访后,如果处于闲暇状态的工夫超过了timeToIdleSeconds属性值,这个对象就会过期,EHCache将把它从缓存中清空。只有当eternal属性为false,该属性才无效。如果该属性值为0,则示意对象能够无限期地处于闲暇状态            timeToLiveSeconds:设定对象容许存在于缓存中的最长工夫,以秒为单位。当对象自从被寄存到缓存中后,如果处于缓存中的工夫超过了 timeToLiveSeconds属性值,这个对象就会过期,Ehcache将把它从缓存中革除。只有当eternal属性为false,该属性才无效。如果该属性值为0,则示意对象能够无限期地存在于缓存中。timeToLiveSeconds必须大于timeToIdleSeconds属性,才有意义            memoryStoreEvictionPolicy:当达到maxElementsInMemory限度时,Ehcache将会依据指定的策略去清理内存。可选策略有:LRU(最近起码应用,默认策略)、FIFO(先进先出)、LFU(起码拜访次数)。    -->    <cache name="CityCache"           maxElementsInMemory="1000"           eternal="true"           overflowToDisk="true"/></ehcache>

CityEhcacheExample.java

public class CityEhcacheExample {    public static Cache cache = null;    static {        System.out.println("Init Ehcache");        // 1. 创立缓存管理器        CacheManager cacheManager = CacheManager.create("./src/main/resources/ehcache.xml");        // 2. 获取缓存对象        cache = cacheManager.getCache("CityCache");        List<City> cities = City.getCitysFromDB();        for(City city : cities) {            // 3. 创立元素            Element element = new Element(city.getId(), city.getName());            // 4. 将元素增加到缓存            cache.put(element);        }    }    public static String getCityName(int cityId) {        Element city = cache.get(cityId);        if (city == null) {            return "未知";        }        return (String) city.getObjectValue();    }    public static void main(String[] args) {        System.out.println(getCityName(1));    }}

Guava Cache

Guava Cache是Google开源的Java重用工具集库Guava里的一款缓存工具,其次要实现的缓存性能有:

  • 主动将entry节点加载进缓存构造中;
  • 当缓存的数据超过设置的最大值时,应用LRU算法移除;
  • 具备依据entry节点上次被拜访或者写入工夫计算它的过期机制;
  • 缓存的key被封装在WeakReference援用内;
  • 缓存的Value被封装在WeakReference或SoftReference援用内;
  • 统计缓存应用过程中命中率、异样率、未命中率等统计数据。

Guava Cache继承了ConcurrentHashMap的思路,应用多个segments形式的细粒度锁,在保障线程平安的同时,反对高并发场景需要。Cache相似于Map,它是存储键值对的汇合,不同的是它还须要解决evict、expire、dynamic load等算法逻辑,须要一些额定信息来实现这些操作。

Guava Cache提供Builder模式的CacheBuilder生成器来创立缓存的形式,非常不便,并且各个缓存参数的配置设置,相似于函数式编程的写法,可自行设置各类参数选型。它提供三种形式加载到缓存中。别离是:

  • 在构建缓存的时候,应用build办法外部调用CacheLoader办法加载数据;
  • callable 、callback形式加载数据;
  • 应用粗犷间接的形式,间接Cache.put 加载数据,但主动加载是首选的,因为它能够更容易的推断所有缓存内容的一致性。

应用示例:
引入guava cache 的依赖

<dependency>  <groupId>com.google.guava</groupId>  <artifactId>guava</artifactId>  <version>26.0-jre</version></dependency>

GuavaCacheExample.java

public class GuavaCacheExample {    private static final LoadingCache<Integer, Optional<String>> cityCache;    static {        cityCache = CacheBuilder.newBuilder()                .maximumSize(1000).build(new CacheLoader<Integer, Optional<String>>() {                    @Override                    public Optional<String> load(Integer key) throws Exception {                        String name = getNameById(key);                        if (null == name) {                            return Optional.empty();                        } else {                            return Optional.of(name);                        }                    }                });    }    private static String getNameById(Integer id) {        List<City> cities = City.getCitysFromDB();        for(City city : cities) {            if (city.getId() == id) {                return city.getName();            }        }        return null;    }    public static String getCityName(int cityId) throws ExecutionException {        Optional<String> name =  cityCache.get(cityId);        return name.orElse("未知");    }    public static void main(String[] args) throws ExecutionException {        System.out.println(getCityName(1));        System.out.println(getCityName(0));    }}

Caffeine

Caffeine是基于java8实现的新一代缓存工具,缓存性能靠近实践最优。能够看作是Guava Cache的增强版,性能上两者相似,不同的是Caffeine采纳了一种联合LRU、LFU长处的算法:W-TinyLFU,在性能上有显著的优越性。
相比Guava Cache来说,Caffeine无论从性能上和性能上都有显著劣势。同时两者的API相似,应用Guava Cache的代码很容易能够切换到Caffeine

引入Caffeine的依赖

<dependency>  <groupId>com.github.ben-manes.caffeine</groupId>  <artifactId>caffeine</artifactId>  <version>2.5.5</version></dependency>

CaffeineCacheExample.java

public class CaffeineCacheExample {    private static final Cache<Integer, String> cityCache;    static {        cityCache = Caffeine.newBuilder()                .maximumSize(1000).build(new CacheLoader<Integer,String>() {                    @Override                    public String load(Integer key) throws Exception {                        return getNameById(key);                    }                });    }    private static String getNameById(Integer id) {        List<City> cities = City.getCitysFromDB();        for(City city : cities) {            if (city.getId() == id) {                return city.getName();            }        }        return null;    }    public static String getCityName(int cityId) throws ExecutionException {       String name =  cityCache.getIfPresent(cityId);       if (name == null) {           name = cityCache.get(cityId, CaffeineCacheExample::getNameById);           if (name == null) {               return "未知";           }           return name;       }       return name;    }    public static void main(String[] args) throws ExecutionException {        System.out.println(getCityName(0));        System.out.println(getCityName(1));    }}

总结

从易用性角度,Guava Cache、Caffeine和Encache都有非常成熟的接入计划,应用简略。
从功能性角度,Guava Cache和Caffeine性能相似,都是只反对堆内缓存,Encache相比性能更为丰盛
从性能上进行比拟,Caffeine最优、GuavaCache次之,Encache最差。

参考资料

  1. 缓存那些事
  2. Java本地缓存技术选型
  3. https://github.com/ben-manes/caffeine/wiki/Benchmarks