当前位置: 首页 > news >正文

GuavaCache

(一) 应用场景

本地缓存:高并发场景下,使用guavaCache实现本地缓存,提高系统吞吐量,降低数据库压力。

(二)简介

guavaCache是Goole guava库中一个组件,支持多种配置:设置缓存大小、过期策略、且是线程安全的,无需额外同步控制;

(三) 基本特性

  • 线程安全:从数据库加载数据时,只允许一个线程去加载,防止缓存过期时大量请求打到数据库,造成雪崩;
  • 自动加载:缓存失效时,会自动调用指定方法加载;
  • 过期策略:基于时间的过期策略;包括创建后(expire after write)的过期和访问后的过期(expire after access)
  • 惰性删除&惰性加载:缓存过期时不会立即被删除;设置的expire after write到期后reload方法也不会立刻去执行。只有在有请求进来时且缓存未过期,reload方法才会执行
  • 本质是一个ConcurrentMap
class LocalCache<K, V> extends AbstractMap<K, V> implements ConcurrentMap<K, V> {
}

(四) 示例(1)

代码:

public void test3() throws InterruptedException{final AtomicInteger COUNTER = new AtomicInteger(0);ListeningExecutorService refreshPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(8) // 根据实际情况调整线程池大小);LoadingCache<String, String> loadingCacheMap = CacheBuilder.newBuilder().initialCapacity(10) // 初始容量
//            .maximumSize(8)   // 设定最大容量.expireAfterWrite(10L, TimeUnit.SECONDS) // 设定写入过期时间
//            .concurrencyLevel(8)  // 设置最大并发写操作线程数.refreshAfterWrite(5L, TimeUnit.SECONDS) // 设定自动刷新数据时间.build(new CacheLoader<String, String>() {@Overridepublic String load(@NotNull String key) {log.info("load方法执行");try {return fetchDataFromDB(key);} catch (Exception e) {log.error("从数据库加载出现异常,key为:{},异常为:", key, e);}return null;}@Overridepublic ListenableFuture<String> reload(String key, String oldValue) {log.info("reload方法执行");return refreshPool.submit(() -> {return fetchDataFromDB(key); // 重新从数据库加载数据});}public String fetchDataFromDB(String key) {try {Thread.sleep(2000);Random random = new Random();String value = key+ random.nextInt();log.info("新生成的value值为:{}",value);return value;} catch (Exception e) {log.error("fetchDataFromDB出现异常");}return null;}});ScheduledExecutorService pool = Executors.newScheduledThreadPool(5);for (int i = 0; i <= 4; i++) {pool.scheduleAtFixedRate(()-> {try {long start = System.currentTimeMillis();int threadNo = COUNTER.getAndIncrement() % 10; // 自己算编号Thread.currentThread().setName("worker-" + threadNo);log.info("异步线程开始");log.info("获取到的结果:{},耗时:{}", loadingCacheMap.get("aaaa"), System.currentTimeMillis() - start);} catch (ExecutionException e) {throw new RuntimeException(e);}},0,13,TimeUnit.SECONDS);}new CountDownLatch(1).await(2,TimeUnit.MINUTES);log.info("阻塞结束,主线程继续");}

执行结果:

在这里插入图片描述

代码说明

  • 过期策略是创建10s后过期,5s后有请求访问的话,执行reload方法;
  • 定时任务是13s跑一次,所以,每次定时任务访问缓存都是过期的;
  • 模拟并发场景,多个线程同时,访问缓存同一条数据,日志【load方法执行】和日志【新生成的value值为xxx】每次只打印一次,说明guavaCache是有并发控制的,同一时刻只能有一个线程执行更新操作(执行load方法);且每次都是执行的load方法(未执行reload方法,什么时候执行?看下面的示例),说明过期时间是生效的,符合预期;

(五)示例(2)

代码

public void test2() throws InterruptedException {final AtomicInteger COUNTER = new AtomicInteger(0);ListeningExecutorService refreshPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(8) // 根据实际情况调整线程池大小);LoadingCache<String, String> loadingCacheMap = CacheBuilder.newBuilder().initialCapacity(10) // 初始容量
//            .maximumSize(8)   // 设定最大容量.expireAfterWrite(10L, TimeUnit.SECONDS) // 设定写入过期时间
//            .concurrencyLevel(8)  // 设置最大并发写操作线程数.refreshAfterWrite(5L, TimeUnit.SECONDS) // 设定自动刷新数据时间.build(new CacheLoader<String, String>() {@Overridepublic String load(@NotNull String key) {log.info("load方法执行");try {return fetchDataFromDB(key);} catch (Exception e) {log.error("从数据库加载出现异常,key为:{},异常为:", key, e);}return null;}@Overridepublic ListenableFuture<String> reload(String key, String oldValue) {log.info("reload方法执行");return refreshPool.submit(() -> {return fetchDataFromDB(key); // 重新从数据库加载数据});}public String fetchDataFromDB(String key) {try {Thread.sleep(2000);Random random = new Random();String value = key+ random.nextInt();log.info("新生成的value值为:{}",value);return value;} catch (Exception e) {log.error("fetchDataFromDB出现异常");}return null;}});ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);pool.scheduleAtFixedRate(()-> {try {long start = System.currentTimeMillis();int threadNo = COUNTER.getAndIncrement() % 10; // 自己算编号Thread.currentThread().setName("worker-" + threadNo);log.info("异步线程开始");log.info("获取到的结果:{},耗时:{}", loadingCacheMap.get("aaaa"), System.currentTimeMillis() - start);} catch (ExecutionException e) {throw new RuntimeException(e);}},0,13,TimeUnit.SECONDS);new CountDownLatch(1).await(2,TimeUnit.MINUTES);log.info("阻塞结束,主线程继续");}

执行结果

在这里插入图片描述

代码说明

  • 过期策略是创建10s后过期,5s后有请求访问的话,执行reload方法;
  • 定时任务13秒跑一次,每次访问缓存已过期,应重新加载
  • 模拟串行场景,每次异步线程开始,都会打印重新加载的相关日志,说明过期时间是生效的;
  • 定时任务13s跑一次,缓存是10s过期,但是在定时任务访问时没有从缓存中直接取,而是重新加载,说明guavaCache是懒加载的,只有在有请求时,才会加载和更新缓存
  • 那配置的5秒,以及reload方法什么时候生效和执行呢?请看示例(3)

(六) 示例(3)

代码(以串行执行为例)

public void test2() throws InterruptedException {final AtomicInteger COUNTER = new AtomicInteger(0);ListeningExecutorService refreshPool = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(8) // 根据实际情况调整线程池大小);LoadingCache<String, String> loadingCacheMap = CacheBuilder.newBuilder().initialCapacity(10) // 初始容量
//            .maximumSize(8)   // 设定最大容量.expireAfterWrite(10L, TimeUnit.SECONDS) // 设定写入过期时间
//            .concurrencyLevel(8)  // 设置最大并发写操作线程数.refreshAfterWrite(5L, TimeUnit.SECONDS) // 设定自动刷新数据时间.build(new CacheLoader<String, String>() {@Overridepublic String load(@NotNull String key) {log.info("load方法执行");try {return fetchDataFromDB(key);} catch (Exception e) {log.error("从数据库加载出现异常,key为:{},异常为:", key, e);}return null;}@Overridepublic ListenableFuture<String> reload(String key, String oldValue) {log.info("reload方法执行");return refreshPool.submit(() -> {return fetchDataFromDB(key); // 重新从数据库加载数据});}public String fetchDataFromDB(String key) {try {Thread.sleep(2000);Random random = new Random();String value = key+ random.nextInt();log.info("新生成的value值为:{}",value);return value;} catch (Exception e) {log.error("fetchDataFromDB出现异常");}return null;}});ScheduledExecutorService pool = Executors.newScheduledThreadPool(10);pool.scheduleAtFixedRate(()-> {try {long start = System.currentTimeMillis();int threadNo = COUNTER.getAndIncrement() % 10; // 自己算编号Thread.currentThread().setName("worker-" + threadNo);log.info("异步线程开始");log.info("获取到的结果:{},耗时:{}", loadingCacheMap.get("aaaa"), System.currentTimeMillis() - start);} catch (ExecutionException e) {throw new RuntimeException(e);}},0,8,TimeUnit.SECONDS);new CountDownLatch(1).await(2,TimeUnit.MINUTES);log.info("阻塞结束,主线程继续");}

执行结果

在这里插入图片描述

代码说明

  • 过期策略是创建10s后过期,5s后有请求访问的话,执行reload方法;

  • 定时任务是8秒跑一次,理论上,每次访问不会过期

  • 模拟串行场景:可以看到,定时任务第一访问时(第0秒),缓存为空,执行load方法加载数据;

  • 第二次访问时(第8秒),缓存还未过期,从缓存中取旧值,且可以看到耗时很短,毫秒级别(可以说明是从缓存中取的值且取到的值和上一次的值一样);

  • 同时,在第8秒,此线程打印了【reload方法执行】(说明reload方法执行;我们配置的是5s执行,但是在第8s执行,说明reload方法也是懒加载的,只在有请求时执行)

  • 与示例(1)对比,为什么示例(3)执行了reload方法,示例(1)没有执行,是因为示例(3)中,定时任务在访问缓存中,缓存还未过期,所以才会执行reload方法

  • 同时,在第8秒第二次定时任务访问缓存时,获取完数据后,下面有一个异步线程(pool-19-thread-1),打印了【新生成的value值】,这说明执行了reload方法;而且,第三次定时任务取到的就是第二次异步线程加载的新值

  • 从图中也可以看到,除第一次外,每次定时任务取到的值都是上一次定时任务的异步线程生成的新值

  • 线程名称是pool-19-thread-xx 这种的都是异步线程
    在这里插入图片描述

  • 因此,若每次访问缓存数据还没过期且设置了refreshAfterWrite参数,那么之后,只会执行reload方法,不会再执行load方法了

(七) 总结

  • load方法和reload方法都是懒加载的,只有在请求到达时,才可能执行;
  • load方法和reload方法都是线程安全的,同一时刻只能有一个线程执行;
  • reload方法的执行时机是要保证访问的缓存还在有效期内,才能执行reload方法;若访问的缓存已经过了有效期,那只会执行load方法
  • 在高并发场景下,使用refreshAfterWrite(reload方法的执行频率)参数是为了给缓存续命,减少数据库的压力,所以该参数的设置应该小于expireAfterWrite(load方法的执行频率,创建后的过期时间)参数,这样才能达到目的。

(八) 彩蛋

  • 有个问题,如果设置的过期是10s(expireAfterWrite参数),自动刷新时间(refreshAfterWrite参数)是5s,定时任务每2s访问缓存一次,那么guavaCache是怎么判断在第几秒执行reload方法呢?
  • 个人拙见:设自动刷新时间是5s,过期时间是17:25:30,当前访问时间是17:25:24,当前请求进来时,判断当前时间与过期时间的差值是否小于配置的自动刷新时间,即17:25:30-17:25:24=6s,不小于,则不执行reload;若小于,则执行reload方法。
http://www.dtcms.com/a/419434.html

相关文章:

  • 免费空间如何放网站搜索引擎优化培训免费咨询
  • LeetCode 53 最大子数字和(动态规划)
  • 如何为100Tops机器人“退烧”?世强芯片热管理方案,释放100%算力!
  • 【NodeJS】使用 NVM 安装 Node.js 22 并配置国内镜像加速
  • 边缘计算与AI:移动端设计软件的实时性能突破
  • 芜湖有没有网站建设公司吗wordpress邮件分析插件
  • 网上做外贸都有哪些网站组织架构及营销网络怎么填写
  • 网站建设费开票税收代码模板网站好还是自助建站好
  • 苏州网站建设数据网络wordpress添加广告插件
  • 江西哪里可以做企业网站h5案例网站
  • 洛谷题解——C语言(9.17——9.19)
  • vue3 element-plus自定义el-select后缀图标
  • 突破速度瓶颈:为可道云连接雨云对象存储,实现私人网盘高速上传下载
  • 第二章:模块的编译与运行-6 Compiling and Loading
  • Coze源码分析-资源库-编辑插件-前端源码-核心API
  • 如何做导购网站电子商务网站软件建设的核心是
  • 新奇特:神经网络的集团作战思维,权重共享层的智慧
  • 从零开始学神经网络——CNN(卷积神经网络)
  • Fork/Join框架性能调优:工作窃取算法与伪共享问题的终极解决方案
  • 网站的风格有哪些网站建设一般都有什么项目
  • Vue2 插槽(Slot)核心总结
  • 二维数组前缀和
  • 代码随想录第23天第24天 | 回溯 (二)
  • 初始化VUE3项目
  • [C++项目框架库]redis的简单介绍和使用
  • redis特性和应用场景
  • 手机网站建设制作wordpress2019谷歌字体
  • 网站建设一个月多少钱网站图片设置教程
  • Linux零基础入门:权限与常用命令详解
  • 【Pyzmq】python 跨进程线程通信 跨平台跨服务器通信