Spring Boot는 Spring Cache를 통합하고 Redis, Caffeine, JCache, Ehcache 등과 같은 여러 캐시 구현이 있습니다. 그러나 하나의 캐시 만 사용하면 네트워크 소비가 크거나 메모리 사용량 (예 : Caffeine의 응용 프로그램 메모리 캐시)이 너무 많습니다. 많은 시나리오에서 제 1 레벨 및 2 차 캐시를 결합하여 응용 프로그램의 처리 효율을 대규모 개선 할 수 있습니다.
내용 설명 :
간단한 이해를 위해 캐시는 느린 읽기 미디어의 데이터를 읽고 디스크-> 메모리와 같은 더 빠른 판독 값으로 매체에 넣는 것입니다. 일반적으로 데이터베이스와 같은 디스크에 데이터를 저장합니다. 매번 데이터베이스에서 읽으면 디스크 자체가 읽기 속도에 영향을 미치므로 Redis와 같은 메모리 캐시가 있습니다. 데이터를 읽고 메모리에 넣을 수 있으므로 데이터를 얻어야 할 때 메모리에서 데이터를 직접 가져 와서 반환하여 속도를 크게 향상시킬 수 있습니다. 그러나 일반적으로 Redis는 클러스터에 별도로 배포되므로 네트워크 IO에 소비됩니다. Redis 클러스터에 연결하기위한 연결 풀링 도구가 있지만 데이터 전송에는 여전히 소비됩니다. 카페인과 같은 인앱 캐시가 있습니다. 애플리케이션 캐시의 기준을 충족하는 데이터가 있으면 네트워크를 통해 REDIS로 얻지 않고도 직접 사용할 수 있으므로 2 레벨 캐시를 형성 할 수 있습니다. 인앱 캐시는 1 단계 캐시라고하며 원격 캐시 (예 : Redis)는 2 단계 캐시라고합니다.
스프링 캐시
캐시를 사용할 때 다음 과정은 일반적으로 다음과 같습니다.
흐름도에서 캐시를 사용하기 위해 원래 비즈니스 처리에 따라 많은 캐시 작업이 추가되었음을 알 수 있습니다. 이것들이 비즈니스 코드에 결합된다면, 개발할 때 많은 반복적 인 작업이있을 것이며, 코드를 기반으로 비즈니스를 이해하는 데 도움이되지 않습니다.
Spring Cache는 주석을 기반으로 Spring-Context 패키지에 제공된 캐시 구성 요소입니다. 일부 표준 인터페이스를 정의합니다. 이러한 인터페이스를 구현함으로써 메소드에 주석을 추가하여 캐시를 달성 할 수 있습니다. 이것은 캐시 코드가 비즈니스 처리와 결합되는 문제를 피할 수 있습니다. 스프링 캐시의 구현은 스프링 AOP에서 메소드 인터페이스 (Methodinternector) 캡슐화의 확장입니다. 물론 Spring AOP는 측면을 기반으로 구현됩니다.
스프링 캐시의 두 가지 핵심 인터페이스가 있습니다 : 캐시 및 캐시미너
캐시 인터페이스
캐시를 넣고 읽기 및 청소하는 것과 같은 특정 캐시 작업을 제공합니다. Spring Framework에서 제공하는 구현은 다음과 같습니다.
스프링 데이터-레디스 패키지에있는 REASCACHE를 제외하고는 기본적으로 스프링 컨텍스트 지원 패키지에 있습니다.
#cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interface cache {// cachename, 캐시 이름. 기본 구현에서 CacheManager는 캐시 빈을 만들 때 캐시 이름을 전달합니다. 문자열 getName (); // redistemplate, com.github.benmanes.caffeine.cache.cache <object, object>와 같은 실제 캐시를 가져옵니다. 아직 실제 사용을 찾지 못했습니다. 일부 캐시 작업 또는 통계를 확장해야 할 수 있도록 기본 캐시를 얻는 Bean을 제공 할 수 있습니다. 객체 getNativeCache (); // 키를 통해 캐시 값을 가져 오면 반환 된 값은 ValueWrapper입니다. NULL 값과 호환 되려면 리턴 값이 레이어로 감싸고 Get 메소드 ValueWrapper get (개체 키)을 통해 실제 값을 얻습니다. // 키를 통해 캐시 값을 가져옵니다. 이는 실제 값, 즉 get get (개체 키, 클래스 <t> type)의 반환 값 유형을 반환합니다. // 키를 통해 캐시 값을 가져 오면 valueloader.call ()을 사용하여 @Cachable 주석을 사용하여 메소드를 호출 할 수 있습니다. @Cachable 주석의 동기화 속성이 true로 구성 될 때이 메소드를 사용하십시오. 따라서 데이터베이스에 대한 리턴 소스의 동기화는 메소드 내에서 보장되어야합니다. 캐시가 실패 할 때 소스로 데이터베이스로 돌아가 겠다는 많은 양의 요청을 피하십시오. <t> t get (Object Key, Callable <T> valueloader); // @Cachable Annotation 메소드에 의해 반환 된 데이터를 캐시 void Put (개체 키, 개체 값)에 넣습니다. // 캐시에 키가 없을 때만 캐시를 넣습니다. 리턴 값은 키가 valuewrapper putifabsent (객체 키, 객체 값)가있는 경우 원래 데이터입니다. // 캐시 삭제 void evict (객체 키); // 캐시에서 모든 데이터를 삭제합니다. 특정 구현에서 @Cachable 주석을 사용하여 캐시 된 모든 데이터 만 삭제되며 응용 프로그램 void star ()의 다른 캐시에 영향을 미치지 않습니다. // 캐시 반환 값 인터페이스 ValueWRapper {// 실제 캐시 된 개체 객체 get (); } // {@link #get (Object, Callable)}에 의해 예외가 발생하면 @SuppressWarnings ( "Serial") Class ValuereRetRievalException이 runtimeexception {Private Final Object Key; public valueretriveLexception (객체 키, 호출 가능 <?> 로더, 던질 수있는 ex) {super (string.format ( "value for key '%s' ','%s '", 키, 로더), Ex); this.key = 키; } public Object getKey () {return this.key; }}}Cachemanager 인터페이스
주로 캐시 구현 빈의 생성을 제공합니다. 각 응용 프로그램은 캐시 이름을 통해 캐시를 격리 할 수 있으며 각 캐시 이름은 캐시 구현에 해당합니다. Spring Framework에서 제공하는 구현과 캐시 구현은 쌍으로 나타나며 패키지 구조도 위 그림에 있습니다.
#cachemanager.javapackage org.springframework.cache; import java.util.collection; public interface cachemanager {// 캐시 이름을 통해 캐시 구현 bean 생성. 특정 구현은 반복적 인 생성을 피하기 위해 생성 된 캐시 구현 원이를 저장하고 메모리 캐시 객체 (예 : 카페인)가 캐시 getCache (문자열 이름)를 재현 한 후 원래 캐시 컨텐츠가 손실되는 상황을 피해야합니다. // 모든 캐시 이름 컬렉션을 반환합니다 <String> getCachenames ();}일반적인 주석
@Cachable : 주로 데이터 쿼리 방법에 적용됩니다
패키지 org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementtype; import java.lang.annotation.inherited; import java.lang.annotation.retrention; import java.lang.annotation.rantonpolicy; import java.lang.annotation.target; import java.util.concurrent.callable; import org.springframework.core.annotation.aliasfor; @target ({elementtype.method, electtype.type.type.type.type.type.runtime)@inherited@inheritested@interedpublic. Cachenames, Cachemanager는이 이름 @aliasfor ( "Cachenames") String [] value () default {}; @Aliasfor ( "value") String [] cachenames () default {}; // 캐시 키는 SPEL 표현식을 지원합니다. 기본값은 모든 매개 변수와 해당 해시 코드 (SimpleKey) String key () 기본값 ""으로 감싸는 객체입니다. // 캐시 키 생성기, 기본 구현은 SimpleKeyGenerator String keyGenerator () Default ""입니다. "; // string cachemanager () 기본값 "" "을 사용할 캐시 메너를 지정합니다. // 캐시 Parser String Cacheresolver () Default ""; // 캐시 조건, SPEL 표현식을 지원하며 만족스러운 조건이 충족 될 때만 캐시 데이터. 문자열 조건 () 기본값 "" "메소드를 호출하기 전과 후에 판단됩니다. // 조건이 충족되면 캐시가 업데이트되지 않으며, SPEL 표현식이 지원되고 () 기본값 ""이 메소드를 호출 한 후에 만 판단되지 않는 한 문자열이 지원됩니다. // 소스로 소스로 돌아와 실제 메소드로 돌아와서 데이터를 얻으려면 동기화를 유지 해야하는지 여부는? false 인 경우 cache.get (키) 메소드가 호출됩니다. true 인 경우 cache.get (키, 호출 가능) 메소드를 boolean sync () default false;}라고합니다. @CacheEvict : CLEAR CACHE, 주로 데이터 삭제 방법에 적용됩니다. 캐시 가능보다 두 가지 속성이 더 있습니다
패키지 org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementtype; import java.lang.annotation.inherited; import java.lang.annotation.retrention; import java.lang.annotation.rantonpolicy; import java.lang.annotation.target; import org.springframework.core.annotation.aliasfor; @target ({elementtype.method, elementtype.type})@rendent (retentionpolicy.runtime)@withered@rateed@interface cacheevict r in the attrection in the attrection in the attrection in the hertreation@retention (retentionpolicy.runtime)@ @Cachable // 모든 캐시 된 데이터를 지우려면 cache.evict (키) 메소드가 false시 호출됩니다. true 일 때 cache.clear () 메소드를 boolean allentries () default false라고합니다. // 메소드를 호출하기 전후에 캐시를 지우기 전에 boolean weforeInVocation () default false;}스프링 캐시는 스프링 부팅에 통합되었으며 다양한 캐시 구성을 제공합니다. 그것을 사용할 때는 사용할 캐시 (Enum Cachetype) 만 구성하면됩니다.
Spring Boot에 추가 된 확장 장치가 추가되어 Cachemanagercustomizer 인터페이스입니다. 이 인터페이스를 사용자 정의한 다음 다음과 같은 Cachemanager에 대한 설정을 만들 수 있습니다.
package com.itopener.demo.cache.redis.config; import java.util.map; import java.util.concurrent.concurrenthashmap; import org.springframewort.boot.autoconfigure.cache.cachemanagercustomizer; import org .springframework.redata.redis.redis.redis.redis.redis REASISCACHEMANAGERCUSTOMIZER는 CACHEMANAGERCUSTOMIZER <ReadiscacheManager> {@override public void customize (readiscachemanager cachemanager) {// 기본 만료 시간, 단위 seconds cachemanager.setdefaultexpiration (1000); cachemanager.setUsePrefix (false); map <string, long> 만료 = new ConcurrenthashMap <string, long> (); 만료. Cachemanager.setexpires (만료); }}이 콩을로드하십시오 :
package com.itopener.demo.cache.redis.config;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * @author fuwei.deng * @date December 22, 2017 at 10:24:54 am * @version 1.0.0 */@Configurationpublic class Cacheredisconfiguration {@bean public readiscachemanagercustomizer readiscachemanagercustomizer () {return new retisiscacemanagercustomizer (); }}일반적으로 사용되는 캐시는 Redis입니다. Redis는 Spring-Data-Redis 패키지에서 스프링 캐시 인터페이스를 구현합니다.
다음은 Readiscache 구현의 몇 가지 단점입니다.
1. 캐시가 실패한 순간 스레드가 캐시 데이터를 얻으면 NULL을 반환 할 수 있습니다. 그 이유는 다음 단계가 Rediscache의 구현에 있기 때문입니다.
따라서 키가 존재한다고 판단한 후 캐시가 실패하면 캐시에 데이터가 없으면 NULL을 반환합니다.
2. readiscaceManager에 null 값을 저장할 수있는 속성 (CachenullValues)은 기본적으로 False입니다. 즉, 널 값을 저장할 수 없으므로 캐시 침투 위험이 있습니다. 결함은이 속성이 최종 유형이며 객체가 생성자 방법을 통해 객체를 생성 할 때만 생성 될 수 있다는 것입니다. 따라서 캐시 침투를 피하기 위해 응용 프로그램에서 RediscacheManager Bean 만 선언 할 수 있습니다.
3. READISCACHEMANAGER의 속성은 구성 파일을 통해 직접 구성 할 수 없습니다. 그들은 Cachemanagercustomizer 인터페이스에서만 설정할 수 있습니다. 나는 개인적으로 그것이 편리하지 않다고 생각합니다.
카페인은 Google의 오픈 소스 Guava 설계 개념을 기반으로 한 고성능 메모리 캐시입니다. Java 8을 사용하여 개발되었습니다. Spring Boot가 도입 된 후 카페인이 점차 포기되었습니다. 카페인 소스 코드 및 소개 주소 : 카페인
카페인은 다양한 캐시 충전 전략 및 가치 재활용 전략을 제공하며 캐시 최적화에 큰 도움이 될 수있는 캐시 히트와 같은 통계도 포함합니다.
카페인을 도입하려면 http://www.vevb.com/article/134242.htm을 참조하십시오
여기서 우리는 다음 유형의 카페인 시간 기반 재활용 전략에 대해 간단히 이야기합니다.
처음에는 Redis 캐시가 사용 되더라도 네트워크 전송에 어느 정도의 소비가있을 것이라고 언급했습니다. 실제 애플리케이션에서는 변경 사항이 매우 낮은 데이터가 있으며 응용 프로그램 내에서 직접 캐시 할 수 있습니다. 실시간 요구 사항이 적은 일부 데이터의 경우 REDIS에 대한 액세스를 줄이고 응답 속도를 향상시키기 위해 특정 기간 동안 응용 프로그램 내에서 캐시 할 수도 있습니다.
Redis는 Spring-Data-Redis 프레임 워크에서 스프링 캐시를 구현하는 데 약간의 단점이 있으므로 사용할 때 일부 문제가 발생할 수 있으므로 원래 구현을 기반으로 확장하지 않습니다. 캐시 및 CacheManager 인터페이스를 구현하기 위해 구현 방법을 직접 참조합니다.
또한 일반적으로 애플리케이션이 여러 노드를 배포하고 있으며 첫 번째 레벨 캐시는 응용 프로그램 내의 캐시이므로 데이터가 업데이트되고 지우면 캐시를 정리하려면 모든 노드에 알려야합니다. Zookeeper, MQ 등과 같은이 효과를 달성하는 방법에는 여러 가지가 있지만 Redis 자체는 구독/게시 기능을 지원하므로 다른 구성 요소에 의존하지 않습니다. Redis 채널을 직접 사용하여 다른 노드에 캐시 작업을 정리하도록 알립니다.
다음은 스타터 캡슐화 단계와 스프링 부트 + 스프링 캐시의 소스 코드입니다.
속성 정의 구성 속성 클래스
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import java.util.hashmap; import java.util.hashset; import java.util.map; import java.util.set; import org.springframewort.context.configerationproperties fuwei.deng * @date 2018 년 1 월 29 일 오전 11:32:15 AM * @version 1.0.0 */ @configurationProperties (prefix = "spring.cache.multi") public cacherediscefeineproperties {private set <string> cachenames = new Hashset <> (); / ** NULL 값을 저장할지, 기본값, 캐시 침투 방지*/ 개인 부울 CachenullValues = true; / ** 캐시 이름을 기반으로 캐시 구현을 동적으로 생성 할 것인지, 기본적으로 true*/ private boolean dynamic = true; / ** 캐시 키의 접두사*/ 개인 문자열 CachePrefix; 개인 Redis redis = New Redis (); 개인 카페인 카페인 = 새로운 카페인 (); 공개 클래스 redis { / ** 글로벌 만료 시간, 단위 밀리 초, 기본 만료* / private long defaultexpiration = 0; / ** 만료 시간, 단위 밀리 초, 우선 순위는 defaultexpiration보다 높습니다*/ private map <string, long> 만료 = new Hashmap <> (); / ** 캐시가 업데이트 될 때 주제 이름의 다른 노드에 알림*/ private String topic = "캐시 : redis : caffeine : topic"; public long getDefaultexpiration () {return defaultexpiration; } public void setDefaultexpiration (Long defaultexpiration) {this.defaultexpiration = defaultexpiration; } public map <string, long> getExpires () {return 만료; } public void setexpires (map <string, long> 만료) {this.expires = 만료; } public String getTopic () {return icture; } public void settopic (문자열 주제) {this.topic = topic; }} 공개 클래스 카페인 { / ** 액세스 후 만료 시간, 밀리 초* / 개인 장기 후기 액세스; / ** 작성 후 만료 시간, 단위 밀리 초*/ 개인 장기 후기 작성; / ** 작성 후 시간을 새로 고침, 단위 밀리 초*/ 개인 긴 새로 고침 자료; / ** 초기화 크기*/ 개인 int 초기 범위; / ** 캐시 개체의 최대 수,이 숫자를 초과하기 전에 배치 된 캐시는 유효하지 않습니다*/ private long maxumSize; /** 캐시 객체가 가중치를 제공해야하므로 스프링 캐시 사용과 같은 시나리오에 적합하지 않으므로 아직 구성이 지원되지 않습니다*/// Private Long MaximumSeight; public long getexpiration ateRaccess () {return expirteRaccess; } public void setexpirated Access (Long ExpripirtAraccess) {this.expirate ateCcess = ExpirtAccess; } public long getexpirbestwerwrite () {return expirtwerwrite; } public void setexpirjectwerwrite (Long ExpirberTerwrite) {this.expirjectwerwrite = expirtterwrite; } public long getRefreshafterwrite () {return CompertOffterWrite; } public void setRefreshAfterWrite (Long CrefreshFterWrite) {this.RefreshAfterWrite = ReformateFterWrite; } public int getInitialCapacity () {return initialcapacity; } public void setInitialCapacity (int initialcapacity) {this.initialCapacity = 초기 커피; } public long getMaxumSize () {return maximsize; } public void setMaxumSize (긴 최대 규모) {this.maxumSize = 최대 규모; }} public set <string> getCachenames () {return Cachenames; } public void setCachenames (set <string> 캐시 이름) {this.cachenames = Cachenames; } public boolean iscachenullvalues () {return cachenullvalues; } public void setCachenullValues (부울 CachenullValues) {this.cachenullValues = CachenullValues; } public boolean isdynamic () {return dynamic; } public void setDynamic (부울 동적) {this.dynamic = dynamic; } public String getCachePrefix () {return cachepRefix; } public void setCachePrefix (String CachePrefix) {this.cachePrefix = CachePrefix; } public redis getredis () {return redis; } public void setredis (redis redis) {this.redis = redis; } public caffeine getcaffeine () {반환 카페인; } public void setcaffeine (카페인 카페인) {this.caffeine = 카페인; }} Spring Cache의 캐시 인터페이스를 구현하는 Abstract Class AbstractValueAdaptingCache가 있으며, 여기에는 빈 값의 포장 및 캐시 값의 포장이 포함되어 있으므로 캐시 인터페이스를 구현하고 AbstractValueAdaptingCache Abstract 클래스를 직접 구현할 필요가 없습니다.
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.lang.reflect.constructor; import java.util.map; import java.util.set; import java.util.concurrent.call.concall.concurrent.concurrent; java.util.concurrent.locks.reentrantlock; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.support.abstractvalueadaptingcache; import org.springframework.data.core.redistemplate; org.springframework.util.stringutils; import com.github.benmanes.caffeine.cache.cache; import com.itopener.cache.redis.caffeine.spring.autoconfigure.cacherediscafeineproperties;/** * @author fuwei. * @version 1.0.0 */public class readiscaffeinecache actract and acpractValueAdaptingCache {private final logger logger = loggerfactory.getLogger (readiscaffeinecache.class); 개인 문자열 이름; 개인 Redistemplate <객체, 객체> Redistemplate; 개인 캐시 <객체, 객체> 카페인 시카지; 개인 문자열 캐시 프리 픽스; 비공개 긴 defaultexpiration = 0; 개인지도 <String, Long> 만료; 개인 문자열 주제 = "캐시 : Redis : 카페인 : 주제"; 보호 된 Resiscaffeinecache (부울 anlownullValues) {super (allownullvalues); } public readiscaffeinecache (문자열 이름, Redistemplate <개체, 객체> redistemplate, cache <객체, 객체> Caffeinecache, CacherediscaffeineProperties cacherediscaffeineproperties) {super (cacherediscefeineproperties.iscacenullvalues ()); this.name = 이름; this.redistemplate = redistemplate; this.caffeinecache = Caffeinecache; this.cacheprefix = CacherediscaffeineProperties.getCachepRefix (); this.defaultexpiration = CacherediscaffeineProperties.getRedis (). getDefaultexPiration (); this.expires = CacherediscaffeineProperties.getRedis (). getExpires (); this.topic = CacherediscaffeineProperties.getRedis (). getTopic (); } @override public String getName () {return this.name; } @override public object getNativeCache () {reture this; } @suppresswarnings ( "선택 취소") @override public <t> t get (개체 키, 호출 가능 <t> valueloader) {object value = lookup (키); if (value! = null) {return (t) 값; } ReentrantLock Lock = New ReintrantLock (); {lock.lock (); 값 = 조회 (키); if (value! = null) {return (t) 값; } value = valueloader.call (); Object StoreValue = TostoreValue (Valueloader.call ()); put (키, StoreValue); 반환 (t) 값; } catch (예외 e) {try {class <?> c = class.forname ( "org.springframework.cache.cache $ valueRetrivealexception"); 생성자 <?> 생성자 = C. getConstructor (Object.Class, Callable.Class, Throwable.class); runtimeexception Exception = (runtimeexception) 생성기. 던지기 예외; } catch (Exception E1) {Throw New ImperalStateException (E1); }} 마침내 {lock.unlock (); }} @override public void put (객체 키, 개체 값) {if (! super.isallownullValues () && value == null) {this.evict (키); 반품; } long expire = getexpire (); if (expripe> 0) {redistemplate.opsforValue (). set (getkey (key), tostoreValue (value), fexire, timeUnit.milliseconds); } else {redistemplate.opsforValue (). set (getkey (key), tostoreValue (value)); } push (새 CACHEMESSAGE (this.Name, key)); caffeinecache.put (키, 값); } @override public valuewrapper putifabsent (객체 키, 객체 값) {Object Cachekey = getkey (키); 객체 prevvalue = null; // 분산 잠금을 사용하거나 Redis의 Setifabsent를 원자 작동 동기화 (key) {prevvalue = redistemplate.opsforValue (). get (cachekey); if (prevvalue == null) {long expire = getexpire (); if (expripe> 0) {redistemplate.opsforValue (). set (getkey (key), tostoreValue (value), fexire, timeUnit.milliseconds); } else {redistemplate.opsforValue (). set (getkey (key), tostoreValue (value)); } push (새 CACHEMESSAGE (this.Name, key)); caffeinecache.put (key, tostorevalue (value)); }} return tovaluewrapper (prevvalue); } @override public void evict (Object Key) {// 먼저 Redis에서 캐시 된 데이터를 지우고 카페 캐시를 먼저 지우고 캐시 캐시가 먼저 지워지면 캐시를 피하기 위해 캐시를 피하고 다른 요청이 Redis에서 Caffeine Redistemplate.delete (getkey (key))에로드됩니다. 푸시 (새 CACHEMESSAGE (this.name, key)); caffeinecache.invalidate (키); } @Override public void clear () {// 캐시 된 데이터를 Redis에서 지우고 카페 캐시를 지우고 카시 캐시를 먼저 지우려면 캐시 캐시가 먼저 지워지면 캐시를 피하면 Redis에서 Caffeine에서 다른 요청이로드됩니다. for (개체 키 : 키) {redistemplate.delete (키); } 푸시 (새 CACHEMESSAGE (this.Name, null)); caffeinecache.invalidateall (); } @Override Protected Object Lookup (Object Key) {Object CacheKey = GetKey (키); 객체 값 = caffeinecache.getifpresent (key); if (value! = null) {logger.debug ( "카페인으로부터 캐시 받기, 키는 다음과 같습니다. {}", cachekey); 반환 값; } value = redistemplate.opsforValue (). get (cachekey); if (value! = null) {logger.debug ( "Redis에서 캐시를 가져 와서 카페인에 넣으면 키는 다음과 같습니다. {}", Cachekey); cacheinecache.put (키, 값); } 반환 값; } private object getKey (Object Key) {return this.name.concat ( ":"). concat (stringUtils.isempty (cachepRefix)? key.toString () : cachepRefix.concat ( ":"). concat (key.tostring ()); } private long getexpire () {long expire = defaultexpiration; long cachenameexpire = Expires.get (this.name); Cachenameexpire == null을 반환하십시오. 만료 : cachenameexpire.longvalue (); } / ** * @description 캐시가 변경 될 때 다른 노드가 로컬 캐시를 정리하도록 통지 * @author fuwei.deng * @date 2018 년 1 월 31 일 오후 3:20:28 pm * @version 1.0.0 * / private void push (cachemessage message) {redistemplate.convertandSend (주제); } / ** * @description Clean Clean Clean Clean Cled * @author fuwei.deng * @date 2018 년 1 월 31 일 오후 3시 15 분 39 일 * @version 1.0.0 * @param key * / public void clearlocal (객체 키) {logger.debug ( "Clear Local Cache, Key는 : {}", 키); if (key == null) {caffeinecache.invalidateall (); } else {caffeinecache.invalidate (key); }}} Cachemanager 인터페이스를 구현하십시오
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.util.collection; import java.util.set; import java.util.concurrent.concurrenthashmap; import java.util.concurrentmap; import java org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.cache; import org.sprameframework.cache.cachemanager; import org.springframework.data.redis.core.redistemplate; import com.github.benmans.ceine.cache com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineproperties;/** * ** * @author fuwei.deng * @date 2018 년 1 월 26 일 오후 5:24:52 pm * @version 1.0.0 */public readister gger = Cachemanager emplements cachemanager empriver {public rogger {public rogger = loggerfactory.getLogger (readiscaffeineCacheManager.class); Private ConcurrentMap <String, Cache> Cachemap = New ConcurrEthashMap <String, Cache> (); 개인 Cacherediscaffeineproperties Cacherediscaffeineproperties; 개인 Redistemplate <객체, 객체> Redistemplate; 개인 부울 동적 = true; 개인 세트 <string> 캐시 이름; 공개 readiscaffeinecaffeinemanager (Cacherediscaffeineproperties cacherediscaffeineproperties, redistemplate <객체, 객체> redistemplate) {super (); this.cacherediscaffeineproperties = Cacherediscaffeineproperties; this.redistemplate = redistemplate; this.dynamic = CacherediscaffeineProperties.isdynamic (); this.cachenames = CacherediscaffeineProperties.getCachenames (); } @override public cache getCache (문자열 이름) {캐시 캐시 = cachemap.get (name); if (cache! = null) {return cache; } if (! dynamic &&! cachenames.contains (name)) {return cache; } CACHE = 새로운 READISCAFFEINECACHE (이름, RedistemPlate, Cache (), CacherediscaffeineProperties); 캐시 oldcache = cachemap.putifabsent (이름, 캐시); logger.debug ( "캐시 인스턴스 만들기, 캐시 이름은 다음과 같습니다. {}", name); oldcache == null? 캐시 : OldCache; } public com.github.benmanes.caffeine.cache.cache <Object, Object> CaffeineCache () {Caffeine <Object, Object> CacheBuilder = Caffeine.newbuilder (); if (CacherediscaffeineProperties.getCaffeine (). getExpirbest ataccess ()> 0) {CacheBuilder.expirtatecccess (CacherediscaffeineProperties.getCaffeine (). getExpirteRafterAccess (), timeUnit.milliseconds); } if (CacherediscaffeineProperties.getCaffeine (). getExpirbestwerwrite ()> 0) {CacheBuilder.expirjperadterWrite (CacheredIscaffeineProperties.getCaffeine (). GetExpipterwerWrite (), timeUnit.milliseconds); } if (CacherediscaffeineProperties.getCaffeine (). getInitialCapacity ()> 0) {CacheBuilder.initialCapacity (CacherediscaffeineProperties.getCaffeine (). GetInitialCapacity ()); } if (CacherediscaffeineProperties.getCaffeine (). getMaxumSize ()> 0) {CacheBuilder.MaxumSize (CacheredIscaffeineProperties.getCaffeine (). getMaxumSize ()); } if (CacherediscaffeineProperties.getCaffeine (). getRefreshafterwrite ()> 0) {CacheBuilder.RefreshAfterWrite (CacheredIscaffeineProperties.getCaffeine (). getRefreshfterWrite (), timeUnit.milliseConds); } return cacheBuilder.Build (); } @override public collection <string> getCachenames () {return this.cachenames; } public void clearlocal (문자열 캐시 이름, 객체 키) {캐시 캐시 = cachemap.get (Cachename); if (cache == null) {return; } readiscaffeinecache readiscaffeinecache = (Rediscaffeinecache) 캐시; readiscaffeinecache.clearlocal (키); }} Redis 메시지 게시/구독, 전송을위한 메시지 클래스
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.io.serializable;/** * @author fuwei.deng * @date 2018 1:31:17 PM * @version 1.0.0 */public class at and extation {/**/{public class appinsage emplements emplements emplements. SerialVersionUid = 5987219310442078193L; 개인 문자열 캐시 이름; 개인 객체 키; public cachemessage (String Cacheame, Object Key) {super (); this.cachename = 캐시 이름; this.key = 키; } public String getCachename () {return CacheName; } public void setCacheName (String Cachename) {this.cachename = Cachename; } public Object getKey () {리턴 키; } public void setkey (객체 키) {this.key = 키; }} Redis 메시지를 듣으려면 Messagelistener 인터페이스가 필요합니다
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.spramframework.data.redis.connection.message; 가져 오기; org.springframework.data.redis.connection.messagelistener; import org.springframework.data.redis.core.redistemplate;/** * @author fuwei.deng * @date 2018 년 1 월 30 일 5:22:33 PM * @version 1.0.0 */public classe at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at at and averseListner cachemessaGelistness MessagelistneS MessagelistneS Messagelistnes. logger = loggerfactory.getLogger (CacheMessAgelistener.class); 개인 Redistemplate <객체, 객체> Redistemplate; Private readisscaffeinecachemanager readiscaffeinecachemanager; Public CacheMessAgelistener (Redistemplate <Object, Object> Redistemplate, RediscaffeineCacheManager ResiscaffeineCacheManager) {super (); this.redistemplate = redistemplate; this.rediscaffeinecachemanager = readiscaffeinecachemanager; } @Override public void onMessage (메시지 메시지, byte [] 패턴) {cacheMessage cachemessage = (cachemessage) redistemplate.getValueserializer (). deserialize (message.getbody ()); logger.debug ( "recevice a redis 주제 메시지, 클리어 로컬 캐시, 캐시 이름은 {}, 키는 {}", cachemessage.getCachename (), cachemessage.getKey ()); READISCAFFEINECACHEMANAGER.CLEARLOCAL (CACHEMESSAGE.GETCACHENAME (), CACHEMESSAGE.GETKEY ()); }} 스프링 부팅 구성 클래스를 추가하십시오
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import org.springframework.bean.beans.AntoWired; import org.springframework.boot.autoconfigure.autoconfiguration; import org.springframework.boot.autoconfigure.condition.conditionalonbean; import org.springframework.boot.autoconfigure.data.redis.rediatoconfiguration; import org.springframework.context.properties.enableconfigurations org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.data.redis.core.RedisTemplate;import org.springframework.data.redis.listener.ChannelTopic;import org.springframework.data.redis.listener.RedisMessageListenerContainer;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.CacheMessageListener;import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support.RedisCaffeineCacheManager;/** * @author fuwei.deng * @date January 26, 2018 at 5:23:03 pm * @version 1.0.0 */@Configuration@AutoConfigureAfter(RedisAutoConfiguration.class)@EnableConfigurationProperties(CacheRedisCaffeineProperties.class)public class CacheRedisCaffeineAutoConfiguration { @Autowired private CacheRedisCaffeineProperties cacheRedisCaffeineProperties; @Bean @ConditionalOnBean(RedisTemplate.class) public RedisCaffeineCaffeine cacheManager cacheManager(RedisTemplate<Object, Object> redisTemplate) { return new RedisCaffeineCacheManager(cacheRedisCaffeineProperties, redisTemplate); } @Bean public RedisMessageListenerContainer redisMessageListenerContainer(RedisTemplate<Object, Object> redisTemplate, RedisCaffeineCacheManager redisCaffeineCacheManager) { RedisMessageListenerContainer redisMessageListenerContainer = new RedisMessageListenerContainer(); redisMessageListenerContainer.setConnectionFactory(redisTemplate.getConnectionFactory()); CacheMessageListener cacheMessageListener = new CacheMessageListener(redisTemplate, redisCaffeineCaffeineCacheManager); redisMessageListenerContainer.addMessageListener(cacheMessageListener, new ChannelTopic(cacheRedisCaffeineProperties.getRedis().getTopic())); return redisMessageListenerContainer; }}在resources/META-INF/spring.factories文件中增加spring boot配置扫描
# Auto Configureorg.springframework.boot.autoconfigure.EnableAutoConfiguration=/com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.CacheRedisCaffeineAutoConfiguration
接下来就可以使用maven引入使用了
<dependency> <groupId>com.itopener</groupId> <artifactId>cache-redis-caffeine-spring-boot-starter</artifactId> <version>1.0.0-SNAPSHOT</version> <type>pom</type></dependency>
在启动类上增加@EnableCaching注解,在需要缓存的方法上增加@Cacheable注解
package com.itopener.demo.cache.redis.caffeine.service;import java.util.Random;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.cache.annotation.CacheEvict;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.CachePut;import org.springframework.cache.annotation.Cacheable;import org.springframework.stereotype.Service;import com.itopener.demo.cache.redis.caffeine.vo.UserVO;import com.itopener.utils.TimestampUtil;@Servicepublic class CacheRedisCaffeineService { private final Logger logger = LoggerFactory.getLogger(CacheRedisCaffeineService.class); @Cacheable(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public UserVO get(long id) { logger.info("get by id from db"); UserVO user = new UserVO(); user.setId(id); user.setName("name" + id); user.setCreateTime(TimestampUtil.current()); return user; } @Cacheable(key = "'cache_user_name_' + #name", value = "userNameCache", cacheManager = "cacheManager") public UserVO get(String name) { logger.info("get by name from db"); UserVO user = new UserVO(); user.setId(new Random().nextLong()); user.setName (이름); user.setCreateTime(TimestampUtil.current()); return user; } @CachePut(key = "'cache_user_id_' + #userVO.id", value = "userIdCache", cacheManager = "cacheManager") public UserVO update(UserVO userVO) { logger.info("update to db"); userVO.setCreateTime(TimestampUtil.current()); return userVO; } @CacheEvict(key = "'cache_user_id_' + #id", value = "userIdCache", cacheManager = "cacheManager") public void delete(long id) { logger.info("delete from db"); }} properties文件中redis的配置跟使用redis是一样的,可以增加两级缓存的配置
#两级缓存的配置spring.cache.multi.caffeine.expireAfterAccess=5000spring.cache.multi.redis.defaultExpiration=60000#spring cache配置spring.cache.cache-names=userIdCache,userNameCache#redis配置#spring.redis.timeout=10000#spring.redis.password=redispwd#redis pool#spring.redis.pool.maxIdle=10#spring.redis.pool.minIdle=2#spring.redis.pool.maxActive=10#spring.redis.pool.maxWait=3000#redis clusterspring.redis.cluster.nodes=127.0.0.1:7001,127.0.0.1:7002,127.0.0.1:7003,127.0.0.1:7004,127.0.0.1:7005,127.0.0.1:7006spring.redis.cluster.maxRedirects=3
펼친
个人认为redisson的封装更方便一些
后续可以增加对于缓存命中率的统计endpoint,这样就可以更好的监控各个缓存的命中情况,以便对缓存配置进行优化
源码下载
starter目录:springboot / itopener-parent / spring-boot-starters-parent / cache-redis-caffeine-spring-boot-starter-parent
示例代码目录: springboot / itopener-parent / demo-parent / demo-cache-redis-caffeine
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.