• 本地缓存框架对比


    本地缓存框架:ConcurrentHashMap,Caffeine、GuavaCache、EhCache总结

    一、ConcurrentHashMap

    连接

    二、Caffeine

    介绍

            Caffeine是一个基于Java8开发的提供了近乎最佳命中率的高性能的缓存库,支持丰富的缓存过期策略,使用的是:TinyLfu淘汰算法

            caffeine的API操作功能和Guava 是基本保持一致的。并且caffeine为了兼容之前使用Guava 的用户,做了一个Guava的Adapter可供兼容。

            缓存和ConcurrentMap有点相似,区别是ConcurrentMap将会持有所有加入到缓存当中的元素,直到它们被从缓存当中手动移除。但是,Caffeine的缓存Cache 通常会被配置成自动驱逐缓存中元素,以限制其内存占用。

        Caffeine 关联的缓存有三种:Cache,LoadingCache 和 AsyncLoadingCache。
        springboot + CaffeineCacheManager 所使用的是 LoadingCache。
    Caffeine 配置说明
    
    参数类型描述
    initialCapacityint初始的缓存空间大小
    maximumSizelong缓存的最大条数
    maximumWeightlong缓存的最大权重
    expireAfterWrite或expireAfterAccessduration最后一次写入或访问后经过固定时间过期
    refreshAfterWriteduration创建缓存或者最近一次更新缓存后经过固定的时间间隔,刷新缓存
    weakKeysboolean打开 key 的弱引用
    weakValuesboolean打开 value 的弱引用
    softValuesboolean打开 value 的软引用
    recordStats开发统计功能

    备注:

    • weakValues 和 softValues 不可以同时使用。
    • maximumSize 和 maximumWeight 不可以同时使用。
    • expireAfterWrite 和 expireAfterAccess 同事存在时,以 expireAfterWrite 为准。

    特点:

    (1)缓存淘汰策略:

            提供了三种缓存淘汰策略,分别是基于大小、权重、时间、引用方式、手动清除。

    基于大小的方式:

    1、可以使用Caffeine.maximumSize(long)方法来指定缓存的最大容量。

    1. LoadingCache graphs = Caffeine.newBuilder()
    2. .maximumSize(10_000)
    3. .build();

    2、可以使用权重的策略来进行驱逐,

    1. LoadingCache graphs = Caffeine.newBuilder()
    2. .maximumWeight(10_000)
    3. .build();
    4. Caffeine caffeine = Caffeine.newBuilder()
    5. .maximumWeight(30)
    6. .weigher((String key, Person value)-> value.getAge());

    3.基于时间的方式:
    1、expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期

    2、expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期

    3、expireAfter(Expiry): 自定义策略,过期时间由Expiry实现独自计算。

    1. Caffeine
    2. .newBuilder()
    3. // 二十分钟之内没有被写入,则回收
    4. .expireAfterWrite(20, TimeUnit.MINUTES)
    5. // 二十分钟之内没有被访问,则回收
    6. .expireAfterAccess(20, TimeUnit.MINUTES)
    7. .expireAfter(new Expiry() {
    8. @Override
    9. public long expireAfterCreate(String s, UserInfo userInfo, long l) {
    10. if(userInfo.getAge() > 60){ //首次存入缓存后,年龄大于 60 的,过期时间为 4 秒
    11. return 4000000000L;
    12. }
    13. return 2000000000L; // 否则为 2 秒
    14. }
    15. @Override
    16. public long expireAfterUpdate(String s, UserInfo userInfo, long l, long l1) {
    17. if(userInfo.getName().equals("one")){ // 更新 one 这个人之后,过期时间为 8 秒
    18. return 8000000000L;
    19. }
    20. return 4000000000L; // 更新其它人后,过期时间为 4 秒
    21. }
    22. @Override
    23. public long expireAfterRead(String s, UserInfo userInfo, long l, long l1) {
    24. return 3000000000L; // 每次被读取后,过期时间为 3 秒
    25. }
    26. }
    27. )
    28. .build();
    29. import lombok.Data;
    30. import lombok.ToString;
    31. @Data
    32. @ToString
    33. public class UserInfo {
    34. private Integer id;
    35. private String name;
    36. private String sex;
    37. private Integer age;
    38. }

    4.基于引用的方式:

            Caffeine.newBuilder().weakKeys(); 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
            Caffeine.newBuilder().weakValues(); 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
            Caffeine.newBuilder().softValues(); 使用软引用存储值.按照全局最近最少使用的顺序回收。

    1. Caffeine
    2. .newBuilder()
    3. // 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
    4. .weakKeys()
    5. // 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
    6. .weakValues()
    7. // 使用软引用存储值.按照全局最近最少使用的顺序回收
    8. .softValues()
    9. .build();

    注意:Caffeine.weakValues()和Caffeine.softValues()不可以一起使用。 

    5.手动清除:

    1. Cache cache = Caffeine.newBuilder().maximumSize(5).build();
    2. // 单个清除
    3. cache.invalidate("key");
    4. // 批量清除
    5. cache.invalidateAll(Arrays.asList("key", "key1"));
    6. // 清空
    7. cache.invalidateAll();

    (2)加载方式

    caffeine主要有3种加载方式:

    • 手动加载
    • 同步加载
    • 异步加载

    手动加载Cache:

    1. import com.github.benmanes.caffeine.cache.Cache;
    2. import com.github.benmanes.caffeine.cache.Caffeine;
    3. import java.util.concurrent.ConcurrentMap;
    4. import java.util.concurrent.TimeUnit;
    5. /**
    6. * Caffeine 手动加载
    7. */
    8. public class CaffeineDemo {
    9. public static void main(String[] args) throws InterruptedException {
    10. Cache cache = Caffeine.newBuilder()
    11. // 基于时间失效,写入之后开始计时失效
    12. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    13. // 缓存容量
    14. .maximumSize(5)
    15. .build();
    16. // 使用java8 Lambda表达式声明一个方法,get不到缓存中的值调用这个方法运算、缓存、返回
    17. String value = cache.get("key", key -> key + "_" + System.currentTimeMillis());
    18. System.out.println(value);
    19. //让缓存到期
    20. Thread.sleep(2001);
    21. // 存在就取,不存在就返回空
    22. System.out.println(cache.getIfPresent("key"));
    23. // 重新存值
    24. cache.put("key", "value");
    25. String key = cache.get("key", keyOne -> keyOne + "_" + System.currentTimeMillis());
    26. System.out.println(key);
    27. // 获取所有值打印出来
    28. ConcurrentMap concurrentMap = cache.asMap();
    29. System.out.println(concurrentMap);
    30. // 删除key
    31. cache.invalidate("key");
    32. // 获取所有值打印出来
    33. System.out.println(cache.asMap());
    34. }
    35. }

    同步加载LoadingCache:

    1. import com.github.benmanes.caffeine.cache.CacheLoader;
    2. import com.github.benmanes.caffeine.cache.Caffeine;
    3. import com.github.benmanes.caffeine.cache.LoadingCache;
    4. import org.checkerframework.checker.nullness.qual.NonNull;
    5. import org.checkerframework.checker.nullness.qual.Nullable;
    6. import java.util.Arrays;
    7. import java.util.Map;
    8. import java.util.concurrent.TimeUnit;
    9. /**
    10. * @description Caffeine 同步加载
    11. */
    12. public class CaffenineLoadingCacheDemo {
    13. public static void main(String[] args) throws InterruptedException {
    14. LoadingCache cache = Caffeine.newBuilder()
    15. // 基于时间失效,写入之后开始计时失效
    16. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    17. // 缓存容量
    18. .maximumSize(5)
    19. // 可以使用java8函数式接口的方式,这里其实是重写CacheLoader类的load方法
    20. .build(new MyLoadingCache());
    21. // 获取一个不存在的kay,让它去调用CacheLoader的load方法
    22. System.out.println(cache.get("key"));
    23. // 等待2秒让key失效
    24. TimeUnit.SECONDS.sleep(2);
    25. System.out.println(cache.getIfPresent("key"));
    26. // 批量获取key,让他批量去加载
    27. Map all = cache.getAll(Arrays.asList("key1", "key2", "key3"));
    28. System.out.println(all);
    29. }
    30. static class MyLoadingCache implements CacheLoader {
    31. @Nullable
    32. @Override
    33. public Object load(@NonNull Object key) throws Exception {
    34. return key + "_" + System.currentTimeMillis();
    35. }
    36. }
    37. }

    异步加载AsyncLoadingCache:

    1. import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
    2. import com.github.benmanes.caffeine.cache.CacheLoader;
    3. import com.github.benmanes.caffeine.cache.Caffeine;
    4. import org.checkerframework.checker.nullness.qual.NonNull;
    5. import org.checkerframework.checker.nullness.qual.Nullable;
    6. import java.util.Objects;
    7. import java.util.concurrent.CompletableFuture;
    8. import java.util.concurrent.ExecutionException;
    9. import java.util.concurrent.TimeUnit;
    10. /**
    11. * @description Caffeine 异步加载
    12. */
    13. public class CaffeineAsyncLoadingCacheDemo {
    14. public static void main(String[] args) throws InterruptedException, ExecutionException {
    15. AsyncLoadingCache cache = Caffeine.newBuilder()
    16. // 基于时间失效,写入之后开始计时失效
    17. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    18. // 缓存容量
    19. .maximumSize(5)
    20. // 可以使用java8函数式接口的方式,这里其实是重写CacheLoader的load方法
    21. .buildAsync(new MyCacheLoader());
    22. // 获取一个不存在的kay,让它异步去调用CacheLoader的load方法。这时候他会返回一个CompletableFuture
    23. CompletableFuture future = cache.get("key");
    24. //验证
    25. future.thenAccept(s -> System.out.println("当前的时间为:" + System.currentTimeMillis() + " -> 异步加载的值为:" + s));
    26. // 睡眠2秒让它的key失效
    27. TimeUnit.SECONDS.sleep(2);
    28. // 注意:当使用getIfPresent时,也是返回的CompletableFuture
    29. // 因为getIfPresent从缓存中找不到是不会去运算key既不会调用(CacheLoader.load)方法
    30. // 所以得到的CompletableFuture可能会为null,如果想从CompletableFuture中取值的话.先判断CompletableFuture是否会为null
    31. CompletableFuture completableFuture = cache.getIfPresent("key");
    32. if (Objects.nonNull(completableFuture)) {
    33. System.out.println(completableFuture.get());
    34. }
    35. }
    36. static class MyCacheLoader implements CacheLoader{
    37. @Nullable
    38. @Override
    39. public Object load(@NonNull Object key) throws Exception {
    40. return key + "_" + System.currentTimeMillis();
    41. }
    42. }
    43. }

    (3)更新策略

       更新策略:在设定多长时间后会自动刷新缓存。

    1. LoadingCache build = Caffeine.newBuilder().
    2. //1s后刷新缓存
    3. refreshAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader() {
    4. @Override
    5. public String load(String key) {
    6. // TODO 此处可以查询数据库 更新该key对应的value数据
    7. return "1111";
    8. }
    9. });

    (4)缓存状态监控

            默认是使用Caffeine自带的,也可以自己进行实现。在StatsCounter接口中,定义了需要打点的方法:

    1. recordHits:记录缓存命中
    2. recordMisses:记录缓存未命中
    3. recordLoadSuccess:记录加载成功(指的是CacheLoader加载成功)
    4. recordLoadFailure:记录加载失败
    5. recordEviction:记录淘汰数据

    默认: 

    1. public class CaffeineStatsCounterDemo {
    2. public static void main(String[] args) throws InterruptedException {
    3. Cache cache = Caffeine.newBuilder()
    4. // 基于时间失效,写入之后开始计时失效
    5. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    6. // 缓存容量
    7. .maximumSize(5)
    8. .recordStats()
    9. .build();
    10. cache.put("key", "value");
    11. CacheStats cacheStats = cache.stats();
    12. System.out.println(cacheStats.toString());
    13. }
    14. }

     自定义:

    1. import com.github.benmanes.caffeine.cache.RemovalCause;
    2. import com.github.benmanes.caffeine.cache.stats.CacheStats;
    3. import com.github.benmanes.caffeine.cache.stats.StatsCounter;
    4. import lombok.Data;
    5. import java.util.concurrent.atomic.LongAdder;
    6. /**
    7. * 自定义的缓存状态收集器
    8. */
    9. @Data
    10. public class MyStatsCounter implements StatsCounter {
    11. private final LongAdder hitCount = new LongAdder();
    12. private final LongAdder missCount = new LongAdder();
    13. private final LongAdder loadSuccessCount = new LongAdder();
    14. private final LongAdder loadFailureCount = new LongAdder();
    15. private final LongAdder totalLoadTime = new LongAdder();
    16. private final LongAdder evictionCount = new LongAdder();
    17. private final LongAdder evictionWeight = new LongAdder();
    18. public MyStatsCounter() {
    19. }
    20. @Override
    21. public void recordHits(int i) {
    22. hitCount.add(i);
    23. System.out.println("命中次数:" + i);
    24. }
    25. @Override
    26. public void recordMisses(int i) {
    27. missCount.add(i);
    28. System.out.println("未命中次数:" + i);
    29. }
    30. @Override
    31. public void recordLoadSuccess(long l) {
    32. loadSuccessCount.increment();
    33. totalLoadTime.add(l);
    34. System.out.println("加载成功次数:" + l);
    35. }
    36. @Override
    37. public void recordLoadFailure(long l) {
    38. loadFailureCount.increment();
    39. totalLoadTime.add(l);
    40. System.out.println("加载失败次数:" + l);
    41. }
    42. @Override
    43. public void recordEviction() {
    44. evictionCount.increment();
    45. System.out.println("因为缓存权重限制,执行了一次缓存清除工作");
    46. }
    47. @Override
    48. public void recordEviction(int weight, RemovalCause cause) {
    49. evictionCount.increment();
    50. evictionWeight.add(weight);
    51. System.out.println("因为缓存权重限制,执行了一次缓存清除工作,清除的数据的权重为:" + weight);
    52. }
    53. @Override
    54. public void recordEviction(int weight) {
    55. evictionCount.increment();
    56. evictionWeight.add(weight);
    57. System.out.println("因为缓存权重限制,执行了一次缓存清除工作,清除的数据的权重为:" + weight);
    58. }
    59. @Override
    60. public CacheStats snapshot() {
    61. return CacheStats.of(
    62. negativeToMaxValue(hitCount.sum()),
    63. negativeToMaxValue(missCount.sum()),
    64. negativeToMaxValue(loadSuccessCount.sum()),
    65. negativeToMaxValue(loadFailureCount.sum()),
    66. negativeToMaxValue(totalLoadTime.sum()),
    67. negativeToMaxValue(evictionCount.sum()),
    68. negativeToMaxValue(evictionWeight.sum()));
    69. }
    70. private static long negativeToMaxValue(long value) {
    71. return (value >= 0) ? value : Long.MAX_VALUE;
    72. }
    73. }
    74. MyStatsCounter myStatsCounter = new MyStatsCounter();
    75. Cache cache = Caffeine.newBuilder()
    76. // 基于时间失效,写入之后开始计时失效
    77. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    78. // 缓存容量
    79. .maximumSize(5)
    80. .recordStats(()->myStatsCounter)
    81. .build();
    82. cache.put("one", "one");
    83. cache.put("two", "two");
    84. cache.put("three","three");
    85. cache.getIfPresent("ww");
    86. CacheStats stats = myStatsCounter.snapshot();
    87. Thread.sleep(1000);
    88. System.out.println(stats.toString());

    (5)淘汰监听

    1. Caffeine caffeine = Caffeine.newBuilder()
    2. .maximumWeight(30)
    3. .removalListener((String key, UserInfo value, RemovalCause cause)->{
    4. System.out.println("被清除人的年龄:" + value.getAge() + "; 清除的原因是:" + cause);
    5. }).weigher((String key, UserInfo value)-> value.getAge());
    6. Cache cache = caffeine.build();
    7. cache.put("one", new UserInfo(12, "1"));
    8. cache.put("two", new UserInfo(18, "2"));
    9. cache.put("one", new UserInfo(14, "3"));
    10. cache.invalidate("one");
    11. cache.put("three", new UserInfo(31, "three"));
    12. Thread.sleep(2000);

    在Caffeine中被淘汰的原因有很多种:

    1. EXPLICIT: 这个原因是,用户造成的,通过调用remove方法从而进行删除。
    2. REPLACED: 更新的时候,其实相当于把老的value给删了。
    3. COLLECTED: 用于我们的垃圾收集器,也就是我们上面减少的软引用,弱引用。
    4. EXPIRED:过期淘汰。
    5. SIZE: 大小淘汰,当超过最大的时候就会进行淘汰。

    原理分析:

            Java 进程内存是有限的,不可能无限地往里面放缓存对象。这就需要有合适的淘汰算法淘汰无用的对象,为新进的对象留有空间。常见的缓存淘汰算法有 FIFO、LRU、LFU

            LRU(Least Recently Used):最近最久未使用。它是优先淘汰掉最久未访问到的数据。缺点是不能很好地应对偶然的突发流量。比如一个数据在一分钟内的前59秒访问很多次,而在最后1秒没有访问,但是有一批冷门数据在最后一秒进入缓存,那么热点数据就会被冲刷掉。

            FIFO(First In First Out):先进先出。它是优先淘汰掉最先缓存的数据、是最简单的淘汰算法。缺点是如果先缓存的数据使用频率比较高的话,那么该数据就不停地进进出出,因此它的缓存命中率比较低。

            LFU(Least Frequently Used):最近最少频率使用。它是优先淘汰掉最不经常使用的数据,需要维护一个表示使用频率的字段。主要有两个缺点:一、大多数据访问频率比较高,内存堪忧;二、无法合理更新新上的热点数据,比如某个数据历史较多,新旧数据一起需要操作,那内存堪忧。

            Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,其特点:高命中率、低内存占用。是一种为了解决传统LFU算法空间存储比较大的问题LFU算法,它可以在较大访问量的场景下近似的替代LFU的数据统计部分,它的原理有些类似BloomFilter

            BloomFilter原理:在BloomFilter中,使用一个大的bit数组用于存储所有key,每一个key通过多次不同的hash计算来映射数组的不同bit位,如果key存在将对应的bit位设置为1,这样就可以通过少量的存储空间进行大量的数据过滤。

            在TinyLFU中,把多个bit位看做一个整体,用于统计一个key的使用频率,TinyFLU中的key也是通过多次不同的hash计算来映射多个不同的bit组。在读取时,取映射的所有值中的最小的值作为key的使用频率。

            TinyLFU根据最大数据量设置生成一个long数组,然后将频率值保存在其中的四个long的4个bit位中(4个bit位不会大于15),取频率值时则取四个中的最小一个。

            Caffeine认为频率大于15已经很高了,是属于热数据,所以它只需要4个bit位来保存,long有8个字节64位,这样可以保存16个频率。取hash值的后左移两位,然后加上hash四次,这样可以利用到16个中的13个,利用率挺高的,或许有更好的算法能将16个都利用到。

    过程说明:

    • 假设有四个hash函数,每当元素被访问时,将进行次数加1;
    • 此时会按照约定好的四个hash函数进行hash计算找到对应的位置,相应的位置进行+1操作;
    • 当获取元素的频率时,同样根据hash计算找到4个索引位置;
    • 取得四个位置的频率信息,然后根据Count Min取得最低值作为本次元素的频率值返回,即Min(Count);

     使用方式:

    一、方式一

    1. org.springframework.boot
    2. spring-boot-starter-cache
    1. @EnableCaching
    2. @Configuration
    3. public class CaffeineCacheConfig {
    4. /**
    5. * 在 springboot 中使用 CaffeineCacheManager 管理器管理 Caffeine 类型的缓存,Caffeine 类似 Cache 缓存的工厂,
    6. * 可以生产很多个 Cache 实例,Caffeine 可以设置各种缓存属性,这些 Cache 实例都共享 Caffeine 的缓存属性。
    7. * @return
    8. */
    9. @Bean(name = "caffeineCacheManager")
    10. public CacheManager oneHourCacheManager(){
    11. Caffeine caffeine = Caffeine.newBuilder()
    12. .initialCapacity(10) //初始大小
    13. .maximumSize(11) //最大大小
    14. //写入/更新之后1小时过期
    15. .expireAfterWrite(1, TimeUnit.HOURS);
    16. CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
    17. caffeineCacheManager.setAllowNullValues(true);
    18. caffeineCacheManager.setCaffeine(caffeine);
    19. return caffeineCacheManager;
    20. }
    21. }
    1. @RestController
    2. public class CaffeineController {
    3. @Autowired
    4. private CaffeineService caffeineService;
    5. @GetMapping("/cache-Data/{key}")
    6. public String cacheDataL(@PathVariable String key) {
    7. return caffeineService.cacheData(key);
    8. }
    9. @GetMapping("/cache-put-Data/{key}")
    10. public String cachePutDataL(@PathVariable String key) {
    11. return caffeineService.cachePutData(key);
    12. }
    13. @GetMapping("/cache-delete-Data/{key}")
    14. public String cacheDelteDataL(@PathVariable String key) {
    15. return caffeineService.deleteCaffeineServiceTest(key);
    16. }
    17. @GetMapping("/getCaffeineServiceTest")
    18. public String getCaffeineServiceTest(String name,Integer age) {
    19. return caffeineService.getCaffeineServiceTest(name,age);
    20. }
    21. }
    1. import lombok.extern.slf4j.Slf4j;
    2. import org.springframework.cache.annotation.CacheConfig;
    3. import org.springframework.cache.annotation.CacheEvict;
    4. import org.springframework.cache.annotation.CachePut;
    5. import org.springframework.cache.annotation.Cacheable;
    6. import org.springframework.stereotype.Service;
    7. /**
    8. * @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
    9. * @CacheEvict 触发缓存的eviction(用于删除的方法上)
    10. * @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
    11. * @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
    12. * @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
    13. */
    14. @Service
    15. @CacheConfig(cacheManager = "caffeineCacheManager")
    16. @Slf4j
    17. public class CaffeineService {
    18. @Cacheable(value = "data", key = "#key")
    19. public String cacheData(String key) {
    20. log.info("cacheData()方法执行");
    21. return getCache(key);
    22. }
    23. @CachePut(value = "data", key = "#key")
    24. public String cachePutData(String key) {
    25. log.info("cachePutData()方法执行");
    26. return "cachePutData--" + key;
    27. }
    28. private String getCache(String key) {
    29. try {
    30. log.info("getCache()方法执行");
    31. Thread.sleep(3000);
    32. } catch (InterruptedException e) {
    33. e.printStackTrace();
    34. }
    35. return key;
    36. }
    37. /**
    38. * CacheEvict删除key,会调用cache的evict
    39. * */
    40. @CacheEvict(cacheNames = "outLimit",key = "#name")
    41. public String deleteCaffeineServiceTest(String name){
    42. String value = name + " nihao";
    43. log.info("deleteCaffeineServiceTest value = {}",value);
    44. return value;
    45. }
    46. /**
    47. * condition条件判断是否要走缓存,无法使用方法中出现的值(返回结果等),条件为true放入缓存
    48. * unless是方法执行后生效,决定是否放入缓存,返回true的放缓存
    49. * */
    50. @Cacheable(cacheNames = "outLimit",key = "#name",condition = "#value != null ")
    51. public String getCaffeineServiceTest(String name,Integer age){
    52. String value = name + " nihao "+ age;
    53. log.info("getCaffeineServiceTest value = {}",value);
    54. return value;
    55. }
    56. }

    二、方式二

    1. com.github.ben-manes.caffeine
    2. caffeine
    3. 2.9.3
    1. @Configuration
    2. public class CaffeineCacheConfig {
    3. @Bean
    4. public Cache caffeineCache() {
    5. return Caffeine.newBuilder()
    6. // 设置最后一次写入或访问后经过固定时间过期
    7. .expireAfterWrite(5000, TimeUnit.SECONDS)
    8. // 初始的缓存空间大小
    9. .initialCapacity(200)
    10. // 缓存的最大条数
    11. .maximumSize(2000)
    12. .build();
    13. }
    14. }
    1. import lombok.Data;
    2. import lombok.ToString;
    3. @Data
    4. @ToString
    5. public class UserInfo {
    6. private Integer id;
    7. private String name;
    8. private String sex;
    9. private Integer age;
    10. public UserInfo(Integer id, String name) {
    11. this.id = id;
    12. this.name = name;
    13. }
    14. }
    1. import com.caffeine.lean.entity.UserInfo;
    2. import com.caffeine.lean.utils.CaffeineUtils;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.apache.commons.lang3.StringUtils;
    5. import org.springframework.beans.factory.annotation.Autowired;
    6. import org.springframework.stereotype.Service;
    7. import java.util.Map;
    8. import java.util.concurrent.ConcurrentHashMap;
    9. /**
    10. * 用户模块接口
    11. */
    12. @Service
    13. @Slf4j
    14. public class UserInfoService {
    15. //模拟数据库存储数据
    16. private Map userInfoMap = new ConcurrentHashMap<>();
    17. @Autowired
    18. private CaffeineUtils caffeineUtils;
    19. public void addUserInfo(UserInfo userInfo) {
    20. log.info("create");
    21. userInfoMap.put(userInfo.getId(), userInfo);
    22. // 加入缓存
    23. caffeineUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    24. }
    25. public UserInfo getByName(Integer userId) {
    26. // 先从缓存读取
    27. UserInfo userInfo = caffeineUtils.getObjCacheByKey(String.valueOf(userId), UserInfo.class);
    28. if (userInfo != null) {
    29. return userInfo;
    30. }
    31. // 如果缓存中不存在,则从库中查找
    32. log.info("get");
    33. userInfo = userInfoMap.get(userId);
    34. // 如果用户信息不为空,则加入缓存
    35. if (userInfo != null) {
    36. caffeineUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    37. }
    38. return userInfo;
    39. }
    40. public UserInfo updateUserInfo(UserInfo userInfo) {
    41. log.info("update");
    42. if (!userInfoMap.containsKey(userInfo.getId())) {
    43. return null;
    44. }
    45. // 取旧的值
    46. UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
    47. // 替换内容
    48. if (StringUtils.isNotBlank(oldUserInfo.getName())) {
    49. oldUserInfo.setName(userInfo.getName());
    50. }
    51. if (StringUtils.isNotBlank(oldUserInfo.getSex())) {
    52. oldUserInfo.setSex(userInfo.getSex());
    53. }
    54. oldUserInfo.setAge(userInfo.getAge());
    55. // 将新的对象存储,更新旧对象信息
    56. userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
    57. // 替换缓存中的值
    58. caffeineUtils.putAndUpdateCache(String.valueOf(oldUserInfo.getId()), oldUserInfo);
    59. return oldUserInfo;
    60. }
    61. public void deleteById(Integer id) {
    62. log.info("delete");
    63. userInfoMap.remove(id);
    64. // 从缓存中删除
    65. caffeineUtils.removeCacheByKey(String.valueOf(id));
    66. }
    67. }
    1. **
    2. * Caffeine缓存工具类
    3. * Cache 可以有的操作
    4. * V getIfPresent(K key) :如果缓存中 key 存在,则获取 value,否则返回 null
    5. * void put( K key, V value):存入一对数据
    6. * Map getAllPresent(Iterable var1) :参数是一个迭代器,表示可以批量查询缓存。
    7. * void putAll( Map var1); 批量存入缓存。
    8. * void invalidate(K var1):删除某个 key 对应的数据。
    9. * void invalidateAll(Iterable var1):批量删除数据。
    10. * void invalidateAll():清空缓存。
    11. * long estimatedSize():返回缓存中数据的个数。
    12. * CacheStats stats():返回缓存当前的状态指标集。
    13. * ConcurrentMap asMap():将缓存中所有的数据构成一个 map。
    14. * void cleanUp():会对缓存进行整体的清理,比如有一些数据过期了,但是并不会立马被清除,所以执行一次 cleanUp 方法,会对缓存进行一次检查,清除那些应该清除的数据。
    15. * V get( K var1, Functionsuper K, ? extends V> var2):第一个参数是想要获取的 key,第二个参数是函数
    16. **/
    17. @Component
    18. public class CaffeineUtils {
    19. @Autowired
    20. Cache caffeineCache;
    21. /**
    22. * 添加或更新缓存
    23. *
    24. * @param key
    25. * @param value
    26. */
    27. public void putAndUpdateCache(String key, Object value) {
    28. caffeineCache.put(key, value);
    29. }
    30. /**
    31. * 获取对象缓存
    32. *
    33. * @param key
    34. * @return
    35. */
    36. public T getObjCacheByKey(String key, Class t) {
    37. caffeineCache.getIfPresent(key);
    38. return (T) caffeineCache.asMap().get(key);
    39. }
    40. /**
    41. * 根据key删除缓存
    42. *
    43. * @param key
    44. */
    45. public void removeCacheByKey(String key) {
    46. // 从缓存中删除
    47. caffeineCache.asMap().remove(key);
    48. }
    49. }
    1. import com.caffeine.lean.entity.UserInfo;
    2. import com.caffeine.lean.service.UserInfoService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.*;
    5. /**
    6. * 用户模块入口
    7. */
    8. @RestController
    9. @RequestMapping
    10. public class UserInfoController {
    11. @Autowired
    12. private UserInfoService userInfoService;
    13. @GetMapping("/getUserInfo/{id}")
    14. public Object getUserInfo(@PathVariable Integer id) {
    15. UserInfo userInfo = userInfoService.getByName(id);
    16. if (userInfo == null) {
    17. return "没有该用户";
    18. }
    19. return userInfo;
    20. }
    21. @PostMapping("/createUserInfo")
    22. public Object createUserInfo(@RequestBody UserInfo userInfo) {
    23. userInfoService.addUserInfo(userInfo);
    24. return "SUCCESS";
    25. }
    26. @PutMapping("/updateUserInfo")
    27. public Object updateUserInfo(@RequestBody UserInfo userInfo) {
    28. UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
    29. if (newUserInfo == null) {
    30. return "不存在该用户";
    31. }
    32. return newUserInfo;
    33. }
    34. @DeleteMapping("/deleteUserInfo/{id}")
    35. public Object deleteUserInfo(@PathVariable Integer id) {
    36. userInfoService.deleteById(id);
    37. return "SUCCESS";
    38. }
    39. }

    三、GuavaCache

            Google Guava Cache是本地缓存,提供了基于容量、时间、引用的缓存回收方式,内部实现采用LRU算法,基于引用回收很好的利用了java虚拟机的垃圾回收机制.

            设计灵感来源ConcurrentHashMap,使用多个segments方式的细粒度锁,在保证线程安全的同时,支持高并发场景需求,同时支持多种类型的缓存清理策略,包括基于容量的清理、基于时间的清理、基于引用的清理等。

            Guava Cache和ConcurrentHashMap很相似,但也不完全一样.最基本的区别是ConcurrentHashMap会一直保存所有添加的元素,直至显示地移除.Guava Cache为了限制内存占用,通常都设定为自动回收元素。

    注意:springboot 2.x以上就踢出了guava cache.

    使用场景

    1. 愿意消耗一些内存空间来提升速度
    2. 预料到某些键会被多次查询
    3. 缓存中存放的数据总量不会超出内存容量

    功能:

            自动将节点加载至缓存结构中,当缓存的数据超过最大值时,使用LRU算法替换;它具备根据节点上一次被访问或写入时间计算缓存过期机制,缓存的key被封装在WeakReference引用中,缓存的value被封装在WeakReference或SoftReference引用中;还可以统计缓存使用过程中的命中率、异常率和命中率等统计数据。

            Guava Cache实质是一个在本地缓存KV数据的LocalCache。而LocalCache的数据结构和ConcurrentHashMap一样,都是采用分segment来细化管理HashMap中的节点Entry。不同的是LocalCahce中的ReferenceEntry节点更为复杂。

    常用方法:

    1. get : 要么返回已经缓存的值,要么使用CacheLoader向缓存原子地加载新值;
    2. getUnchecked:CacheLoader 会抛异常,定义的CacheLoader没有声明任何检查型异常,则可以 getUnchecked 查找缓存;反之不能;
    3. getAll :方法用来执行批量查询;
    4. put : 向缓存显式插入值,Cache.asMap()也能修改值,但不具原子性;
    5. getIfPresent :该方法只是简单的把Guava Cache当作Map的替代品,不执行load方法;
    6. put(K key, V value) if cached, return; otherwise create, cache , and return.
    7. invalidate(Object key); 删除缓存
    8. invalidateAll(); 清楚所有的缓存,相当远map的clear操作。
    9.  long size(); 获取缓存中元素的大概个数。为什么是大概呢?元素失效之时,并不会实时的更新size,所以这里的size可能会包含失效元素。
    10. CacheStats stats(); 缓存的状态数据,包括(未)命中个数,加载成功/失败个数,总共加载时间,删除个数等。
    11. ConcurrentMap asMap(); 将缓存存储到一个线程安全的map中。

    特性:

    (1) 创建缓存的方式

    1.CacheLoader

    1. //LoadingCache类型的缓存,可以使用get(K)或get(K,Callable)方法,并且如果使用的是get(K,Callable)方法,
    2. // 当K值不存在时,使用的是Callable计算值,不走load方法计算,然后将值放入缓存。
    3. LoadingCache cache = CacheBuilder
    4. .newBuilder().maximumSize(100)
    5. .expireAfterWrite(100, TimeUnit.SECONDS) // 根据写入时间过期
    6. .build(new CacheLoader() {
    7. @Override
    8. public String load(String key) {
    9. return getSchema(key);
    10. }
    11. });
    12. private static String getSchema(String key) {
    13. System.out.println("load...");
    14. return key + "schema";
    15. }

    2.callable

    1. //Cache类型的缓存只能使用Callable的方式get(K,Callable)方法
    2. Cache cache2 = CacheBuilder.newBuilder()
    3. .maximumSize(1000)
    4. .expireAfterWrite(100, TimeUnit.SECONDS) // 根据写入时间过期
    5. .build();
    6. try {
    7. String value = cache2.get("key4", new Callable() {
    8. @Override
    9. public String call() throws Exception {
    10. System.out.println("i am callable...");
    11. return "i am callable...";
    12. }
    13. });
    14. System.out.println(value);
    15. } catch (ExecutionException e1) {
    16. e1.printStackTrace();
    17. }
    18. private static String getSchema(String key) {
    19. System.out.println("load...");
    20. return key + "schema";
    21. }

    2)缓存淘汰策略:

            提供了三种缓存淘汰策略,分别是基于大小、权重、时间、引用方式、手动清除。

     基于大小的方式:

    1、可以使用CacheBuilder.maximumSize(long)方法来指定缓存的最大容量。

    1. CacheBuilder.newBuilder()
    2. // 缓存的最大条数
    3. .maximumSize(1000)
    4. .build();

    2、可以使用权重的策略来进行驱逐,

    1. CacheBuilder.newBuilder()
    2. .maximumWeight(10_000)
    3. .build();
    4. CacheBuilder.newBuilder()
    5. .maximumWeight(30)
    6. .weigher((String key, UserInfo value)-> value.getAge());

    3.基于时间的方式:
    1、expireAfterAccess(long, TimeUnit):在最后一次访问或者写入后开始计时,在指定的时间后过期。假如一直有请求访问该key,那么这个缓存将一直不会过期

    2、expireAfterWrite(long, TimeUnit): 在最后一次写入缓存后开始计时,在指定的时间后过期。

    注意:expireAfterWrite时guava重新加载数据时使用的是load方法,不会调用loadAll。

    1. CacheBuilder
    2. .newBuilder()
    3. // 二十分钟之内没有被写入,则回收
    4. .expireAfterWrite(20, TimeUnit.MINUTES)
    5. // 二十分钟之内没有被访问,则回收
    6. .expireAfterAccess(20, TimeUnit.MINUTES)
    7. .build();

            注:Guava Cache不会专门维护一个线程来回收这些过期的缓存项,只有在读/写访问时,才去判断该缓存项是否过期,如果过期,则会回收。而且注意,回收后会同步调用load方法来加载新值到cache中。

    4.基于引用的方式:

            CacheBuilder.newBuilder().weakKeys(); 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
            CacheBuilder.newBuilder().weakValues(); 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
            CacheBuilder.newBuilder().softValues(); 使用软引用存储值.按照全局最近最少使用的顺序回收。

    1. CacheBuilder
    2. .newBuilder()
    3. // 使用弱引用存储键.当key没有其他引用时,缓存项可以被垃圾回收
    4. .weakKeys()
    5. // 使用弱引用存储值.当value没有其他引用时,缓存项可以被垃圾回收
    6. .weakValues()
    7. // 使用软引用存储值.按照全局最近最少使用的顺序回收
    8. .softValues().build();

    注意:CacheBuilder.weakValues()和CacheBuilder.softValues()不可以一起使用。 

    5.手动清除:

    1. Cache cache = CacheBuilder.newBuilder().maximumSize(5).build();
    2. // 单个清除
    3. cache.invalidate("key");
    4. // 批量清除
    5. cache.invalidateAll(Arrays.asList("key", "key1"));
    6. // 清空
    7. cache.invalidateAll();

    (3)监听

            在guava cache中移除key可以设置相应得监听操作,以便key被移除时做一些额外操作。缓存项被移除时,RemovalListener会获取移除通知[RemovalNotification],其中包含移除原因[RemovalCause]、键和值。监听有同步监听和异步监听两种 :

    同步监听:

    1. CacheLoader cacheLoader = CacheLoader.from(String::toUpperCase);
    2. LoadingCache loadingCache = CacheBuilder.newBuilder().maximumSize(4)
    3. .removalListener((notification) -> {
    4. if (notification.wasEvicted()) {
    5. RemovalCause cause = notification.getCause();
    6. System.out.println("remove cacase is:" + cause.toString());
    7. System.out.println("key:" + notification.getKey() + "value:" + notification.getValue());
    8. }
    9. }).build(cacheLoader);
    10. loadingCache.getUnchecked("a");
    11. loadingCache.getUnchecked("b");
    12. loadingCache.getUnchecked("c");
    13. loadingCache.getUnchecked("d");//容量是4 吵了自动清除,监听程序清除是单线程。
    14. loadingCache.getUnchecked("e");

    异步监听:

    1. CacheLoader cacheLoader = CacheLoader.from(String::toUpperCase);
    2. RemovalListener listener =(notification) -> {
    3. if (notification.wasEvicted()) {
    4. RemovalCause cause = notification.getCause();
    5. System.out.println("remove cacase is:" + cause.toString());
    6. System.out.println("key:" + notification.getKey() + "value:" + notification.getValue());
    7. }
    8. };
    9. //同步监听
    10. LoadingCache loadingCache = CacheBuilder.newBuilder().maximumSize(4)
    11. //异步监控
    12. .removalListener(RemovalListeners.asynchronous(listener, Executors.newSingleThreadExecutor()))
    13. .build(cacheLoader);
    14. loadingCache.getUnchecked("a");
    15. loadingCache.getUnchecked("b");
    16. loadingCache.getUnchecked("c");
    17. loadingCache.getUnchecked("d");//容量是4 吵了自动清除,监听程序清除是单线程。
    18. loadingCache.getUnchecked("e");

    (4)统计

            CacheBuilder.recordStats()用来开启Guava Cache的统计功能。统计打开后Cache.stats()方法返回如下统计信息:

    • hitRate():缓存命中率;
    • hitMiss(): 缓存失误率;
    • loadcount() ; 加载次数;
    • averageLoadPenalty():加载新值的平均时间,单位为纳秒;
    • evictionCount():缓存项被回收的总数,不包括显式清除。
    1. Cache cache = CacheBuilder.newBuilder()
    2. // 基于时间失效,写入之后开始计时失效
    3. .expireAfterWrite(2000, TimeUnit.MILLISECONDS)
    4. // 缓存容量
    5. .maximumSize(5)
    6. .recordStats()
    7. .build();
    8. cache.put("key", "value");
    9. CacheStats cacheStats = cache.stats();
    10. System.out.println(cacheStats.toString());

    (5)并发操作

    GuavaCache通过设置 concurrencyLevel 使得缓存支持并发的写入和读取

    1. LoadingCache build = CacheBuilder.newBuilder()
    2. // 最大3个 同时支持CPU核数线程写缓存
    3. .maximumSize(3).concurrencyLevel(Runtime.getRuntime().availableProcessors()).build(new CacheLoader() {
    4. @Override
    5. public UserInfo load(Long id) throws Exception {
    6. // 读取 db 数据
    7. return null;
    8. }
    9. });

    (6)更新策略

       更新策略:在设定多长时间后会自动刷新缓存。

    1. LoadingCache build = CacheBuilder.newBuilder().
    2. //1s内阻塞会返回旧数据
    3. refreshAfterWrite(1, TimeUnit.SECONDS).build(new CacheLoader() {
    4. @Override
    5. public String load(String key) {
    6. // TODO 此处可以查询数据库 更新该key对应的value数据
    7. return null;
    8. }
    9. });

    案例:

    使用方式一、

    1. com.google.guava
    2. guava
    3. 27.0.1-jre
    1. import com.google.common.cache.Cache;
    2. import org.springframework.beans.factory.annotation.Autowired;
    3. import org.springframework.stereotype.Component;
    4. @Component
    5. public class GuavaCacheUtils {
    6. @Autowired
    7. Cache guavaCache;
    8. /**
    9. * 添加或更新缓存
    10. *
    11. * @param key
    12. * @param value
    13. */
    14. public void putAndUpdateCache(String key, Object value) {
    15. guavaCache.put(key, value);
    16. }
    17. /**
    18. * 获取对象缓存
    19. *
    20. * @param key
    21. * @return
    22. */
    23. public T getObjCacheByKey(String key, Class t) {
    24. //通过key获取缓存中的value,若不存在直接返回null
    25. guavaCache.getIfPresent(key);
    26. return (T) guavaCache.asMap().get(key);
    27. }
    28. /**
    29. * 根据key删除缓存
    30. *
    31. * @param key
    32. */
    33. public void removeCacheByKey(String key) {
    34. // 从缓存中删除
    35. guavaCache.asMap().remove(key);
    36. }
    37. }

    1. import com.guava.cache.lean.entity.UserInfo;
    2. import com.guava.cache.lean.utils.GuavaCacheUtils;
    3. import lombok.extern.slf4j.Slf4j;
    4. import org.springframework.beans.factory.annotation.Autowired;
    5. import org.springframework.stereotype.Service;
    6. import org.springframework.util.StringUtils;
    7. import java.util.Map;
    8. import java.util.concurrent.ConcurrentHashMap;
    9. /**
    10. * 用户模块接口
    11. */
    12. @Service
    13. @Slf4j
    14. public class UserInfoService {
    15. //模拟数据库存储数据
    16. private Map userInfoMap = new ConcurrentHashMap<>();
    17. @Autowired
    18. private GuavaCacheUtils guavaCacheUtils;
    19. public void addUserInfo(UserInfo userInfo) {
    20. log.info("create");
    21. userInfoMap.put(userInfo.getId(), userInfo);
    22. // 加入缓存
    23. guavaCacheUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    24. }
    25. public UserInfo getByName(Integer userId) {
    26. // 先从缓存读取
    27. UserInfo userInfo = guavaCacheUtils.getObjCacheByKey(String.valueOf(userId), UserInfo.class);
    28. if (userInfo != null) {
    29. return userInfo;
    30. }
    31. // 如果缓存中不存在,则从库中查找
    32. log.info("get");
    33. userInfo = userInfoMap.get(userId);
    34. // 如果用户信息不为空,则加入缓存
    35. if (userInfo != null) {
    36. guavaCacheUtils.putAndUpdateCache(String.valueOf(userInfo.getId()), userInfo);
    37. }
    38. return userInfo;
    39. }
    40. public UserInfo updateUserInfo(UserInfo userInfo) {
    41. log.info("update");
    42. if (!userInfoMap.containsKey(userInfo.getId())) {
    43. return null;
    44. }
    45. // 取旧的值
    46. UserInfo oldUserInfo = userInfoMap.get(userInfo.getId());
    47. // 替换内容
    48. if (!StringUtils.isEmpty(oldUserInfo.getName())) {
    49. oldUserInfo.setName(userInfo.getName());
    50. }
    51. if (!StringUtils.isEmpty(oldUserInfo.getSex())) {
    52. oldUserInfo.setSex(userInfo.getSex());
    53. }
    54. oldUserInfo.setAge(userInfo.getAge());
    55. // 将新的对象存储,更新旧对象信息
    56. userInfoMap.put(oldUserInfo.getId(), oldUserInfo);
    57. // 替换缓存中的值
    58. guavaCacheUtils.putAndUpdateCache(String.valueOf(oldUserInfo.getId()), oldUserInfo);
    59. return oldUserInfo;
    60. }
    61. public void deleteById(Integer id) {
    62. log.info("delete");
    63. userInfoMap.remove(id);
    64. // 从缓存中删除
    65. guavaCacheUtils.removeCacheByKey(String.valueOf(id));
    66. }
    67. }

    1. import com.guava.cache.lean.entity.UserInfo;
    2. import com.guava.cache.lean.service.UserInfoService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.web.bind.annotation.*;
    5. /**
    6. * 用户模块入口
    7. */
    8. @RestController
    9. @RequestMapping
    10. public class UserInfoController {
    11. @Autowired
    12. private UserInfoService userInfoService;
    13. @GetMapping("/getUserInfo/{id}")
    14. public Object getUserInfo(@PathVariable Integer id) {
    15. UserInfo userInfo = userInfoService.getByName(id);
    16. if (userInfo == null) {
    17. return "没有该用户";
    18. }
    19. return userInfo;
    20. }
    21. @PostMapping("/createUserInfo")
    22. public Object createUserInfo(@RequestBody UserInfo userInfo) {
    23. userInfoService.addUserInfo(userInfo);
    24. return "SUCCESS";
    25. }
    26. @PutMapping("/updateUserInfo")
    27. public Object updateUserInfo(@RequestBody UserInfo userInfo) {
    28. UserInfo newUserInfo = userInfoService.updateUserInfo(userInfo);
    29. if (newUserInfo == null) {
    30. return "不存在该用户";
    31. }
    32. return newUserInfo;
    33. }
    34. @DeleteMapping("/deleteUserInfo/{id}")
    35. public Object deleteUserInfo(@PathVariable Integer id) {
    36. userInfoService.deleteById(id);
    37. return "SUCCESS";
    38. }
    39. }
    1. import com.google.common.cache.*;
    2. import org.springframework.context.annotation.Bean;
    3. import org.springframework.context.annotation.Configuration;
    4. import java.util.concurrent.TimeUnit;
    5. @EnableCaching
    6. @Configuration
    7. public class GuavaCacheConfig {
    8. @Bean
    9. public Cache guavaCache() {
    10. return CacheBuilder.newBuilder()
    11. // 设置最后一次写入或访问后经过固定时间过期
    12. .expireAfterWrite(6000, TimeUnit.SECONDS)
    13. // 初始的缓存空间大小
    14. .initialCapacity(100)
    15. // 缓存的最大条数
    16. .maximumSize(1000)
    17. .build();
    18. }
    19. }

    使用方式二、

    1. org.springframework.boot
    2. spring-boot-starter-cache
    3. com.google.guava
    4. guava
    5. 27.0.1-jre
    1. import com.google.common.cache.*;
    2. import org.springframework.cache.CacheManager;
    3. import org.springframework.cache.annotation.EnableCaching;
    4. import org.springframework.cache.guava.GuavaCacheManager;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.context.annotation.Configuration;
    7. import java.util.concurrent.TimeUnit;
    8. @EnableCaching
    9. @Configuration
    10. public class GuavaCacheConfig {
    11. @Bean(name = "guavaCacheManager")
    12. public CacheManager oneHourCacheManager(){
    13. CacheBuilder cacheBuilder = CacheBuilder.newBuilder()
    14. .initialCapacity(10) //初始大小
    15. .maximumSize(11) //最大大小
    16. //写入/更新之后1小时过期
    17. .expireAfterWrite(1, TimeUnit.HOURS);
    18. //默认 使用CacheLoader
    19. GuavaCacheManager cacheManager = new GuavaCacheManager();
    20. cacheManager.setCacheBuilder(cacheBuilder);
    21. return cacheManager;
    22. }
    23. }

    1. import com.guava.cache.lean.entity.UserInfo;
    2. import com.guava.cache.lean.service.GuavaCacheService;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.cache.CacheManager;
    5. import org.springframework.web.bind.annotation.GetMapping;
    6. import org.springframework.web.bind.annotation.PathVariable;
    7. import org.springframework.web.bind.annotation.RestController;
    8. @RestController
    9. public class GuavaCacheController {
    10. @Autowired
    11. private GuavaCacheService guavaCacheService;
    12. @Autowired
    13. private CacheManager cacheManager;
    14. @GetMapping("/users/{name}")
    15. public UserInfo getUser(@PathVariable String name) {
    16. System.out.println("==================");
    17. UserInfo user = guavaCacheService.getUserByName(name);
    18. System.out.println(cacheManager.toString());
    19. return user;
    20. }
    21. }
    1. import com.guava.cache.lean.entity.UserInfo;
    2. import lombok.extern.slf4j.Slf4j;
    3. import org.springframework.cache.annotation.CacheConfig;
    4. import org.springframework.cache.annotation.Cacheable;
    5. import org.springframework.stereotype.Service;
    6. import java.util.List;
    7. import java.util.Objects;
    8. /**
    9. * @Cacheable 触发缓存入口(这里一般放在创建和获取的方法上)
    10. * @CacheEvict 触发缓存的eviction(用于删除的方法上)
    11. * @CachePut 更新缓存且不影响方法执行(用于修改的方法上,该注解下的方法始终会被执行)
    12. * @Caching 将多个缓存组合在一个方法上(该注解可以允许一个方法同时设置多个注解)
    13. * @CacheConfig 在类级别设置一些缓存相关的共同配置(与其它缓存配合使用)
    14. */
    15. @Service
    16. @CacheConfig(cacheManager = "guavaCacheManager")
    17. @Slf4j
    18. public class GuavaCacheService {
    19. /**
    20. * 获取用户信息(此处是模拟的数据)
    21. */
    22. /**
    23. * 缓存 key 是 username 的数据到缓存 users 中,
    24. * 如果没有指定 key,则方法参数作为 key 保存到缓存中
    25. */
    26. @Cacheable(value = "Userdata", key = "#username")
    27. public UserInfo getUserByName(String username) {
    28. UserInfo user = getUserFromList(username);
    29. return user;
    30. }
    31. /**
    32. * 从模拟的数据集合中筛选 username 的数据
    33. */
    34. private UserInfo getUserFromList(String username) {
    35. List userDaoList = UserDataFactory.getUserDaoList();
    36. for (UserInfo user : userDaoList) {
    37. if (Objects.equals(user.getName(), username)) {
    38. return user;
    39. }
    40. }
    41. return null;
    42. }
    43. }
    1. import com.guava.cache.lean.entity.UserInfo;
    2. import java.util.ArrayList;
    3. import java.util.List;
    4. public class UserDataFactory {
    5. private UserDataFactory() {
    6. }
    7. private static List userDtoList;
    8. static {
    9. // 初始化集合
    10. userDtoList = new ArrayList<>();
    11. UserInfo user = null;
    12. for (int i = 0; i < 5; i++) {
    13. user = new UserInfo();
    14. user.setName("star" + i);
    15. user.setAge(23);
    16. userDtoList.add(user);
    17. }
    18. }
    19. public static List getUserDaoList() {
    20. return userDtoList;
    21. }
    22. }
    1. @Data
    2. @ToString
    3. @AllArgsConstructor
    4. @NoArgsConstructor
    5. public class UserInfo {
    6. private Integer id;
    7. private String name;
    8. private String sex;
    9. private Integer age;
    10. }

     

     

    四、EhCache

    Ehcache:纯java进程内存缓存框架,具有快速、精干等特点。

    核心组件:

    1. cache manager:缓存管理器
    2. cache:缓存管理器内可以放置若干cache,存放数据的实质,所有cache都实现了Ehcache接口
    3. element:单条缓存数据的组成单位
    4. system of record(SOR):可以取到真实数据的组件,可以是真正的业务逻辑、外部接口调用、存放真实数据的数据库等等,缓存就是从SOR中读取或者写入到SOR中去的。

    特点:

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

    Ehcache的缓存数据过期策略

            Ehcache采用的是懒淘汰机制,每次往缓存放入数据的时候,都会存一个时间,在读取的时候要和设置的时间做TTL比较来判断是否过期。

    使用介绍:

    ehcache.xml

    1. "1.0" encoding="UTF-8"?>
    2. "java.io.tmpdir"/>
    3. maxElementsInMemory="10000"
    4. eternal="false"
    5. overflowToDisk="true"
    6. timeToIdleSeconds="10"
    7. timeToLiveSeconds="20"
    8. diskPersistent="false"
    9. diskExpiryThreadIntervalSeconds="120"/>
    10. "simpleCache"
    11. maxElementsInMemory="1000"
    12. eternal="false"
    13. overflowToDisk="true"
    14. timeToIdleSeconds="10"
    15. timeToLiveSeconds="20"/>

     可以看看这个文章,挺详细,并且写了关于其在分布式方面的应用

    代码

  • 相关阅读:
    【Python】【Pyinstaller】打包过程问题记录及解决
    企业管理系统有哪些?
    MySQL 语句
    【Python】第三课 分支和循环的使用
    高性能网络编程 - 解读5种I/O模型
    Python生物医学专业案例 - 细胞计数
    第二章:ShardingSphere简介
    动态内存开辟(下)以及柔性数组的介绍
    C语言力扣刷题13——最大子数组和[线性动态规划]
    【Java 基础篇】Java函数式接口详解
  • 原文地址:https://blog.csdn.net/weixin_43549578/article/details/126519606