多级缓存之Google Guava的实现方案
文章目录
- 背景
- 如何创建?
- pom引入依赖
- CacheLoader方式
- 何时使用?
- 案例
- CacheBuilder 的详细方法
- Callable方式
- 何时使用?
- 案例
- 如何删除?
- 被动
- 基于数据大小的删除
- 基于过期时间的删除
- 基于引用的删除
- 主动
- 删除单条
- 批量删除
- 清空缓存
- 总结
背景
数据库扛不住了可以使用Redis来分担读请求,在大访问量的系统中Redis集中式缓存方案,会成为大型系统的瓶颈。有什么方案解决呢?
可以在增加一层缓存层,即JVM进程的内存中进行本地缓存,分摊Redis的压力。Guava cache的设计来源于CurrentHashMap,可以按照多种策略来清理存储在其中的缓存值且保持很高的并发读写性能。以下场景适合于做本地缓存:
- 内存占用较小
- 数据极少变化
- 需要访问整个集合
- 数据实时性要求不高
如何创建?
创建缓存方式有两种。
pom引入依赖
在项目中添加如下的依赖即可
<dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version>
</dependency>
CacheLoader方式
何时使用?
是否存在一个默认函数来加载或计算与键关联的值?如果是这样,则应使用CacheLoader。
案例
package com.linfanchen.springboot.lab.guava;import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;import static com.alibaba.druid.sql.ast.SQLPartitionValue.Operator.List;@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheTest {LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {/*** 获取单条数据*/@Overridepublic String load(final String key) {return getCar(key);}/*** 获取所有数据*/@Overridepublic Map<String, String> loadAll(final Iterable<? extends String> keys) throws Exception {// 此包完整路径位于: com.google.common.collect.ListsArrayList<String> keysList = Lists.newArrayList(keys);return getCars(keysList);}});/*** Mock 读取DB中的单条数据*/private static String getCar(String key) {return "Lexus";}/*** Mock 读取DB中的多条数据*/private static Map<String, String> getCars(List<String> keys) {Map<String,String> map = new HashMap<>();map.put("bmw", "BMW 530Li");map.put("benz", "E300L");map.put("audi", "Audi A6L");return map;}@Testpublic void firstTest() {java.util.List<String> keys = new ArrayList<>();keys.add("bmw");keys.add("benz");// 从本地缓存读取数据try {System.out.println(cache.getAll(keys)); // 输出 {bmw=BMW 530Li, benz=E300L}} catch (ExecutionException e1) {e1.printStackTrace();}}}
CacheBuilder 的详细方法
LoadingCache<Object, Object> userCache = CacheBuilder.newBuilder()// 基于容量回收。缓存的最大数量。超过就取MAXIMUM_CAPACITY = 1 << 30。依靠LRU队列recencyQueue来进行容量淘汰.maximumSize(1000)// 基于容量回收。但这是统计占用内存大小,maximumWeight与maximumSize不能同时使用。设置最大总权重.maximumWeight(1000)// 设置权重(可当成每个缓存占用的大小).weigher((o, o2) -> 5)// 软弱引用(引用强度顺序:强软弱虚)// -- 弱引用key.weakKeys()// -- 弱引用value.weakValues()// -- 软引用value.softValues()// 过期失效回收// -- 没读写访问下,超过5秒会失效(非自动失效,需有任意getput方法才会扫描过期失效数据).expireAfterAccess(5L, TimeUnit.SECONDS)// -- 没写访问下,超过5秒会失效(非自动失效,需有任意putget方法才会扫描过期失效数据).expireAfterWrite(5L, TimeUnit.SECONDS)// 没写访问下,超过5秒会失效(非自动失效,需有任意putget方法才会扫描过期失效数据。但区别是会开一个异步线程进行刷新,刷新过程中访问返回旧数据).refreshAfterWrite(5L, TimeUnit.SECONDS)// 移除监听事件.removalListener(removal -> {// 可做一些删除后动作,比如上报删除数据用于统计System.out.printf("触发删除动作,删除的key=%s%n", removal);})// 并行等级。决定segment数量的参数,concurrencyLevel与maxWeight共同决定.concurrencyLevel(16)// 开启缓存统计。比如命中次数、未命中次数等.recordStats()// 所有segment的初始总容量大小.initialCapacity(512)// 用于测试,可任意改变当前时间。.ticker(new Ticker() {@Overridepublic long read() {return 0;}})// 开始构造.build(new CacheLoader<Object, Object>() {@Overridepublic Object load(Object name) {// 在cache找不到就取数据return String.format("重新load(%s):%s", System.currentTimeMillis(), name);}});// 简单使用userCache.put("car", "BMW 530Li 行政版");System.out.println(userCache.get("car"));
Callable方式
何时使用?
希望使用原子的“ get-if-absent-compute”语义,则应将Callable传递给get调用。
案例
package com.linfanchen.springboot.lab.guava;import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Lists;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;@RunWith(SpringRunner.class)
@SpringBootTest
public class CacheCallableTest {LoadingCache<String, String> cache = CacheBuilder.newBuilder().build(new CacheLoader<String, String>() {/*** 获取单条数据*/@Overridepublic String load(final String key) {return getCar(key);}});/*** Mock 读取DB中的单条数据*/private static String getCar(String key) {return "Lexus" + key;}/*** Mock 读取DB中的多条数据*/private static Map<String, String> getCars(List<String> keys) {Map<String,String> map = new HashMap<>();map.put("bmw", "BMW 530Li");map.put("benz", "E300L");map.put("audi", "Audi A6L");return map;}@Testpublic void secondTest() {try {// 从本地缓存读取数据String value = cache.get("es300h", new Callable<String>() {@Overridepublic String call() throws Exception {System.out.println("Now invoking callable code...");return "callable code...";}});System.out.println(value); // 输出: callable code...} catch (ExecutionException e1) {e1.printStackTrace();}}}
如何删除?
被动
基于数据大小的删除
当数据的个数多于 maximumSize所设置的值时候,会根据 LRU+FIFO 策略进行淘汰。
基于过期时间的删除
隔多长时间后没有被访问过的key被删除,时间标准参照 expireAfterAccess所设置的时间。
基于引用的删除
可以通过weakKeys和weakValues方法指定Cache只保存对缓存记录key和value的弱引用。这样当没有其他强引用指向key和value时,key和value对象就会被垃圾回收器回收。
主动
删除单条
cache.invalidate("bmw");
批量删除
cache.invalidateAll(Arrays.asList("bmw","benz"));
清空缓存
cache.invalidateAll();
总结
guava cache 的异步 reload 策略可以有效实现容错、节约调用耗时的目的,但有一个致命的缺陷:主线程返回的数据有可能是已过期的。
通常,我们对于缓存中数据的实际失效时间并不敏感,在这样的情况下,即使 guava cache 返回了已失效数据,也并不会造成任何业务问题,而由此带来的性能提升与容错的好处是显而易见的。
参考文档:
官方网址
官方使用案例
Google Guava Cache高效本地缓存
Guava Cache简介、应用场景分析、代码实现以及核心的原理
guava cache详细介绍
多级缓存之Google Guava的实现方案相关推荐
- Java内存缓存-通过Google Guava创建缓存
谷歌Guava缓存 Guava介绍 Guava是Google guava中的一个内存缓存模块,用于将数据缓存到JVM内存中.实际项目开发中经常将一些公共或者常用的数据缓存起来方便快速访问. Guava ...
- 千万级并发!如何设计一个多级缓存系统?
作者:不清不慎,目前在杭州蘑菇街公司任职,Java大数据开发工程师一枚,热爱研究开源技术! 架构师社区合伙人! 首先我们需要明白,什么是一个多级缓存系统,它有什么用.所谓多级缓存系统,就是指在一个系统 ...
- 设计高并发架构与系统多级缓存架构如何设计?
高并发经常会发生在有大活跃用户量,用户高聚集的业务场景中,如:秒杀活动,定时领取红包等. 为了让业务可以流畅的运行并且给用户一个好的交互体验,我们需要根据业务场景预估达到的并发量等因素,来设计适合自己 ...
- google guava 入门教程
Guava(瓜娃)学习笔记 (代码下载地址) Guava工程包含了若干被google的java项目广泛依赖的核心库,例如:集合 [collections] .缓存 [caching] .原生类型支持 ...
- 真正的缓存之王,Google Guava 只是弟弟
欢迎关注方志朋的博客,回复"666"获面试宝典 来源:https://blog.csdn.net/a953713428/article/details/92159746 前面刚说到 ...
- Google Guava 只是弟弟,什么才是真正的缓存之王?(荣耀典藏版)
目录 前言 1. Caffine Cache 在算法上的优点-W-TinyLFU 2.使用 2.1 缓存填充策略 2.1.1.手动加载 2.1.2. 同步加载 2.1.3. 异步加载 2.2 回收策略 ...
- Google guava第一讲:guava缓存实战/使用场景/缓存清理/最佳实践/caffeine实战
Guava缓存实战及使用场景 摘要:本文是Google guava 第一件,本文先介绍了为什么使用Guava Cache缓存,然后讲解了使用方法及底层数据结构,结合实际业务,讲解使用guava过程中踩 ...
- guava_使用Google Guava Cache进行本地缓存
guava 很多时候,我们将不得不从数据库或另一个Web服务获取数据或从文件系统加载数据. 在涉及网络呼叫的情况下,将存在固有的网络延迟,网络带宽限制. 解决此问题的方法之一是在应用程序本地拥有一个缓 ...
- 使用Google Guava Cache进行本地缓存
很多时候,我们将不得不从数据库或另一个Web服务获取数据或从文件系统加载数据. 在涉及网络呼叫的情况下,将存在固有的网络等待时间,网络带宽限制. 解决此问题的方法之一是在应用程序本地拥有一个缓存. 如 ...
最新文章
- winform实现翻书效果_虚拟翻书系统解决方案
- 物理学家发现粒子是如何自我组装的
- 微软 python_微软推出 Pylance,改善 VS Code 中的 Python 体验
- maven的环境搭建
- Oracle TNSListener服务启动后自动停止问题
- excel操作的几种方法
- 【鲲鹏来了】手把手教你在鲲鹏上使用编程语言——C语言
- 详解语音识别的技术原理
- linux七周四次课(3月22日)
- 常见的IT自动化运维工具有哪些?推荐一款好用的?
- 串口通信USART的波特率误差计算GD32、STM32
- 社会网络分析法(Social Network Analysis)中矩阵二值化阈值的确定
- 2008服务器系统开启ftp,win 2008服务器开启FTP功能
- 洛杉矶儿童医院被列为美国西部第一,在全美儿童医院中的总体排名升至第五
- 实现手机扫描二维码进行登录
- linux rapidsvn 使用教程,推荐一款简单方便的SVN客户端,在百度BAE中使用RapidSVN,百度BAE SVN教程...
- 树莓派3代ROS系统镜像文件下载链接
- 更新应用程序安卓apk时出现解析程序包时出现问题
- python数据类型的转化和获取
- 教你一招永久去除WPS广告