搜尋
首頁Javajava教程SpringBoot整合本地快取效能之Caffeine實例分析

引言

使用缓存的目的就是提高性能,今天码哥带大家实践运用 spring-boot-starter-cache 抽象的缓存组件去集成本地缓存性能之王 Caffeine。

大家需要注意的是:in-memeory 缓存只适合在单体应用,不适合与分布式环境。

分布式环境的情况下需要将缓存修改同步到每个节点,需要一个同步机制保证每个节点缓存数据最终一致。

Spring Cache 是什么

不使用 Spring Cache 抽象的缓存接口,我们需要根据不同的缓存框架去实现缓存,需要在对应的代码里面去对应缓存加载、删除、更新等。

比如查询我们使用旁路缓存策略:先从缓存中查询数据,如果查不到则从数据库查询并写到缓存中。

伪代码如下:

public User getUser(long userId) {
    // 从缓存查询
    User user = cache.get(userId);
    if (user != null) {
        return user;
    }
    // 从数据库加载
    User dbUser = loadDataFromDB(userId);
    if (dbUser != null) {
        // 设置到缓存中
        cache.put(userId, dbUser)
    }
    return dbUser;
}

我们需要写大量的这种繁琐代码,Spring Cache 则对缓存进行了抽象,提供了如下几个注解实现了缓存管理:

  • @Cacheable:触发缓存读取操作,用于查询方法上,如果缓存中找到则直接取出缓存并返回,否则执行目标方法并将结果缓存。

  • @CachePut:触发缓存更新的方法上,与 Cacheable 相比,该注解的方法始终都会被执行,并且使用方法返回的结果去更新缓存,适用于 insert 和 update 行为的方法上。

  • @CacheEvict:触发缓存失效,删除缓存项或者清空缓存,适用于 delete 方法上。

除此之外,抽象的 CacheManager 既能集成基于本地内存的单体应用,也能集成 EhCache、Redis 等缓存服务器。

最方便的是通过一些简单配置和注解就能接入不同的缓存框架,无需修改任何代码。

集成 Caffeine

码哥带大家使用注解方式完成缓存操作的方式来集成,完整的代码请访问 github:在 pom.xml 文件添加如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>com.github.ben-manes.caffeine</groupId>
    <artifactId>caffeine</artifactId>
</dependency>

使用 JavaConfig 方式配置 CacheManager:

@Slf4j
@EnableCaching
@Configuration
public class CacheConfig {
    @Autowired
    @Qualifier("cacheExecutor")
    private Executor cacheExecutor;
    @Bean
    public Caffeine<Object, Object> caffeineCache() {
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterAccess(7, TimeUnit.DAYS)
                // 初始的缓存空间大小
                .initialCapacity(500)
                // 使用自定义线程池
                .executor(cacheExecutor)
                .removalListener(((key, value, cause) -> log.info("key:{} removed, removalCause:{}.", key, cause.name())))
                // 缓存的最大条数
                .maximumSize(1000);
    }
    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
        caffeineCacheManager.setCaffeine(caffeineCache());
        // 不缓存空值
        caffeineCacheManager.setAllowNullValues(false);
        return caffeineCacheManager;
    }
}

准备工作搞定,接下来就是如何使用了。

