Spring Boot integriert Spring -Cache und verfügt über mehrere Cache -Implementierungen wie Redis, Koffein, JCache, EHCACHE usw., aber wenn Sie nur einen Cache verwenden, hat es entweder einen großen Netzwerkverbrauch (wie z. B. Redis), oder es wird zu viel Speicherverbrauch (z. B. Koffein -Anwendungsspeicher -Cache). In vielen Szenarien können die Caches der ersten und zweiten Ebene kombiniert werden, um die Verarbeitungseffizienz der Anwendung in großem Maßstab zu verbessern.
Inhaltsbeschreibung:
Zu einem einfachen Verständnis besteht Cache darin, Daten aus langsameren Lesemedien zu lesen und mit einem schnelleren Lesen wie Festplatten-> Speicher auf Medium zu setzen. Normalerweise speichern wir Daten auf der Festplatte wie: Datenbank. Wenn Sie sie jedes Mal aus der Datenbank lesen, wirkt sich die Festplatte selbst auf die Lesegeschwindigkeit aus, sodass ein Speichercache wie Redis vorliegt. Sie können die Daten lesen und in den Speicher setzen, damit Sie die Daten direkt aus dem Speicher abrufen und zurückgeben können, was die Geschwindigkeit erheblich verbessern kann. Im Allgemeinen wird Redis jedoch getrennt in einen Cluster eingesetzt, sodass der Verbrauch im Netzwerk -IO stattfinden wird. Obwohl es Verbindungs -Pooling -Tools für die Verknüpfung mit dem Redis -Cluster gibt, wird die Datenübertragung immer noch zu einem gewissen Verbrauch kommen. Es gibt also einen In-App-Cache wie: Koffein. Wenn es Daten gibt, die die Kriterien im Anwendungs-Cache erfüllen, kann sie direkt verwendet werden, ohne dass sie über das Netzwerk zum Redis erhalten werden müssen, wodurch ein zweistufiger Cache gebildet wird. Der In-App-Cache wird als Cache der ersten Ebene bezeichnet, und Remote-Cache (z. B. Redis) wird als Cache auf zweiter Ebene bezeichnet.
Frühlingscache
Bei der Verwendung von Cache ist der folgende Vorgang im Allgemeinen der folgende:
Aus dem Flow -Diagramm ist ersichtlich, dass zur Verwendung von Cache eine Menge Cache -Vorgänge basierend auf der ursprünglichen Geschäftsabwicklung hinzugefügt wurden. Wenn diese mit der Geschäftsordnung verbunden sind, wird es bei der Entwicklung viel wiederholte Arbeit geben, und es ist nicht förderlich, das Geschäft auf der Grundlage des Code zu verstehen.
Spring Cache ist eine Cache-Komponente, die im Spring-Context-Paket basierend auf Annotation bereitgestellt wird. Es definiert einige Standardschnittstellen. Durch die Implementierung dieser Schnittstellen kann Cache durch Hinzufügen von Anmerkungen zur Methode erreicht werden. Dadurch wird das Problem vermieden, dass der Cache -Code mit der Geschäftsverarbeitung verbunden ist. Die Implementierung von Feder -Cache ist eine Erweiterung der Methode -Interface -Kapselung (MethodInterceptor) in Feder AOP. Natürlich wird auch Frühlings -AOP basierend auf Aspekt implementiert.
Es gibt zwei Kernoberflächen von Spring -Cache: Cache und CacheManager
Cache -Schnittstelle
Geben Sie spezifische Cache -Operationen an, z. B. das Einfügen, Lesen und Reinigen von Caches. Die im Spring Framework bereitgestellten Implementierungen sind:
Mit Ausnahme von Rediscache, das sich im Feder-Daten-Redis-Paket befindet, befinden sich die anderen im Grunde genommen im Paket für das Frühlingskontext-Support.
#Cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interface cache {// cachename, der Name des Cache. In der Standardimplementierung besteht Cachemanager beim Erstellen der Cache -Bean den CacheName. String GetName (); // Erhalten Sie den tatsächlichen Cache, wie z. Ich habe die tatsächliche Verwendung noch nicht gefunden. Ich kann nur Bohnen zur Verfügung stellen, die native Cache erhalten, damit einige Cache -Operationen oder -statistiken erweitert werden müssen. Objekt getNativeCache (); // Beachten Sie, dass der zurückgegebene Wert ValueWrapper ist. Um mit Nullwerten kompatibel zu sein, wird der Rückgabewert in eine Ebene eingewickelt und der tatsächliche Wert wird über den Get -Methode -Wert -Wrapper GET (Objektschlüssel) erhalten. // Den Cache -Wert über den Schlüssel abrufen, der den tatsächlichen Wert zurückgibt, dh den Rückgabewerttyp der Methode <T> T GET (Objektschlüssel, Klasse <T> Typ); // den Cache -Wert über den Schlüssel abrufen, können Sie Valueloader.call () verwenden, um die Methode mit @cacheable Annotation aufzurufen. Verwenden Sie diese Methode, wenn das Synchronisierungsattribut der @cacheable Annotation für true konfiguriert ist. Daher muss die Synchronisation der Rückgabequelle in die Datenbank innerhalb der Methode gewährleistet werden. Vermeiden Sie eine große Anzahl von Anfragen, um zur Quelle in die Datenbank zurückzukehren, wenn der Cache ausfällt. <t> T GET (Objektschlüssel, Callable <T> Valueloader); // Die Daten, die von der @cacheable Annotation -Methode zurückgegeben werden, in den Cache -Void Put (Objektschlüssel, Objektwert); // Setzen Sie den Cache nur dann ein, wenn der Cache keinen Schlüssel enthält. Der Rückgabewert sind die ursprünglichen Daten, wenn der Schlüssel existiert ValueWrapper putIfabSent (Objektschlüssel, Objektwert). // Cache void evict (Objektschlüssel) löschen; // Alle Daten im Cache löschen. Es ist zu beachten, dass in der spezifischen Implementierung nur alle mit @cacheable Annotation zwischengespeicherten Daten gelöscht werden und andere Caches in der Anwendung void Clear () nicht beeinflussen. // Wrapper Der Cache -Return Value Interface ValueWrapper {// Rückgabe des tatsächlichen Zwischenobjekts Get (); } // Wenn eine Ausnahme von {@link #get (Object, Callable)} ausgeliefert wird, wird diese Ausnahme von @SuppressWarnings ("Serial") KlassenvaluerTeTexception erweitert. public ValuerTeRievalexception (Objektschlüssel, Callable <?> Loader, Throwable Ex) {Super (string.format ("Wert für Schlüssel '%s' konnte mit '%s'", Key, Loader), Ex) nicht geladen werden; this.key = key; } öffentliches Objekt getKey () {return this.key; }}}CacheManager -Schnittstelle
Es bietet hauptsächlich die Erstellung von Cache -Implementierungsbohnen. Jede Anwendung kann den Cache über CacheName isolieren, und jeder Cachenamen entspricht einer Cache -Implementierung. Die Implementierung des Spring -Frameworks und die Implementierung von Cache werden paarweise angezeigt, und die Paketstruktur befindet sich ebenfalls in der obigen Abbildung.
#CACHEMANAGER.JAVAPACKAGE org.springframework.cache; importieren java.util.collection; öffentliche Schnittstelle CacheManager {// Erstellen Sie die Cache -Implementierungsbean über CacheName. Die spezifische Implementierung muss die erstellten Cache -Implementierungsbohnen speichern, um eine wiederholte Erstellung zu vermeiden, und zu vermeiden, dass die Situation, in der der ursprüngliche Cache -Inhalt nach dem Speicher -Cache -Objekt (wie Koffein) verloren geht, Cache getCache (String -Name) nachgebildet ist. // Alle CacheName Collection <String> getCacheNames () zurückgeben;}Gemeinsame Anmerkungen
@Cacheable: hauptsächlich auf die Methode zur Abfrage von Daten angewendet
Paket org.springframework.cache.Annotation; Import Java.lang.Annotation.Documented; Import Java.lang.Annotation.elementtype; Import Java.lang.annotation java.lang.annotation.target; import Java.util.concurrent.callable; import org.springframework.core.annotation CacheNames, CacheManager erstellt die entsprechende Cache -Implementierungsbean über diesen Namen @aliasfor ("CachenNames") String [] value () Standard {}; @Aliasfor ("value") string [] cacheNames () Standard {}; // Cache -Taste unterstützt Spelausdrücke. Die Standardeinstellung ist ein Objekt, das von allen Parametern und deren HashCode (SimpleKey) String key () Standard "" "" "" "; // Cache -Tastengenerator ist die Standardimplementierung von SimpleKeyGenerator String keygenerator () Standard ""; // Geben Sie an, welcher CACHEMANAGER STRING CACHEMANAGER () Standard ""; // Cache Parser String cacheresolver () Standard ""; // Cache -Bedingung unterstützt die Spel -Expression und Cache -Daten nur, wenn die zufriedenstellenden Bedingungen erfüllt sind. String Condition () Standard "" wird vor und nach dem Aufrufen der Methode beurteilt. // Der Cache wird nicht aktualisiert, wenn die Bedingung erfüllt ist, der Spel -Ausdruck unterstützt wird und der String, es sei denn, () Standard "" "wird erst nach dem Aufrufen der Methode beurteilt. // Bei der Rückkehr zur Quelle zur tatsächlichen Methode, um Daten zu erhalten, ob synchronisiert werden soll? Wenn falsch, wird die Methode cache.get (Schlüssel) aufgerufen; Wenn wahr, wird die Methode cache.get (Schlüssel, Callable) als Boolean Sync () Standard False;} bezeichnet @Cacheevict: Cache löschen, hauptsächlich auf die Methode zum Löschen von Daten angewendet. Es gibt zwei weitere Eigenschaften als zwischengespeichert
Paket org.springframework.cache.Annotation; Import Java.lang.Annotation.Documented; Import Java.lang.Annotation.elementtype; Import Java.lang.annotation java.lang.annotation.target; import org.springframework.core.annotation @Cacheable // Ob alle zwischengespeicherten Daten gelöscht werden, wird die Methode cache.evict (Schlüssel) aufgerufen, wenn falsch; Wenn der wahr ist, wird die Methode cache.clear () als Boolean Allentries () Standard Falsch bezeichnet. // Cache vor oder nach dem Aufrufen der Methode boolean vorinvocation () Standard Falsch;}
Spring -Cache wurde in Spring Boot integriert und bietet eine Vielzahl von Cache -Konfigurationen. Bei der Verwendung müssen Sie nur den Cache (Enum Cachetype) konfigurieren.
Der Spring -Boot ist eine zusätzliche Erweiterungssache hinzugefügt, die die CachemanAGercustomizer -Schnittstelle ist. Sie können diese Schnittstelle anpassen und dann einige Einstellungen für CacheManager vornehmen, z. B.:
Paket com.itopener.demo.cache.redis.config; import Java.util.map; import Java.util.Concurrent.ConcurrentHasMap; RediscaCheManagerCustomizer implementiert CacheManAgerCustomizer <EdiscaCheManager> {@Override public void Customize (rediscaCheManager CacheManager) {// Default -Ablaufzeit, Einheit Sekunden CacheManager.SetDefaultExpiration (1000); CACHEMANATER.SETUSEPREFIX (FALSE); Map <String, Long> expires = new ConcurrentHasMap <String, Long> (); läuft.put ("userIdcache", 2000l); CACHEMANAGER.SETEXPIRES (läuft ab); }}Laden Sie diese Bohne:
Paket com.itopener.demo.cache.redis.config; import org.springframework.context.annotation.bean; Klasse Cacheredisconfiguration {@Bean public rediscaCheManAGerCustomizer rediscaCheManAGerCustomizer () {return New rediscaCheManAGerCustomizer (); }}Der häufig verwendete Cache ist Redis. Redis implementiert die Feder-Cache-Schnittstelle im Spring-Data-Redis-Paket.
Hier sind einige der Mängel in der Rediscache -Implementierung, denke ich:
1. Im Moment, in dem der Cache fehlschlägt und ein Thread die Cache -Daten erhält, kann er NULL zurückgeben. Der Grund dafür ist, dass die folgenden Schritte in der Implementierung von Rediscache liegen:
Wenn der Cache nach der Beurteilung des Schlüssels fehlschlägt, und dann keine Daten zu erhalten, wird NULL zurückgegeben.
2. Das Attribut (CachenullValues), das Nullwerte in rediscaCheManager speichern darf, ist standardmäßig falsch, dh es darf Nullwerte nicht speichern, was die Penetration des Cache riskiert. Der Defekt ist, dass diese Eigenschaft endgültig ist und das Objekt nur erstellt werden kann, wenn das Objekt über die Konstruktormethode erstellt wird. Um die Cache -Penetration zu vermeiden, können Sie daher nur die RediscaCheManager -Bohne in der Anwendung deklarieren.
3. Die Eigenschaften in rediscaCheManager können nicht direkt über Konfigurationsdateien konfiguriert werden. Sie können nur in der CachemanAGercustomizer -Schnittstelle eingestellt werden. Ich persönlich finde es nicht bequem.
Koffein ist ein Hochleistungsspeicher-Cache, das auf dem Open-Source-Guava-Designkonzept von Google basiert. Es wurde mit Java 8 entwickelt. Nachdem Spring Boot Koffein eingeführt hat, wurde die Guavenintegration allmählich aufgegeben. Koffeinquellcode und Einführung Adresse: Koffein
Koffein bietet eine Vielzahl von Cache -Füllstrategien und Wertrecyclingstrategien sowie Statistiken wie Cache -Treffer, die eine große Hilfe bei der Cache -Optimierung bieten können.
Die Einführung von Koffein finden Sie unter: http://www.vevb.com/article/134242.htm
Hier sprechen wir kurz über die folgenden Arten von zeitbasierten Recyclingstrategien von Koffein:
Ich habe am Anfang erwähnt, dass auch wenn Redis -Cache verwendet wird, ein gewisses Maß an Verbrauch bei der Netzwerkübertragung auftritt. In den tatsächlichen Anwendungen werden einige Daten mit sehr geringen Änderungen vorliegen, die direkt innerhalb der Anwendung zwischengespeichert werden können. Für einige Daten mit weniger Echtzeitanforderungen kann sie auch innerhalb der Anwendung für einen bestimmten Zeitraum zwischengespeichert werden, um den Zugriff auf Redis zu verringern und die Reaktionsgeschwindigkeit zu verbessern
Da Redis einige Mängel bei der Implementierung von Spring-Cache im Spring-Data-Redis-Framework enthält, können einige Probleme bei der Verwendung auftreten, sodass wir es nicht auf der Grundlage der ursprünglichen Implementierung erweitern. Wir werden direkt auf die Implementierungsmethode verweisen, um die Schnittstellen von Cache und CacheManager zu implementieren
Es sollte auch beachtet werden, dass Anwendungen im Allgemeinen mehrere Knoten bereitstellen und der Cache der ersten Stufe ein Cache in der Anwendung ist. Wenn die Daten aktualisiert und gelöscht werden, müssen alle Knoten benachrichtigt werden, um den Cache zu beseitigen. Es gibt viele Möglichkeiten, diesen Effekt zu erzielen, wie z. Es verwendet direkt den Redis -Kanal, um andere Knoten zur Bereinigung von Cache -Vorgängen zu benachrichtigen.
Im Folgenden finden Sie die Starterkapselungsschritte und den Quellcode für Spring Boot + Spring Cache, um zwei Ebenen Cache (Redis + Coffein) zu implementieren.
Definieren Sie Eigenschaftenkonfigurationseigenschaftsklasse
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure; import Java.util.hashMap; Import Java.util.hashset; Import Java.util.map; @Author Fuwei / ** ob Nullwerte gespeichert werden, standardmäßig true, verhindern Sie die Cache -Penetration*/ privat boolean cachenullvalues = true; / ** Ob die Cache -Implementierung dynamisch basierend auf CacheName erstellt werden soll, Standard true*/ private boolean dynamic = true; / ** Präfix des Cache -Schlüssels*/ private String Cacheprefix; private Redis Redis = new Redis (); privates Koffein Koffein = neues Koffein (); öffentliche Klasse Redis { / ** Globale Ablaufzeit, Einheit Millisekunden, Standardablauf* / privat Long DefaultExpiration = 0; / ** Ablaufzeit, Einheiten -Millisekunden, Priorität ist höher als defaultExpiration*/ private map <String, Long> abläuft = new Hashmap <> (); / ** Benachrichtigen Sie andere Knoten von Themennamen, wenn Cache -Updates*/ privat String topic = "Cache: Redis: Coffein: Thema"; public long getDefaultExpiration () {return defaultExpiration; } public void setDefaultExpiration (long defaultExpiration) {this.DefaultExpiration = defaultExpiration; } public map <string, long> getExpires () {return verfällt; } public void setExpires (MAP <String, Long> läuft ab) {this.expires = abläuft; } public String gettopic () {return topic; } public void settopic (String -Thema) {this.topic = Thema; }} öffentliche Klasse Koffein { / ** Ablaufzeit nach dem Zugang, in Millisekunden* / privat langer Zeitpunkt; / ** Ablaufzeit nach dem Schreiben, Millisekunden in Einheiten*/ privater langer Ablauf der Nachfolger; / ** Nach dem Schreiben aktualisieren, Einheit Millisekunden*/ Private Long Refreshastwrite; / ** Initialisierungsgröße*/ privat init initialCapacity; / ** Die maximale Anzahl von Cache -Objekten, der vorübergeordnete Cache, der diese Zahl überschreitet, ist ungültig*/ privat lang Maximumsize; /** Da das Gewicht von Cache -Objekten bereitgestellt werden muss, ist es nicht sehr geeignet für Szenarien wie die Verwendung von Feder -Cache. Daher wird die Konfiguration noch nicht unterstützt*/// Private Long Maximumgewicht. public long getexprea.Access () {return expiresAccess; } public void setExpreaAnAccess (langlebiger Access) {this.exprea.Access = ExpirateAccess; } public long getexpreaMeWrite () {return expireswrite; } public void setExpreaNWrite (langlebiger Verlaufswrite) {this.expreaaNWrite = Expireamwrite; } public Long GetReFreshAfterWrite () {requitreesreswrite return; } public void setReFreshafterWrite (Long Refreshafrite) {this.refreshafterwrite = refreshafterWrite; } public int getInitialCapacity () {Return initialCapacity; } public void setInitialCapacity (init initialCapacity) {this.initialCapacity = initialCapacity; } public Long GetMaximumsize () {maximalSize return; } public void setMaximumsize (lange maximalsize) {this.maximumsize = maximalSize; }} public set <string> getCacheNames () {return cachenNames; } public void setCacheNames (set <string> cacheNames) {this.cachenAnames = cacheNames; } public boolean iscachenullValues () {return cachenullvalues; } public void setCachenUllValues (boolean cachenullvalues) {this.cachenullValues = cachenullValues; } public boolean isdynamic () {return dynamic; } public void setdynamic (boolean dynamic) {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; } öffentliches Koffein getCaffein () {return Coffein; } public void setCaffein (Koffein Coffein) {this.caffein = Coffein; }} Es gibt eine abstrakte AbstractValueAdaptingCache, die die Cache -Schnittstelle im Feder -Cache implementiert, das die Verpackung leerer Werte und die Verpackung von Cache -Werten enthält, sodass die Cache -Schnittstelle nicht implementiert und die AbstractValueadaptingCache -Abstract -Klasse direkt implementiert werden muss.
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure.support; Import Java.lang.Reflect.Constructor; Import Java.util.map; Import Java.util.set; java.util.concurrent org.springframework 17:24:11 PM * @VERSION 1.0.0 */public class rediscaffeinecache erweitert abstractValueadapingCache {private final logger logger = loggerfactory.getLogger (rediscaffeinecache.class); privater Zeichenfolge Name; private redistemplate <Objekt, Objekt> redistemplate; privates Cache <Objekt, Objekt> Caffeinecache; private Zeichenfolge Cacheprefix; private long defaultExpiration = 0; private map <string, lang> läuft ab; private String topic = "Cache: Redis: Koffein: Thema"; geschützte rediscaffeinecache (boolean alownullvalues) {super (alownullvalues); } public rediscaffeinecache (String -Name, Redistemplate <Objekt, Objekt> Redistemplate, Cache <Objekt, Objekt> Caffeinecache, cacherediscaffineproperties cacherediscaffineproperties) {Super (CacherediscaffeinProperties.iscacheLvalues ()); this.name = name; this.redistemplate = redistemplate; this.caffeinecache = caffeinecache; this.cacheprefix = cacherediscaffineProperties.getCacheprefix (); this.defaultExpiration = cacherediscaffineProperties.getRedis (). getDefaultExpiration (); this.expires = cacherediscaffineProperties.getRedis (). getExpires (); this.topic = cacherediscaffineProperties.getRedis (). Gettopic (); } @Override public String getName () {return this.name; } @Override public Object GetNATTIVECache () {return this; } @SuppressWarnings ("Deaktiviert") @Override public <T> T GET (Objektschlüssel, Callable <T> Valueloader) {Object Value = Lookup (Schlüssel); if (value! = null) {return (t) Wert; } Reentrantlock lock = new Reentrantlock (); try {lock.lock (); value = suchup (Schlüssel); if (value! = null) {return (t) Wert; } value = valueloader.call (); Object storeValue = tostoreValue (Valueloader.call ()); Put (Schlüssel, StoreValue); Rückgabe (t) Wert; } catch (Ausnahme e) {try {class <?> c = class.forname ("org.springFramework.cache.cache $ valuerTrevalexception"); Constructor <?> Constructor = C.GetConstructor (Object.Class, Callable.class, Throwable.class); RunTimeException Exception = (RunTimeException) Constructor.NewinStance (Schlüssel, Valueloader, E.getCause ()); Ausnahme werfen; } catch (Ausnahme E1) {neue IllegalStateException (E1) werfen; }} endlich {lock.unlock (); }} @Override public void put (Objektschlüssel, Objektwert) {if (! Super.isallownullValues () && value == null) {this.evict (key); zurückkehren; } long expire = getExpire (); if (ablauf> 0) {redistemplate.opsforValue (). set (getKey (key), tostorevalue (Wert), Ablauf, Zeiteinheit.Millisekunden); } else {redistemplate.opsforValue (). set (getKey (key), tostoreValue (value)); } push (New Cachemessage (this.name, key)); Caffeinecache.put (Schlüssel, Wert); } @Override public valueWrapper putifabSent (Objektschlüssel, Objektwert) {Objekt cachekey = getKey (Key); Objekt prevValue = null; // Überlegen Sie, ob verteilte Sperren verwendet werden oder redis 'setiFabSent in den atomarischen Betrieb synchronisiert (Schlüssel) {prevValue = redistemplate.opsforValue (). Get (cachekey); if (prevValue == null) {long expire = getExpire (); if (ablauf> 0) {redistemplate.opsforValue (). set (getKey (key), tostorevalue (Wert), Ablauf, Zeiteinheit.Millisekunden); } else {redistemplate.opsforValue (). set (getKey (key), tostoreValue (value)); } push (New Cachemessage (this.name, key)); Caffeinecache.put (Schlüssel, TostoreValue (Wert)); }} return toValueWrapper (prevValue); } @Override public void evict (Objektschlüssel) {// Löschen Sie zuerst die zwischengespeicherten Daten in Redis und löschen Sie den Cache in Koffein, um den Cache in kurzer Zeit zu vermeiden, wenn der Cache -Cache zuerst gelöscht wird, und andere Anfragen werden von Redis zu Koffein -Redistemplatten geladen. Push (New Cachemessage (this.name, Schlüssel)); Caffeinecache.invalidat (Schlüssel); } @Override public void clear () {// Die zwischengespeicherten Daten in Redis löschen und dann den Caffe in Koffein löschen, um den Cache in kurzer Zeit zu vermeiden, wenn der Cache -Cache zuerst gelöscht wird, werden andere Anfragen von Redis zu Koffein von redis set <objekte> redistemplate.KEYS.KEYS (is.name für (Objektschlüssel: Schlüssel) {redistemplate.delete (Schlüssel); } push (New Cachemessage (this.name, null)); Caffeinecache.invalidateAll (); } @Override Protected Object Lookup (Objektschlüssel) {Objekt cachekey = getKey (Key); Objektwert = caffeinecache.getIfPresent (Schlüssel); if (value! Rückgabewert; } value = redistemplate.opsforValue (). get (cachekey); if (value! cacheInecache.put (Schlüssel, Wert); } Rückgabewert; } private Object getKey (Objektschlüssel) {return this.name.concat (":"). concat (stringutils.isempty (cacheprefix)? key.toString (): cacheprefix.concat (":"). concat (key.toString ()); } private long getexpire () {long expire = defaultExpiration; Long CacheNameExpire = läuft.get (this.name); CacheNameExpire zurückgeben == NULL? Ablauf: cachenameExpire.longValue (); } / ** * @Description benachrichtigen Sie andere Knoten, um den lokalen Cache zu bereinigen, wenn sich der Cache ändert } / ** * @Description den lokalen Cache * @Author fuwei if (key == null) {caffeinecache.invalidateAll (); } else {Caffeinecache.invalidat (Schlüssel); }}} Implementieren Sie die CacheManager -Schnittstelle
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure.support; Import Java.util.Collection; Import Java.util.set; Import Java.util.Concurent.ConcurentHasMap; java.util.concurrent com.github.benmanes.caffein.cache.caffein; import com.itopener.cache.redis.caffein.spring.boot.autoconfigure.cacherediscaffineproperties; Rediscaffeinecachemanager implementiert CacheManager (private Final Logger Logger = loggerfactory.getLogger (rediscaffeineCacheManager.Class); private concurrentmap <string, cache> cachemap = new ConcurrentHasMap <String, Cache> (); private cacherediscaffineproperties cacherediscaffeinproperties; private redistemplate <Objekt, Objekt> redistemplate; private boolean dynamic = true; private set <string> cachenNames; public rediscaffeinecaffeinemanager (cacherediscaffeinproperties cacherediscaffineproperties, redistemplate <Objekt, Objekt> redistemplate) {Super (); this.cacherediscaffeinproperties = cacherediscaffeinproperties; this.redistemplate = redistemplate; this.dynamic = cacherediscaffeinproperties.isdynamic (); this.cacheNames = cacherediscaffineProperties.getCacheAnes (); } @Override public cache getCache (String -Name) {cache cache = cachemap.get (name); if (cache! = null) {return cache; } if (! dynamic &&! cachenNames.contains (name)) {return cache; } cache = neuer rediscaffeinecache (Name, redistemplate, cache (), cacherediscaffeinproperties); Cache oldcache = cachemap.putiFabSent (Name, Cache); logger.debug ("Cache -Instanz erstellen, der Cache -Name ist: {}", Name); oldcache == null zurückgeben? Cache: OldCache; } public com.github.benmanes.caffein.cache.cache <Objekt, Objekt> Caffeinecache () {Coffein <Objekt, Objekt> cachebuilder = Coffein.newbuilder (); if (cacherediscaffeinproperties.getCaffein (). } if (cacherediscaffineProperties.getCaffein (). } if (cacherediscaffineProperties.getCaffein (). } if (cacherediscaffineProperties.getCaffein (). getMaximumsize ()> 0) {cacheBuilder.maximumsize (cacherediscaffineProperties.getCaffein (). getmaximumsize ()); } if (cacherediscaffineProperties.getCaffine (). } return cacheBuilder.build (); } @Override public collection <string> getCacheNames () {return this.cacheNames; } public void clearLocal (String CacheName, Objektschlüssel) {cache cache = cachemap.get (cacheName); if (cache == null) {return; } Rediscaffeinecache rediscaffeinecache = (rediscaffeinecache) cache; rediscaffeinecache.clearLocal (Schlüssel); }} Redis -Nachricht veröffentlichen/abonnieren, Nachrichtenklasse für die Übertragung
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure.support; import Java.io.Serializable;/** * @Author fuwei Finale lange Serialversionuid = 5987219310442078193L; private Zeichenfolge Cachenname; privater Objektschlüssel; public Cachemessage (String CachenName, Objektschlüssel) {Super (); this.cacheName = cacheName; this.key = key; } public String getCacheName () {return cacheName; } public void setCacheName (String cacheName) {this.cacheName = cacheName; } öffentliches Objekt getKey () {return key; } public void setKey (Objektschlüssel) {this.key = key; }} Das Anhören von Redis -Nachrichten erfordert die Messagelistener -Schnittstelle
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure.support; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.data.redis.connection.message; org.springframework.data.redis.connection.messagelistener; import org.springframework.data.redis.core.redistemplate;/** * @Author fuwei {private final logger logger = loggerfactory.getLogger (cachemessagelistener.class); private redistemplate <Objekt, Objekt> redistemplate; private rediscaffeineCacheManager rediscaffeineCacheManager; public cachemessagelistener (redistemplate <Objekt, Objekt> redistemplate, rediscaffeinecachemanager rediscaffeinecachemanager) {super (); this.redistemplate = redistemplate; this.rediscaffeineCacheManager = RediscaffineCacheManager; } @Override public void onMessage (Nachrichtennachricht, byte [] muster) {cachemessage cachemessage = (cachemessage) redistemplate.getValueSerializer (). Deserialize (message.getBody ()); logger.debug ("Aufrechnung einer Redis -Themennachricht, Löschen lokaler Cache, der CacheName ist {}, der Schlüssel ist {}", cachemessage.getCacheName (), cachemessage.geKey ()); rediscaffineCacheManager.ClearLocal (Cachemessage.getCacheName (), Cachemessage.getKey ()); }} Feder -Boot -Konfigurationsklasse hinzufügen
Paket com.itopener.cache.redis.caffine.spring.boot.autoconfigure; import org.springframework.bean.factory.Annotation.autowired; org.springframework.boot.autoconfigure.condition.conditionalonbean; import org.springframework.boot.autoconfigure.data.Redis.Redisautoconfiguration; Import org 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 (name); 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
Erweitert
个人认为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
Das obige ist der gesamte Inhalt dieses Artikels. Ich hoffe, es wird für das Lernen aller hilfreich sein und ich hoffe, jeder wird Wulin.com mehr unterstützen.