gRPCを使ってみた

gRPC for Javaをいじってみたので、使い方のまとめ

gRPCとは

Googleが開発した、RPC(Remote Procedure Call)を実装するためのフレームワークです。

Protocol Bufferを利用した高速通信、.protoによるインターフェース定義、JavaやNode.jsなどを含む、多数のプログラミング言語に対応していることなどが特徴。

仕組み

大雑把な流れは、以下の通りです。

  1. .proto形式でインターフェース定義を記述
  2. .protoから、利用したいプログラミング言語用の、サーバーのスケルトン実装とクライアントスタブを生成
    1. で生成されたものをベースにサーバー、クライアントを実装

クライアントでスタブのメソッドを呼び出すと、通信はフレームワークがよしなにやってくれます。

f:id:charlier_shoe:20160908135226p:plain

公式ドキュメント「What is gRPC?」http://www.grpc.io/docs/guides/ より抜粋

Javaで実際にやってみた

Javaで実際にやってみました。 (コードはこちら

.protoの作成

以下の様な感じで、.protoを作成します。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.oracle.jdt2016.hackathon.hr";
option java_outer_classname = "HrProto";
option objc_class_prefix = "HR";

import "google/protobuf/empty.proto";

package hr;

service Hr {
    rpc Employees(google.protobuf.Empty) returns (EmployeesReply) {}
}

message EmployeesReply {
    repeated Employee employee = 1;
}

message Employee {
    float commissionPct = 1;
    int64 departmentId = 2;
    string email = 3;
    int64 employeeId = 4;
    string firstName = 5;
    int64 hireDate = 6;
    string jobId = 7;
    string lastName = 8;
    int64 managerId = 9;
    string phoneNumber = 10;
    float salary = 11;
}

今回は、サーバーにあるダミーの従業員データを取得するインターフェースを作りました。引数は無し、返り値は従業員データの配列です。

"service"で始まる部分がメソッドの定義です。引数なしを定義するときは、"google/protobuf/empty.proto"をインポートして”google.protobuf.Empty”を引数に指定します。
返り値の型は”message”で始まる部分で定義しています。要素に配列を含みたい場合、”repeated"と記述すると、後続する型の配列だという宣言になります。

今回は、クライアント、サーバーとも同期的に処理を行なうインターフェースをとして定義していますが、非同期のインターフェースも使えます。
詳細は公式のドキュメントを参照して下さい。

サーバースケルトン、クライアントスタブの生成

今回はGradleを使います(Mavenでもいけるようです)。

まず、IDEを使うなりして適当なGradleプロジェクトを作成しておきます。

次に、src/main/protoフォルダを作成し、.protoファイルを配置します。

続いて、build.gradleを編集します。 今回使ったbuild.gradle(関連する部分を抜粋)は、以下の通りです。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'com.google.protobuf'def grpcVersion = '1.0.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    …
    compile "io.grpc:grpc-netty:${grpcVersion}"
    compile "io.grpc:grpc-protobuf:${grpcVersion}"
    compile "io.grpc:grpc-stub:${grpcVersion}"
}

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {
                // To generate deprecated interfaces and static bindService method,
                // turn the enable_deprecated option to true below:
                option 'enable_deprecated=false'
            }
        }
    }
}

以下のコマンドを実行すると、build/generated配下に目的のコードが生成されます。

 > gradle generateProto

生成されるコードのパッケージ名には、.protoの"option java_package"で宣言したものになります。

サーバーの実装

サーバーの実装は以下の通りです(ほとんど公式のHelloWorldサンプルから持ってきています)。

//importは省略

public class HrServer {
    /* The port on which the server should run */
    private int port = 50051;
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(port).addService(new HrImpl())
          .build().start();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                HrServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (server != null) {
          server.shutdown();
        }
    }

    /**
     * Await termination on the main thread since the grpc library uses daemon threads.
     */
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    /**
     * Main launches the server from the command line.
     */
    public static void main(String[] args)
            throws IOException, InterruptedException {
        final HrServer server = new HrServer();
        server.start();
        server.blockUntilShutdown();
    }

    private class HrImpl extends HrGrpc.HrImplBase {
        @Override
        public void employees(
                Empty request, StreamObserver<EmployeesReply> responseObserver) {
            // DBから従業員データを取得する
            EntityManager em = EntityManagerUtils.getEntityManager();
            @SuppressWarnings("unchecked")
            List<Employee> entities =
                    em.createNamedQuery("Employee.findAll").getResultList();
            em.close();
            // 返り値を作成
            EmployeesReply.Builder replyBuilder = EmployeesReply.newBuilder();
            for (Employee entity : entities) {
                com.oracle.jdt2016.hackathon.hr.Employee employee =
                        com.oracle.jdt2016.hackathon.hr.Employee.newBuilder()
                        .setCommissionPct(entity.getCommissionPct())
                        .setDepartmentId(entity.getDepartmentId())
                        .setEmail(entity.getEmail())
                        .setEmployeeId(entity.getEmployeeId())
                        .setFirstName(entity.getFirstName())
                        .setHireDate(entity.getHireDate().getTime())
                        .setJobId(entity.getJobId())
                        .setLastName(entity.getLastName())
                        .setManagerId(entity.getManagerId())
                        .setPhoneNumber(entity.getPhoneNumber())
                        .setSalary(entity.getSalary())
                        .build();
                replyBuilder.addEmployee(employee);
            }
            responseObserver.onNext(replyBuilder.build());
            responseObserver.onCompleted();
        }
    }
}

