インメモリデータグリッドについて勉強してみたついでに、実際にアプリに組み込むとどうなるのか気になったので入門してみた。
インメモリデータグリッドとは
- データを複数サーバで分散管理する仕組み。
- 全サーバが重複してデータを持つこと(レプリケーション方式)もできれば、あるグループのサーバにとってのみ必要なデータを、そのグループ内で重複して管理する(パーティション方式)といった柔軟なデータの信頼性確保が可能。
- DBのようにディスクI/Oが発生しないため、高速にデータのCRUD操作、P2Pのデータ同期が行える。
- 信頼性、高速性を備えたアーキテクチャ。らしい。
下記の記事を参考に学習
SpringBoot + Apach GEODEでアプリを作ってみよう
-
色々と記事をザッピングしたところで実際に作ってみたくなったので
SpringDataGeode
というプロジェクトがSpringのgitリポジトリにあるので、それを使ってSpringBootでインメモリデータグリッドを体感してみようと思います。 -
ユーザ登録・検索を行うサーバアプリケーションを想定して作成します。
Apache GEODEのclient/serverモデルでアプリを作る
-
Spring Initializerでclient,serverのアプリの雛形を作成する。
- Web,lombokのみ選択。
- 今回は
gradle
でプロジェクトを作成。
client側アプリを作成
- build.gradleの依存関係に
spring-data-geode
を追加。
dependencies {
implementation('org.springframework.boot:spring-boot-starter-web'){
// log4jのライブラリがspring-data-geodeの依存するlog4jのライブラリと競合するため、除外
exclude group: 'org.springframework.boot', module:'spring-boot-starter-logging'
}
// ドメインモデルを扱うプロジェクトを依存に追加
compile project(':geodeCommon')
compileOnly('org.projectlombok:lombok')
compile(group: 'org.springframework.data', name: 'spring-data-geode', version: '2.1.3.RELEASE')
}
- 起動クラスのコード
package spring.geode.client.geodeClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.gemfire.config.annotation.ClientCacheApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
import spring.geode.client.geodeClient.repository.UserRepository;
import spring.geode.geodeCommon.model.User;
@SpringBootApplication
@ClientCacheApplication(name = "SpringGeodeClientApplication") //①
@EnableGemfireRepositories(basePackageClasses = UserRepository.class) //②
@EnableEntityDefinedRegions(basePackageClasses = User.class) //③
@EnablePdx //④
public class GeodeClientApplication {
public static void main(String[] args) {
SpringApplication.run(GeodeClientApplication.class, args);
}
}
-
アノテーションの説明
- ①:
Apache GEODE
におけるclientのアプリケーションとして起動する設定 - ②: 指定したクラスを
Apache GEODE
のデータアクセサとして機能させる設定 - ③: 指定したRegion(RDBで言うところのテーブル)を自動的に作成する設定
- ④:
Apache GEODE
の扱うデータのシリアライズ/デシリアライズに関する設定(必須ではなさそう)
- ①:
-
Controllerクラス
package spring.geode.client.geodeClient.controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import lombok.RequiredArgsConstructor;
import spring.geode.client.geodeClient.service.UserService;
import spring.geode.geodeCommon.model.User;
import spring.geode.geodeCommon.model.UserRequest;
@RestController
@RequiredArgsConstructor
public class UserController {
private final UserService userService;
//nameでのユーザ検索API
@RequestMapping(path = "/find/user/{name}", method = RequestMethod.GET)
public User findById(@PathVariable String name) {
return userService.findByName(name);
}
//ユーザ全件検索API
@RequestMapping("/findAll")
public List<User> findAll() {
return userService.findAll();
}
//新規ユーザ登録API
@RequestMapping(path = "/register/user", method = RequestMethod.POST)
public String register(@RequestBody UserRequest request) {
return userService.register(request).getName();
}
}
- serviceクラス
package spring.geode.server.geodeServer.service;
import java.util.ArrayList;
import java.util.List;
import org.springframework.stereotype.Service;
import lombok.RequiredArgsConstructor;
import spring.geode.geodeCommon.model.User;
import spring.geode.geodeCommon.model.UserRequest;
import spring.geode.server.geodeServer.repository.UserRepository;
@RequiredArgsConstructor
@Service
public class UserService {
private final UserRepository rep;
public User findByName(String name) {
User user=rep.findByName(name).get(0);
return user;
}
public User register(UserRequest request) {
User commited = rep.save(new User(request));
return commited;
}
public List<User> findAll(){
List<User> users=new ArrayList<>();
rep.findAll().forEach(user -> users.add(user));;
return users;
}
}
Repositoryクラス
package spring.geode.server.geodeServer.repository;
import java.util.List;
import org.springframework.data.gemfire.repository.GemfireRepository;
import spring.geode.geodeCommon.model.User;
public interface UserRepository extends GemfireRepository<User, Integer> {
List<User> findByName(String name);
}
- 設定ファイル
spring.data.gemfire.pool.locators=localhost[40404]
server.port=9000
clientアプリケーションが接続する先のlocatorのIP,portを設定。
今回はserverアプリケーションの設定でlocatorを起動するため、localhost
を指定。
Apache GEODE
におけるclient,server,locatorの関係は以下の記事に記載。
Apache GEODE の概要
ここまででclientアプリケーションは実装完了。
扱うデータオブジェクト(Userクラス)はclient,serverプロジェクトで共通のクラスを使う必要があるため、Commonプロジェクトに集約して後ほど作成する。
server側アプリを作成
起動クラス以外は、clientアプリケーションのコードをそのままserverアプリケーションのプロジェクトに持ち込めば良い。
- 起動クラス
package spring.geode.server.geodeServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.gemfire.config.annotation.CacheServerApplication;
import org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions;
import org.springframework.data.gemfire.config.annotation.EnableLocator;
import org.springframework.data.gemfire.config.annotation.EnableManager;
import org.springframework.data.gemfire.config.annotation.EnablePdx;
import org.springframework.data.gemfire.repository.config.EnableGemfireRepositories;
import spring.geode.geodeCommon.model.User;
import spring.geode.server.geodeServer.repository.UserRepository;
@SpringBootApplication
@CacheServerApplication(locators = "localhost[40404]") //①
@EnableGemfireRepositories(basePackageClasses = UserRepository.class)
@EnableEntityDefinedRegions(basePackageClasses = User.class)
@EnablePdx
public class GeodeServerApplication {
public static void main(String[] args) {
SpringApplication.run(GeodeServerApplication.class, args);
}
@Configuration
@EnableLocator(port = 40404) //②
@EnableManager(start = true) //③
static class LocatorManagerConfiguration {
}
}
- アノテーションの説明
- ①:
Apache GEODE
におけるserverのアプリケーションとして起動する設定。接続してくるlocatorはlocalhost
の40404ポートであることを設定。 - ②: locatorを40404ポートで起動する設定
- ③: client/serverのアプリケーションの監視を行うサービスを起動する設定
- ①:
ここまででserverアプリケーションは実装完了。
データモデルの作成
- Webアプリにおけるclientからのリクエストモデル(
Apache GEODE
におけるclientとは異なる)
package spring.geode.geodeCommon.model;
import java.io.Serializable;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserRequest implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
}
-
Apache GEODE
に永続化するドメインモデル
package spring.geode.geodeCommon.model;
import java.io.Serializable;
import java.util.UUID;
import org.springframework.data.gemfire.mapping.annotation.Region;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Region("Users") //①
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Integer id;
private String name;
private int age;
public User(UserRequest request) {
this.name=request.getName();
this.age=request.getAge();
this.id=UUID.randomUUID().hashCode();
}
}
-
アノテーションの説明
- ①: このモデルが紐付くRegionを設定する。
-
データモデルを実装したプロジェクトはclient,serverアプリケーションのプロジェクトに依存されるため、client,serverアプリケーションの
settings.gradle
に以下の内容を記載
// geodeCommonは自分の作成したプロジェクト名に読み換える
include ':geodeCommon'
project(':geodeCommon').projectDir = new File('../geodeCommon')
ここまでで、データモデルの実装は完了
起動してみる
clientアプリケーションは起動時にlocatorへ接続するため、先にserverアプリケーションを起動する必要がある。
- serverアプリケーション起動(組み込みTomacatはポート番号
9090
で起動)
- clientアプリケーション起動(組み込みTomcatはポート番号
9000
で起動)
正常に両方のアプリケーションが起動できれば、client,locator,serverの接続はできているはずです。
ユーザをclientアプリケーションに登録してみる
curl -H "Content-Type: application/json" -X POST -d '{"name":"John","age":23}' http://localhost:9000/register/user/;
curl -H "Content-Type: application/json" -X POST -d '{"name":"Bob","age":10}' http://localhost:9000/register/user/;
serverアプリケーションからユーザを検索してみる
curl -i http://localhost:9090/findAll
検索結果で、clientアプリケーションに登録したユーザが検索できればOK
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 26 Jan 2019 17:10:37 GMT
[{"id":-1174841827,"name":"Bob","age":10},{"id":-516984913,"name":"John","age":23}]
念のため、name指定での検索も行う
curl -i http://localhost:9090/find/user/John;
HTTP/1.1 200
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sat, 26 Jan 2019 17:12:33 GMT
{"id":-516984913,"name":"John","age":23}
client,serverでデータが同期されていることを確認できました。
以下の公式ドキュメントを参考に実装してみました。
https://geode.apache.org/docs/
今後はpeerモデルでのアプリケーション作成や、AWSでのアプリ構成、非同期永続化など作り込んでいってみようと思います。