Spring Boot intègre Spring Cache et dispose de plusieurs implémentations de cache, telles que Redis, Caffeine, JCache, Ehcache, etc. Mais si vous n'utilisez qu'un seul cache, soit il aura une grande consommation de réseau (tel que Redis), ou il aura trop d'utilisation de la mémoire (comme le cache de mémoire de la caféine de la caféine). Dans de nombreux scénarios, les caches de premier et deuxième niveau peuvent être combinés pour obtenir une amélioration à grande échelle de l'efficacité de traitement de l'application.
Description du contenu:
Pour une compréhension simple, Cache consiste à lire les données des supports de lecture plus lents et à le mettre sur le milieu avec une lecture plus rapide, comme le disque -> Memory. Habituellement, nous stockons des données sur le disque, telles que: base de données. Si vous le lisez à partir de la base de données à chaque fois, le disque lui-même affectera la vitesse de lecture, il y aura donc un cache de mémoire comme Redis. Vous pouvez lire les données et les mettre en mémoire, afin que lorsque vous devez obtenir les données, vous pouvez obtenir directement les données de la mémoire et les renvoyer, ce qui peut améliorer considérablement la vitesse. Cependant, Redis est généralement déployé séparément dans un cluster, il y aura donc la consommation sur le réseau IO. Bien qu'il existe des outils de regroupement de connexions pour se lier au cluster Redis, il y aura toujours une certaine consommation dans la transmission de données. Il y a donc un cache dans l'application, comme: la caféine. Lorsqu'il y a des données qui répondent aux critères du cache d'application, il peut être utilisé directement sans avoir à l'obtenir via le réseau pour Redis, formant ainsi un cache à deux niveaux. Le cache dans l'application est appelé cache de premier niveau, et le cache distant (comme redis) est appelé cache de deuxième niveau
cache de printemps
Lors de l'utilisation du cache, le processus suivant est généralement le suivant:
Il peut être vu à partir du graphique de flux que pour utiliser le cache, de nombreuses opérations de cache ont été ajoutées en fonction du traitement commercial d'origine. Si ceux-ci sont couplés au code commercial, il y aura beaucoup de travail répétitif lors du développement, et il n'est pas propice à la compréhension de l'entreprise en fonction du code.
Spring Cache est un composant de cache fourni dans le package Spring-Context basé sur l'annotation. Il définit certaines interfaces standard. En implémentant ces interfaces, le cache peut être réalisé en ajoutant des annotations à la méthode. Cela évitera que le problème du code de cache soit couplé au traitement commercial. L'implémentation du cache de ressort est une extension de l'interface de la méthode (méthode Interceptor) dans le printemps AOP. Bien sûr, Spring AOP est également mis en œuvre en fonction de l'aspect.
Il y a deux interfaces de base de Cache Spring: Cache et CacheManager
Interface de cache
Fournir des opérations de cache spécifiques, telles que la mise en place, la lecture et le nettoyage des caches. Les implémentations fournies par le cadre Spring sont:
À l'exception de Rediscache, qui se trouve dans le package de printemps-data-redis, les autres se trouvent essentiellement dans le package de support Spring-Context.
# Cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interface cache {// cachename, le nom du cache. Dans l'implémentation par défaut, CacheManager passe Cachename lors de la création du haricot de cache. String getName (); // Obtenez le cache réel, tel que: Redemplate, com.github.benmanes.caffeine.cache.cache <objet, objet>. Je n'ai pas encore trouvé l'utilisation réelle. Je peux simplement fournir des haricots qui obtiennent un cache natif afin que certaines opérations de cache ou statistiques doivent être étendues. Objet getNatiVECache (); // Obtenez la valeur de cache via la touche, notez que la valeur renvoyée est ValueWrapper. Afin d'être compatible avec les valeurs nulles, la valeur de retour est enveloppée dans une couche et la valeur réelle est obtenue via la méthode Get ValueWrapper Get (clé d'objet); // Obtenez la valeur de cache via la touche, qui renvoie la valeur réelle, c'est-à-dire le type de valeur de retour de la méthode <T> T GET (clé d'objet, type <t> type); // Obtenez la valeur du cache via la clé, vous pouvez utiliser ValuEloader.Call () pour appeler la méthode en utilisant @cacheable annotation. Utilisez cette méthode lorsque l'attribut SYNC de l'annotation @cacheable est configuré dans true. Par conséquent, la synchronisation de la source de retour dans la base de données doit être assurée dans la méthode. Évitez une grande quantité de demandes de retour à la source dans la base de données lorsque le cache échoue. <T> T GET (clé d'objet, callable <T> VALUELOADER); // Mette les données renvoyées par la méthode d'annotation @cacheable dans le put de cache void (clé d'objet, valeur d'objet); // Mettez le cache uniquement lorsqu'il n'y a pas de clé dans le cache. La valeur de retour est les données d'origine lorsque la clé existe ValueWrapper Putifabsent (touche d'objet, valeur de l'objet); // supprimer le cache void Evict (clé d'objet); // supprime toutes les données du cache. Il convient de noter que dans l'implémentation spécifique, seules toutes les données mises en cache à l'aide de l'annotation @cacheable sont supprimées et n'affectent pas d'autres caches dans l'application void clear (); // Wrappers L'interface de valeur de retour de cache Valuewrapper {// renvoie l'objet objet cache réel get (); } // Lorsqu'une exception est lancée par {@Link #get (objet, callable)}, il sera enveloppé comme cette exception lancée par @SuppressWarnings ("Serial") classe ValueRereTrievalexception étend RuntimeException {Final Final Object Key; public ValueReTevieAxception (touche d'objet, Callable <?> Loader, Throwable Ex) {super (String.Format ("Valeur pour Key '% S' n'a pas pu être chargé à l'aide de '% S'", Key, Loder), ex); this.key = key; } public objet getKey () {return this.key; }}}Interface cachemanager
Il fournit principalement la création de haricots d'implémentation de cache. Chaque application peut isoler le cache via Cachename, et chaque Cachename correspond à une implémentation de cache. La mise en œuvre fournie par le cadre Spring et la mise en œuvre du cache apparaissent en paires, et la structure du package est également dans la figure ci-dessus.
# Cachemanager.javapackage org.springframework.cache; import java.util.collection; interface publique cacheManager {// créer le bean d'implémentation du cache via cachename. L'implémentation spécifique doit stocker les haricots d'implémentation de cache créés pour éviter la création répétée, et également éviter la situation où le contenu du cache d'origine est perdu après l'objet de cache de mémoire (comme la caféine) est le cache recréé GetCache (nom de chaîne); // Renvoie toute la collection Cachename <string> getCachenames ();}Annotations communes
@Cacheable: principalement appliqué à la méthode de requête des données
package org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; import java.lang.annotation.inherite java.lang.annotation.target; import java.util.concurrent.callable; import org.springframework.core.annotation.aliasfor; @target ({elementType.method, elementType.type}) @ rétention (rétentionpolicy.runtime) @ inherite Cachenames, CacheManager crée le bean d'implémentation de cache correspondant via ce nom @aliasfor ("cachenames") String [] value () default {}; @Aliasfor ("valeur") string [] cachenames () default {}; // clé de cache, prend en charge les expressions de Spel. La valeur par défaut est un objet enveloppé par tous les paramètres et leur HashCode (SimpleKey) String Key () par défaut ""; // Générateur de clé de cache, l'implémentation par défaut est la chaîne SimpleKeyGenerator keyGenerator () par défaut ""; // Spécifiez quel cacheManager utilise String cacheManager () par défaut ""; // Cache Parser String Cacheresolver () par défaut ""; // Condition de cache, prend en charge l'expression de Spel et les données de cache uniquement lorsque les conditions satisfaisantes sont remplies. String condition () par défaut "" sera jugé avant et après avoir appelé la méthode; // Le cache n'est pas mis à jour lorsque la condition est remplie, l'expression de Spel est prise en charge et la chaîne à moins que () par défaut "" ne soit jugée qu'après avoir appelé la méthode; // Lorsque vous retournez à la source à la méthode réelle pour obtenir des données, si vous devez rester synchronisé? Si false, la méthode cache.get (key) est appelée; Si vrai, la méthode cache.get (clé, callable) est appelée booléen sync () par défaut false;} @Cacheevict: cache effacer, principalement appliquée à la méthode de suppression des données. Il y a deux propriétés supplémentaires que cacheables
package org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; import java.lang.annotation.inherite java.lang.annotation.Target;import org.springframework.core.annotation.AliasFor;@Target({ElementType.METHOD, ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Inherited@Documentedpublic @interface CacheEvict { // ...For the same attribute description, please refer to the instructions in @Cacheable // Il faut effacer toutes les données mises en cache, la méthode cache.evict (clé) est appelée lorsqu'elle est fausse; Lorsque true, la méthode cache.clear () est appelée booléen allentries () par défaut false; // effacer le cache avant ou après avoir appelé la méthode booléen avantinvocation () par défaut false;}Spring Cache a été intégré dans Spring Boot et fournit une variété de configurations de cache. Lorsque vous l'utilisez, il vous suffit de configurer le cache (enum Cachetype) à utiliser.
Il y a une extension supplémentaire ajoutée à Spring Boot, qui est l'interface CacheManagerComzer. Vous pouvez personnaliser cette interface, puis créer des paramètres pour CacheManager, tels que:
Package com.itopener.demo.cache.redis.config; import java.util.map; import java.util.concurrent.concurrenthashmap; import org.springframework.boot.autoconfigure.cache.cachemanagerstomzizer; import org.springframework.data.redis.cache.rediscrywork; RedisCacheManagerStomzer implémente CacheManagerCustomzer <DenisCacheManager> {@Override public void personnaliser (redecacheManager Cachemanager) {// Temps d'expiration par défaut, unité secondes cacheManager.SetDefaultExpiration (1000); cacheManager.SetUtePrefix (false); Map <string, long> expires = new concurrenthashmap <string, long> (); expires.put ("userIdCache", 2000l); cacheManager.SetExpires (expire); }}Chargez ce haricot:
Package com.itopener.demo.cache.redis.config; import org.springframework.context.annotation.bean; import org.springframework.context.annotation.configuration; / ** * @Author fuwei.deng * @Date 22 décembre 2017 à 10:24:54 Am * @version 1.0.0.0 * / @ CONFICRAINTION CASH Cacheredisconfiguration {@bean public rediscacheManagerSomzer redisCacheManAgerCerSomnizer () {return new RedisCacheManagerStomzer (); }}Le cache couramment utilisé est redis. Redis implémente l'interface de cache de ressort dans le package de printemps-data-redis.
Voici quelques-unes des lacunes de la mise en œuvre de Rediscache, je pense:
1. Au moment où le cache échoue, si un thread obtient les données du cache, elle peut retourner null. La raison en est que les étapes suivantes concernent la mise en œuvre de Rediscache:
Par conséquent, lorsque le cache échoue après avoir jugé que la clé existe, puis l'obtention du cache n'a pas de données, elle renvoie Null.
2. L'attribut (cachenullvalues) qui est autorisé à stocker les valeurs nulles dans RedeiscacheManager est faux par défaut, c'est-à-dire qu'il n'est pas autorisé à stocker les valeurs nulles, ce qui risquera la pénétration du cache. Le défaut est que cette propriété est de type final, et l'objet ne peut être créé que lorsque l'objet est créé via la méthode du constructeur. Par conséquent, pour éviter la pénétration du cache, vous ne pouvez déclarer que le haricot de redécacheur dans l'application.
3. Les propriétés de RedisCacheManager ne peuvent pas être directement configurées via des fichiers de configuration. Ils ne peuvent être définis que dans l'interface CacheManagerCustomzer. Je pense personnellement que ce n'est pas pratique.
La caféine est un cache de mémoire haute performance basé sur le concept de conception de goyave open source de Google. Il est développé à l'aide de Java 8. Après que Spring Boot a introduit la caféine, l'intégration de goyave a été progressivement abandonnée. Code source de la caféine et adresse d'introduction: caféine
La caféine fournit une variété de stratégies de remplissage de cache et de stratégies de recyclage de valeur, et comprend également des statistiques telles que les tubes en cache, qui peuvent fournir une grande aide dans l'optimisation du cache.
Pour l'introduction de la caféine, veuillez vous référer à: http://www.vevb.com/article/134242.htm
Ici, nous parlons brièvement des types suivants de stratégies de recyclage basées sur la caféine:
J'ai mentionné au début que même si le cache redis est utilisé, il y aura un certain degré de consommation sur la transmission du réseau. Dans les applications réelles, il y aura des données avec des modifications très faibles, qui peuvent être mises en cache directement dans l'application. Pour certaines données avec des exigences en temps réel moins, elles peuvent également être mises en cache dans l'application pendant une certaine période de temps pour réduire l'accès à Redis et améliorer la vitesse de réponse
Étant donné que Redis présente quelques lacunes dans la mise en œuvre du cache de printemps dans le cadre de printemps-data-redis, certains problèmes peuvent survenir lors de l'utilisation, nous ne l'étendrons donc pas en fonction de l'implémentation d'origine. Nous nous référerons directement à la méthode d'implémentation pour implémenter les interfaces Cache et CacheManager
Il convient également de noter que généralement les applications déploient plusieurs nœuds et que le cache de premier niveau est un cache dans l'application, donc lorsque les données sont mises à jour et effacées, tous les nœuds doivent être informés pour nettoyer le cache. Il existe de nombreuses façons d'atteindre cet effet, tel que: ZooKeeper, MQ, etc., mais comme Redis Cache est utilisé, Redis prend en charge les fonctions d'abonnement / publication, de sorte qu'elle ne s'appuie pas sur d'autres composants. Il utilise directement le canal Redis pour informer d'autres nœuds pour nettoyer les opérations de cache.
Voici les étapes d'encapsulation du démarreur et le code source du cache Spring Boot + Spring pour implémenter le cache à deux niveaux (redis + caféine)
Définir la classe de propriétés de configuration des propriétés
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.springframework.boot.context.properties.configurationproperts; / ** fuwei.deng * @Date 29 janvier 2018 à 11:32:15 AM * @version 1.0.0 * / @ ConfigurationProperties (prefix = "Spring.cache.multi") Classe publique CachereDiscaffeineProperties {private set <string> cachenames = new HasHset <> (); / ** Que ce soit pour stocker les valeurs nulles, par défaut True, empêcher la pénétration du cache * / booléen privé cachenullvalues = true; / ** Si vous devez créer une implémentation de cache dynamiquement basée sur Cachename, par défaut true * / private boolean dynamic = true; / ** Préfixe de la touche de cache * / CachePrefix de chaîne privée; Redis privé Redis = nouveau redis (); caféine privée de caféine = nouvelle caféine (); classe publique redis {/ ** Temps d'expiration globale, millisecondes d'unité, expiration par défaut * / private Long DefaultExpiration = 0; / ** Temps d'expiration, millisecondes unitaires, la priorité est supérieure à la valeur de défaut * / map privé <chaîne, long> expire = new hashmap <> (); / ** informer les autres nœuds des noms de sujet lorsque le cache met à jour * / private String topic = "Cache: redis: Caffeine: Topic"; public long getDefaultExpiration () {return DefaultExpiration; } public void setDefaultExpiration (long defaultExpiration) {this.defaultExpiration = defaultExpiration; } Public Map <String, long> getExpires () {return expire; } public void setExpires (map <string, long> expire) {this.expires = expire; } public String getTopic () {return topic; } public void Settopic (String Topic) {this.topic = topic; }} classe publique Caffeine {/ ** Temps d'expiration après accès, en millisecondes * / private Long ExpurafterAccess; / ** Temps d'expiration après écriture, unité de millisecondes * / privé long expirewritewrite; / ** Rafraîchir le temps après l'écriture, unité de millisecondes * / Private Long RefreshafterWrite; / ** taille d'initialisation * / private int initialCapacity; / ** Nombre maximum d'objets de cache, le cache placé avant dépasse ce nombre sera invalide * / private long maximumSize; / ** Étant donné que le poids doit être fourni par les objets Cache, il n'est pas très adapté aux scénarios comme l'utilisation du cache Spring, donc la configuration n'est pas encore prise en charge * /// le poids maximum long privé; public long getExpirefterAccess () {return expire } public void setExpire } public long getExpire } public void setExpirefterWrite (Long ExpirafterWrite) {this.expirefterwrite = expirefterwrite; } public long getRefreshafterwrite () {return RefreshafterWrite; } public void setRefreshafterwrite (long refreshafterwrite) {this.refreshafterwrite = refreshafterwrite; } public int getInitialCapacity () {return initialCapacity; } public void SetInitialCapacity (int initialCapacity) {this.InitialCapacity = initialCapacity; } public long getMaxImSize () {return maximumSize; } public void setMaxImusze (long maximumSize) {this.MaximUmSize = maximumSize; }} public set <string> getCachenames () {return cachenames; } public void setCachenames (set <string> cachenames) {this.cachenames = cachenames; } public boolean iscachenullvalues () {return cachelullvalues; } public void setCachenullValues (booléen cachelullvalues) {this.cachellValues = cachelullValues; } public boolean isdynamic () {return dynamic; } public void setDynamic (booléen dynamic) {this.damic = 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 () {return Cafeine; } public void setCaffeine (caféine caféine) {this.caffeine = caféine; }} Il existe une classe abstraite AbstractValueAdaptingCache qui implémente l'interface de cache dans le cache Spring, qui contient l'emballage des valeurs vides et l'emballage des valeurs de cache, il n'est donc pas nécessaire d'implémenter l'interface de cache, et d'implémenter directement la classe abstraite AbstractValuAdaptingCache.
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.callable; import java.util.concurrent.timit; java.util.concurrent.locks.reentrantLock; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.support.abstrvalueadaptingcache; import org.springframework.data.redis.core.redistimplate; org.springframework.util.stringutils; import com.github.benmanes.caffeine.cache.cache; import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacheredisaffeineproperties; / ** * @Autor Fuwei.Deng * @Date janvier 26. * @version 1.0.0 * / classe publique RedisfeineCache étend AbstractValuEadaptingCache {private final logger logger = loggerfactory.getLogger (rediscafeineCache.class); nom de chaîne privé; Private ReDistetemplate <objet, objet> redesttemplate; cache privé <objet, objet> CaffeineCache; Cacheprefix de chaîne privée; privé long defaultExpiration = 0; Carte privée <String, Long> Expire; STRING PRIVATE Topic = "Cache: Redis: Cafeine: Topic"; Protégé RedisfefeineCache (booléen allownullvalues) {super (allownullvalues); } public redisfefeineCache (nom de chaîne, redesttemplate <objet, objet> redesttemplate, cache <objet, objet> caaffeineCache, cacherediscafeineproperties cacherediscafeineproperties) {super (cacheredisfeineproperties.iscachenullValues ()); this.name = name; this.redistemplate = reidemplate; this.CaffeineCache = CaffeineCache; this.cacheprefix = cacherediscaffeineproperties.getCachePrefix (); this.defaultExpiration = cacherediscaffeineproperties.getRedis (). getDefaultExpiration (); this.expires = cacherediscafeineproperties.getRedis (). getExpires (); this.topic = cacherediscafeineproperties.getRedis (). getTopic (); } @Override public String getName () {return this.name; } @Override Public Object getNatiVECache () {Renvoie ceci; } @SuppressWarnings ("Unchecked") @Override public <T> T Get (clé d'objet, callable <T> VAUELOADER) {objet Value = Lookup (Key); if (valeur! = null) {return (t) value; } ReentrantLock Lock = new reentrantLock (); essayez {lock.lock (); Value = Lookup (clé); if (valeur! = null) {return (t) value; } value = valuelloader.call (); Object StoreValue = TostoreValue (VALUELOADER.CALL ()); put (clé, storevalue); valeur return (t); } catch (exception e) {try {class <?> c = class.forname ("org.springframework.cache.cache $ valiseRevalexception"); Constructor <?> Constructor = C.GetConstructor (object.class, callable.class, throwable.class); RuntimeException exception = (runtimeException) constructor.newinstance (key, valueloader, e.getCause ()); lancer une exception; } catch (exception e1) {lance un nouveau IllégalStateException (E1); }} enfin {lock.unlock (); }} @Override public void put (clé d'objet, valeur d'objet) {if (! Super.isallownullvalues () && value == null) {this.evict (key); retour; } long expire = getExpire (); if (expire> 0) {reidemplate.opsforvalue (). set (getKey (key), tostoreValue (value), expire, timeunit.milliseconds); } else {reidemplate.opsforvalue (). set (getKey (key), tostoreValue (value)); } push (new CacheMessage (this.name, key)); CaffeineCache.put (clé, valeur); } @Override public ValueWrapper Putifabsent (clé d'objet, valeur objet) {objet cacheKey = getKey (key); Objet prevvalue = null; // Envisagez d'utiliser des verrous distribués, ou modifiez le setifabsen de redis vers l'opération atomique synchronisée (clé) {prevValue = redetemplate.opsforvalue (). Get (cacheKey); if (prevValue == null) {long expire = getExpire (); if (expire> 0) {reidemplate.opsforvalue (). set (getKey (key), tostoreValue (value), expire, timeunit.milliseconds); } else {reidemplate.opsforvalue (). set (getKey (key), tostoreValue (value)); } push (new CacheMessage (this.name, key)); CaffeineCache.put (Key, TostoreValue (valeur)); }} return toValueWrapper (prevValue); } @Override public void Evict (clé d'objet) {// Effacez d'abord les données mises en cache dans Redis, puis effacez le cache dans la caféine, pour éviter le cache en peu de temps si le cache de cache est effacé en premier, et d'autres demandes seront chargées de Redis à la caféine redestemplate.delete (Getkey)); push (new cacheMessage (this.name, key)); CaffeineCache.invalidate (clé); } @Override public void clear () {// effacer les données mises en cache dans redis, puis effacer le cache dans la caféine, pour éviter le cache en peu de temps si le cache du cache est effacé d'abord, puis d'autres demandes seront chargées de redis à la caféine à partir de redis set <Bobile> Keys = redestyplate.keys (this.name.Concat (":")); pour (clé d'objet: touches) {redemplate.delete (key); } push (new cacheMessage (this.name, null)); CaffeineCache.InvalidateAll (); } @Override Protected Object Lookup (objet Key) {objet CacheKey = getKey (key); Valeur d'objet = CaffeineCache.GetIfPresent (KEY); if (value! = null) {logger.debug ("Obtenez le cache de la caféine, la clé est: {}", cacheKey); valeur de retour; } value = redestemplate.opsforvalue (). get (cacheKey); if (value! = null) {logger.debug ("Obtenez le cache de redis et mettez en caféine, la clé est: {}", cacheKey); cacheineCache.put (clé, valeur); } RETOUR-valeur; } objet privé getKey (clé d'objet) {return this.name.concat (":"). concat (stringUtils.isempty (cacheprefix)? key.tostring (): cacheprefix.concat (":"). concat (key.tostring ())); } private long getExpire () {long expire = defaultExpiration; Long cachenameExpire = exires.get (this.name); return cachenameExpire == null? expire: cachenameExpire.LongValue (); } / ** * @Description informer les autres nœuds pour nettoyer le cache local lorsque le cache change * @author fuwei.deng * @Date 31 janvier 2018 à 3:20:28 PM * @version 1.0.0 * @param message * / private void push (CacheMessage Message) {redeidemplate.convertSend (thème, message); } / ** * @description Clean the local cache * @author fuwei.deng * @date 31 janvier 2018 à 15:15:39 PM * @version 1.0.0 * @param key * / public void clearLocal (objet Key) {Logger.debug ("Clear Cache local, la touche est: {}", clé); if (key == null) {caaffeineCache.invalidateAll (); } else {caaffeineCache.invalidate (key); }}} Implémentez l'interface 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.concurrent.concurrentmap; import; java.util.concurrent.TimeUnit; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.cache.cache; import org.springframework.cache.cachemanager; import org.springframework.data.Redis.Core.redemplate; com.github.benmanes.caffeine.cache.caffeine; import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacheredisfeineproperties; / ** * @author fuwei.deng * @Date 26 janvier 2018 à 5:24:52 PM * @version * @Date Januan RediscafeineCacheManager implémente CacheManager {private final logger logger = loggerfactory.getLogger (redisfeinecachemanager.class); privé concurrentmap <string, cache> cachemap = new concurrenthashmap <string, cache> (); cacherediscaféineproperties privées cacherediscafeineproperties; Private ReDistetemplate <objet, objet> redesttemplate; dynamique booléen privé = true; set privé <string> cachenames; Public RedisfefeineCaffeineManager (Cacheredisfeineproperties CacherediscaffeineProperties, Redemplate <objet, objet> redesttemplate) {super (); this.cacherediscaffeineproperties = cacherediscafeineproperties; this.redistemplate = reidemplate; this.dynamic = cacherediscafeineproperties.isdynamic (); this.cachenames = cacherediscaffeineproperties.getCachenames (); } @Override public cache getCache (string name) {cache cache = cachemap.get (name); if (cache! = null) {return cache; } if (! dynamic &&! cachenames.contains (name)) {return cache; } cache = new rediscafeineCache (nom, redesttemplate, cache (), cacherediscafeineproperties); Cache oldcache = cachemap.putifabsent (nom, cache); logger.debug ("Créer une instance de cache, le nom du cache est: {}", nom); retourne oldcache == null? Cache: OldCache; } public com.github.benmanes.caffeine.cache.cache <objet, objet> caaffeineCache () {Caffeine <objet, objet> cachebuilder = caféine.newbuilder (); if (cacheredisaffeineproperties.getCaffeine (). GetExpirefterAccess ()> 0) {cachebuilder.expire } if (cacheredisaffeineproperties.getCaffeine (). GetExpirefterWrite ()> 0) {cachebuilder.expirefterwrite (cacheredisaffeineproperties.getCaffeine (). GetExpire } if (cacherediscafeineproperties.getCaffeine (). getInitialCapacity ()> 0) {cachebuilder.initialcapacity (cacherediscafeineproperties.getCaffeine (). getInitialCapacity ()); } if (cacheredisaffeineproperties.getCaffeine (). getMaxiMumSize ()> 0) {cachebuilder.maximumSize (cacherediscafeineproperties.getCaffeine (). getMaxImMuSize ()); } if (cacherediscafeineproperties.getCaffeine (). getRefreshafterwrite ()> 0) {cachebuilder.refreshafterwrite (cacherediscafeineproperties.getCaffeine (). getRefreshafterwrite (), timeUnit.MeLiseConds); } return cachebuilder.build (); } @Override public Collection <string> getCachenames () {return this.cachenames; } public void ClearLocal (String cachename, clé d'objet) {cache cache = cachemap.get (cachename); if (cache == null) {return; } RediscafeineCache rediscafeineCache = (redisfeinenecache); rediscafeineCache.Clearlocal (clé); }} Redis message publier / abonner, classe de messages pour la transmission
Package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import java.io.serializable; / ** * @author fuwei.deng * @Date 29 janvier 2018 1:31:17 SerialVersionUID = 5987219310442078193L; Cachename à chaîne privée; clé d'objet privé; public cacheMessage (String cachename, clé d'objet) {super (); this.cachename = cachename; this.key = key; } public String getCachename () {return cachename; } public void setCachename (String cachename) {this.cachename = cachename; } public objet getKey () {return key; } public void setKey (objet clé) {this.key = key; }} L'écoute des messages redis nécessite l'interface MessageListener
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.data.redis.connection.mesage; org.springframework.data.redis.connection.sessageListener; import org.springframework.data.redis.core.redistetemplate; / ** * @author fuwei.deng * @Date 30 janvier 2018 à 5:22:33 PM * @version 1.0.0 * / Private Class CachemmemsagleListor imprime messaglener tulistagletener its Messaglener tuder Finale Class CachemememmemleMeListor implore Messaglener tulistage final CACKEMEMEMEMEMEDER ITIM Logger Logger = LoggerFactory.getLogger (CacheMesageListener.class); Private ReDistetemplate <objet, objet> redesttemplate; Rediscafeinenecachemanger privé RediscafeineCacheManager; public CacheMeSageListener (Redemplate <objet, objet> redesttemplate, rediscafeinecacheManager RedisfeineCacheManager) {super (); this.redistemplate = reidemplate; this.redisfeinecacheManager = redisfeinenecacheManager; } @Override public void OnMessage (message de message, byte [] modèle) {cacheMessage cacheMEssage = (cacheMeSage) redetemplate.getValueSerializer (). Deserialize (message.getBody ()); Logger.debug ("Recevice un message de sujet redis, cache local clairement, le nom de cachename est {}, la clé est {}", cacheMesage.getCachename (), cacheMessage.getKey ()); rediscafeinecacheManager.Clearlocal (cacheMessage.getCachename (), cacheMessage.getKey ()); }} Ajouter une classe de configuration de démarrage Spring
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import org.springframework.beans.factory.annotation.autowired; import org.springframework.boot.autoconfigure.auto-onfiguration; importation; org.springframework.boot.autoconfigure.condition.conditionalonbean; import org.springframework.boot.autoconfigure.data.redis.redisautoconfiguration; import org.springframework.boot.contex 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 (nom); 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
Étendu
个人认为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
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.