サーバーの起動はServerBuilderからServerを作成→startという流れ。

ロジックは、"HrGrpc.HrImplBase"をextendsして、Employeesメソッドをオーバーライドして記述します。

クライアントに返却するデータも.protoファイルを元に生成されたクラスを使います。今回はEmployeesReply、Employeeというクラスが作られています。 オーバーライドしたメソッドの中でこれらのオブジェクトを作って、responseObsererに渡して上げればよいようです。

クライアントの実装

クライアントの実装は以下の通りです(こちらも公式のHelloWorldサンプルをベースにしています)。

//importは省略

public class HrClient {
    private final ManagedChannel channel;
    private final HrGrpc.HrBlockingStub blockingStub;

    public HrClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext(true).build();
        blockingStub = HrGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void getEmployees() {
        EmployeesReply response;
        long begin = System.currentTimeMillis();
        try {
            response = blockingStub.employees(Empty.newBuilder().build());
        } catch (StatusRuntimeException e) {
            System.out.println("RPC failed");
            return;
        }
        System.out.println("Employees: " + response.getEmployeeCount());
    }

    public static void main(String[] args) throws Exception {
        HrClient client = new HrClient("localhost", 50051);
        try {
            client.getEmployees();
        } finally {
            client.shutdown();
        }
    }
}

スタブオブジェクトから、ローカルのメソッドを呼び出す感覚でemployeeを実行できます。

gRPCは、Protocol Bufferを使って高速な通信を実現しているわけですが、アプリケーションを実装する分には、プロトコル依存の部分を意識する必要はほとんどありません。

Spring Boot で組み込みDerbyデータベースを使う

【2016/06/14 追記】
JPAの部分は、Spring Data JPAの機能を利用するとpersistence.xmlを書かなくても動かせるはずです。コードを直したら、本ブログも修正する予定です。 コメント頂いた@makingさん。ありがとうございます。


先日のJava Day Tokyo 2016では、ショートハッカソンのセッションがあったのですが、そちらの準備と一部講師を担当させていただいてました。
3時間という、一般的なハッカソンに比べるとかなり短い枠でしたので、さすがに一からモノを作ってもらうわけには行かず、予めサンプル・アプリケーションを作っておいてそれをカスタマイズしてもらうという形態をとりました。

サンプルアプリを作っていく中で、Spring bootのアプリ内で組み込みDerby(読み取り専用)を動かすという技を習得したので、やり方を書きたいと思います。

ポイント

今回のアプリでは、DBのバイナリ/データを含む全ての依存成果物を、1つのjarに収める必要がありました。
Application Container Cloud(ACC)というOracleのコンテナ型のPaaSを利用するハッカソンでしたので、マイクロサービスらしくアプリをひとつにまとめたかったためです(本当は外部にDBを設けることもできます)。

これを実現するため、以下の2点を抑える必要がありました。

  1. Derbyのバイナリ/データファイルをjarに組み込む
  2. サンプルアプリから同じJVM上のDerbyに接続する

これらを踏まえて、以下、手順にいきます。

手順

1. データファイルの作成

はじめに、通常の手順でDerbyのデータベースを作成し、データを投入します。

以下の例では、ijを使ってC:\temp\dbフォルダ配下にhrというデータベースを作成し、同時に接続しています。ijは、DerbyのCLIクライアント(Oracle Databaseのsqlplusのようなもの)です。

> cd ${JAVA_HOME}\db\bin
ijバージョン10.11
> ij.bat
ij> connect 'jdbc:derby:C:\temp\db\hr;create=true';

続いてデータを投入していきます。例えば、create_tables.sql、populate_tables.sqlというスクリプトを使う場合、以下のコマンドを実行します。

ij> run 'C:\temp\db\scripts\create_tables.sql';
…
(実行結果の出力)
…
ij> run 'C:\temp\db\scripts\populate_tables.sql';
…
(実行結果の出力)
…

データの投入が終わったら接続を解除します。

ij> exit;

データファイルは接続時に指定したパスに作成されています。ディレクトリ構成は以下のようになっています。

C:\temp\db
└── hr
    ├── log
    │   ├── log.ctrl
    │   ├── log1.dat
    │   ├── logmirror.ctrl
    │   └── README_DO_NOT_TOUCH_FILES.txt
    ├── README_DO_NOT_TOUCH_FILES.txt
    ├── seg0
    │   ├── c10.dat
    │   ├── cf0.dat
    │   ├── …(.datがたくさん)
    │   └── README_DO_NOT_TOUCH_FILES.txt
    └── service.properties

次に、上記のファイル群の中にあるservice.propertiesを編集します。これはDerbyデータベースの設定を記述するファイルです。