@Slf4j
@Service
public class AddressService {
    public static final String CACHE_NAME = "caffeine:address";
    private static final AtomicLong ID_CREATOR = new AtomicLong(0);
    private Map<Long, AddressDTO> addressMap;
    public AddressService() {
        addressMap = new ConcurrentHashMap<>();
        addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址1").build());
        addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址2").build());
        addressMap.put(ID_CREATOR.incrementAndGet(), AddressDTO.builder().customerId(ID_CREATOR.get()).address("地址3").build());
    }
    @Cacheable(cacheNames = {CACHE_NAME}, key = "#customerId")
    public AddressDTO getAddress(long customerId) {
        log.info("customerId:{} 没有走缓存,开始从数据库查询", customerId);
        return addressMap.get(customerId);
    }
    @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId")
    public AddressDTO create(String address) {
        long customerId = ID_CREATOR.incrementAndGet();
        AddressDTO addressDTO = AddressDTO.builder().customerId(customerId).address(address).build();
        addressMap.put(customerId, addressDTO);
        return addressDTO;
    }
    @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId")
    public AddressDTO update(Long customerId, String address) {
        AddressDTO addressDTO = addressMap.get(customerId);
        if (addressDTO == null) {
            throw new RuntimeException("没有 customerId = " + customerId + "的地址");
        }
        addressDTO.setAddress(address);
        return addressDTO;
    }
    @CacheEvict(cacheNames = {CACHE_NAME}, key = "#customerId")
    public boolean delete(long customerId) {
        log.info("缓存 {} 被删除", customerId);
        return true;
    }
}

使用 CacheName 隔离不同业务场景的缓存,每个 Cache 内部持有一个 map 结构存储数据,key 可用使用 Spring 的 Spel 表达式。

单元测试走起:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = CaffeineApplication.class)
@Slf4j
public class CaffeineApplicationTests {
    @Autowired
    private AddressService addressService;
    @Autowired
    private CacheManager cacheManager;
    @Test
    public void testCache() {
        // 插入缓存 和数据库
        AddressDTO newInsert = addressService.create("南山大道");
        // 要走缓存
        AddressDTO address = addressService.getAddress(newInsert.getCustomerId());
        long customerId = 2;
        // 第一次未命中缓存,打印 customerId:{} 没有走缓存,开始从数据库查询
        AddressDTO address2 = addressService.getAddress(customerId);
        // 命中缓存
        AddressDTO cacheAddress2 = addressService.getAddress(customerId);
        // 更新数据库和缓存
        addressService.update(customerId, "地址 2 被修改");
        // 更新后查询,依然命中缓存
        AddressDTO hitCache2 = addressService.getAddress(customerId);
        Assert.assertEquals(hitCache2.getAddress(), "地址 2 被修改");
        // 删除缓存
        addressService.delete(customerId);
        // 未命中缓存, 从数据库读取
        AddressDTO hit = addressService.getAddress(customerId);
        System.out.println(hit.getCustomerId());
    }
}

大家发现没,只需要在对应的方法上加上注解,就能愉快的使用缓存了。需要注意的是, 设置的 cacheNames 一定要对应,每个业务场景使用对应的 cacheNames。

另外 key 可以使用 spel 表达式,大家重点可以关注 @CachePut(cacheNames = {CACHE_NAME}, key = "#result.customerId"),result 表示接口返回结果,Spring 提供了几个元数据直接使用。

名称 地点 描述 例子
methodName 根对象 被调用的方法的名称 #root.methodName
method 根对象 被调用的方法 #root.method.name
target 根对象 被调用的目标对象 #root.target
targetClass 根对象 被调用的目标的类 #root.targetClass
args 根对象 用于调用目标的参数(作为数组) #root.args[0]
caches 根对象 运行当前方法的缓存集合 #root.caches[0].name
参数名称 评估上下文 任何方法参数的名称。如果名称不可用(可能是由于没有调试信息),则参数名称也可在#a where#arg代表参数索引(从 开始0)下获得。 #iban或#a0(您也可以使用#p0或#p表示法作为别名)。
result 评估上下文 方法调用的结果(要缓存的值)。仅在unless 表达式、cache put表达式(计算key)或cache evict 表达式(when beforeInvocationis false)中可用。对于支持的包装器(例如 Optional),#result指的是实际对象,而不是包装器。 #result

核心原則

Java Caching定義了5個核心接口,分別是 CachingProvider, CacheManager, Cache, Entry 和 Expiry。

SpringBoot整合本地快取效能之Caffeine實例分析

核心類別圖:

