Coherenceを使ってみよー
- DBのデータをメモリ上にキャッシュ
- キャッシュデータは分散
オープンソース化したCoherenceを使って上記を実現する!
メモリからデータを取得してディスクIO失くしてアプリを爆速にしよー
使ってみた感想としては、キャッシュの分散が手軽にできるし、耐障害性も簡単に実現できて
べんりーですね
情報を本投稿に細かく記載しているので誰でもできると思います!
1個のjarを動かすだけで簡単に使えるので、既存のアプリに組み込んで、遅い画面だけ
Coherenceを使って、メモリにデータ持つとかもありかなーと思いました!
windows機でEclipseを使ってアプリケーションを作るので、実際の開発工程がなんとなく分かるし、
発展させて、自分流のアプリケーションを作ってみるのも楽しいかもですね
日本語の使い方の情報が少ないし、本があっても激古情報なので、有益なはず
なお、DBはderbyを使います
Coherenceダウンロード
今回はmavenの設定はせず、mvnのセントラルレポジトリから直接jarを仕入れます!
依存関係がないので、このjarだけで動きます、シンプル!
※本格的なプロジェクト運用するなら、eclipseでmavenプロジェクトとかgradleプロジェクト作った方がよいですね
https://mvnrepository.com/artifact/com.oracle.coherence.ce/coherence/20.12
以下に配置してみた
C:\work\coherence\20.12\jar
ソースとREADMEをみたければ以下です
https://github.com/oracle/coherence
なんかカッコいいコミュニティサイトも立ち上がってる!
マイクロサービスでのCoherenceの使い方が紹介されてますね(英語)
https://coherence.community/
Javaダウンロード
以下からダウンロード
https://www.oracle.com/java/technologies/javase-downloads.html
※オラクルアカウントの作成が必要です
現在(2021年2月)githubのREADMEには、JDK 8以上をサポートと記載。
JDK15は非対応っぽいので、JDK11を使用してみる
今回はEclipseを使った実装も行うので、Sierの開発機として一般的???な
windows環境に作成します
以下に解凍して配置
C:\work\coherence\20.12\jdk-11.0.10_windows-x64_bin
Pathに以下の追加も忘れずに
%JAVA_HOME%\bin
動作確認
java -version
Eclipseのダウンロード
以下からダウンロード
https://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/2020-12/R/eclipse-jee-2020-12-R-win32-x86_64.zip
※Oracle Enterprise PackのEclipseを使えると便利ですが、現在(2021年2月)、Java8までの対応っぽいし、Coherenceは商用版(~12系)までしかサポートしていなのであまり使う理由を思い当たらず。
※最近のエクリプスは、中二病っぽい名前(Luna、oxygen等)ではなく、2020-12とかになったんですね、ちょっと悲しい
以下に解凍して配置
C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64
eclipse.iniを編集してJDK11で動くようにしよー
※デフォルトはJava15でしたー
-vm
C:/work/coherence/20.12/jdk-11.0.10_windows-x64_bin/jdk-11.0.10/bin
起動してみる
eclipse.exe
ワークスペースは以下につくりました
※デフォルトのワークスペースの接頭辞に、"eclipse-"がつくようになったんですね
C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64\eclipse\eclipse-workspace
とりあえず、Javaのソースした時にフォーマッターが適用されるように設定
xmlが間違ってもいないのにエラーが出てうざいので、チェックを全外し
スペルチェックもうざいので、チェックを外し
Apache Derbyのダウンロード
JDK7までは、JDKに同梱されてたけど、JDK8以降はないので、自分でダウンロードしようー
http://db.apache.org/derby/releases/release-10_14_2_0.cgi
解凍して以下に配置
C:\work\coherence\20.12\db-derby-10.14.2.0-bin
起動してみますかー以下実行するだけ
startNetworkServer.bat
こんな感じの画面でるよ
失敗した場合は以下を確認しましょー
※私はゾンビderby?がいて、ポート競合で失敗しましたー
derby.log
ElipseからApache Derbyに接続
表の作成
create table person (id integer primary key, name varchar(128));
リフレッシュしても、繁栄されなかったので、いったんDisconnectして、再connect
※connectする際に出力されるダイアログのパスワード保存にチェック入れると次回からダイアログが出なくなるので便利
PERSONテーブルできてること確認できましたー
Eclipse上にプロジェクトを作ろう
設定ファイルを作成&編集
coherence-20.12.jarを解凍しましょー
※7-zipというツールを使って私は解凍してます
中に以下の三つのファイルがあります
coherence-cache-config.xml
pof-config.xml
tangosol-coherence-override-dev.xml
上記三つのファイルをsrc配下にコピペしましょー
coherence-cache-config.xml
キャッシュの設定
私はテキストエディタでの編集が好きなので、テキストエディタをオープン
こんな感じに編集しよー
<?xml version="1.0"?>
<cache-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.oracle.com/coherence/coherence-cache-config"
xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-cache-config coherence-cache-config.xsd">
<caching-scheme-mapping>
<cache-mapping>
<cache-name>Person</cache-name>
<scheme-name>my-scheme</scheme-name>
</cache-mapping>
</caching-scheme-mapping>
<caching-schemes>
<!-- partitioned caching scheme for servers -->
<distributed-scheme>
<scheme-name>my-scheme</scheme-name>
<service-name>my-service</service-name>
<serializer>pof</serializer>
<backing-map-scheme>
<read-write-backing-map-scheme>
<internal-cache-scheme>
<local-scheme />
</internal-cache-scheme>
<cachestore-scheme>
<class-scheme>
<class-name>
jp.coherence.PersonCache
</class-name>
</class-scheme>
</cachestore-scheme>
</read-write-backing-map-scheme>
</backing-map-scheme>
<autostart>true</autostart>
</distributed-scheme>
</caching-schemes>
</cache-config>
pof-config.xml
シリアライズの対象設定
こんな感じに編集しよー
<?xml version="1.0"?>
<pof-config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.oracle.com/coherence/coherence-pof-config"
xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-pof-config coherence-pof-config.xsd">
<user-type-list>
<!-- by default just include coherence POF user types -->
<include>coherence-pof-config.xml</include>
<user-type>
<type-id>1001</type-id>
<class-name>jp.coherence.Person</class-name>
</user-type>
</user-type-list>
<enable-type-discovery>true</enable-type-discovery>
</pof-config>
tangosol-coherence-override-dev.xml
コヒーレンスの設定(上書き分)
まず名前を編集。以下へ
tangosol-coherence-override.xml
あとは、こんな感じに編集しよー
<?xml version="1.0"?>
<coherence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://xmlns.oracle.com/coherence/coherence-operational-config"
xsi:schemaLocation="http://xmlns.oracle.com/coherence/coherence-operational-config coherence-operational-config.xsd">
<cluster-config>
<multicast-listener>
<time-to-live system-property="coherence.ttl">0</time-to-live>
</multicast-listener>
</cluster-config>
<management-config>
<managed-nodes system-property="coherence.management">all</managed-nodes>
<reporter>
<autostart system-property="coherence.management.report.autostart">true</autostart>
</reporter>
</management-config>
</coherence>
ビルドパスの設定
まずは、作成したxml君たちを読み込む設定をしよー
以下を指定
C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64\eclipse\eclipse-workspace\CoherenceTest\bin
次に、coherenceのjarを参照する設定をしよー
以下を指定
こんな感じになってればOK
※順番大切なので、xmlの読み込み先にくるようにー
キャッシュのプログラミングしよー
Personクラス
xmlにpath記載してあるので、パッケージ名とクラス名の変更はしないように
ソースはこんな感じで
package jp.coherence;
import java.io.IOException;
import com.tangosol.io.pof.PofReader;
import com.tangosol.io.pof.PofWriter;
import com.tangosol.io.pof.PortableObject;
public class Person implements PortableObject, Comparable<Person> {
private static final long serialVersionUID = 1L;
private int id;
private String name;
public Person() {
}
public Person(String name) {
this.name = name;
}
public Person(int id, String name) {
this.id = id;
this.name = name;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public int compareTo(Person person) {
return Integer.valueOf(id).compareTo(Integer.valueOf(person.getId()));
}
@Override
public void readExternal(PofReader reader) throws IOException {
this.id = reader.readInt(0);
this.name = reader.readString(1);
}
@Override
public void writeExternal(PofWriter writer) throws IOException {
writer.writeInt(0, this.id);
writer.writeString(1, this.name);
}
@Override
public String toString() {
return "Person [id=" + id + ", name=" + name + "]";
}
}
PersonCacheクラス
xmlにpath記載してあるので、パッケージ名とクラス名の変更はしないように
ソースはこんな感じで
package jp.coherence;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Map.Entry;
import com.tangosol.net.cache.CacheStore;
public class PersonCache implements CacheStore {
private static ThreadLocal<Connection> tcon = new ThreadLocal<>();
private Connection getConnection() throws ClassNotFoundException, SQLException {
Connection con = tcon.get();
if (con == null || con.isClosed()) {
Class.forName("org.apache.derby.jdbc.ClientDriver");
con = DriverManager.getConnection("jdbc:derby://127.0.0.1:1527/sample", "test", "test");
con.setAutoCommit(false);
tcon.set(con);
}
return con;
}
private Person findPerson(int personId, Connection con) throws SQLException {
String sql = "SELECT * FROM PERSON WHERE ID = ?";
try (PreparedStatement pstmt = con.prepareStatement(sql)) {
pstmt.setInt(1, personId);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
return new Person(rs.getInt("ID"), rs.getString("NAME"));
}
}
return null;
}
private void storePerson(int personId, Person person, Connection con) throws SQLException {
if (findPerson(personId, con) != null) {
String sql = "UPDATE PERSON SET NAME = ? WHERE ID = ?";
try (PreparedStatement pstmt = con.prepareStatement(sql)) {
pstmt.setString(1, person.getName());
pstmt.setInt(2, personId);
pstmt.executeUpdate();
}
} else {
String sql = "INSERT INTO PERSON VALUES (?,?)";
try (PreparedStatement pstmt = con.prepareStatement(sql)) {
pstmt.setInt(1, personId);
pstmt.setString(2, person.getName());
pstmt.executeUpdate();
}
}
}
@Override
public Object load(Object key) {
try {
Connection con = getConnection();
int personId = Integer.parseInt(key.toString());
return findPerson(personId, con);
} catch (SQLException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void erase(Object key) {
int id = Integer.parseInt(key.toString());
String sql = "DELETE FROM PERSON WHERE ID = ?";
try {
Connection con = getConnection();
try (PreparedStatement pstmt = con.prepareStatement(sql)) {
pstmt.setInt(1, id);
pstmt.executeUpdate();
con.commit();
} catch (SQLException ex) {
con.rollback();
}
} catch (SQLException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void store(Object key, Object value) {
Person person = (Person) value;
int personId = Integer.parseInt(key.toString());
try {
Connection con = getConnection();
try {
storePerson(personId, person, con);
con.commit();
} catch (SQLException ex) {
con.rollback();
throw ex;
}
} catch (SQLException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
@Override
public void storeAll(Map entries) {
Map<Integer, Person> persons = (Map<Integer, Person>) entries;
try {
Connection con = getConnection();
try {
for (Entry<Integer, Person> entry : persons.entrySet()) {
int playerId = entry.getKey();
Person player = entry.getValue();
storePerson(playerId, player, con);
}
con.commit();
} catch (SQLException ex) {
con.rollback();
throw ex;
}
} catch (SQLException | ClassNotFoundException ex) {
throw new RuntimeException(ex);
}
}
}
クライアントのプログラミングして行こー
ここからは適当な名前でつくってOK
ClientGet
データ取得用のクラス
キャッシュにあれば、キャッシュからデータとるし、
キャッシュになければ、データベースからデータ取得するよ
ソースはこんな感じで
package jp.coherence;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
public class ClientGet {
public static void main(String[] args) {
NamedCache person = CacheFactory.getCache("Person");
System.out.println("size: " + person.size());
System.out.println(person.get(1));
System.out.println(person.get(2));
System.out.println(person.get(3));
System.out.println(person.get(4));
System.out.println(person.get(5));
System.out.println("size: " + person.size());
}
}
ClientPut
データ挿入する用のクラス
ソースはこんな感じで
package jp.coherence;
import java.util.Iterator;
import java.util.Map;
import com.tangosol.net.CacheFactory;
import com.tangosol.net.NamedCache;
public class ClientPut {
public static void main(String[] args) {
NamedCache person = CacheFactory.getCache("Person");
person.put(1, new Person(1, "aaaa"));
person.put(2, new Person(2, "bbbb"));
person.put(3, new Person(3, "cccc"));
person.put(4, new Person(4, "dddd"));
Iterator<Map.Entry<Integer, Person>> pi = person.entrySet().iterator();
System.out.println("size:" + person.size());
while (pi.hasNext()) {
Map.Entry entry = pi.next();
Person p = (Person) entry.getValue();
System.out.println(p.toString());
}
}
}
さあ、動作確認だー①
まずは、キャッシュ用のサーバーを立ち上げよう
コマンドプロンプトで以下を入力してね
set memory=1g
set java_opts=-Xms%memory% -Xmx%memory%
java -server -showversion %java_opts% -cp "C:\work\coherence\20.12\db-derby-10.14.2.0-bin\lib\derbyclient.jar;C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64\eclipse\eclipse-workspace\CoherenceTest\bin;C:\work\coherence\20.12\jar\coherence-20.12.jar" com.tangosol.net.DefaultCacheServer %*
クラスパスに以下が指定してあるよー
- C:\work\coherence\20.12\db-derby-10.14.2.0-bin\lib\derbyclient.jar
- C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64\eclipse\eclipse-workspace\CoherenceTest\bin
- C:\work\coherence\20.12\jar\coherence-20.12.jar"
分散してデータを持ちたいので、2台たちあげよー
※カレントディレクトリにレポートが出力される設定になっています
同じディレクトリで立ち上げると、レポート内容が重複するので、
2台は別々のディレクトリで起動しよー
なお、設定の変更は可能です
ちゃんとスタートしたようですね
さあ、動作確認だー②
データ(キャッシュ&DBへ)を入れまーす
※derby立ち上げておいてね
ClientPutを実行
以下が表示されれば成功!
DBにデータが入ったか確認
DBにデータ入っているので、キャッシュ上にデータがあるか、それからデータが2台で分散してるか確認
jconsole(jdkに同梱されてるよ)で確認しよー
コマンドプロンプトで以下を実行
jconsole
あとで1号機を落とすので、2号機を選択
※情報はどっちでもみれるよー
1号機には、3つデータ入ってる
2号機には、1つデータ入ってる
データあるし、一応分散できてるね
次に1号機落としてみよー(コマンドプロンプト上でctrl+cとかで停止させてください)
2号機にデータうつるはず
お、成功しましたねー
アプリからもゲット(ClientGetを実行)してみよー
どうやらうまくいっているようですね
あとは各自色々なことして遊んでみてください