はじめに
最近は関数型言語が流行りつつあるので、僕も乗り遅れまいと(すでに乗り遅れている感じが否めないが)Vertxを学んでみる。
ちなみにVertxとは何か・・・というと僕は正確な答えを持ってない。
僕のイメージでは負荷分散プログラム
のフレームワークというイメージ。
とりあえず、僕のイメージでVertx
を語ってみる。(間違ってるかもしれないので、鵜呑みにしないように!w)
Vertxって?
プログラムの最小単位をVerticle
と定めて、Verticle
単位でプログラムを動かす事を目的としている。
例えばServerVerticle
というHTTPリクエスト
を待ち受けるプログラムを書いたとした場合に、リクエストを受けるのはServerVerticle
だが実際の処理はUserVerticle
に任せる場合は以下のようになる。
HTTPリクエスト → ServerVerticle → UserVerticle → HTTPレスポンス
これだけだと何が良いのかわからないと思う。
Vertxの特徴はVerticle
単位でプログラムが動くため、ServerVerticleとUserVerticleは別々のメモリ空間で動作している。
そのVerticle間のやりとりはメッセージで行うことによって疎結合を実現しているのだ。
なので、プログラム単位に割くメモリを割り当てる事ができて負荷が重いプログラムにはメモリを多く割り当てるなどを動的
に行えるのだ!
動的にメモリを割り当てる(正確にはVerticle数を増やしたり減らしたりできる)というのは、あるプログラムの負荷が上がった際に局部的にメモリを増やせるということだ。
通常のアプリケーションの場合は、サーバー台数を増やしたりしなければならないところをプログラム単位で増強できればコスト削減にも繋がるし高負荷時の対応も即時に行える。
プログラムが自動的にVerticleの数を制御してくれれば突然高負荷になっても何も対応しないで済むかもしれないのだ!
・・・と書いてますが、僕は実際に検証したわけではありません。(汗
Verticleって?
Verticle
はVertx上のでプログラム最小単位です。(最大単位と書いても合ってる気がする・・・。)
上記にも記載した通りで、VertxはVerticle単位
でメモリ空間を確保してます。
なので、Verticle同士の干渉
はメッセージのやり取りだけで行います。
ということは・・・状態をサーバー側で持てない事を示してます。
すなわち副作用のない関数を書く必要があります。
(Vertxではサーバーを跨いでデータを扱えるSyncMapがあります。また、DBへのアクセスなどもあるのでこの表現は間違ってますが説明上このように書いてます。)
「副作用がない・状態を保持しない」プログラムと言うと、オブジェクト指向をずっと使ってた人には若干とっつきにくそうです。
というか、僕はとっつきにくかったです。
しかし、この概念があるためVertx
は以下の恩恵があります。
- Verticle単位でプログラムの数を変更できる。
- メインループが専有されて他のプログラムに影響することがない。(重い処理をVerticleに記載してはいけない。)
- Verticle内のプログラム言語を自由に選べる。(インターフェースが決まっているため)
これは、素早いレスポンスを要求されるWebSocket
などで大きな威力を発揮します。
Vertxのインストール
とりあえず、イメージをつかむために動かしてみましょう!
まずはJava8をインストールしてください。
僕は1.8.0_31
をインストールします。
また、以下の説明はMacで行いますが特にプラットフォームには依存しないのでWindowsでも動きます。
Vertx3をインストールする。
vertxのホームページより本体をダウンロードしてください。
僕はここからvert.x-3.0.0-full.zip
をダウンロードして解凍しました。
※パスを通すため全角文字や空白が含まれていないフォルダに解凍すると余計な障害に見舞われません。
Pathを通します。
僕は.bash_profile
に以下のように記載しました。
PATH="$PATH":[解凍フォルダのパス]/vert.x-3.0.0/bin
パスを読み込みます。(Windows用にvertx.bat
も用意されてました。未確認ですが、多分Windowsでも動きます。)
$> source .bash_profile
インストールの確認をします。
$> vertx
Listening for transport dt_socket at address: 8000
....
と表示されればvertxの設定は完了です。
動かしてみる。
設定が終わったら動かしてみましょう。
Java編
以下のファイルを適当なフォルダに作成してください。
import org.vertx.java.core.Handler;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.platform.Verticle;
public class JavaVerticle extends Verticle {
public void start() {
vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
public void handle(HttpServerRequest req) {
req.response().headers().set("Content-Type", "text/plain");
req.response().end("Hello Java!");
}
}).listen(8080);
}
}
同フォルダでvertxを起動します。
$> vertx run JavaVerticle.java
Listening for transport dt_socket at address: 8000
Succeeded in deploying verticle
と表示されればOKです。
localhost:8080
をブラウザで見てもらえるとHello Java!
と表示されているはずです。
Groovy編
Javaと同様にGroovyでも動かしてみましょう。
Groovyを動かすために何か必要なものはありません。
import org.vertx.groovy.platform.Verticle
class GroovyVerticle extends Verticle {
@Override
def start() {
vertx.createHttpServer().requestHandler { request ->
request.response.putHeader("Content-Type", "text/plain")
request.response.end("Hello Groovy!")
}.listen(8070)
}
}
動かしてみましょう。
$> vertx run groovy:GroovyVerticle
Listening for transport dt_socket at address: 8000
Succeeded in deploying verticle
Groovyの起動ではgroovy:
が必要です。代わりにファイル拡張子は不要です。指定すると落ちます。
localhost:8070
をブラウザで見てもらえるとHello Groovy!
と表示されているはずです。
両方同時に動かしてみましょう
先ほどVerticle
は自由に増やしたりできると書きました。
実際に複数のVerticleを起動してみましょう。
container.deployVerticle('groovy:GroovyVerticle')
container.deployVerticle('JavaVerticle.java')
こちらのファイルを作成します。
$> vertx run App.groovy
Listening for transport dt_socket at address: 8000
Succeeded in deploying verticle
上記のファイルを実行するとVerticle
が二つ起動されます。
試しにlocalhost:8080
とlocalhost:8070
へアクセスしてみてください。
両方のVerticleが動作していることを確認できると思います。
※なぜかここはgroovy:
を書かなくても動く。Verticleを起動するときだけ必要なのかもしれない。(汗
container.deployVerticle('groovy:GroovyVerticle', 10)
container.deployVerticle('JavaVerticle.java', 15)
上記のように設定するとGroovyVerticle
を10個、JavaVerticle
を15個起動させることができます。
(多分w。確認はしてませんが、動きました・・・。)
また、外部からverticleの数を変えることも出来ますが・・・やり方をメモったファイルを紛失してしまったので本記事では割愛します。(汗
最後に説明
とりあえず、Verticle
の起動方法は分かったかと思います。
コードを見ていただければ分かると思いますが、Verticle
が起動するとstart
メソッドがまず呼び出されます。
ここでVerticleの初期処理を行います。
上記の例ではHTTPの待ち受けを行ってますが、他では以下のことを行ったりします。
- WebSocketの待ち受け
- イベントハンドラ(vertx.eventBus)の登録
・・・って、書いておきながら僕は上記の2点くらいしかしたことがなかったです。はい。
長くなってきたのでVerticle同士のメッセージやりとりをするイベントハンドラについては別記事で記載します。
また、その際にSyncMapについても書こうと思います。
ReactiveがVertx3で使えるようになったので、こちらについても調べたいのですが・・・ここまで調べれるかどうかは疑問です・・・。
(というのも、Vertxは業務で使用しているわけではないので・・・)
おまけ
本当はWebSocketの通信方法を書きたかったのですが、検証が面倒なのでここでの題材にあげませんでした。
が!
興味のある方もいると思いますので、Groovyのコードだけ貼り付けておきます。
※Vertx2で動かしていたコードです。説明はしませんw
import org.vertx.groovy.core.buffer.Buffer
import org.vertx.groovy.core.eventbus.Message
import org.vertx.groovy.core.http.ServerWebSocket
import org.vertx.groovy.platform.Verticle
import org.vertx.java.core.impl.VertxInternal
import org.vertx.java.core.json.JsonArray
import org.vertx.java.core.spi.cluster.ClusterManager
class WsServerVerticle extends Verticle {
@Override
def start() {
// スタックオーバーフローでは下記のように取得していた。
// http://stackoverflow.com/questions/12299132/clustering-and-shared-data-in-vert-x
// ClusterManager clusterManager = ((VertxInternal)vertx).clusterManager()
// Map map = clusterManager.getSyncMap("mapName");
// 実際は以下のように取ってた。(以下のコードはこのサンプルでは使ってません。)
ClusterManager clusterManager = ((VertxInternal)vertx.toJavaVertx()).clusterManager()
Map map = clusterManager.getSyncMap("mapName");
// WebSocketを起動
vertx.createHttpServer().websocketHandler {ServerWebSocket ws ->
onOpen(ws)
}.listen([ポート番号], [アドレス(0.0.0.0)])
}
@Override
def stop() {
}
// WebSocketのコネクションが開かれた場合の処理
private def onOpen(ServerWebSocket ws) {
println "open"
// クライアントへ書き込む関数
def writeHandler = { Message message ->
// wsインスタンスをキャプチャーしてる。(上の説明で状態を持たないって書いてたが、こういう場合もある。)
ws?.writeBinaryFrame(new Buffer((byte[])message.body))
}
// 他のVerticleからもクラアントへ書き込めるようにする。
vertx.eventBus.registerHandler([クライアント毎に識別した名称("WS_ID_"+userIdみたいな感じ)], writeHandler)
// WebScoketへ通知があった場合の処理
ws.dataHandler { data ->
onMessage(data)
}
// WebSocket切断処理
ws.closeHandler {
// 作成したイベントバスを削除しておく。
vertx.eventBus.unregisterHandler([クライアント毎に識別した名称("WS_ID_"+userIdみたいな感じ)], writeHandler)
println "close"
}
}
// WebSocketのデータ受信ハンドラの定義
private def onMessage(Buffer data) {
// 受け取ったメッセージの処理を行う。
// 実際の処理を行うVerticleを呼び出す。
vertx.eventBus.send([他のVerticleで登録したイベントバスを呼び出す。], data) { Message msg ->
// 結果をクライアントへ通知する
vertx.eventBus.send([クライアント毎に識別した名称("WS_ID_"+userIdみたいな感じ)], (JsonArray)msg.body)
}
}
}
参考にしたサイト
Vertx2のメモ
僕用のメモです。
Vertx2で使ってた設定をメモに残しておきます。
読者の方は読み飛ばしてください。
デバッグ
Vertx3では未確認ですが、Vertx2では以下の設定を[解凍フォルダのパス]/vert.x-3.0.0/bin/vertx
に記載しました。
JVM_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n"
mod.jsonはどこに・・・?
Vertx3でも同じ構成なのだろうか??
Vertx2では以下の場所に配置されていたが・・・。
https://github.com/vert-x/vertx-gradle-template/tree/master/src/main/resources
Vertx3ではなくなってる・・・。
ってか、別にテンプレートがあるのか???
https://github.com/vert-x3/vertx-examples/tree/master/gradle-simplest
Gradleの設定が大きく変わってないか??
Vertx2
https://github.com/vert-x/vertx-gradle-template/blob/master/build.gradle
Vertx3
https://github.com/vert-x3/vertx-examples/blob/master/gradle-simplest/build.gradle
僕はGradleに明るくないけど、Vertx3
ではMain-Class
で初期処理をするVerticle
を指定できるようになったのかな?
Vertx2
では以下のコマンドで起動してた。
$> CLASSPATH=[先に読み込みたいライブラリ]/lib/*:. vertx run [起動したいVerticle] -cluster -cp build/classes/main -conf [読み込みたいConfig]
[VERTX_HOME]/conf/langs.properties
もなくなってる・・・。