derby.storage.tempDirectoryという設定を追記して、一時ファイルの出力先を変更します。
デフォルトでは、データファイル群のトップのフォルダ(上記のhrフォルダ)に一時ファイルが出力されますが、今回はそのフォルダはjarの中に格納されてしまいます。そのため、書き込み可能な領域(jarファイルの外)を一時ファイルの出力先にしておきます。

以下の例は、プログラムの実行カレント以下、temp/db/hrフォルダに設定しています。

SysschemasIndex2Identifier=225
SyscolumnsIdentifier=144
…
derby.storage.tempDirectory=temp/db/hr (←追記)

最後に、データファイル群をMavenプロジェクトの配下にコピーします。 今回の場合、src/main/resources/の下にdataという階層を切ってファイルを配置しました。こうしておくと、ビルドしたときに、jarファイル内のトップにdata/hrというフォルダ構成が作られてデータファイルが格納されます。

Mavenプロジェクト内のデータファイルの階層

[プロジェクトトップ]
├─src
│  ├─main
│  │  ├─java
│  │  │  └─…
│  │  └─resources
│  │      ├─data
│  │      │  └─hr
│  │      │      ├─log
│  │      │      └─seg0
│  ├─…  ├─…

jarファイル内のデータファイルの階層

xxx.jar
├─data
│  └─hr
│      ├─log
│      └─seg0
├─lib
├─…

2. JPAの設定

データベースにアクセスするには、クラスパスを使ったDBアクセス*1を利用します。
これは、データファイルをパッケージしたjarをクラスパスに入れておいて、クラスパス中のDBにアクセスする方法です。ただし、今回はアプリケーション本体のjarにデータファイルが入っていますので、新たにクラスパスを追加する必要はありません*2

注意すべきは、JDBC URLが、この方法特有の書き方になることと、JDBCドライバーにEmbeddedDriverを指定することです。

今回の場合、ビルド成果物のjar直下にdata/hrというパスでデータファイルが格納されています。それに合わせて、persistence.xmlに以下のように記述します。

    <persistence-unit name="HR" transaction-type="RESOURCE_LOCAL"><properties>
            <property
                name="javax.persistence.jdbc.url"
                value="jdbc:derby:classpath:data/hr"/>
            <property
                name="javax.persistence.jdbc.driver"
                value="org.apache.derby.jdbc.EmbeddedDriver"/>
        </properties></persistence-unit>

ユーザー名/パスワードは、データベースを作成した時に設定していれば記述する必要があると思われますが、とくに何もしていなければ不要です。

3. ビルド

pom.xmlには、依存モジュールにderbyのjarを追加しておきます。

    <dependencies>
        …
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derby</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

あとはビルドして、アプリを実行します。

> cd [pom.xmlのあるフォルダ]
> maven package
> java -jar target/[jarファイル名]

まとめ

実際にハッカソンで使ったアプリケーションは、GitHubにコードがあります。今回の手法の実装例として、ご参考にどうぞ。

Derbyのマニュアルのトップはこちら

*1:Accessing databases within a jar file using the classpath

*2:この機能は、データファイルだけのjarを作っておいて、それをクラスパスに入れるという使い方が想定されているようです。しかし、データファイルのjarを作っておいてSpring Bootアプリのjarに入れる構造にすると、クラスパスからデータファイルのjarが見えなくなってしまい、DBにアクセスできません。

ローカル環境のWebLogic Serverに、warを自動デプロイする

WebLogic Maven プラグインには、ビルドした成果物をテスト環境に自動デプロイする機能があります。 今回は、この機能を使って、ローカルPCで稼働するDockerコンテナ上のWebLogic Serverに、Mavenから自動デプロイしてみます。

この機能の本来の用途は、インテグレーションテスト等のテストフェーズを自動化することだと思いますが、これにとどまらず、実装フェーズでも活用できます。
例えば、UI画面の開発時など、コード修正→画面確認を繰り返して試行錯誤するような場面がありますが、こんなとき自動でデプロイができるととても作業が捗ります。

手順

0. WebLogic ServerがインストールされたDockerコンテナを用意する

本記事では、ローカル環境のVirtualBox上でLinux仮想マシンを動かし、その仮想マシンをDockerのホストとして利用します。
WebLogic ServerのDockerコンテナの作成手順は、過去の記事を参照ください。

また、DockerそのものとDocker on WebLogicの詳しい解説は、先日のWebLogic Server勉強会の資料が参考になりますので、こちらもぜひご参照ください(←宣伝)。

1. WebLogic Maven プラグインをインストールする

WebLogic Maven プラグインを、ローカル環境にインストールします。
こちらの手順も、過去の記事を参照ください。「1-2. WebLogic Mavenプラグインのインストール」までの作業をやれば良いはずです。

2. pom.xmlを編集する

以下の例のように、<build> > <plugins>タグの配下に、WebLogic Mavenプラグインの設定を記述します。

