乐趣区

关于缓存:本地缓存的使用

本地缓存的优缺点

  • 应用场景
    在单利用不须要集群反对或者集群状况下各节点无需相互告诉的场景下应用本地缓存较适合。
  • 长处
    利用和 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
退出移动版