Spring BootはSpring Cacheを統合し、Redis、Caffeine、Jcache、Ehcacheなどの複数のキャッシュの実装を備えていますが、1つのキャッシュのみを使用する場合、ネットワーク消費量が大きく(Redisなど)、またはメモリの使用量が多すぎます(カフェインのアプリケーションメモリキャッシュなど)。多くのシナリオでは、1回目と第2レベルのキャッシュを組み合わせて、アプリケーションの処理効率の大規模な改善を実現できます。
コンテンツの説明:
簡単な理解のために、キャッシュは、遅いリーディングメディアのデータを読み取り、ディスク - >メモリなどの読み取りをより速い読み取りでメディアに置くことです。通常、データベースなどのディスクにデータを保存します。毎回データベースから読み取ると、ディスク自体が読み取り速度に影響するため、Redisのようなメモリキャッシュがあります。データを読み取り、メモリに配置できるため、データを取得する必要がある場合は、メモリからデータを直接取得して返すことができます。これにより、速度が大幅に向上する可能性があります。ただし、通常、Redisはクラスターに個別に展開されるため、ネットワークIOで消費が行われます。 Redisクラスターにリンクするための接続プーリングツールがありますが、データ送信にはまだいくらかの消費があります。そのため、カフェインなどのアプリ内キャッシュがあります。アプリケーションキャッシュの基準を満たすデータがある場合、ネットワークを介してRedisを取得することなく直接使用でき、2レベルのキャッシュが形成されます。アプリ内キャッシュは第1レベルのキャッシュと呼ばれ、リモートキャッシュ(Redisなど)はセカンドレベルのキャッシュと呼ばれます
スプリングキャッシュ
キャッシュを使用する場合、次のプロセスは一般に次のものです。
フローチャートから、キャッシュを使用するために、元のビジネス処理に基づいて多くのキャッシュ操作が追加されていることがわかります。これらがビジネスコードに結合されている場合、開発時に多くの反復作業が行われ、コードに基づいてビジネスを理解するのに役立ちません。
スプリングキャッシュは、注釈に基づいたスプリングコンテキストパッケージで提供されるキャッシュコンポーネントです。いくつかの標準インターフェイスを定義します。これらのインターフェイスを実装することにより、メソッドに注釈を追加することにより、キャッシュを実現できます。これにより、キャッシュコードがビジネス処理と結びついている問題が回避されます。スプリングキャッシュの実装は、スプリングAOPでのメソッドインターフェイス(MethodInterceptor)カプセル化の拡張です。もちろん、Spring AOPもアスペクトに基づいて実装されています。
スプリングキャッシュには2つのコアインターフェイスがあります:キャッシュとキャッシュマネージャー
キャッシュインターフェイス
キャッシュの入力、読み取り、クリーニングなど、特定のキャッシュ操作を提供します。 Springフレームワークによって提供される実装は次のとおりです。
Spring-Data-RedisパッケージにあるRediscacheを除き、その他は基本的にSpring-Context-Supportパッケージにあります。
#cache.javapackage org.springframework.cache; import java.util.concurrent.callable; public interface cache {// cachename、キャッシュの名前。デフォルトの実装では、Cache MeanagerはCache Beanを作成するときにCachenameに合格します。文字列getName(); //次のような実際のキャッシュを取得します:redistemplate、com.github.benmanes.caffeine.cache.cache <object、object>私はまだ実際の使用を見つけていません。一部のキャッシュ操作または統計を拡張する必要があるように、ネイティブキャッシュを取得する豆を提供するだけです。オブジェクトgetNativeCache(); //キーを介してキャッシュ値を取得し、返される値はValueWrapperであることに注意してください。 null値と互換性があるため、返品値はレイヤーにラップされ、実際の値はGETメソッドValueWrapper get(Object Key)を介して取得されます。 //キーを介してキャッシュ値を取得します。キーは、実際の値を返します。つまり、メソッドの返品値<t> t get(object key、class <t> type); //キーを介してキャッシュ値を取得するには、valueloader.call()を使用して @cachable annotationを使用してメソッドを呼び出すことができます。 @cachableアノテーションの同期属性がtrueに構成されている場合、この方法を使用します。したがって、データベースへの返品ソースの同期は、メソッド内で確保する必要があります。キャッシュが失敗したときに、データベースにソースに戻るための大量のリクエストを避けてください。 <t> t get(オブジェクトキー、呼び出し可能<t> valueloader); // @Cachable Annotationメソッドによって返されたデータをCache void put(オブジェクトキー、オブジェクト値)に返します。 //キャッシュにキーがない場合にのみキャッシュを配置します。キーがvalueWrapper putifabsent(オブジェクトキー、オブジェクト値)が存在する場合、返品値は元のデータです。 //キャッシュvoid evict(object key)を削除します。 //キャッシュ内のすべてのデータを削除します。特定の実装では、 @Cachable Annotationを使用してキャッシュされたすべてのデータのみが削除され、Application void clear()の他のキャッシュには影響しないことに注意してください。 //ラッパーキャッシュを返す値インターフェイスvaluewrapper {//実際のcachedオブジェクトを返しますget(); } //例外が{@link #get(object、callable)}によってスローされると、@suppresswarnings( "serial")class valueretrievalexceptionによってスローされるこの例外として巻き付けられます。 public Valueretrievalexception(オブジェクトキー、呼び出し可能<?>ローダー、スロー可能なEx){super(string.format( "for key '%s'は '%s'"、key、loader)、exを使用してロードできませんでした); this.key = key; } public Object getKey(){return this.key; }}}Cachemanagerインターフェイス
主にキャッシュ実装豆の作成を提供します。各アプリケーションは、Cachenameを介してキャッシュを分離でき、各Cachenameはキャッシュの実装に対応します。 Springフレームワークによって提供される実装とキャッシュの実装は、ペアで表示され、パッケージ構造も上の図にも表示されます。
#cachemanager.javapackage org.springframework.cache; import java.util.collection; public interface cachemanager {// cachenameを介してキャッシュ実装Beanを作成します。特定の実装では、作成されたキャッシュ実装豆を繰り返し作成しないように保存する必要があります。また、メモリキャッシュオブジェクト(カフェインなど)が再現されたキャッシュGetCache(String名)が再作成された後に元のキャッシュコンテンツが失われる状況を回避する必要があります。 //すべてのcachenameコレクション<string> getCachenames();}を返します一般的な注釈
@Cachable:主にデータのクエリの方法に適用されます
Package org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; Import java.lang.annotation.inherited; import java.lang.annotation.retention; import java.lang.notation.Lentintention.LentionPolicly; java.lang.annotation.target; Import java.util.concurrent.callable; import org.springframework.core.annotation.aliasfor; @target({elementtype.method、elementtype.type})@retention(retentionpolicy.runtime@bubicedpubicedpubicedpubicedpectime) CACHENAMES、CACHENAGERは、この名前@aliasfor( "cachenames")string [] value()default {}を介して対応するキャッシュ実装Beanを作成します。 @aliasfor( "value")string [] cachenames()default {}; //キャッシュキーは、スペル式をサポートします。デフォルトは、すべてのパラメーターとそれらのハッシュコード(SimpleKey)String key()default ""によってラップされたオブジェクトです。 //キージェネレーターのキャッシュ、デフォルトの実装はSimpleKeyGenerator String keygenerator()default "";です。 //文字列cachemanager()default ""を使用するCacheManagerを指定します。 //キャッシュパーサー文字列cacheresolver()default ""; //キャッシュ状態、Spel式、および満足のいく条件が満たされた場合にのみデータをサポートします。文字列条件()デフォルト ""は、メソッドを呼び出す前後に審査されます。 //条件が満たされたときにキャッシュが更新され、スペルの式がサポートされ、文字列は、()デフォルト ""がメソッドを呼び出した後にのみ判断されない限り、文字列。 //実際のメソッドにソースに戻って、同期し続けるかどうかにかかわらず、データを取得するときは? falseの場合、cache.get(key)メソッドが呼び出されます。 trueの場合、cache.get(key、callable)メソッドはboolean sync()default falseと呼ばれます;} @cacheevict:主にデータの削除方法に適用されたキャッシュをクリアします。キャッシュ可能なものよりも2つのプロパティがあります
Package org.springframework.cache.annotation; import java.lang.annotation.documented; import java.lang.annotation.elementType; Import java.lang.annotation.inherited; import java.lang.annotation.retention; import java.lang.notation.Lentintention.LentionPolicly; java.lang.annotation.target; import org.springframework.core.annotation.aliasfor; @target({elementType.Method、elementType.type})@retention(retentionPolicy.runtime)@documented public @interface cachevitd@interbublic cachevict@interniet @cachable //すべてのキャッシュデータをクリアするかどうか、cache.evict(key)メソッドがfalseの場合に呼び出されます。 trueの場合、cache.clear()メソッドはboolean allentries()default falseと呼ばれます。 //メソッドを呼び出す前または後にキャッシュをクリアします。スプリングキャッシュはスプリングブートに統合されており、さまざまなキャッシュ構成を提供します。使用する場合、使用するキャッシュ(enum cacheType)を構成するだけです。
Spring Bootに追加の拡張機能が追加されています。これは、Cachemanagercustomizerインターフェイスです。このインターフェイスをカスタマイズしてから、次のようなCacheManagerの設定を作成できます。
パッケージcom.itopener.demo.cache.redis.config; Import java.util.map; import java.util.concurrent.concurrenthashmap; import org.springframework.boot.autoconfigure.cache.cache.cachemanagercustomizer;クラスのrediscachemanagercustomizerは、cachemanagercustomizer <rediscachemanager> {@override public void customize(rediscachemanager cachemanager){//デフォルトの有効期間、ユニット秒Cachemanager.setDefaultExpiration(1000); cachemanager.setuseprefix(false); map <string、long> expires = new concurrenthashmap <string、long>(); expires.put( "useridcache"、2000l); cachemanager.setexpires(expires); }}この豆をロードします:
パッケージcom.itopener.demo.cache.redis.config; Import org.springframework.context.annotation.bean; Import org.springframework.context.annotation.configuration;/** * @author fuwei.deng * @date 12月22日cacheredisconfiguration {@bean public rediscachemanagercustomizer rediscachemanagercustomizer(){return new rediscachemanagercustomizer(); }}一般的に使用されるキャッシュはRedisです。 Redisは、Spring-Data-Redisパッケージのスプリングキャッシュインターフェイスを実装します。
再隔離の実装の欠点のいくつかは次のとおりです。
1.キャッシュが失敗した瞬間、スレッドがキャッシュデータを取得した場合、nullを返す可能性があります。その理由は、次の手順が再発見の実装にあるためです。
したがって、キーが存在することを判断した後にキャッシュが失敗し、キャッシュを取得するとデータがない場合、nullを返します。
2。null値を再discachemanagerに保存できる属性(cachenullvalues)はデフォルトでは虚偽です。つまり、null値を保存することは許可されておらず、キャッシュの浸透が危険にさらされます。欠陥は、このプロパティが最終タイプであり、オブジェクトはコンストラクターメソッドを介して作成されたときにのみ作成できることです。したがって、キャッシュの浸透を避けるために、アプリケーションで再課税マネーガーの豆のみを宣言することができます。
3. RediscaceManagerのプロパティは、構成ファイルを介して直接構成することはできません。それらは、CacheManagerCustomizerインターフェイスにのみ設定できます。個人的には便利ではないと思います。
カフェインは、GoogleのオープンソースGuavaデザインコンセプトに基づいた高性能メモリキャッシュです。 Java 8を使用して開発されました。SpringBootがカフェインを導入した後、Guavaの統合は徐々に放棄されました。カフェインソースコードと紹介アドレス:カフェイン
カフェインは、さまざまなキャッシュ充填戦略と価値リサイクル戦略を提供し、キャッシュヒットなどの統計も含まれています。
カフェインの導入については、http://www.vevb.com/article/134242.htmを参照してください
ここでは、次の種類のカフェイン時間ベースのリサイクル戦略について簡単に説明します。
最初に、Redisキャッシュが使用されていても、ネットワーク伝送にある程度の消費があると述べました。実際のアプリケーションでは、変更が非常に低いデータがあり、アプリケーション内で直接キャッシュできます。リアルタイムの要件が少ないデータの場合、Redisへのアクセスを減らして応答速度を改善するために、一定期間アプリケーション内でキャッシュすることもできます
Redisには、Spring-Data-Redisフレームワークでスプリングキャッシュの実装にいくつかの欠点があるため、使用するといくつかの問題が発生する可能性があるため、元の実装に基づいて拡張しません。キャッシュとセイスマネージャーのインターフェイスを実装するための実装方法を直接参照します
また、一般にアプリケーションは複数のノードを展開し、最初のレベルのキャッシュはアプリケーション内のキャッシュであるため、データを更新およびクリアする場合、キャッシュをクリーンアップするためにすべてのノードを通知する必要があることに注意する必要があります。 Zookeeper、MQなどなど、この効果を達成するには多くの方法がありますが、Redisキャッシュが使用されるため、Redis自体はサブスクリプション/公開機能をサポートするため、他のコンポーネントに依存しません。 Redisチャネルを直接使用して、他のノードを通知してキャッシュ操作をクリーンアップします。
スプリングブート +スプリングキャッシュ用のスターターカプセル化ステップとソースコードは、2レベルのキャッシュ(Redis +カフェイン)を実装するスターターのカプセル化ステップとソースコードです。
プロパティ構成プロパティクラスを定義します
パッケージcom.itopener.cache.redis.caffeine.spring.boot.autoconfigure; java.util.hashmap; Import java.util.hashset; Import java.util.map; Import java.util.set; Import org.context.context.conpprameworks; @author fuwei.deng * @date 2018年1月29日午前11時32:15 AM * @version 1.0.0 */ @configurationProperties(prefix = "spring.cache.multi")public class cacherediscaffeineproperties {private set <string> cachenames = new Hashset <>(); / ** null値を保存するかどうか、デフォルトのtrue、キャッシュの侵入を防ぐ*/ private boolean cachenullvalues = true; / ** cachenameに基づいて動的にキャッシュ実装を作成するかどうか、デフォルトのtrue*/ private boolean dynamic = true; / **キャッシュキーのプレフィックス*/ private string cacheprefix; private redis redis = new Redis();プライベートカフェインカフェイン=新しいカフェイン();パブリッククラスredis { / **グローバル有効期限、ユニットミリ秒、デフォルトの有効期限* /プライベート長いデフォルトエクスピレーション= 0; / **有効期限、ユニットミリ秒、優先度はデフォルトエクスパイアリングよりも高く*/プライベートマップ<文字列、long> expires = new Hashmap <>(); / **キャッシュの更新時にトピック名の他のノードに通知*/ private文字列トピック= "cache:redis:caffeine:topic"; public long getDefaultExpiration(){return DefaultExpiration; } public void setDefaultExpiration(long DefaultExpiration){this.defaultExpiration = defaultExpiration; } public map <string、long> getExpires(){return expries; } public void setExpires(map <string、long> expires){this.expires = expires; } public string getTopic(){return topic; } public void settopic(string topic){this.topic = topic; }} public class caffeine { / **アクセス後の有効期限、ミリ秒単位* /プライベート長いexpreafteraccess; / **執筆後の有効期限、ユニットミリ秒*/プライベート長いexpreafterwrite; / **執筆後の更新時間、ユニットMilliseconds*/ private long refreshafterwrite; / **初期化サイズ*/ private int initialcapacity; / **キャッシュオブジェクトの最大数、この数を超える前に配置されたキャッシュは無効になります*/ private long maximize。 /**キャッシュオブジェクトによって重量を提供する必要があるため、スプリングキャッシュを使用するなどのシナリオにはあまり適していないため、構成はまだサポートされていません*///プライベートロングマキシマム級。 public long getExpireafteraccess(){return expireafteraccess; } public void setExpireafteraccess(long expireafteraccess){this.expireafteraccess = expireafteraccess; } public long getExpireAffterwrite(){return expireferwrite; } public void setExpireafterwrite(long expireafterwrite){this.expireafterwrite = expireafterwrite; } 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 getmaximumsize(){return maximumsize; } public void setmaximumsize(long maximumsize){this.maximumsize = maximumsize; }} public set <string> getCachenames(){return cachenames; } public void setCachenames(set <string> cachenames){this.cachenames = 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; } public caffeine getcaffeine(){return caffeine; } public void setcaffeine(カフェインカフェイン){this.caffeine = caffeine; }}空の値のパッケージとキャッシュ値のパッケージを含むスプリングキャッシュのキャッシュインターフェイスを実装する抽象クラスAbstractValueEadaptingCacheがあります。
パッケージcom.itopener.cache.redis.caffeine.caffeine.spring.boot.autoconfigure.support; import java.lang.lang.reflt.constructor; import java.util.map; Import java.util.set; Import java.util.current.call.call.calr.calurnit; java.util.concurrent.locks.reentrantlock; Import org.slf4j.logger; import org.slf4j.loggerfactory; Import org.springframework.cache.support.AbstractValueAdaptingCache; Import org.springframework.data.data.data.core.core.core.core.core.core.core.core.core.core.core springframework.util.stringutils;インポートcom.github.benmanes.caffeine.cache.cache; Import com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineporties; 5:24:11 pm * @version 1.0.0 */public class rediscaffeinecache extends abstractvalueeadaptingcache {private final logger logger = loggerfactory.getlogger(rediscaffeinecache.class);プライベート文字列名; private redistemplate <object、object> redistemplate;プライベートキャッシュ<オブジェクト、オブジェクト> caffeinecache;プライベートストリングcacheprefix;プライベートLong DefaultExpiration = 0;プライベートマップ<文字列、long>有効期限が切れます。プライベート文字列トピック= "キャッシュ:redis:カフェイン:トピック";保護されたrediscaffeinecache(boolean allownullvalues){super(allownullvalues); } public rediscaffeinecache(String name、Redistemplate <object、object> redistemplate、cache <object、object> caffeinecache、cacherediscaffeineporties cacherediscaffeineperties){super(cacherediscaffeineproperties.iscachenullues()); this.name = 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(){return this; } @suppresswarnings( "unchecked")@override public <t> t get(object key、callable <t> valueloader){object value = lookup(key); if(value!= null){return(t)value; } reentrantLock lock = new ReentrantLock(); try {lock.lock(); value = lookup(key); if(value!= null){return(t)value; } value = valueloader.call(); object storevalue = tostorevalue(valueloader.call()); put(key、storevalue); return(t)value; } catch(例外e){try {class <?> c = class.forname( "org.springframework.cache.cache $ valueretrievalexception"); constructor <?> constructor = c.getConstructor(object.class、callable.class、throwable.class); runtimeexception例外=(runtimeexception)constructor.newinstance(key、valueloader、e.getCause());例外をスローします。 } catch(Exception E1){新しいIllegalStateException(E1); }}最後に{lock.unlock(); }} @Override public void put(object key、object value){if(!super.isallownullvalues()&& value == null){this.evict(key);戻る; } long expire = getExpire(); if(expire> 0){redistemplate.opsforvalue()。set(getKey(key)、tostorevalue(value)、expire、timeunit.milliseconds); } else {redistemplate.opsforvalue()。set(getKey(key)、tostorevalue(value)); } push(new cachemessage(this.name、key)); caffeinecache.put(key、value); } @Override public ValueWrapper putifabsent(object key、object value){object cachekey = getKey(key);オブジェクトprevvalue = null; //分散ロックの使用を検討するか、Redisのsetifabsentを原子操作に変更した(key){prevvalue = redistemplate.opsforvalue()。get(cachekey); if(prevvalue == null){long expire = getExpire(); if(expire> 0){redistemplate.opsforvalue()。set(getKey(key)、tostorevalue(value)、expire、timeunit.milliseconds); } else {redistemplate.opsforvalue()。set(getKey(key)、tostorevalue(value)); } push(new cachemessage(this.name、key)); caffeinecache.put(key、tostorevalue(value)); }} return tovaluewrapper(prevvalue); } @Override public void evict(object key){//最初にRedisでキャッシュされたデータをクリアし、次にカフェインのキャッシュをクリアして、キャッシュキャッシュが最初にクリアされた場合に短期間キャッシュを回避し、他のリクエストがRedisからCaffine Redistemplate.delete(getkey(key))にロードされます。 Push(new Cachemessage(this.name、key)); caffeinecache.invalidate(key); } @Override public void clear(){// Redisのキャッシュデータをクリアしてから、カフェインのキャッシュをクリアして、キャッシュキャッシュが最初にクリアされた場合に短期間キャッシュを避けます。次に、RedisからRedis set <オブジェクト> keys = redistemplate.keys(name.cat); for(オブジェクトキー:keys){redistemplate.delete(key); } push(new cachemessage(this.name、null)); caffeinecache.invalidateall(); } @Overrideプロテクションオブジェクトルックアップ(オブジェクトキー){object cachekey = getKey(key);オブジェクト値= caffeinecache.getifpresent(key); if(value!= null){logger.debug( "カフェインからキャッシュを取得すると、キーは{}"、cachekey);返品値。 } value = redistemplate.opsforvalue()。get(cachekey); if(value!= null){logger.debug( "redisからキャッシュを取得してカフェインを入れて、キーは{}"、cachekey); cacheinecache.put(key、value); } return値; }プライベートオブジェクトgetKey(オブジェクトキー){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 * @version 1.0.0 * @paramメッセージ * / private void push(cachemessage message){redistemplate.convertandsend(topice); } / ** * @description local cache * @author fuwei.deng * @date 2018年1月31日3:15:39 pm * @version 1.0.0 * @param key * / public void clearlocal(object key){logger.debug( "clear local cache、key is:{}"、key); if(key == null){caffeinecache.invalidateall(); } else {caffeinecache.invalidate(key); }}} CacheManagerインターフェイスを実装します
パッケージcom.itopener.cache.redis.caffeine.caffeine.spring.boot.autoconfigure.support; Import java.util.collection; import java.util.set; Import java.util.concurrent.concurrenthashmap; Import java.util.concurrentmap; Import.current.currentmap; 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.data.data.data.data.core.core.core com.github.benmanes.caffeine.cache.caffeine;インポートcom.itopener.cache.redis.caffeine.spring.boot.autoconfigure.cacherediscaffeineproperties; RediscaffeinecachemanagerはCachemanagerを実装します{プライベートファイナルロガーLogger = loggerFactory.getLogger(Rediscaffeinecachemanager.class); private concurrentmap <string、cache> cachemap = new concurrenthashmap <string、cache>();民間のcacherediscaffeineproperties cacherediscaffeineproperties; private redistemplate <object、object> redistemplate; private boolean dynamic = true;プライベートセット<文字列> cachenames; Public Rediscaffeinecaffeinemanager(cacherediscaffeineproperties cacherediscaffeineporties、redistemplate <object、object> redistemplate){super(); this.cacherediscaffeineproperties = cacherediscaffeineproperties; this.redistemplate = redistemplate; this.dynamic = cacherediscaffeineproperties.isdynamic(); this.cachenames = cacherediscaffeineproperties.getCachenames(); } @Override public cache getCache(string name){cache cache = cachemap.get(name); if(cache!= null){cache; } if(!dynamic &&!cachenames.contains(name)){return cache; } cache = new RediscaffeInecache(name、redistemplate、cache()、cacherediscaffeineproperties);キャッシュoldcache = cachemap.putifabsent(name、cache); 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()。getExpireAfteraccess()> 0){cachebuilder.expireaffteraccess(cacherediscaffeineproperties.getCaffeine()。 } if(cacherediscaffeineProperties.getCaffeine()。getExpireAffterWrite()> 0){cachebuilder.expireafterwrite(cacherediscaffeineproperties.getCaffeine()。 } if(cacherediscaffeineproperties.getCaffeine()。getInitialCapacity()> 0){cacheBuilder.initialCapacity(cacherediscaffeineProperties.getCaffeine()。getInitialCafcacity(); } if(cacherediscaffeineProperties.getCaffeine()。getMaximumsize()> 0){cachebuilder.maximumsize(cacherediscaffeineProperties.getCaffeine()。getMaximumsize(); } if(cacherediscaffeineProperties.getCaffeine()。getRefreshafterwrite()> 0){cachebuilder.refreshafterwrite(cacherediscaffeineproperties.getCaffeine()。 } cachebuilder.build()を返します。 } @Overrideパブリックコレクション<String> getCachenames(){return this.cachenames; } public void clearlocal(string cachename、object key){cache cache = cachemap.get(cachename); if(cache == null){return; } rediscaffeinecache rediscaffeinecache =(rediscaffeinecache)キャッシュ; rediscaffeinecache.clearlocal(key); }} Redis Message Publish/subscribe、メッセージクラスの送信
package com.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support;import java.io.Serializable;/** * @author fuwei.deng * @date January 29, 2018 1:31:17 pm * @version 1.0.0 */public class CacheMessage implements Serializable { /** */ private static最終的な長いserialversionuid = 5987219310442078193l;プライベート文字列cachename;プライベートオブジェクトキー。 public cachemessage(string cachename、object key){super(); this.cachename = cachename; this.key = key; } public string getCachename(){return cachename; } public void setCachename(string cachename){this.cachename = cachename; } public Object getKey(){return key; } public void setKey(object key){this.key = key; }} Redisメッセージを聞くには、MessageListenerインターフェイスが必要です
パッケージcom.itopener.cache.redis.caffeine.spring.boot.autoconfigure.support; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.data.redis.connection.message; import; org.springframework.data.redis.connection.messageListener; Import org.springframework.data.redis.core.redistemplate;/** * @author fuwei.deng * @date 2018年1月30日午後5時22分33 PM {private final logger logger = loggerfactory.getLogger(cachemessagelistener.class); private redistemplate <object、object> redistemplate;プライベートRediscaffeinecachemanager Rediscaffeinecachemanager; public cachemessagelistener(redistemplate <object、object> redistemplate、rediscaffeinecachemanager rediscaffeinecachemanager){super(); this.redistemplate = redistemplate; this.rediscaffeinecachemanager = rediscaffeinecachemanager; } @Override public void onmessage(message message、byte [] pattern){cachemessage cachemessage =(cachemessage)redistemplate.getValueserializer()。deserialize(message.getBody()); logger.debug( "Redisトピックメッセージを受け取り、ローカルキャッシュをクリアし、cachenameは{}、キーは{}"、cachemessage.getCachename()、cachemessage.getKey()); rediscaffeinecachemanager.clearlocal(cachemessage.getCachename()、cachemessage.getKey()); }}スプリングブート構成クラスを追加します
パッケージcom.itopener.cache.redis.caffeine.spring.boot.autoconfigure; import org.springframework.beans.actory.annotation.autowired; import org.springframework.boot.autoconfigure.autoconfiguration; Import; springframework.boot.autoconfigure.condition.conditionalonbean; import org.springframework.boot.autoconfigure.data.redisautoconfiguration; Import org.springframework.boot.context.properties.properties.ableconfigurationPropertes; 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
拡張
个人认为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をもっとサポートすることを願っています。