blog

CacheのGuavaシリーズ

日々の開発において、同じデータを何度も取得し、データの変更頻度が低い場合、キャッシュの利用を検討することができます。 Guavaのキャッシュはローカルキャッシュの実装であり、似ていますが全く同じではあ...

Jul 13, 2020 · 9 min. read
シェア

キャッシュは、パフォーマンスを向上させるための方法で、毎日の開発の非常に頻繁に使用され、それは実際にCPU、メモリ、ハードディスクのパフォーマンスなどのハードウェアレベルで不平等なパフォーマンスの問題を解決するデータの読み取りと転送の深刻な影響の大きな違いの間に、キャッシュは、このようなパフォーマンスの違いの手段のバランスをとるために使用されます。

キャッシュは広く、多くのシステムやアーキテクチャで使用されます:

  • 分散キャッシュ
  • オペレーティングシステムのキャッシュ
  • データベースキャッシュ
  • 分散キャッシュ
  • ローカルキャッシュ

日常的な開発における使用シナリオ

日々の開発において、同じデータを何度も取得し、データの変更頻度が低い場合、キャッシュの使用を検討することができます。

ここで使われるのは、メモリとハードディスクの性能が不均等であることです。メモリは読み込みが非常に高速ですが、ハードディスクは比較的低速です。

伝統的なリレーショナル・データベースはハードディスクにデータを保存するため、高度に同時並行的な読み書きのシナリオではパフォーマンスのボトルネックが発生します。

この時、データベースの前にキャッシュの層を追加する場合は、ホットデータキャッシュメモリのコピー内のデータベースは、直接メモリから読み取るので、大幅に読書のパフォーマンスを向上させることができます!

グアバキャッシュの実装

Guavaのキャッシュは、ローカルキャッシュの実装では、ConcurrentMapに似ていますが、まったく同じではありません。基本的な違いは、ConcurrentMapは追加された要素を、あなたが積極的に削除しない限り、常に保持することです。一方、Guavaキャッシュは通常、メモリ使用量を制限するために自動回復を設定します。

Guava Cacheの使用シナリオ:

  • スペースと時間を交換するということは、メモリ消費と読み取りパフォーマンスの向上を交換することを厭わないということです。
  • あなたは、いくつかのデータが頻繁にクエリされることを予測しています。
  • キャッシュに保存されるデータは、メモリ容量

サンプルコード

 @Test
 public void cacheCreateTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(100) //キャッシュの最大サイズを設定する
 .expireAfterWrite(1,TimeUnit.MINUTES) //有効期限ポリシー、書き込み1分後に期限切れ
 .build();
 cache.put("a","a1");
 String value = cache.getIfPresent("a");
 }

キャッシュ

Cache は Guava が提供する最も基本的なキャッシュインターフェイスです。

 @Test
 public void cacheCreateTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(100) //キャッシュの最大サイズを設定する
 .expireAfterWrite(1,TimeUnit.MINUTES) //有効期限ポリシー、書き込み1分後に期限切れ
 .build();
 cache.put("a","a1");
 String value = cache.getIfPresent("a");
 }

Cache は CacheBuilder オブジェクトを使用して構築されます。

ロードキャッシュ

LoadingCache は Cache を継承し、キャッシュからキーを読み込む際に、値が読み込まれない場合は LoadingCache が自動的にデータをキャッシュに読み込みます。

 @Test
 public void loadingCacheTest() throws ExecutionException {
 LoadingCache<String,String> loadingCache = CacheBuilder.newBuilder()
 .maximumSize(3)
 .refreshAfterWrite(Duration.ofMillis(10))//10数分後にキャッシュされたデータをリフレッシュする
 .build(new CacheLoader<String, String>() {
 @Override
 public String load(String key) throws Exception {
 Thread.sleep(1000);
 System.out.println(key + " load data");
 return key + " add value";
 }
 });
 System.out.println(loadingCache.get("a"));
 System.out.println(loadingCache.get("b"));
 }

結果は実行されます:

a load data
a add value
b load data
b add value

LoadingCache も CacheBuilder によって作成されますが、作成するときに、ビルド・メソッドで CacheLoader を渡す必要があります。

CacheLoader クラスの load メソッドは、キーが見つからない場合にデータを自動的にロードするために使用されます。

Cache共通パラメータ

以下のプロパティは、Guava Cache で一般的に使用され、Cache と LoadingCache の両方に適用されます。

1、容量の初期化

public void initialCapacityTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .initialCapacity(1024) //初期容量
 .build();
 }

2、最大容量