SpringBoot整合本地快取效能之Caffeine實例分析

  • #Cache:抽象了快取的操作,例如, get()、put();

  • CacheManager:管理Cache,可以理解成Cache 的集合管理,之所以有多個Cache,是因為可以根據不同場景使用不同的緩存失效時間和數量限制。

  • CacheInterceptor、CacheAspectSupport、AbstractCacheInvoker:CacheInterceptor 是AOP 方法攔截器,在方法前後做額外的邏輯,例如查詢操作,先查緩存,找不到資料再執行方法,並且把方法的結果寫入快取等,它繼承了CacheAspectSupport(快取操作的主體邏輯)、AbstractCacheInvoker(封裝了對Cache 的讀寫)。

  • CacheOperation、AnnotationCacheOperationSource、SpringCacheAnnotationParser:CacheOperation定義了快取操作的快取名字、快取key、快取條件condition、CacheManager等,AnnotationCacheOperationSource 是一個取得快取註解對應CacheOperation 的類別,而OacheOperation ache是解析註解的類別,解析後會封裝成CacheOperation 集合供AnnotationCacheOperationSource 查找。

CacheAspectSupport:快取切面支援類,是CacheInterceptor 的父類,封裝了所有的快取操作的主體邏輯。

主要流程如下:

  • 透過CacheOperationSource,取得所有的CacheOperation清單

  • 如果有@CacheEvict註解、並且標記為在呼叫前執行,則做刪除/清空快取的操作

  • 如果有@Cacheable註解,查詢快取

  • 如果快取未命中(查詢結果為null),則新增至cachePutRequests,後續執行原始方法後會寫入快取

  • 快取命中時,使用快取值作為結果;快取未命中、或有@CachePut註解時,需要呼叫原始方法,使用原始方法的回傳值作為結果

  • #如果有@CachePut註解,則加入cachePutRequests

  • #如果快取未命中,則把查詢結果值寫入快取;如果有@CachePut註解,也把方法執行結果寫入快取

  • 如果有@CacheEvict註解、並且標記為在呼叫後執行,則做刪除/清空快取的操作

以上是SpringBoot整合本地快取效能之Caffeine實例分析的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文轉載於:亿速云。如有侵權,請聯絡admin@php.cn刪除
怎么使用SpringBoot+Canal实现数据库实时监控怎么使用SpringBoot+Canal实现数据库实时监控May 10, 2023 pm 06:25 PM

Canal工作原理Canal模拟MySQLslave的交互协议,伪装自己为MySQLslave,向MySQLmaster发送dump协议MySQLmaster收到dump请求,开始推送binarylog给slave(也就是Canal)Canal解析binarylog对象(原始为byte流)MySQL打开binlog模式在MySQL配置文件my.cnf设置如下信息:[mysqld]#打开binloglog-bin=mysql-bin#选择ROW(行)模式binlog-format=ROW#配置My

Spring Boot怎么使用SSE方式向前端推送数据Spring Boot怎么使用SSE方式向前端推送数据May 10, 2023 pm 05:31 PM

前言SSE简单的来说就是服务器主动向前端推送数据的一种技术,它是单向的,也就是说前端是不能向服务器发送数据的。SSE适用于消息推送,监控等只需要服务器推送数据的场景中,下面是使用SpringBoot来实现一个简单的模拟向前端推动进度数据,前端页面接受后展示进度条。服务端在SpringBoot中使用时需要注意,最好使用SpringWeb提供的SseEmitter这个类来进行操作,我在刚开始时使用网上说的将Content-Type设置为text-stream这种方式发现每次前端每次都会重新创建接。最

SpringBoot怎么实现二维码扫码登录SpringBoot怎么实现二维码扫码登录May 10, 2023 pm 08:25 PM