<build>
    <plugins>
        <plugin>
            <groupId>com.oracle.weblogic</groupId>
            <artifactId>weblogic-maven-plugin</artifactId>
            <version>12.2.1-0-0</version>
            <executions>
                <execution>
                    <id>wls-redeploy</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>redeploy</goal>
                    </goals>
                    <configuration>
                        <adminurl>t3://localhost:8001</adminurl>
                        <user>weblogic</user>
                        <password>welcome1</password>
                        <targets>AdminServer</targets>
                        <upload>true</upload>
                        <remote>false</remote>
                        <source>${project.build.directory}/${project.build.finalName}.${project.packaging}</source>
                        <name>${project.build.finalName}</name>
                        <verbose>true</verbose>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

goalに"redeploy"を指定しているので、既にデプロイされている物があった場合、自動的に更新されます。

configration配下の記述は、環境に合わせて変更する必要があります。
各設定の意味は、以下のとおりです。

  • adminurl
  • user
    • WebLogic Serverの管理者ユーザーのユーザー名
  • password
    • WebLogic Serverの管理者ユーザーのパスワード
  • targets
    • ビルドした成果物のデプロイ先
  • upload
    • 成果物をアップロードするかどうか(管理コンソールからデプロイするときの、アップロード操作に相当すると思われる)
  • remote
    • サーバーがリモートに設置されているかどうか
  • source
    • 成果物(warなど)のパス
  • name
    • 成果物の名前
  • verbose
    • デプロイ時に詳細メッセージを出力するかどうか

今回のように、VirtualBox上の仮想マシン上のDockerコンテナでWebLogic Serverを動かしている場合、uploadは"true"、remoteは"false"が正解のようです。

3. ビルドを実行する

実際にビルドを実行してみます。以下のコマンドを実行します。

> mvn clean pre-integration-test

warのビルドが終わると、デプロイが開始されます。
以下のように、最終的に"redeploy 完了"のメッセージが表示されれば、デプロイ完了です。

[INFO] --- weblogic-maven-plugin:12.2.1-0-0:redeploy (wls-redeploy) @ linguistic
s ---
[INFO] Command flags are: -noexit -redeploy -username weblogic -password *******
-name linguistics-1.0 -source C:\Users\hhayakaw\Documents\GitRepository\linguis
tics\app\target\linguistics-1.0.war -targets AdminServer -upload -verbose -admin
url t3://localhost:8001
…
ターゲットの状態: サーバー AdminServerでredeploy 完了

実際に所定のURLにアクセスすると、アプリケーションが稼働していることがわかると思います。

せっかくなので、デプロイが完了したらブラウザで開くところまで一気にやるようにすると、さらにいい感じです。

WebLogic Maven Pluginを使った自動デプロイの手順は、以上です。
WebLogic Maven Pluginは、使いこなせるとまだまだ色々できそうなので、探求を続けていきたいところです。

東京Node学園 20時限目で発表してきました

約1週間ほど立ってしまいましたが、4/5に開催された東京Node学園 20時限目に、発表者として参加してきました。

東京Node学園 20時限目の全体のまとめについては、すでに受付の方が書いてくださったブログと、Togetterがありますので、この記事では、各発表資料へのリンクをポイントしておきます。

資料リンク一覧(見つけられたもののみ)

(@saneyukiさん、@niccolliさん、見つけられてないだけだったらごめんなさい。)

僕の発表では、「Oracle が Node.js を始めたというのだが!」と題しまして、最近Oracleがリリースした、Oracle Node CloudとOracle JETの紹介をさせていただきました。左記2製品については、発表資料を参照下さい。

お礼など

Oracleの人間が自社製品の紹介をするという、勉強会の空気を全く読んでいないテーマにも関わらず、参加者の皆さんには暖かく受け入れていただきました。Node学園の皆さんの懐の深さに感謝です。
登壇したら冒頭いきなり盛大な拍手をもらったのには、正直感動したなー。

僕自身、運営にOracleが絡んでいない勉強会に参加するのはかなり久しぶりでしたが、やっぱりこういう勉強会に出るとモチベーション上がりますね。
今更ではありますが、Node.jsやフロントエンドのJavaScriptについて、僕自身もしっかりキャッチアップしていきたいと思います。

最後に、Node学園代表の @yosuke_furukawa 氏にお礼を。
今回の発表について相談を持ちかけたとき、快く承知してもらいました。彼の存在無くしては、今回の機会が実現することは難しかったことでしょう。
前職の新卒入社以来の縁ですが、今後ともよろしくお願いします。

なんだか若干退職エントリーみたいだな。そういった予定は今現在まったく無いので、この記事を見てい頂いている皆様も、今後ともよろしくお願いします。

WebLogic Mavenプラグインを使って依存モジュールの解決を楽にする

jBatch×WebLogicのネタも考え中ですが、閑話休題ということで。

以下のような開発をしているエンジニアの方は、WebLogic Mavenプラグインを利用するのがおすすめです。

  • WebLogic Serverで動かすアプリケーションを開発している
  • アプリケーションのビルドにMavenを利用している