最大容量は2つの次元で設定できます

  • maximumSize レコードの最大数、保存データの数。
  • maximumWeight 最大容量、保存データのサイズ。
 @Test
 public void maxSizeTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(2)//キャッシュの最大数
 .build();
 cache.put("a","1");
 cache.put("b","2");
 cache.put("c","3");
 System.out.println(cache.getIfPresent("a"));
 System.out.println(cache.getIfPresent("b"));
 System.out.println(cache.getIfPresent("c"));
 Cache<String,String> cache1 = CacheBuilder.newBuilder()
 .maximumWeight( * 1024)//最大容量は1M
 //の容量を計算するために使用される。Weigher
 .weigher(new Weigher<String, String>() {
 @Override
 public int weigh(String key, String value) {
 return key.getBytes().length + value.getBytes().length;
 }
 })
 .build();
 cache1.put("x","1");
 cache1.put("y","2");
 cache1.put("z","3");
 System.out.println(cache1.getIfPresent("x"));
 System.out.println(cache1.getIfPresent("y"));
 System.out.println(cache1.getIfPresent("z"));
 }
 

実行結果

null 2 3 1 2 3

キャッシュの最大レコード数を 2 に設定し、要素が 3 つ追加されたときに、前に追加された要素が上書きされるようにします。

3、有効期限

  • expireAfterWrite 書き込み後、データが失効する時間。
  • expireAfterAccess データにアクセスされない時間。
null
2
3
1
2
3

実行結果

 @Test
 public void expireTest() throws InterruptedException {
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(100)//キャッシュの最大数
 .expireAfterWrite(5,TimeUnit.SECONDS)//有効期限は書き込みの5分後
 .build();
 cache.put("a","1");
 int i = 1;
 while(true){
 System.out.println(" " + i + "秒単位で得られるデータは" + cache.getIfPresent("a"));
 i++;
 Thread.sleep(1000);
 }
 }

結果からわかるように、書き込み後6秒目にデータが利用できなくなっています。

最初の1秒間に取得されたデータは次のとおりである:1
2秒目にフェッチされたデータは次のとおりである:1
3秒目にフェッチされたデータは次のとおりである:1
4秒目にフェッチされたデータは次のとおりである:1
5秒目にフェッチされたデータは次のとおりである:1
6秒目にフェッチされたデータは次のとおりである:null
7秒目にフェッチされたデータは次のとおりである:null
8秒目にフェッチされたデータは次のとおりである:null
9秒目にフェッチされたデータは次のとおりである:null

実行結果

 @Test
 public void expireAfterAccessTest() throws InterruptedException {
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(100)//キャッシュの最大数
 .expireAfterAccess(5,TimeUnit.SECONDS)//5キャッシュが1秒間アクセスされないと、失効する。
 .build();
 cache.put("a","1");
 Thread.sleep(3000);
 System.out.println("休止状態から3秒後にアクセスされる:" + cache.getIfPresent("a"));
 Thread.sleep(4000);
 System.out.println("休止状態から4秒後にアクセスされた:" + cache.getIfPresent("a"));
 Thread.sleep(5000);
 System.out.println("休止状態から5秒後にアクセスされる:" + cache.getIfPresent("a"));
 }

結果からわかるように、キャッシュされたデータは、一定時間後に誰もアクセスしないとすぐに失効します。

リサイクル戦略

上記のリサイクル戦略には2つのタイプがあります

  • expireAfterWrite 書き込み後、長期間経過した後にリサイクルします。
  • expireAfterAccess アクセスされていない時間が経過するとリサイクルします。

Guavaのキャッシュはまた、参照レベルに基づいて回復をサポートしています、この回復戦略は、Javaに固有のもので、Javaのオブジェクトの回復メカニズムでは、オブジェクトの強さに応じて強い参照、ソフト参照、弱い参照、仮想参照に分けることができます。

強い参照

強い参照は、最も一般的に使用される参照は、直接新しいオブジェクトなど、強い参照です。

休止状態から3秒後にアクセスされる:1
休止状態から4秒後にアクセスされた:1
休止状態から5秒後にアクセスされる:null

強参照は自動的には再生成されず、メモリ不足になるとメモリ・オーバーフローとして報告されます。

ソフト参照

ソフト参照はオブジェクトを参照する不安定な方法です。 オブジェクトがソフト参照を持っている場合、GC は十分なメモリがあるときにはソフト参照を積極的に再要求しませんが、十分なメモリがないときにはソフト参照を再要求します。

Map<String,String> map = new HashMap<String,String>();

弱い参照

弱参照は、メモリの有無に関係なく再要求される可能性があるため、ソフト参照よりも不安定な参照方法です。

SoftReference<Object> softRef=new SoftReference<Object>(new Object()); //  
Object object = softRef.get(); // ソフト参照の取得

ダミー参照

オブジェクトがダミー参照しか持っていない場合、それは参照を持っていないのと同じだからです。また、実際にはほとんど使われません。

