この章では、CoherenceキャッシングAPIを使用して、基本的なキャッシュ操作を実行する手順について説明します。
この章の内容は次のとおりです。
com.tangosol.net.NamedCache<K, V>インタフェースは、キャッシュ・インスタンスの取得と操作のためにアプリケーションによって使用される主要インタフェースです。NamedCache<K, V>インタフェースは、他のインタフェースを拡張し、Coherenceに固有でデータ・グリッド操作の実行に使用される追加のキャッシュ機能をそれぞれ提供します。
java.util.Map<K, V> – get()、put()、remove()などの基本的なMapメソッド。
com.tangosol.net.cache.CacheMap<K, V> – キャッシュ内のキーのコレクションを取得(Mapとして)するため、およびオブジェクトをキャッシュに入れるためのメソッド。エントリをキャッシュに入れるときに、有効期限の値を追加することもできます。
com.tangosol.util.QueryMap<K, V> – キャッシュの問合せ用のメソッド。第22章「キャッシュ内のデータの問合せ」を参照してください。
com.tangosol.util.InvocableMap<K, V> – キャッシュ・データのサーバー側の処理用メソッド。第24章「キャッシュ内のデータの処理」を参照してください。
com.tangosol.util.ObservableMap<K, V> – キャッシュ・イベントをリスニングするメソッド。第25章「マップ・イベントの使用」を参照してください。
com.tangosol.util.ConcurrentMap<K, V> – lock()やunlock()などの同時アクセス用のメソッド。第29章「トランザクションの実行」を参照してください。
NamedCacheおよび関連するインタフェースの詳細は、Oracle Coherence Java APIリファレンスを参照してください。
NamedCacheインスタンスへの参照を取得するには、CacheFactory.getCacheメソッドを使用して、キャッシュの名前をパラメータとして含めます。必要に応じて、基礎となるキャッシュ・サービスが起動されます。例:
import com.tangosol.net.*;
...
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
キャッシュ・インスタンスは、キャッシュ構成ファイル(デフォルトでcoherence-cache-config.xml)に定義されているキャッシュ・スキームを使用して作成されます。キャッシュ・スキームは、名前MyCacheにマップされます。キャッシュ・スキームおよびマッピングの定義の詳細は、「キャッシュの構成」を参照してください。
NamedCacheインスタンスでは、任意のタイプのキーと値を格納できます。ただし、キャッシュ・エントリを操作する場合は、型の安全性をアプリケーションで確保する必要があります。アプリケーションでは、特定の型のNamedCacheインスタンスを作成可能で、APIレベルの型チェックも提供されます。キャッシュの型チェックの詳細は、「NameCache型チェックの使用」を参照してください。
キャッシュのキーおよび値はシリアライズ可能である必要があります(例: java.io.SerializableまたはCoherence Portable Object Formatのシリアライズ)。さらに、キャッシュ・キーはhashCode()とequals()メソッドの実装を提供する必要があり、これらのメソッドはクラスタ・ノード間で一貫性のある結果を返す必要があります。これは、hashCode()およびequals()の実装は、オブジェクトのシリアライズ可能な状態(つまり、オブジェクトの一時的でないフィールド)にのみ基づいている必要があることを示しています。String、Integer、Dateなどのほとんどの組込みのJava型はこの要件を満たしています。一部のキャッシュの実装(特にパーティション・キャッシュ)では、等価の検証にキー・オブジェクトのシリアライズされた形式を使用し、equals()でtrueを返すキーは、同様の方法でシリアライズされる必要があり、ほとんどの組込みのJava型はこの要件を満たしています。シリアライズの詳細は、次を参照してください
基本的なキャッシュput操作は、Mapインタフェースで定義されているputメソッドを使用して実行されます。putメソッドは、エントリをキャッシュに追加して、指定したキーの前の値を返します。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
putAllメソッドは、単一のバルク・ロードで複数のエントリをキャッシュに追加する場合に使用され、エントリはMap型のデータ構造内にある必要があります。putAllメソッドの使用およびバルク・ロード操作の詳細は、「キャッシュの事前ロード」を参照してください。
基本的なキャッシュget操作は、Mapインタフェースで定義されているgetメソッドを使用して実行されます。マップされている値は、指定したキーで返されます。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
System.out.println(cache.get(key));
基本的なキャッシュ削除操作は、Mapインタフェースで定義されているremoveメソッドを使用して実行されます。指定したキーのマッピングはキャッシュから削除され、前の値が返されます。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
System.out.println(cache.get(key));
cache.remove(key);
java.util.Mapインタフェースには、putIfAbsent、replaceAll、mergeなどの操作を実行するためのデフォルトのメソッドが含まれます。Coherenceでは、これらのメソッド実装をオーバーライドして、これらの操作が分散環境で適切に実行されるようにします。エントリ・プロセッサを使用して、ラムダ式を活用できるように、メソッドが再実装されています。メソッドは、NamedCacheインタフェースを使用する場合に使用できます。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("hello-example");
cache.putIfAbsent(key, value);
System.out.println(cache.get(key));
ラムダ式は、必要な処理をエントリで実行する場合にも使用できます。例:
cache.replaceAll((key, value) ->
{
value.setLastName(value.getLastName().toUpperCase());
return value;
});
使用可能なデフォルトのメソッドの詳細は、MapインタフェースのJavaDocを参照してください。エントリ・プロセッサの詳細は、「ターゲット、パラレルおよび問合せベースの処理のためのエージェントの使用」を参照してください。
キャッシュの事前ロードは、データがアプリケーションで使用される前にキャッシュに移入する場合に使用される一般的なシナリオです。
例20-1は、putメソッドを使用したデータのロードを示しています。このテクニックは有益ですが、特にパーティション・キャッシュおよびレプリケート・キャッシュでは、putをコールするたびにネットワーク・トラフィックが発生する場合があります。また、putをコールするたびにキャッシュで置換されたばかりのオブジェクトが返され(java.util.Mapインタフェースで定義されている)、不要なオーバーヘッドが追加されます。
例20-1 キャッシュの事前ロード
public static void bulkLoad(NamedCache cache, Connection conn)
{
Statement s;
ResultSet rs;
try
{
s = conn.createStatement();
rs = s.executeQuery("select key, value from table");
while (rs.next())
{
Integer key = new Integer(rs.getInt(1));
String value = rs.getString(2);
cache.put(key, value);
}
...
}
catch (SQLException e)
{...}
}
かわりに、ConcurrentMap.putAllメソッドを使用すると、キャッシュのロードを大幅に効率化できます。これを例20-2に示します。
例20-2 ConcurrentMap.putAllを使用したキャッシュの事前ロード
public static void bulkLoad(NamedCache cache, Connection conn)
{
Statement s;
ResultSet rs;
Map buffer = new HashMap();
try
{
int count = 0;
s = conn.createStatement();
rs = s.executeQuery("select key, value from table");
while (rs.next())
{
Integer key = new Integer(rs.getInt(1));
String value = rs.getString(2);
buffer.put(key, value);
// this loads 1000 items at a time into the cache
if ((count++ % 1000) == 0)
{
cache.putAll(buffer);
buffer.clear();
}
}
if (!buffer.isEmpty())
{
cache.putAll(buffer);
}
...
}
catch (SQLException e)
{...}
}
Coherenceのパーティション・キャッシュに大量のデータ・セットを事前移入する際は、Coherenceのクラスタ・メンバーに作業を分散すると効率が向上する可能性があります。分散ロードを使用すると、クラスタのネットワーク帯域幅およびCPU処理能力の集積を活用することによって、データ・スループット率を高めることができます。分散ロードの実行時には、アプリケーションで次の事項を決定する必要があります。
ロードを実行するクラスタのメンバー
メンバー間でのデータ・セットの分割方法
メンバーの選択および作業の分割時は、基礎となるデータ・ソース(データベースやファイル・システムなど)に対する負荷をアプリケーションで考慮する必要があります。たとえば、問合せを同時に実行するメンバーが多すぎると、1つのデータベースでは対応しきれなくなる場合があります。
この項では、簡単な分散ロードを実行する一般的な手順の概要について説明します。この例では、データがファイルに保存されていて、クラスタ内で記憶域が有効なすべてのメンバーに分散されることを前提としています。
記憶域が有効なメンバーのセットを取得します。たとえば、次の方法ではgetStorageEnabledMembersメソッドを使用して、分散キャッシュ内で記憶域が有効なメンバーを取得します。
記憶域が有効なクラスタのメンバー間で作業を分割します。たとえば、次のルーチンでは、メンバーに割り当てられたファイルの一覧が含まれるマップが、メンバーをキーとして返されます。
例20-4 キャッシュのメンバーに割り当てられたファイルの一覧を取得するルーチン
protected Map<Member, List<String>> divideWork(Set members, List<String> fileNames)
{
Iterator i = members.iterator();
Map<Member, List<String>> mapWork = new HashMap(members.size());
for (String sFileName : fileNames)
{
Member member = (Member) i.next();
List<String> memberFileNames = mapWork.get(member);
if (memberFileNames == null)
{
memberFileNames = new ArrayList();
mapWork.put(member, memberFileNames);
}
memberFileNames.add(sFileName);
// recycle through the members
if (!i.hasNext())
{
i = members.iterator();
}
}
return mapWork;
}
各メンバーへのロードを実行するタスクを起動します。たとえば、タスクの起動にはCoherenceのInvocationServiceなどを使用します。この場合、LoaderInvocableの実装では、memberFileNamesを反復して各ファイルを処理し、その内容をキャッシュにロードする必要があります。通常、クライアント上で実行されるキャッシュ処理は、LoaderInvocableを使用して実行する必要があります。
例20-5 キャッシュの各メンバーをロードするクラス
public void load()
{
NamedCache cache = getCache();
Set members = getStorageMembers(cache);
List<String> fileNames = getFileNames();
Map<Member, List<String>> mapWork = divideWork(members, fileNames);
InvocationService service = (InvocationService)
CacheFactory.getService("InvocationService");
for (Map.Entry<Member, List<String>> entry : mapWork.entrySet())
{
Member member = entry.getKey();
List<String> memberFileNames = entry.getValue();
LoaderInvocable task = new LoaderInvocable(memberFileNames, cache.getCacheName());
service.execute(task, Collections.singleton(member), this);
}
}
キャッシュの内容は、NamedCacheインタフェースで定義されるclearまたはtruncateメソッドを使用してクリアできます。clearメソッドにより、メモリーおよびCPUオーバーヘッドが増加するため、分散キャッシュのクリアには一般にお薦めしません。別の方法として、truncateメソッドを使用できます。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
System.out.println(cache.get(key));
Cache.truncate;
truncateメソッドでは、メモリーおよびCPUリソースを効率的に使用できるため、大きなキャッシュやリスナーを使用するキャッシュをクリアする場合に最適なオプションになります。truncateメソッドは、フロント・マップおよびバック・マップの両方をクリアするため、ニア・キャッシュのクリアにも最適です。truncateメソッドでエントリが削除されたかどうかは、リスナー、トリガーおよびインターセプタで監視できません。ただし、CacheLifecycleEventイベントが発生し、この操作の実行がすべてのサブスクライバに通知されます。
キャッシュが不要になったアプリケーションでは、CacheFactory.releaseメソッドを使用して、指定されたキャッシュのインスタンスに関連付けられているローカル・リソースを解放するようにしてください。キャッシュを解放すると、キャッシュは使用できなくなりますが、クラスタ全体でのキャッシュへの他の参照またはキャッシュの内容は影響を受けません。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
System.out.println(cache.get(key));
CacheFactory.releaseCache(cache);
CacheFactoryクラスを使用して作成されるキャッシュは、CacheFactory.destroyメソッドを使用して破棄できます。destroyメソッドは、クラスタ全体で指定されたキャッシュを破棄します。キャッシュへの参照が無効化され、キャッシュされたデータがクリアされて、すべてのリソースが解放されます。NamedCache.clearメソッドは、分散環境でメモリーとCPUの両方の負荷が高くなるタスクになる可能性があるため、多くの場合destroyメソッドの方が適しています。例:
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
cache.put(key, value);
System.out.println(cache.get(key));
CacheFactory.destroyCache(cache);
com.tangosol.net.AsyncNameCache<K, Vインタフェースにより、キャッシュ操作をパラレルに完了できます。このインタフェースでは、Java CompletableFutureクラスを使用し、このクラスでは、完了または例外(あるいは両方)のコールバックを提供して、複数の非同期コールを連鎖して順に実行し、パラレルに実行しているすべてのコールが完了するまで待機します。NameCache操作を非同期に実行すると、スループットが改善し、ユーザー・インタフェースの応答性が向上します。Coherenceの例では、非同期キャッシュ操作の実行の追加の例を提供します。詳細は、『Oracle Coherenceのインストール』を参照してください。
非同期キャッシュ操作を実行するには、CompletableFutureクラスを使用して、Futureインタフェースを実装する必要があります。例:
import com.tangosol.net.AsyncNamedCache;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
public class HelloWorld {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
String key = "k1";
String value = "Hello World!";
NamedCache<Object, Object> cache = CacheFactory.getCache("MyCache");
AsyncNamedCache<Object, Object> as = cache.async();
Future future = as.put(key, value);
future.get();
CompletableFuture cf = as.get(key);
System.out.println(cf.get());
CompletableFuture cfremove = as.remove(key);
System.out.print("Removing key/value: " + cfremove.get() + "\n" );
System.out.println("The key/value is: " + future.get());
}
}
Coherenceでは、明示的な型を使用して、厳密に型指定されたNamedCacheインスタンスをCacheFactoryクラスにリクエストできます。デフォルトでは、CacheFactory.getCacheメソッドはNamedCache<Object, Object>インスタンスを返します。これは、NamedCacheインスタンスを作成および使用するための最も柔軟なメカニズムですが、キャッシュ・インスタンスとのやり取りに必要なキーと値の型をアプリケーションで設定する必要があります。たとえば、任意の型のオブジェクトを格納する場合、アプリケーションでは次を使用できます。
NamedCache<Object,Object> cache = CacheFactory.getCache("MyCache");
CacheFactory.getTypedCacheメソッドは、型チェックすることなく、必要に応じて特定の型のNamedCacheインスタンスをリクエストする場合に使用できます。TypeAssertionインタフェースは、getTypedCacheメソッドともに使用され、NamedCacheインスタンスで使用されるキーと値の型の正確性をアサートします。CacheFactory.getTypedCacheメソッドは、キャッシュがRAW型を使用する必要があることをアサートする場合に使用できます。例:
NamedCache<Object, Object> cache = CacheFactory.getTypedCache("MyCache",
TypeAssertion.withRawTypes());
型の安全性を強化するために、キャッシュを作成して、キャッシュで使用されるキーと値の型を明示的にアサートできます。たとえば、キャッシュを作成して、キーと値の型がStringである必要があることをアサートするには、アプリケーションで次を使用できます。
NamedCache<String, String> cache = CacheFactory.getTypedCache("MyCache",
TypeAssertion.withTypes(String.class, String.class));
NameCacheインスタンスは、アサートされた型に準拠する必要はなく、無視することもできますが、コンパイル時に警告メッセージが表示されます。例:
NamedCache cache = CacheFactory.getTypedCache("MyCache",
TypeAssertion.withTypes(String.class, String.class));
同様に、RAW型を使用し、ネーム・キャッシュ・インスタンスで特定の型を使用できることをアサートすることを、アプリケーションで選択することもできます。ただし、どちらの場合も、型がチェックされないままでは、エラーが発生することがあります。例:
NamedCache<String, String> cache = CacheFactory.getTypedCache("MyCache",
TypeAssertion.withRawTypes());
型の安全性を最も強くするには、特定の型をキャッシュ定義の一部として、キャッシュ構成ファイルに宣言することもできます。キャッシュ定義の部分として構成された型と異なる型をアプリケーションが使用すると、ランタイム・エラーが発生します。次の例では、String型のキーと値のみをサポートするキャッシュを構成します。
<cache-mapping> <cache-name>MyCache</cache-name> <scheme-name>distributed</scheme-name> <key-type>String</key-type> <value-type>String</value-type> </cache-mapping>
最後に、明示的な型チェックの無効化をアプリケーションで選択できます。型チェックが無効の場合、型の安全性はアプリケーションで確保する必要があります。
NamedCache<Object, Object> cache = CacheFactory.getTypedCache("MyCache",
TypeAssertion.withoutTypeChecking());