一、手机扫二维码登录的原理二维码扫码登录是一种基于OAuth3.0协议的授权登录方式。在这种方式下,应用程序不需要获取用户的用户名和密码,只需要获取用户的授权即可。二维码扫码登录主要有以下几个步骤:应用程序生成一个二维码,并将该二维码展示给用户。用户使用扫码工具扫描该二维码,并在授权页面中授权。用户授权后,应用程序会获取一个授权码。应用程序使用该授权码向授权服务器请求访问令牌。授权服务器返回一个访问令牌给应用程序。应用程序使用该访问令牌访问资源服务器。通过以上步骤,二维码扫码登录可以实现用户的快

SpringBoot/Spring AOP默认动态代理方式是什么SpringBoot/Spring AOP默认动态代理方式是什么May 10, 2023 pm 03:52 PM

1.springboot2.x及以上版本在SpringBoot2.xAOP中会默认使用Cglib来实现,但是Spring5中默认还是使用jdk动态代理。SpringAOP默认使用JDK动态代理,如果对象没有实现接口,则使用CGLIB代理。当然,也可以强制使用CGLIB代理。在SpringBoot中,通过AopAutoConfiguration来自动装配AOP.2.Springboot1.xSpringboot1.xAOP默认还是使用JDK动态代理的3.SpringBoot2.x为何默认使用Cgl

spring boot怎么对敏感信息进行加解密spring boot怎么对敏感信息进行加解密May 10, 2023 pm 02:46 PM

我们使用jasypt最新版本对敏感信息进行加解密。1.在项目pom文件中加入如下依赖:com.github.ulisesbocchiojasypt-spring-boot-starter3.0.32.创建加解密公用类:packagecom.myproject.common.utils;importorg.jasypt.encryption.pbe.PooledPBEStringEncryptor;importorg.jasypt.encryption.pbe.config.SimpleStrin

使用Java SpringBoot集成POI实现Word文档导出使用Java SpringBoot集成POI实现Word文档导出Apr 21, 2023 pm 12:19 PM

知识准备需要理解ApachePOI遵循的标准(OfficeOpenXML(OOXML)标准和微软的OLE2复合文档格式(OLE2)),这将对应着API的依赖包。什么是POIApachePOI是用Java编写的免费开源的跨平台的JavaAPI,ApachePOI提供API给Java程序对MicrosoftOffice格式档案读和写的功能。POI为“PoorObfuscationImplementation”的首字母缩写,意为“简洁版的模糊实现”。ApachePOI是创建和维护操作各种符合Offic

springboot怎么整合shiro实现多验证登录功能springboot怎么整合shiro实现多验证登录功能May 10, 2023 pm 04:19 PM

1.首先新建一个shiroConfigshiro的配置类,代码如下:@ConfigurationpublicclassSpringShiroConfig{/***@paramrealms这儿使用接口集合是为了实现多验证登录时使用的*@return*/@BeanpublicSecurityManagersecurityManager(Collectionrealms){DefaultWebSecurityManagersManager=newDefaultWebSecurityManager();

springboot怎么配置mybatis和事务管理springboot怎么配置mybatis和事务管理May 10, 2023 pm 07:13 PM

一、springboot与mybatis的配置1.首先,springboot配置mybatis需要的全部依赖如下:org.springframework.bootspring-boot-starter-parent1.5.1.RELEASEorg.springframework.bootspring-boot-starter-web1.5.1.RELEASEorg.mybatis.spring.bootmybatis-spring-boot-starter1.2.0com.oracleojdbc

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
3 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
3 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能

SecLists

SecLists

SecLists是最終安全測試人員的伙伴。它是一個包含各種類型清單的集合,這些清單在安全評估過程中經常使用,而且都在一個地方。 SecLists透過方便地提供安全測試人員可能需要的所有列表,幫助提高安全測試的效率和生產力。清單類型包括使用者名稱、密碼、URL、模糊測試有效載荷、敏感資料模式、Web shell等等。測試人員只需將此儲存庫拉到新的測試機上,他就可以存取所需的每種類型的清單。

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Atom編輯器mac版下載

Atom編輯器mac版下載

最受歡迎的的開源編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用