ソフト/弱い参照のリサイクルはGuavaでサポートされています。

WeakReference<Object> weakRef = new WeakReference<Object>(new Object()); 
Object obj = weakRef.get(); // 弱い参照を取得する

手動での回収

上記は全て自動キャッシュ再生戦略ですが、Guava Cacheが提供するメソッドを呼び出して手動でクリアすることもできます。

単一のキーまたはキーのバッチを削除することができ、キャッシュ全体をクリアすることもできます。

Cache<String,String> cache = CacheBuilder.newBuilder()
 .weakKeys() //キーを格納するために弱い参照を使用する。キーに他の参照がない場合、キャッシュが再利用されることがある。
 .weakValues() //弱い参照を使用して値を格納する。その値が他に参照を持たない場合、キャッシュが再利用されることがある。
 .softValues() //ソフトリファレンスを使用して値を保存する。メモリが少なくなり、値が別の強い参照によって参照されると、キャッシュが再生される。
 .build();

実行結果

 @Test
 public void invalidateTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder().build();
 cache.put("a","1");
 cache.put("b","2");
 //キャッシュからキーaのデータをクリアする。
 cache.invalidate("a");
 System.out.println(cache.getIfPresent("a"));
 cache.put("x","x1");
 cache.put("y","y1");
 System.out.println("x "+ cache.getIfPresent("x"));
 System.out.println("y "+ cache.getIfPresent("y"));
 List<String> list = Lists.newArrayList("x","y");
 //バッチパージ
 cache.invalidateAll(list);
 System.out.println("xクリア後"+ cache.getIfPresent("x"));
 System.out.println("yクリア後"+ cache.getIfPresent("y"));
 cache.put("y","y1");
 cache.put("z","z1");
 //すべてのデータのキャッシュを空にする
 cache.invalidateAll();
 System.out.println("すべてクリアした後:" + cache.getIfPresent("y"));
 System.out.println("すべてクリアした後:" + cache.getIfPresent("z"));
 }

データ・パージ・リスナー

オブジェクトが削除されたときにイベントが通知されるように、Cache 内のオブジェクトにリスナーを追加できます。

null
x x1
y y1
xクリア後null
yクリア後null
すべてクリアした後:null
すべてクリアした後:null

結果を実行します:

 @Test
 public void removeListenerTest() throws InterruptedException {
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(3)
 .expireAfterWrite(Duration.ofSeconds(5))//5秒後に自動的に期限切れになる
 //削除リスナーを追加する
 .removalListener(new RemovalListener<Object, Object>() {
 @Override
 public void onRemoval(RemovalNotification<Object, Object> notification) {
 System.out.println("[" +notification.getKey() + ":" + notification.getValue() + "] 削除された");
 }
 })
 .build();
 cache.put("a","1");
 Thread.sleep(2000);
 cache.put("b","2");
 cache.put("c","3");
 Thread.sleep(2000);
 cache.put("d","4");
 Thread.sleep(5000);
 cache.put("e","5");
 cache.invalidate("e");
 }

結果からわかるように、クリアされたデータがリッスンされるシナリオは3つあります。

  • 容量を超えると、キャッシュに追加された最も古いデータがクリアされます。
  • 設定された有効期限を超えると、キャッシュ・システムは自動的にデータを削除します。
  • 手動でデータをクリア

統計

キャッシュを使用するとき、キャッシュのヒット率、ロードデータ時間、その他の情報を気にする必要があります。キャッシュオブジェクトをビルドするとき、統計情報を有効にすることができます。

[a:1] 削除された
[b:2] 削除された
[c:3] 削除された
[d:4] 削除された

操作の結果が表示されます:

 @Test
 public void recordStatsTest(){
 Cache<String,String> cache = CacheBuilder.newBuilder()
 .maximumSize(3)
 .recordStats()
 .build();
 cache.put("a","1");
 cache.put("b","2");
 cache.put("c","3");
 cache.put("d","4");
 cache.put("e","5");
 cache.put("f","6");
 cache.getIfPresent("a");
 cache.getIfPresent("a");
 cache.getIfPresent("e");
 cache.getIfPresent("f");
 cache.getIfPresent("h");
 cache.getIfPresent("t");
 System.out.println(cache.stats());
 }

Read next

バートとユニバーサル文コーディングに基づくSpark-NLPテキスト分類

自然言語処理は、テキストを理解または推論する必要がある多くのデータサイエンスシステムの重要なコンポーネントです。一般的な使用例としては、テキストの分類、Q&A、言い換えや要約、感情分析、自然言語BI、言語モデリング、曖昧性解消などがあります。\nNLPは、ますます多くのAIアプリケーションで重要性を増しています。もし

Jul 13, 2020 · 10 min read