例えば、Servlet-APIJDBCドライバのjarファイルを、pom.xmlにひとつひとつ書いて取得してたりしないでしょうか。しかも、WebLogicに入っているモジュールのバージョンを確認して、自力でローカルリポジトリにインストールしたりとか…。

WebLogic Mavenプラグインを使うと、個別にpom.xmlに記述せずとも、WebLogicに入っているモジュールを自動取得することができます。
手間が減るのはもちろん、コンパイルとランタイム間での、バージョン不整合を防止することができます。

というわけで、WebLogic Mavenプラグインを使って、WebLogicに入っているモジュール解決する手順を紹介します。

1. 手順

1-1. 前提条件

この手順は、以下の条件を満たしていることが前提です。

  • ビルドを実行するマシンにmvnがインストールされている
  • WebLogic Serverの、ORACLE_HOMEが参照できる

また、これ移行の手順は、WebLogic Server 12.2.1を使う想定で書きます。

1-2. WebLogic Mavenプラグインのインストール

以下のコマンドを実行します(WebLogic Server 12.2.1 の場合)

> cd ${ORACLE_HOME}\oracle_common\plugins\maven\com\oracle\maven\oracle-maven-sync\12.2.1
> mvn install:install-file -DpomFile=oracle-maven-sync-12.2.1.pom -Dfile=oracle-maven-sync-12.2.1.jar

※${ORACLE_HOME}は環境に合わせて変更してください。

以下のような内容がコンソールに出力されればOKです。

[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building Maven Stub Project (No POM) 1
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] --- maven-install-plugin:2.4:install-file (default-cli) @ standalone-pom---
[INFO] Installing C:\WLSHandsOn\mw\oracle_common\plugins\maven\com\oracle\maven\oracle-maven-sync\12.2.1\oracle-maven-sync-12.2.1.jar to C:\Users\hhayakaw\.m2\repository\com\oracle\maven\oracle-maven-sync\12.2.1-0-0\oracle-maven-sync-12.2.1-0-0.jar
[INFO] Installing C:\WLSHandsOn\mw\oracle_common\plugins\maven\com\oracle\maven\oracle-maven-sync\12.2.1\oracle-maven-sync-12.2.1.pom to C:\Users\hhayakaw\.m2\repository\com\oracle\maven\oracle-maven-sync\12.2.1-0-0\oracle-maven-sync-12.2.1-0-0.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.076 s
[INFO] Finished at: 2016-03-07T09:32:09+09:00
[INFO] Final Memory: 9M/245M
[INFO] ------------------------------------------------------------------------

1-3. ローカルリポジトリにモジュールを取り込む

MavenのローカルリポジトリOracle_Home配下からモジュールを取り込みます。

以下のコマンドを実行します(12.2.1の場合)

>mvn com.oracle.maven:oracle-maven-sync:push -DoracleHome=${ORACLE_HOME}

※${ORACLE_HOME}は環境に合わせて変更してください。

取り込むモジュールがたくさんあるので、コンソールに大量のメッセージが出力されますが、最終的に以下の内容が出力されれば成功のようです。

[INFO] SUMMARY
[INFO] ------------------------------------------------------------------------
[INFO] PUSH SUMMARY - ARTIFACTS PROCESSED SUCCESSFULLY
[INFO] ------------------------------------------------------------------------
[INFO] Number of artifacts pushed: 1013
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] PUSH SUMMARY - ERRORS ENCOUNTERED
[INFO] ------------------------------------------------------------------------
[INFO] No issues encountered.
[INFO]
[INFO] IMPORTANT NOTE
[INFO] This operation may have added/updated archetypes in your repository.
[INFO] To update your archetype catalog, you should run:
[INFO] 'mvn archetype:crawl -Dcatalog=$HOME/.m2/archetype-catalog.xml'
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 01:51 min
[INFO] Finished at: 2016-03-07T09:39:33+09:00
[INFO] Final Memory: 11M/162M
[INFO] ------------------------------------------------------------------------

1-4. pom.xmlを編集する

ビルドするプロジェクトのpom.xml<dependency>と、<plugin>タグを追加します。

以下の様に、両タグを記述します。また、既存の記述の不要な<dependency>は削除しておきます。

<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd"><dependencies><dependency>
            <groupId>com.oracle.weblogic</groupId>
            <artifactId>weblogic-server-pom</artifactId>
            <version>12.2.1-0-0</version>
            <type>pom</type>
            <scope>provided</scope>
        </dependency>
    </dependencies><build>
        <plugins><plugin>
                <groupId>com.oracle.weblogic</groupId>
                <artifactId>weblogic-maven-plugin</artifactId>
                <version>12.2.1-0-0</version>
            </plugin>
        </plugins>
    </build></project>

1-5. ビルドする

普段どおりビルドを実行します。例えば、

> mvn clean package

初回は結構時間がかかります。WebLogicから取り込んだモジュールの、メタデータのダウンロードらしき処理がおこなわれます。

2. 適用例

例えば、以前紹介したjBatchのサンプルアプリケーションですと、以下のモジュールの記述を削減できます。

  • javax.batch-api
  • cdi-api
  • javax.inject
  • javax.transaction-api
  • javax.ws.rs-api
  • javax.persistence

