常见开源内存缓存工具介绍

66 阅读5分钟

第5天持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

Guava Cache 介绍

  • Google Guava Cache 是一种非常优秀本地缓存解决方案,提供了基于容量,时间和引用的缓存回收方式。基于容量的方式内部实现采用LRU算法,基于引用回收很好的利用了Java虚拟机的垃圾回收机制。
  • Guava Cache 与 ConcurrentMap 很相似,但也不完全一样。最基本的区别是ConcurrentMap会一直保存所有添加的元素,直到显式地移除。 Guava Cache 为了限制内存占用,通常都设定为自动回收元素。
  • 适用场景
  1. 愿意消耗一些内存空间来提升速度
  2. 预料到某些键会被多次查询
  3. 缓存中存放的数据总量不会超出内存容量
  • Guava Cache是运行在JVM的本地缓存,并不能把数据存放到外部服务器上。如果有这样的要求,因该尝试Memcached或Reids这类分布式缓存。

Guava Cache 加载

加载方式1- CacheLoader 

loadingCache 是附带CacheLoader 构建而成的缓存实现。创建自己的CacheLoader 通常只需要简单地实现 V load(K key) throws Exception 方法。 

/**
 * @author nicole
 * @version 1.0.0
 * @Description: 加载方式 CacheLoader
 *  * 加载方式1:CacheLoader
 *  * 1.设置缓存容量
 *  * 2.设置超时时间
 *  * 3.提供移除监听器
 *  * 4.提供缓存加载器
 *  * 5.构建缓存
 * @date 2021/12/13
 * @Modify by nicole
 */

public class GuavaCacheDemo_1 {
    public static void main(String[] args) {
        //监听器
        RemovalListener<String, String> listener = new RemovalListener<String, String>() {
            @Override
            public void onRemoval(RemovalNotification<String, String> removalNotification) {
                System.out.println("监听器移除的 [ key = "+removalNotification.getKey()+ ",value = " + removalNotification.getValue()+"]");
            };
        };
        //缓存加载器
        CacheLoader<String,String> loader = new CacheLoader<String, String>() {
            @Override
            public String load(String s) throws Exception {
                Thread.sleep(1000);
                if( "key".equals(s)){
                    return null;
                }
                System.out.println("缓存加载器key = "+ s + "is loading in CacheLoad");
             return "缓存加载器的key是 " + s;
            }
        };

        //提供缓存加载器
        LoadingCache<String,String>   testCache = CacheBuilder.newBuilder()
                // 设置缓存大小
                .maximumSize(5)
                //设置过期时间,单位是分钟
                .expireAfterWrite(10,TimeUnit.MINUTES)
                .expireAfterAccess(10,TimeUnit.MINUTES)
                //提供移除监听器
                .removalListener(listener)
                //提供缓存加载器
                .build(loader);

        //构建缓存
        for (int i = 0; i < 10; i++) {
            String key = "key" + i;
            String value = "value" + i;
            testCache.put(key,value);
            System.out.println("["+key + ":"+ value +"]"+" is put in cache ");
        }
        //如果存在就获取
        System.out.println( "打印 key6 = "+ testCache.getIfPresent("key6"));
        try {
            System.out.println( "打印不存在的key "+testCache.get("key"));
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("不存在的key,会报错");
        }
    }
}

//控制台执行结果如下图。

Guava Cache 缓存回收

  • 回收方式1- 基于容量回收

maximumSize(long) : 当缓存中的元素超过指定值时。

  • 回收方式2- 定时回收

expireAfterAccess(long , TimeUnit):缓存项在给定时间内没有被读/写访问,则回收。

  • 回收方式3 - 基于引用回收(Reference - based Eviction)
  1. CacheBuilder.weakKeys() : 使用弱引用存储键 。当key没有其他引用时,缓存项可以被垃圾回收。

  2. CacheBuilder.weakValues() : 使用弱引用存储值 。当value没有其他引用时,缓存项可以被垃圾回收。

  3. CacheBuilder.softValues() :   使用软引用存储键 ,按照全局最近最少使用的顺序回收。

    /**

    • @author nicole

    • @version 1.0.0

    • @Description:

    • @date 2021/12/15

    • @Modify by nicole

      • 加载方式2:Callable
      • 所有类型的Guava Cache,不管有没有自动加载功能,都支持get(K, Callable)方法。
      • 这个方法返回缓存中相应的值,或者用给定的Callable运算并把结果加入到缓存中。
      • 在整个加载方法完成前,缓存项相关的可观察状态都不会更改。
      • 这个方法简便地实现了模式"如果有缓存则返回;否则运算、缓存、然后返回"。 */ public class GuavaCacheDemo_2 { //构建容器为3的缓存对象 static Cache<String, String> testCache = CacheBuilder.newBuilder().maximumSize(3).build();

      public static void main(String[] args) { testCache.put("1234","我是存在的"); // 如果存在就获取,不存在返回null System.out.println("获取key6的值为" + testCache.getIfPresent("key6"));

      try {
          // 获取key为123的缓存数据,如果有就返回,没有就返回call方法的返回值
          System.out.println(testCache.get("123", new Callable<String>() {
              @Override
              public String call() throws Exception {
                  return "计算、缓存、啥啥啥";
              }
          }));
          // 获取key为1234的缓存数据,如果有就返回,没有就返回call方法的返回值。注意这里key是存在的
          System.out.println(testCache.get("1234", new Callable<String>() {
              @Override
              public String call() throws Exception {
                  return "我是来打酱油的";
              }
          }));
      } catch (ExecutionException e) {
          e.printStackTrace();
      }
      

      }

    } //控制台执行结果如下: 获取key6的值为null 计算、缓存、啥啥啥 我是存在的

