LoginSignup
1
1

More than 3 years have passed since last update.

オープンソース化したCoherence(インメモリデータグリッド)で分散キャッシュを実現しよー

Last updated at Posted at 2021-02-28

Coherenceを使ってみよー

image.png

  • 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
image.png

以下に配置してみた

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環境に作成します

image.png

以下に解凍して配置

C:\work\coherence\20.12\jdk-11.0.10_windows-x64_bin

ユーザー変数設定
image.png

Pathに以下の追加も忘れずに

%JAVA_HOME%\bin

動作確認

java -version

以下のような画面でればOK
image.png

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のソースした時にフォーマッターが適用されるように設定

image.png

xmlが間違ってもいないのにエラーが出てうざいので、チェックを全外し

image.png

スペルチェックもうざいので、チェックを外し

image.png

Apache Derbyのダウンロード

JDK7までは、JDKに同梱されてたけど、JDK8以降はないので、自分でダウンロードしようー
http://db.apache.org/derby/releases/release-10_14_2_0.cgi

image.png

解凍して以下に配置

C:\work\coherence\20.12\db-derby-10.14.2.0-bin

起動してみますかー以下実行するだけ

startNetworkServer.bat

こんな感じの画面でるよ

image.png

失敗した場合は以下を確認しましょー
※私はゾンビderby?がいて、ポート競合で失敗しましたー

derby.log

ElipseからApache Derbyに接続

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

image.png

表の作成

image.png

create table person (id integer primary key, name varchar(128));

image.png

リフレッシュしても、繁栄されなかったので、いったんDisconnectして、再connect
※connectする際に出力されるダイアログのパスワード保存にチェック入れると次回からダイアログが出なくなるので便利

image.png

image.png

PERSONテーブルできてること確認できましたー

image.png

Eclipse上にプロジェクトを作ろう

image.png

image.png

image.png

image.png

設定ファイルを作成&編集

coherence-20.12.jarを解凍しましょー
※7-zipというツールを使って私は解凍してます

image.png

中に以下の三つのファイルがあります

coherence-cache-config.xml
pof-config.xml
tangosol-coherence-override-dev.xml

上記三つのファイルをsrc配下にコピペしましょー

image.png

image.png

coherence-cache-config.xml

キャッシュの設定
私はテキストエディタでの編集が好きなので、テキストエディタをオープン

image.png

こんな感じに編集しよー

<?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>

ビルドパスの設定

image.png

まずは、作成したxml君たちを読み込む設定をしよー

image.png

以下を指定

C:\work\coherence\20.12\eclipse-jee-2020-12-R-win32-x86_64\eclipse\eclipse-workspace\CoherenceTest\bin

次に、coherenceのjarを参照する設定をしよー

image.png

以下を指定

image.png

こんな感じになってればOK
※順番大切なので、xmlの読み込み先にくるようにー

image.png

キャッシュのプログラミングしよー

Personクラス

xmlにpath記載してあるので、パッケージ名とクラス名の変更はしないように

image.png

image.png

ソースはこんな感じで

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記載してあるので、パッケージ名とクラス名の変更はしないように

image.png

ソースはこんな感じで

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台は別々のディレクトリで起動しよー
 なお、設定の変更は可能です

ちゃんとスタートしたようですね

image.png

さあ、動作確認だー②

データ(キャッシュ&DBへ)を入れまーす
※derby立ち上げておいてね

ClientPutを実行

image.png

以下が表示されれば成功!

image.png

DBにデータが入ったか確認

image.png

image.png

DBにデータ入っているので、キャッシュ上にデータがあるか、それからデータが2台で分散してるか確認

jconsole(jdkに同梱されてるよ)で確認しよー
コマンドプロンプトで以下を実行

jconsole

あとで1号機を落とすので、2号機を選択
※情報はどっちでもみれるよー

image.png

1号機には、3つデータ入ってる

image.png

2号機には、1つデータ入ってる

image.png

データあるし、一応分散できてるね

次に1号機落としてみよー(コマンドプロンプト上でctrl+cとかで停止させてください)
2号機にデータうつるはず

image.png

お、成功しましたねー

アプリからもゲット(ClientGetを実行)してみよー

image.png

どうやらうまくいっているようですね

あとは各自色々なことして遊んでみてください

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1