実際のコードはこちらJava EE系が軒並み減らせました。


WebLogic Mavneプラグインの機能は、今回紹介したものだけではありません。ビルドしたものをテスト環境に自動デプロイしたり、WLSTを実行したりなど、もっと高度な機能があります。

詳細は公式のドキュメントを参照ください。

パーティショニングを使ったjBatchのサンプルアプリケーションを作った

本記事では、先日のWebLogic Server勉強会で、デモに使用した、jBatchのサンプルアプリケーションを紹介します。

このアプリケーションでは、jBatchのパーティショニングを利用した並列処理を実装しており、ジョブの開始時に、パーティション数とスレッド数を指定することができます。

アプリケーションの概要

このアプリケーションは、テキストデータを形態素解析(単語に分割する処理)し、結果をデータベースに保存する処理をおこないます。
DBに格納された言語データは後々統計分析をかけるという想定で、その前処理に当たる、DBにデータを格納する部分をバッチジョブとして実装したという想定です。

分析対象のデータは、複数の日本語のテキストファイルです。
処理対象のテキストファイルのパスを特定するため、パスをリストしたインデックスを用意しておきます。バッチジョブは、インデックスを参照しながら、複数のテキストファイルに順次形態素解析をかけていきます。

f:id:charlier_shoe:20160229191107p:plain

このアプリケーションでは、jBatchのパーティショニングを利用した並列処理を実装しています。
インデックスを複数パーティションに分け、パーティション毎に、異なるスレッドで並列に処理を行うことができます。また、ジョブの開始時に、パーティション数とスレッド数を指定することができます。

ソースコード

本アプリケーションのソースコードは、GitHubに公開してあります。

アプリケーションの動かし方

ソースコードリポジトリ上のREADME.mdを参照下さい。

実際に動かしてみる

実際に動かしてみた結果を紹介します。

3パーティション、3スレッドでジョブを実行すると、標準出力に以下の様な内容が出力されます。 パーティション毎に別の番号を振っていますが、うまく3パーティションに分かれて処理が進んでいる事がわかります。

finished(parti tion: 0 | piece: "odazai/1047_ruby_20129_chikusei.txt")
finished(partition: 0 | piece: "odazai/1059_ruby_4748_hashiranu_meiba.txt")
finished(partition: 0 | piece: "odazai/1084_ruby_4753_nyozegamon.txt")
finished(partition: 0 | piece: "odazai/1093_ruby_20122_sakkano_techo.txt")
finished(partition: 1 | piece: "odazai/1573_ruby_4930_yukino_yono_hanashi.txt")
finished(partition: 0 | piece: "odazai/1095_ruby_20124_sange.txt")
finished(partition: 0 | piece: "odazai/1109_ruby_20131_tokyodayori.txt")
finished(partition: 0 | piece: "odazai/1562_ruby_14574_asa.txt")
finished(partition: 0 | piece: "odazai/1563_ruby_9706_gyofukuki.txt")
finished(partition: 0 | piece: "odazai/1564_ruby_14093_mangan.txt")
finished(partition: 1 | piece: "odazai/1574_ruby_15426_omoide.txt")
finished(partition: 2 | piece: "rakutagawa/109_ruby_1423_nidai.txt")
finished(partition: 2 | piece: "rakutagawa/110_ruby_1195_nikko_shohin.txt")
finished(partition: 2 | piece: "rakutagawa/111_ruby_1573_niwa.txt")
finished(partition: 1 | piece: "odazai/1575_ruby_24932_das_gemeine.txt")
finished(partition: 2 | piece: "rakutagawa/112_ruby_3165_noroma_ningyo.txt")
…

以下は、パーティション数、スレッド数を変えてジョブを実行しつつ、モニタリングダッシュボードでその様子を確認したものです。 待機スレッド数、アクティブスレッド数、CPU負荷の様子をグラフに表示しています。

f:id:charlier_shoe:20160229191139p:plain

指定されたスレッド数が増えるにしたがって、アクティブスレッド数も増えていることが分かります。
また、3パーティション/3スレッドまで上げると、CPUにきちんと負荷がかかって(CPUが利用されて)、処理時間が短くなっていることも確認できます。今回は1~1.5分で終わる程度の処理だったので、顕著な差ではありませんでしたが、もっと長時間のジョブではより意味のある効果が得られそうです。

ちなみに今回は、3コアのDockerホスト上で、DBとWebLogic Serverの2つのコンテナを稼働させています。そのためか、4パーティション/4スレッドにあげても、ほとんど性能向上はありませんでした。

以上、jBatchのサンプルアプリケーションの紹介でした。

Dockerコンテナ上にWebLogic Serverのクラスターを構成する

先日のWebLogic Server勉強会 特別編では、DockerコンテナでWebLogic Serverのクラスターを構成するデモを紹介しました。

Oracle公式のDockerfileのリポジトリにいくとreadmeにコマンドの使い方が記載されていますが、勉強会では若干アレンジして使っていた部分があります。
本記事では、その時に使用したコマンドの詳細を解説したいと思います。

