引言
使用缓存的目的就是提高性能,今天码哥带大家实践运用 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 캐싱은 CachingProvider, CacheManager, Cache, Entry 및 Expiry라는 5가지 핵심 인터페이스를 정의합니다.
Core 클래스 다이어그램:
Cache: get(), put()과 같은 캐시 작업을 추상화합니다.
CacheManager: Cache의 컬렉션 관리로 이해될 수 있습니다. , 캐시가 여러 개인 이유는 다양한 시나리오에 따라 다양한 캐시 만료 시간과 수량 제한을 사용할 수 있기 때문입니다.
CacheInterceptor, CacheAspectSupport, AbstractCacheInvoker: CacheInterceptor는 쿼리 작업, 캐시를 먼저 확인한 후 데이터가 없으면 메서드를 실행하고 쓰기 등 메서드 전후에 추가 논리를 수행하는 AOP 메서드 인터셉터입니다. 메서드 결과를 캐시 등에 저장하고 CacheAspectSupport(캐시 작업의 기본 논리) 및 AbstractCacheInvoker(캐시 읽기 및 쓰기를 캡슐화함)를 상속합니다.
CacheOperation, AnnotationCacheOperationSource, SpringCacheAnnotationParser: CacheOperation은 캐시 작업의 캐시 이름, 캐시 키, 캐시 조건, CacheManager 등을 정의합니다. AnnotationCacheOperationSource는 CacheOperation에 해당하는 캐시 주석을 얻는 클래스이고, SpringCacheAnnotationParser는 구문 분석하는 클래스입니다. 구문 분석 후 AnnotationCacheOperationSource를 찾기 위해 CacheOperation 컬렉션으로 캡슐화됩니다.
CacheAspectSupport: CacheInterceptor의 상위 클래스인 캐시 측면 지원 클래스는 모든 캐시 작업의 기본 논리를 캡슐화합니다.
주요 프로세스는 다음과 같습니다.
CacheOperationSource를 통해 모든 CacheOperation 목록을 가져옵니다
@CacheEvict 주석이 있고 호출 전에 실행되도록 표시되어 있으면 캐시를 삭제/지우세요
If @Cacheable 주석이 있고, 쿼리 캐시
캐시가 누락된 경우(쿼리 결과가 null인 경우) 캐시는 캐시PutRequests에 추가되며, 원래 메서드를 후속 실행한 후 캐시가 캐시에 기록됩니다
-
캐시 적중 시 캐시 값이 결과로 사용됩니다. 캐시가 누락되거나 @CachePut으로 주석이 추가되면 원래 메서드를 호출해야 하며 원래 메서드의 반환 값이 결과로 사용됩니다. @CachePut 주석이 있는 경우 이를 캐시PutRequests에 추가합니다.
캐시가 누락된 경우 쿼리 결과 값이 캐시에 기록되고, @CachePut 주석이 있는 경우 메소드 실행 결과도 캐시에 기록됩니다. 캐시
@CacheEvict 주석이 있고 호출 후 실행되도록 표시된 경우 캐시를 삭제/지우세요
위 내용은 SpringBoot 통합 로컬 캐시 성능에 대한 카페인 인스턴스 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

Java는 플랫폼 별 문제를 어떻게 완화합니까? Java는 JVM 및 표준 라이브러리를 통해 플랫폼 독립성을 구현합니다. 1) Bytecode 및 JVM을 사용하여 운영 체제 차이를 추상화합니다. 2) 표준 라이브러리는 Paths 클래스 처리 파일 경로 및 Charset 클래스 처리 문자 인코딩과 같은 크로스 플랫폼 API를 제공합니다. 3) 최적화 및 디버깅을 위해 실제 프로젝트에서 구성 파일 및 다중 플랫폼 테스트를 사용하십시오.

java'splatformincendenceenhancesmicroservicesarchitectureDeploymentFlexibility, 일관성, 확장 성 및 포트 가능성

Graalvm은 Java의 플랫폼 독립성을 세 가지 방식으로 향상시킵니다. 1. 교차 언어 상호 운용성, Java는 다른 언어와 원활하게 상호 작용할 수 있습니다. 2. 독립적 인 런타임 환경, Java 프로그램을 GraalvMnativeImage를 통해 로컬 실행 파일로 컴파일합니다. 3. 성능 최적화, Graal Compiler는 Java 프로그램의 성능과 일관성을 향상시키기 위해 효율적인 기계 코드를 생성합니다.

ToEffectIallyTestJavaApplicationSforplatformcompatibility, followthesesteps : 1) setupAutomatedTestingAcrossMultiplePlatflatformsUsingCitools likeJenkinsorgitHubactions.2) 행동 관리자는 realHardwaretoCathissesnotfoundInvironmentments.3) Checkcross-Pla

Java Compiler는 소스 코드를 플랫폼 독립적 인 바이트 코드로 변환하여 Java의 플랫폼 독립성을 실현하여 JVM이 설치된 JVM 프로그램에서 모든 운영 체제에서 실행할 수 있습니다.

Bytecodeachievesplatformincendence는 executedbirtualmachine (vm)을 beenecutedbyavirtmachine (vm)을 허용합니다

Java는 100% 플랫폼 독립성을 달성 할 수 없지만 플랫폼 독립성은 JVM 및 바이트 코드를 통해 구현되어 코드가 다른 플랫폼에서 실행되도록합니다. 특정 구현에는 다음이 포함됩니다. 1. 바이트 코드로의 컴파일; 2. JVM의 해석 및 실행; 3. 표준 라이브러리의 일관성. 그러나 JVM 구현 차이, 운영 체제 및 하드웨어 차이, 타사 라이브러리의 호환성은 플랫폼 독립성에 영향을 줄 수 있습니다.

Java는 "Writ 2. 유지 보수 비용이 낮 으면 하나의 수정 만 필요합니다. 3. 높은 팀 협업 효율성은 높고 지식 공유에 편리합니다.


핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

SublimeText3 Linux 새 버전
SublimeText3 Linux 최신 버전

WebStorm Mac 버전
유용한 JavaScript 개발 도구

Dreamweaver Mac版
시각적 웹 개발 도구

SublimeText3 영어 버전
권장 사항: Win 버전, 코드 프롬프트 지원!

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경
