Guava之CacheLoader CacheBuilder LoadingCacbe以及两种驱逐策略

本文深入探讨了GuavaCache的应用及特性,通过多个示例演示了如何利用GuavaCache进行高效缓存管理,包括缓存的基本使用、不同缓存策略、统计信息收集等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

缓存,在咱们平常开发中是必不可少的一种解决性能问题的方法。简单的说,cache 就是为了提高系统性能而开辟的一块内存空间。缓存的作用就是将数据保存在内存中,当有其余线程或者客户端须要查询相同的数据资源时,直接从缓存的内存块中返回数据,这样不但能够提高系统的响应时间,同时也能够节省对这些数据的处理流程的资源消耗,总体上来讲,系统性能会有大大的提高。

Guava Cache是一个全内存的本地缓存实现,它提供了线程安全的实现机制。总体上来讲Guava cache 是本地缓存的不二之选,简单易用,性能好。

1 基本使用

package com.cache;

import com.cache.domain.Employee;
import com.google.common.base.Optional;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheBuilderSpec;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.Weigher;
import com.google.common.collect.Maps;
import org.apache.commons.collections4.map.HashedMap;
import org.junit.Test;

import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * 缓存加载程序测试
 *
 * @author zhaoshuai11
 * @date 2022/11/19
 */
public class CacheLoaderTest {

    private Boolean FROM_DB = false;

    @Test
    public void testBasic() throws ExecutionException, InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(10)
                .expireAfterAccess(3, TimeUnit.SECONDS)
                .build(CacheLoader.from(this::findEmployeeByName)); // 数据获取方法

        /**
         * expireAfterAccess:
         *         cache.get("TOM");
         *         assertLoadFromDB();
         *
         *         cache.get("TOM");
         *         assertLoadFromCache();
         */

        /**
         * expireAfterAccess:
         *         cache.get("TOM");
         *         assertLoadFromDB();
         *         TimeUnit.SECONDS.sleep(5);
         *         cache.get("TOM");
         *         assertLoadFromDB();
         */
        cache.get("TOM"); // Returns the value associated with key in this cache, first loading that value if necessary
        assertLoadFromDB();