前提条件

Dockerのホストマシンに、Oracle公式のDockerfileとスクリプトをダウンロードして、配置しておきます。本記事では、"~/sync"配下にこれらのファイルが配置されているものとします。

また、JDKWebLogic Serverのインストーラを別途ダウンロードし、所定のパスに配置する必要があります。
以下のようなディレクトリ構成になるよう、配置して下さい((※)がダウンロードして追加したファイルです)。

~/sync
├── GlassFish
│   └── …
├── MySQL
│   └── …
├── NoSQL
│   └── …
├── OpenJDK
│   └── …
├── OracleCoherence
│   └── …
├── OracleJDK
│   └── …
└── OracleWebLogic
     ├── COPYRIGHT
     ├── dockerfiles
     │   ├── 12.1.3
     │   │   └── …
     │   ├── 12.2.1
     │   │   ├── Dockerfile.developer
     │   │   ├── Dockerfile.generic
     │   │   ├── Dockerfile.infrastructure
     │   │   ├── fmw_12.2.1.0.0_infrastructure_Disk1_1of1.zip.download
     │   │   ├── fmw_12.2.1.0.0_wls_Disk1_1of1.zip.download
     │   │   ├── fmw_12.2.1.0.0_wls_quick_Disk1_1of1.zip(※)
     │   │   ├── fmw_12.2.1.0.0_wls_quick_Disk1_1of1.zip.download
     │   │   ├── install.file
     │   │   ├── oraInst.loc
     │   │   ├── server-jre-8u73-linux-x64.tar.gz(※)
     │   │   ├── server-jre-8u73-linux-x64.tar.gz.download
     │   │   └── …
     │   └── buildDockerImage.sh
     ├── LICENSE
     ├── README.md
     └── samples
         ├── 1213-domain
         │   ├── Dockerfile
         │   ├── README.md
         │   └── …
         ├── 1221-appdeploy
         │   └── …
         ├── 1221-docker-compose
         │   └── …
         ├── 1221-domain
         │   ├── container-scripts
         │   │   ├── add-machine.py
         │   │   ├── add-server.py
         │   │   ├── commonfuncs.py
         │   │   ├── createMachine.sh
         │   │   ├── createServer.sh
         │   │   ├── create-wls-domain.py
         │   │   └── waitForAdminServer.sh
         │   ├── Dockerfile
         │   └── README.md
         ├── 1221-medrec
         │   └── …
         ├── 1221-multihost
         │   └── …
         ├── 1221-webtier-apache
         │   └── …
         ├── clean-up-docker.sh
         └── rm-containers.sh

ツリーをよく見ると、気になる名前のディレクトリが結構ありますね。個人的には、GrassFish、CoherenceFusion Middleware Infrastractureのイメージ当たりを、後々試したいところです。

コマンドの説明

以下、コマンド群の説明です。

1. Install Imageの作成

はじめに、WebLogic ServerのInstall Imageを作成します。Install Imageは、Docker HubのOracle Linuxのイメージに、JDKWebLogicインストーラを適用したものです。

$ cd ~/sync/OracleWebLogic/dockerfiles
$ ./buildDockerImage.sh -v 12.2.1 -d

このコマンドでは、Oracle公式のスクリプトを介してdocker buildコマンドを実行しています。オプションの意味は以下のとおりです。

  • -v 12.2.1
  • -d
    • WebLogicを開発者モードでインストールする

2. Domain Imageの作成

次にWebLogic ServerのDomain Imageを作成します。Domain Imageは、Install Imageをベースにして空のWLSドメインを構成したものです。
以下のようにdocker runコマンドを実行します。

$ cd ~/sync/OracleWebLogic/samples/1221-domain
$ docker build -t oracle/1221-domain --build-arg ADMIN_PASSWORD=welcome1 .

このコマンドで実行されるDockerfileでは、ドメインを構成するWLSTを呼び出すよう記述されています。オプションの意味は以下のとおりです。

  • -t oracle/1221-domain
    • このコマンドで作成されるイメージの名前
  • --build-arg ADMIN_PASSWORD=welcome1
    • WebLogicの管理者ユーザーのパスワード

3. 管理サーバーの起動

管理サーバーを起動します。Domain Imageを指定してdocker runコマンドを実行します。

(※実際には1行)
$ docker run
    -v /vagrant:/tmp/shared --privileged=true
    -p 8001:8001 --hostname=wlsadmin
    --name=wlsadmin
    oracle/1221-domain

オプションの意味は以下のとおりです。

  • -v /vagrant:/tmp/shared
    • Dockerのホストとコンテナとの共有フォルダの指定。上記の場合、ホストの/vagrant配下のファイル、ディレクトリが、コンテナの/tmp/shared配下に共有されます
  • --privileged=true
    • 共有フォルダ(/tmp/shaared)へのアクセス権を与える
  • -p 8001:8001
    • ホストの8001番ポートを、コンテナの8001番ポートにフォワードする
  • --hostname=wlsadmin
    • コンテナホスト名の指定
  • oracle/1221-domain
    • 使用するイメージの名前