Guava Cache 显示清除

任何时候,你都可以显式地清除缓存项,而不是等到它被回收:

  1. 个别清除:Cache.invalidate(key)
  2. 批量清除:Cache.invalidateAll(keys)
  3. 清除所有缓存项 :Cache.invalidateAll()

Guava Cache统计

CacheBuilder.recordStats() : 用来开启Guava Cache 的统计功能。统计打开后, Cache.stats()方法会返回CacheStats 对象以提供如下统计信息:

  • hitRate() : 缓存命中率;

  • averageLoadPenalty() : 加载新值的平均时间,单位为纳秒;

  • evictionCount() : 缓存项被回收的总数,不包括显式清除;

    /**

    • 统计

    • @author nicole

    • @version 1.0.0

    • @Description:

    • @date 2021/12/15

    • @Modify by nicole */ public class GuavaCacheDemo_3 { static Cache<String,Object> testCache = CacheBuilder.newBuilder() // 当值没有其它(强或软)引用时,缓存项可以被垃圾回收 .weakValues() // 开启Guava Cache的统计功能 .recordStats() .build(); public static void main(String[] args) { testCache.put("1234",new String("123"));

        // 主动gc
        System.gc();
        System.out.println(testCache.getIfPresent("1234"));
      
        /*
        stats()方法会返回CacheS tats 对象以提供如下统计信息:
           hitRate():缓存命中率;
           averageLoadPenalty():加载新值的平均时间,单位为纳秒;
           evictionCount():缓存项被回收的总数,不包括显式清除。
        */     
        System.out.println(testCache.stats());
      

      } }

    //控制台输出 null CacheStats{hitCount=0, missCount=1, loadSuccessCount=0, loadExceptionCount=0, totalLoadTime=0, evictionCount=0}

Ehcache

Ehcache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。

主要特性

  1. 快速、简单、支持多种缓存策略
  2. 支持内存和磁盘缓存数据,因此无需担心容量问题
  3. 缓存数据会在虚拟机重启的过程中写入磁盘
  4. 可以通过RMI、可插入API等方式进行分布式缓存(比较弱)
  5. 具有缓存和缓存管理器的侦听接口
  6. 支持多缓存管理器实例,以及一个实例的多个缓存区域
  7. 提供Hibernate的缓存实现

适用场景

  1. 单个应用或者对缓存访问要求很高的应用
  2. 简单的共享可以,但是不适合涉及缓存恢复、大数据缓存
  3. 大型系统,存在缓存共享、分布式部署、缓存内容大不适合使用
  4. 在实际工作中,更多是将Ehcache作为与Redis配合的二级缓存

Caffeine

Caffeine 是Google基于Java8对GuavaCache的重写升级版本,支持丰富的缓存过期策略,尤其是TinyLfu淘汰算法,提供了一个近乎最佳的命中率。从性能上(读、写、读/写)也足以秒杀其他一堆进程内缓存框架。Spring5更是直接放弃使用了多年的Guava,而采用了Caffeine。

Caffeine的API 的操作功能和Guava 是基本保持一致的,并且Caffeine为了兼容之前是Guava的用户,做了一个Guava的Adapter给大家使用也是十分的贴心。

Caffeine是一个非常不错的缓存框架,无论是在性能方面,还是在API方面,都比Guava cache要优秀一些。如果在新的项目中要使用local cache 的话,可以优先考虑使用Caffeine。对于老的项目,如果使用了Guava cache ,想要升级为Caffeine的话,可以使用Caffeine提供的Guava cache适配器,方便的进行切换。 

继续看代码 ,三种加载使用方式。

常见开源内存缓存工具对比