        cache.get("TOM");
        assertLoadFromDB();

    }

    @Test
    public void testEvictionByWeight() {
        Weigher<String, Employee> weigher = (key, value) ->
                value.getName().length() + value.getDept().length() + value.getEmpID().length();
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumWeight(45)
                .concurrencyLevel(1)
                .weigher(weigher)
                .build(CacheLoader.from(this::findEmployeeByName));
        cache.getUnchecked("Gavin");
        assertLoadFromDB();

        cache.getUnchecked("Kevin");
        assertLoadFromDB();

        cache.getUnchecked("Allen");
        assertLoadFromDB();

        assertEquals(cache.size(), 3);
        assertNotNull(cache.getIfPresent("Gavin"));

        cache.getUnchecked("Jason");

        assertNull(cache.getIfPresent("Kevin"));
    }

    /**
     * 测试逐出策略
     */
    @Test
    public void testEviction() throws ExecutionException {
        /**
         *   public static <K, V> CacheLoader<K, V> from(Function<K, V> function) {
         *     return new FunctionToCacheLoader<>(function);
         *   }
         */
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .build(CacheLoader.from(this::findEmployeeByName));

        cache.getUnchecked("ONE");
        assertLoadFromDB();
        cache.getUnchecked("TWO");
        assertLoadFromDB();
        cache.getUnchecked("THREE");

        assertEquals(cache.size(), 3L);

        cache.getUnchecked("FOUR");
        /**
         * 返回与此缓存中的键关联的值,如果没有键的缓存值,则返回null
         */
        assertNull(cache.getIfPresent("ONE"));
        assertNotNull(cache.getIfPresent("FOUR"));


    }

    /**
     * TTL
     * Access time = Write/Update/Read
     *
     * @throws InterruptedException 中断异常
     */
    @Test
    public void testEvictionByAccessTime() throws InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterAccess(2, TimeUnit.SECONDS)
                .build(CacheLoader.from(this::findEmployeeByName));
        assertNotNull(cache.getUnchecked("Alex"));
        assertEquals(cache.size(), 1L);
        TimeUnit.SECONDS.sleep(3);
        assertNull(cache.getIfPresent("Alex"));

        assertNotNull(cache.getUnchecked("Guava"));
        TimeUnit.SECONDS.sleep(1);
        assertNotNull(cache.getIfPresent("Guava"));
        TimeUnit.SECONDS.sleep(1);
        assertNotNull(cache.getIfPresent("Guava"));
        // read 重置时间
        TimeUnit.SECONDS.sleep(1);
        assertNotNull(cache.getIfPresent("Guava"));

    }

    /**
     * write time = write/update
     *
     * @throws InterruptedException 中断异常
     */
    @Test
    public void testEvictionByWriteTime() throws InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build(CacheLoader.from(this::findEmployeeByName));

        assertNotNull(cache.getUnchecked("Alex"));
        assertEquals(cache.size(), 1L);

        assertNotNull(cache.getUnchecked("Guava"));

        TimeUnit.SECONDS.sleep(1);
        assertNotNull(cache.getIfPresent("Guava"));

        TimeUnit.MICROSECONDS.sleep(990);
        assertNotNull(cache.getIfPresent("Guava"));

        TimeUnit.SECONDS.sleep(1);
        assertNull(cache.getIfPresent("Guava"));
    }

    @Test
    public void testWeakKey() throws InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .weakValues()
                .weakKeys()
                .build(CacheLoader.from(this::findEmployeeByName));
        assertNotNull(cache.getUnchecked("Alex"));
        assertNotNull(cache.getUnchecked("Guava"));

        System.gc();
        TimeUnit.SECONDS.sleep(1);

        assertNull(cache.getIfPresent("Alex"));
        assertNull(cache.getIfPresent("Guava"));

    }

    @Test
    public void testSoftKey() throws InterruptedException {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .softValues()
                .build(CacheLoader.from(this::findEmployeeByName));
        int i = 0;
        while (true) {
            cache.put("Alex " + i, Employee.builder().name("Alex" + i).dept("Alex " + i).empID("Alex " + i).build());
            System.out.println("[Employee]-" + i++);
            TimeUnit.MICROSECONDS.sleep(600);
        }
    }

    @Test
    public void testLoadNullValue() {
        LoadingCache<String, Employee> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build(CacheLoader.from(k -> "null".equals(k) ? null : findEmployeeByName(k)));
        Employee alex = cache.getUnchecked("Alex");
        assertNotNull(alex);
        assertEquals(alex.getName(), "Alex");

        /**
         * CacheLoader returned null for key null
         * 不允许拿到的值为 NULL
         */
        try {
            assertNull(cache.getUnchecked("null"));
            fail("should not process to here.");
        } catch (Exception e) {
            assertTrue(e instanceof CacheLoader.InvalidCacheLoadException);
        }
    }

    @Test
    public void testLoadNullValueWithOptional() {
        LoadingCache<String, Optional<Employee>> cache = CacheBuilder.newBuilder()
                .expireAfterWrite(2, TimeUnit.SECONDS)
                .build(CacheLoader.from(e -> "null".equals(e) ?
                        Optional.fromNullable(null) : Optional.fromNullable(findEmployeeByName(e))));

        Optional<Employee> alexOptional = cache.getUnchecked("Alex");
        System.out.println(alexOptional); // Optional.of(Employee(name=Alex, dept=Alex, empID=Alex))
        assertNotNull(alexOptional.get());

        Optional<Employee> aNull = cache.getUnchecked("null");
        System.out.println(aNull); // Optional.absent()
        assertNull(aNull.orNull());

        Employee employee = cache.getUnchecked("null")
                .or(new Employee("default", "default", "default"));
        assertNotNull(employee.getName());
    }

    /**
     * update:
     * Thread1=>Cache Service1 method1 : Alex X
     * Thread2=>Cache Service2 method2 : Alex y
     */
    @Test
    public void testCacheRefresh_1() throws InterruptedException {
        AtomicInteger count = new AtomicInteger();
        LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
                .refreshAfterWrite(2, TimeUnit.SECONDS)
                .build(CacheLoader.from(k -> {
                    count.getAndIncrement();
                    return System.currentTimeMillis();
                }));
        Long res_1 = cache.getUnchecked("Alex");
        TimeUnit.SECONDS.sleep(3);
        Long res_2 = cache.getUnchecked("Alex");
        assertNotEquals(res_1, res_2);
        assertEquals(count.get(), 2);
    }

    @Test
    public void testCacheRefresh_2() throws InterruptedException {
        AtomicInteger count = new AtomicInteger();
        LoadingCache<String, Long> cache = CacheBuilder.newBuilder()
                .build(CacheLoader.from(k -> {
                    count.getAndIncrement();
                    return System.currentTimeMillis();
                }));
        Long res_1 = cache.getUnchecked("Alex");
        TimeUnit.SECONDS.sleep(3);
        Long res_2 = cache.getUnchecked("Alex");
        assertEquals(res_1, res_2);
        assertEquals(count.get(), 1);
    }

    @Test
    public void testCachePreLoad() {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .build(CacheLoader.from(String::toUpperCase));
        Map<String, String> map = Maps.newHashMap(new HashedMap<String, String>() {
            {
                put("name", "NAME");
                put("age", "age");
                put("gender", "GENDER");
            }
        });
        cache.putAll(map);
        assertEquals(cache.size(), 3L);
        assertEquals(cache.getUnchecked("name"), "NAME");
        /**
         * 数据与规则造假
         */
        assertEquals(cache.getUnchecked("age"), "age");
    }

    @Test
    public void testCacheRemoveNotification() {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .maximumSize(3)
                .removalListener(notification -> {
                    if (notification.wasEvicted()) {
                        RemovalCause cause = notification.getCause();
                        /**
                         * 删除缓存项的原因
                         */
                        assertEquals(cause, RemovalCause.SIZE);
                        assertEquals(notification.getKey(), "Alex");
                    }
                })
                .build(CacheLoader.from(String::toUpperCase));
        cache.getUnchecked("Alex");
        cache.getUnchecked("Tom");
        cache.getUnchecked("Jack");
        cache.getUnchecked("Jenny");
    }

    /**
     * Guava Cache提供了一种很是简便的方式,用于收集缓存执行的统计信息,须要注意的是:
     * 跟踪缓存操做将会带来性能的损失,想要收集缓存的信息,咱们只须要在使用CacheBuilder的时候声明咱们想要收集统计信息便可
     * LoadingCache<String, String> cache = CacheBuilder.newBuilder()
     * .recordStats()
     * .build(CacheLoader.from(String::toUpperCase));
     * <p>
     * 想要获取统计的信息,咱们只须要经过Cache或LoadingCache调用stats()方法,就将返回一个CacheStats实例,
     * 经过CacheStats实例能够获取到须要的统计信息:
     * CacheStats stats = cache.stats();
     * <p>
     * 下面是一个概述的清单,咱们能够经过CacheStats获取的一些信息:
     * 1、加载缓存条目值所耗费的平均时间;
     * 2、请求的缓存条目的命中率;
     * 3、请求的缓存条目的未命中率;
     * 4、缓存条数被移除的数量;
     * <p>
     * requestCount():返回Cache的lookup方法查找缓存的次数,不论查找的值是否被缓存。
     * <p>
     * hitCount():返回Cache的lookup方法命中缓存的次数。
     * <p>
     * hitRate():返回缓存请求的命中率,命中次数除以请求次数。
     * <p>
     * missCount():返回缓存请求的未命中的次数。
     * <p>
     * missRate():返回缓存请求未命中的比率,未命中次数除以请求次数。
     * <p>
     * loadCount():返回缓存调用load方法加载新值的次数。
     * <p>
     * loadSuccessCount():返回缓存加载新值的成功次数。
     * <p>
     * loadExceptionCount():返回缓存加载新值出现异常的次数。
     * <p>
     * loadExceptionRate():返回缓存加载新值出现异常的比率。
     * <p>
     * totalLoadTime():返回缓存加载新值所耗费的总时间。
     * <p>
     * averageLoadPenalty():缓存加载新值的耗费的平均时间,加载的次数除以加载的总时间。
     * <p>
     * evictionCount():返回缓存中条目被移除的次数。
     * <p>
     * minus(CacheStats other):返回一个新的表示当前CacheStats与传入CacheStats之间差别的CacheStats实例。
     * <p>
     * plus(CacheStats other):返回一个新的表示当前CacheStats与传入CacheStats之间总计的CacheStats实例。
     */
    @Test
    public void testCacheStat() {
        LoadingCache<String, String> cache = CacheBuilder.newBuilder()
                .recordStats()
                .build(CacheLoader.from(String::toUpperCase));
        /**
         * 第一次,从DB中获取
         */
        assertEquals(cache.getUnchecked("alex"), "ALEX");
        CacheStats stats_1 = cache.stats();
        assertEquals(stats_1.hitCount(), 0L);
        assertEquals(stats_1.missCount(), 1L);

        assertEquals(cache.getUnchecked("alex"), "ALEX");
        CacheStats stats_2 = cache.stats();
        assertEquals(stats_2.hitCount(), 1L);
        assertEquals(stats_2.missCount(), 1L);

        System.out.println(stats_2.missRate());
        System.out.println(stats_2.hitRate());

    }

    /**
     * 配置文件里读取
     */
    @Test
    public void testCacheSpec() {
        String spec = "maximumSize=3";
        LoadingCache<String, String> cache = CacheBuilder.from(CacheBuilderSpec.parse(spec))
                .build(CacheLoader.from(String::toUpperCase));
    }


    private Employee findEmployeeByName(String name) {
        FROM_DB = true;
        return new Employee(name, name, name);
    }

    private void assertLoadFromDB() {
        assertEquals(true, FROM_DB);
        this.FROM_DB = false;
    }

    private void assertLoadFromCache() {
        assertEquals(false, FROM_DB);
    }

    public static void main(String[] args) {
        CacheLoaderTest cacheLoaderTest = new CacheLoaderTest();
        Employee employeeByName = cacheLoaderTest.findEmployeeByName(null);
        System.out.println(employeeByName);
    }
}

2 测试

package com.cache;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import org.apache.commons.collections4.map.HashedMap;
import org.junit.Test;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;

public class TestFinal {
    private static final Map<String, Map<String, String>> MAP = new HashMap<String, Map<String, String>>() {
        {
            put("1001", new HashedMap<String, String>(){{put("name", "zhaoshuai11");}});
            put("1002", new HashedMap<String, String>(){{put("age", "26");}});
            put("1003", new HashedMap<String, String>(){{put("gender", "male");}});
        }};

    private final LoadingCache<String, Map<String, String>> cache =
            CacheBuilder.newBuilder()
                    .maximumSize(10)
                    .build(CacheLoader.from(this::getInfo));
    @Test
    public void testData() {
        getInfoFromCache(); // get info from db.
        getInfoFromCache(); // null
        getInfoFromCache(); // null
    }

    private Map<String, String> getInfoFromCache() {
        Map<String, String> unchecked = cache.getUnchecked("1001");
        assertNotNull(unchecked);
        return unchecked;
    }
    private Map<String, String> getInfo(String id) {
        System.out.println("get info from db.");
        return MAP.get(id);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值