-vオプションでコンテナからホストのファイルを参照できるようにしています。これは、コンテナ上で動作するアプリケーションから、アプリで使用するデータを参照させるためです。
このようなやり方をすると、アプリケーションのためにデータを転送する手間が省けて便利です。

このコマンドでは、runの時に実行されるコマンドを明示的に指定していませんので、イメージ作成時にDockerfileで指定されたコマンドが実行されます。

    # Define default command to start bash.
    CMD ["startWebLogic.sh"]

Domain ImageのDockerfileでは、startWebLogic.shが指定されているので、これが実行されます。

また、-dオプションをつけていないので、フォアグラウンドでstartWebLogic.shが実行されます。管理サーバーの標準出力が表示され続けて操作が帰ってこないので、以降は別のターミナルで作業します。
Dockerコンテナは、ホスト上の1つのプロセスとして実行されていますが、それを体感できる挙動だと思います。

4. 管理対象サーバーの起動

管理対象サーバーを起動します。管理サーバーのときと同じく、Domain Imageを指定してdocker runコマンドを実行します。

(※実際には1行)
$ docker run
    -d
    -v /vagrant:/tmp/shared
    --privileged=true
    -p 5556:5556 -p 7001:7001 --hostname=wlsmanaged0
    --link wlsadmin:wlsadmin
    --name=wlsmanaged0
    oracle/1221-domain
    createServer.sh

オプションの意味は以下のとおりです(管理サーバーと同じものは省略)。

  • -d
    • バックグラウンドでコンテナコンテナを実行する
  • -p 5556:5556 -p 7001:7001
    • ホストの5556/7001番ポートをコンテナの5556/7001番ポートにフォワードする
  • --link wlsadmin:wlsadmin
    • 接続先の管理サーバー(wlsadmin)に対するエイリアス名の設定
  • --hostname=wlsmanaged0
    • コンテナホスト名の指定
  • createServer.sh
    • コンテナ起動時に実行するコマンド

管理サーバーと同じように、-vオプションで、ホスト上の同じファイルを参照できるようにしています。このようにすると、WebLogic Serverドメイン内の各サーバーから同じファイルを参照できるので、アプリケーション用のデータをサーバーごとに用意する手間が省けます。

--linkオプションは、WLSTで管理サーバーに接続する際の、接続先のエイリアスを設定しています。後述のcreateServer.shからWLSTで管理サーバーに接続しますが、WLSTのスクリプトは、wlsadminというエイリアスで接続先を指定しています。これが実際の管理サーバーのホスト名に紐づくようにしています。
この例では、ホスト名もエイリアスも同じ名前になっていますが、"wlsadmin:wlsamdin"の先に記述した方が接続先の実際の名前で、後に記述した方がエイリアスです。

このコマンドでは、コンテナ起動時にcreateServer.shを実行するよう指定しています。 createServer.shでは、管理対象サーバー、ノードマネージャの起動、WLSTによるマシン、サーバー、クラスターの構成をおこないます(wlsmanaged0からwlsamdinに対してWLSTを実行)。

管理対象サーバーを複数起動する場合は、ホスト名とポートフォワードの値だけを変えて、同じコマンドを実行すればOKです。

$ docker run
    -d
    -v /vagrant:/tmp/shared --privileged=true
    -p 15556:5556 -p 17001:7001 --hostname=wlsmanaged1
    --link wlsadmin:wlsadmin
    --name=wlsmanaged1
    oracle/1221-domain
    createServer.sh

数十秒程度待ってからWebLogic Server 管理コンソールを見ると、管理対象サーバー2つを含むクラスターが構成されているはずです。

その他知っておくと役に立つコマンド

イメージやコンテナを操作する上で、よく使うコマンドを以下に挙げておきます。コンテナ名やイメージ名は、これまでのコマンドを使った場合の実際の値になっているので、それ以外の場面で使うときは適宜読み替えてください。

コンテナの停止、起動

1つのコンテナを停止

docker stop wlsadmin

複数のコンテナを停止

docker stop wlsadmin wlsmanaged0 wlsmanaged1

1つのコンテナを起動

docker start wlsadmin

1つのコンテナをフォワグラウンドで起動

docker start -a wlsadmin

複数のコンテナを起動

docker start wlsadmin wlsmanaged0 wlsmanaged1

コンテナの一覧

起動中のコンテナの一覧

docker ps

全てのコンテナの一覧

docker ps -a

コンテナの削除

1つのコンテナを削除

docker rm wlsadmin

複数削除は停止、起動と同様)

イメージの削除

Domain Imageの削除

docker rmi oracle/weblogic:12.2.1

Install Imageの削除

docker rmi oracle/1221-domain

コンテナIPの確認

sudo docker inspect --format '{{ .NetworkSettings.IPAddress }}' wlsmanaged0

覚えるのが面倒であれば、以下のやり方でも十分です。

sudo docker inspect wlsmanaged0 | grep IP

以上、DockerコンテナでWebLogic Serverのクラスターを構成する際の、コマンドの解説でした。