Upload
ssogabe
View
691
Download
5
Embed Size (px)
DESCRIPTION
Netty 3.Xベースの概略とApache Camel Nettyコンポーネントの簡単な説明です
Citation preview
ソケット接続をApache Camelに統合する
日本Apache Camelユーザ会@ssogabe
Netty
Javaで非同期、イベント駆動のサーバ・クライアントアプリケーションを構築するためのフレームワーク
高パフォーマンス、高スケーラビリティo NIOをラッピング
o 処理をレイヤーごとに分離
TCP、UDP以外にも、HTTP、WebSocket、RTSPなどもサポート
Akka、Play!、HornetQなどで使用されている
Nettyでは、以下のバージョンを開発中
o Netty 3.9.3
o Netty 4.0.21
o Netty 4.1.0 Beta1
o Netty 5.0 alpha
Apache Camel 2.10.6では、Netty 3.2.10を使用
ここでは、3.Xベースを対象とする
アーキテクチャ
発生したイベントを、Handlerが処理を行う
Nettyでは、22個のイベントをサポート
主なイベント
No. イベント 説明
1 channelOpen チャネルがオープンしたが、まだ、ポートにバインドも、接続もしていない
2 channelConnected 接続先とのコネクションが確立した
3 writeComplete チャネルに何か書き込まれた
4 messageReceived メッセージを受信した
5 channelDisconnected 接続先とのコネクションが切断された
6 channelClosed チャネルがクローズした
7 exceptionCaught I/OスレッドやChannelHandlerが例外をスローした
8 channelIdle チャネルが一定期間アイドル状態になった
メッセージをHandlerを組み合わせたChannelPipelineで処理
o “データ受信”などのイベントが、ChannelPipelineを流れる
o Handlerが処理するイベントを受け取り、イベントからデータを取得
o Nettyが提供する、ロギング、メッセージのフレーム処理などのHandlerや、メッセージの処理を行うユーザ実装のHandlerを組み合わせて処理を実装
o サーバ、クライアントいずれも同じ仕組み
実際には1つのライン上に配置
大きく分けて5種類のHandlerをサポート
送信用、受信用、送受信共用の3種類
No. カテゴリ 説明 代表的なHandler
1 フレーム ストリームからメッセージを切り出しや、電文長の追加を行う
LengthFieldBasedFrameDecoderLengthFieldPrepender
2 変換 メッセージ⇔文字列、オブジェクトや、SSL、暗号化処理を行う
StringDecoder/EncoderZlibDecoder/EncoderOneToOneDecoder/Encoder
3 イベント メッセージ受信などイベントに対するユーザ実装の処理を行う
SimpleChannelHandlerIdleStateAwareChannelHandler
4 アイドル・タイムアウト
無通信時の処理や、タイムアウトを検出する
IdleStateHandlerRead/WriteTimeoutHandler
5 その他 ロギングなどの処理 LoggingHandler
スレッドセーフではないHandlerがあるので注意o スレッドセーフなHandlerには、クラスに@Sharableを付与
o スレッドセーフの場合は、インスタンスを共有可能
Handler 送受信 スレッドセーフ
フレーム処理
DelimiterBasedFrameDecoder 受信
FixedLengthFrameDecoder 受信
LengthFieldBasedFrameDecoder 受信
LengthFieldPrepender 送信 ○
変換処理
Base64Decoder 受信 ○
Base64Encoder 送信 ○
ZliDecoder 受信
ZlibEncoder 送信
StringDecoder 受信 ○
StringEncoder 送信 ○
ObjectDecoder 受信
ObjectEncoder 送信 ○
SslHandler 送受信
Handler 送受信 スレッドセーフ
イベント処理
SimpleChannelHandler 送受信
IdleStateAwareChannelHandler 送受信
アイドル・タイムアウト処理
IdelStateHandler 受信 ○
ReadTimeoutHandler 受信 ○
WriteTimeoutHandler 送信 ○
その他
ExecutionHandler 送受信 ○
LoggingHandler 送受信 △
BlockingHandler 受信
BufferedWriteHandler 送受信
ChunkedWriteHandler 送受信
多くの場合、次の順序でHandlerを設定
Network
フレーム(ストリーム⇒メッセージ)
変換(メッセージ⇒オブジェクト)
変換(オブジェクト⇒メッセージ)
フレーム(電文長の追加)
イベント(業務処理、レスポンス作成)
UpStream(受信) DownStream(送信)
実装例
ServerBootstrap bootstrap = new ServerBootstrap(factory);bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Overridepublic ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
// 送信pipeline.addLast("frameEncoder", LENGTH_FIELD_PREPENDER);// UserInfoを電文に変換pipeline.addLast("userInfoEncoder", USER_INFO_ENCODER);
// 受信pipeline.addLast("frameDecoder", new LengthFieldBasedFrameDecoder(8192, 0, 4, 0, 4));// 電文をUserInfoに変換。pipeline.addLast("userInfoDecoder", USER_INFO_DECODER);
// ハンドラー追加pipeline.addLast("handler", USER_INFO_SERVER_HANDLER);
return pipeline;}
});
ChannelPipelineでは、受信したバイト列をChannelBufferで受け取る
ChannelBufferを操作して、オブジェクト⇔メッセージ変換を行う
JavaのNIOが提供するByteBufferより使い勝手が良い
o ByteBufferは、position()/limit()/flip()などを使用して、書き込み位置を正確に把握する必要がある
o 多くの場合、ByteBufferより高速に動作
デフォルトのバイトオーダーは、ビッグエンディアン
読み込み位置と書き込み位置を管理する
readXXX()でバイト列を取得、writeXXX()でバイト列を書き込むと、readerIndex/writerIndexが増加
getXXX()、setXXX()は、reader/writerIndexは増加しないので注意が必要
基本は、readXX()、writeXX()を使用
0 readerIndex writerIndex
読み書き不可バイト列 読み込み可能バイト列 書き込み可能バイト列
capacity
Handler
フレーム処理
TCP/IPのストリームベースの通信では、2つのメッセージを送信した場合、OSは2つのメッセージではなく、1つのバイト列として扱う(UDPは不要)
受信APでは、バイト列をフレーム分けする必要がある
ObjectDecoder/Encoderを使用する場合は不要
ABC
DEストリーム通信 AB CD E
ABC
DEフレーム処理
送信AP 受信AP
TCP/IP
3つのクラスを標準で提供、独自実装も可o DelimiterBasedFrameDecoder
• NUL(0x00)や改行文字などの1つ以上のデリミタで分ける
o FixedLengthFrameDecoder
• 固定のバイト長で分ける
o LengthFieldBasedFrameDecoder
• メッセージのあるフィールドから電文長を取得し分ける
動的にバイト長を算出し、先頭に付加するクラスも提供o LengthFieldPrepender
• LengthFieldBasedFrameDecoderと組み合わせることが多い
o 先頭以外にバイト長を設定する場合は、自前で行う
ServerBootstrap bootstrap = new ServerBootstrap(factory);bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
@Overridepublic ChannelPipeline getPipeline() throws Exception {
ChannelPipeline pipeline = Channels.pipeline();
(snip)// 受信 (改行 ‘¥r’, ¥’n’で切り出す)pipeline.addLast("DelimiterDecoder", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter());
(snip)
// 受信 (3バイト固定で切り出す)pipeline.addLast("FixedLengthDecoder", new FixedLengthFrameDecoder(3);
(snip)
return pipeline;}
});
メッセージに含まれるあるフィールドから電文長を取得し、必要な部分をフレーム分けするo 電文長が、ペイロードのみの場合、ヘッダ長も含む場合も対応
o フレーム分けする位置も指定可能
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 04 0A 00 C0 11 FF EF
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 04 0A 00 C0 11 FF EF
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 0C 0A 00 C0 11 FF EF
HDR2 Payload
0A 00 C0 11 FF EF
電文長がペイロードのバイト長で、HDR1~ペイロードフレーム分けする
電文長がメッセージ全体のバイト長で、HDR2~ペイロードをフレーム分けする
5つのパラメータで、フレーム分けする範囲を指定
No. パラメータ 説明
1 maxFrameLength 最大電文長。8192など十分大きい値。この値を超えると例外が発生
2 lengthFieldOffset 電文長を表すフィールドの開始位置
3 lengthFieldLength 電文長を表すフィールドのバイト長
4 lengthAdjustment メッセージを切り出す際の補正値
5 initialBytesToStrip メッセージを切り出す開始位置
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 04 0A 00 C0 11 FF EF
lengthFieldOffset(=2)
lengthFieldLength(=4)
initialBytesToStrip(=8)
lengthAdjustment(=2)
Payload
C0 11 FF EF
電文長は、ペイロードのみの場合、メッセージ全体の場合があるため、何らかの補正が必要
算出例1
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 04 0A 00 C0 11 FF EF
lengthFieldOffset(=2) initialBytesToStrip(=8)
バイト長=
電文長フィールドの値
4バイト 2ba2バイト足すとメッセージ長
lengthAdjustment=2
算出例2
HDR1 電文長 HDR2 Payload
00 C0 00 00 00 0C 0A 00 C0 11 FF EF
lengthFieldOffset(=2)
initialBytesToStrip(=0)
12バイト
2ba6バイト削除するとメッセージ長 lengthAdjustment=-6
バイト長=電文長フィールドの値
フレーム処理を実装する場合は、FrameDecoderを拡張
1. 必要なバイト列を読めない場合は、nullを返す。
• nullを返すと、再度decode()が呼ばれる
2. bufferから必要なバイト列を取得
public class TimeDecoder extends FrameDecoder{
@Overrideprotected Object decode(
ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) {
if (buffer.readableBytes() < 4) { ・・・ (1)return null;
}
return buffer.readBytes(4); ・・・ (2)}
}
Handler
変換処理
フレーム処理で切り出されたChannelBufferを変換
よく使われるSSL、圧縮などを標準で提供
イベント処理で扱いやすいように、ChannelBuffer(バイト列)を、文字列やユーザ定義のオブジェクトに変換
o イベント処理では、バイト列を意識しないようにする
標準で提供する変換処理
o ObjectEncoder/Decoder以外は、フレーム処理が必要
ChannelBufferとユーザ定義オブジェクトの変換は、OneToOneEncoder/Decoderを拡張する
No. クラス 説明
1 Base64Encoder/Decoder Base64でエンコード/デコード
2 ZlibEncoder/Decoder Defalteアルゴリズムで圧縮、展開
3 StringEncoder/Decoder 文字列との変換
4 ObjectEncoder/Decoder オブジェクトのシリアライズ、デシリアライズ
5 SslHandler SSL、TLSおよびStartTLSをサポート
public class UserInfoEncoder extends OneToOneEncoder {
@Overrideprotected Object encode(ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {if (!(msg instanceof UserInfo)) {
return msg;}UserInfo info = (UserInfo) msg;
// 可変サイズのバッファを用意ChannelBuffer buffer = ChannelBuffers.dynamicBuffer();
// Nameのバイト長とName本体byte[] byteName = info.getName().getBytes(StandardCharsets.UTF_8);buffer.writeInt(byteName.length);buffer.writeBytes(byteName);// Agebuffer.writeInt(info.getAge());
return buffer;}
}
public class UserInfoDecoder extends OneToOneDecoder {
@Overrideprotected Object decode(ChannelHandlerContext ctx, Channel channel, Object msg)
throws Exception {if (!(msg instanceof ChannelBuffer)) {
return msg;}ChannelBuffer buffer = (ChannelBuffer) msg;
// Nameのバイト長とName本体int length = buffer.readInt();byte[] byteName = new byte[length];buffer.readBytes(byteName);String name = new String(byteName, StandardCharsets.UTF_8);// ageint age = buffer.readInt();
return new UserInfo(name, age);}
}
Handler
イベント処理
発生したイベントに対するユーザ処理の実装
o メッセージを受信時にレスポンスを返す
o 例外が発生したら、コネクションを切断する など
SimpleChannelHandlerか、IdleStateAwareChannelHandlerを拡張
o 通常は前者を使用
o アイドル時の処理も実装する場合は後者
public class UserInfoServerHandler extends SimpleChannelHandler {
private final Logger log = LoggerFactory.getLogger(UserInfoServerHandler.class);
@Overridepublic void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {UserInfo info = (UserInfo) e.getMessage();log.debug("電文を受信しました {}", info.toString());log.debug("電文を送信します");e.getChannel().write(info);
}
@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
throws Exception {log.warn("例外が発生しました", e.getCause());log.debug("コネクションを切断します");e.getChannel().close();
} }
Camel Netty
Camelでは、Netty、Netty4およびNetty HTTPの3種類コンポーネントをサポート
o Camel Netty:Netty 3.Xベース
o Camel Nett4:Netty 4.Xベース。Camel 2.14でリリース
o Camel Netty Http: ここでは対象外
コンポーネントのURL形式
o netty:tcp://host:port[?options]
o netty:udp://host:port[?options]
Camel Nettyコンポーネントが、メッセージを受信し、Exchangeを生成、ルート実行o messageReceivedイベントのみ実装するため、他のイベント発生時の処理は実装不可
イベント処理以外のHandlerは、オプションで設定
NetworkCamelNetty
Component
Component
Component
オプションで設定 ユーザ実装
request-replyタイプ、one-wayタイプをサポート
o request-reply
• Out Bodyに設定されたオブジェクトをレスポンスとして返す
o one-way
• レスポンスは返さない
syncオプションで設定
o デフォルトはRequest-Reply
次のオプションのいずれかでHandlerを指定
o encode/decoder
o encoders/decoders
o serverPipelineFactory/clientPipelineFactory
encoder/decoder、encoders/decodersを使用する場合、sslは別オプションで指定
encoders/decodersを推奨o encoder/decoderは、1つしか指定できない
o serverPipelineFactory/clientPipelineFactoryは、オプションの一部が使えなくなる可能性 ⇒ 詳細は「おまけ」参照
Handlerをbeanタグで定義
o ChannelHandlerFactoriesのHandler生成メソッドで定義
• newStringDecoder()/newStringEncoder()
• newObjectDecoder()/newObjectEncoder()
• newDelimiterBasedFrameDecoder()
• newLengthFieldBasedFrameDecoder
o Handlerがスレッドセーフかどうか意識する必要がなくなる
<bean id="length-decoder" class="org.apache.camel.component.netty.ChannelHandlerFactories" factory-method="newLengthFieldBasedFrameDecoder">
(snip)</bean>
すべてのHandlerが
用意されているわけではない
Handlerを独自実装した場合は、ChannelHandleFactoryを返すクラスを実装
public final class MyHandlerFactories {
private MyHandlerFactories() {}
public static ChannelHandlerFactory newUserInfoEncoder() {return new ShareableChannelHandlerFactory(new UserInfoEncoder());
}
public static ChannelHandlerFactory newUserInfoEncoder() {return new ChannelHandlerFactory() {
@Overridepublic ChannelHandler newChannelHandler() {
return new UserInfoEncoder();}
};}
}
スレッドセーフの場合
スレッドセーフでない場合
Handlerをカンマ区切りで複数設定
<camelContext xmlns="http://camel.apache.org/schema/spring"><route>
<from uri="netty:tcp://localhost:5150?decoders=#length-decoder,#string-decoder&sync=false"/>
<to uri="mock:multiple-codec"/></route>
</camelContext><bean id="length-decoder"
class="org.apache.camel.component.netty.ChannelHandlerFactories" factory-method="newLengthFieldBasedFrameDecoder"><constructor-arg value="1048576"/><constructor-arg value="0"/><constructor-arg value="4"/><constructor-arg value="0"/><constructor-arg value="4"/>
</bean><bean id="string-decoder"
class="org.apache.camel.component.netty.ChannelHandlerFactories" factory-method="newStringDecoder"><constructor-arg value="UTF-8" />
</bean>
順番が重要!
Handlerをutil:listで設定
<camelContext xmlns="http://camel.apache.org/schema/spring"><route>
<from uri="netty:tcp://localhost:5150?decoders=#decoders&sync=false"/><to uri="mock:multiple-codec"/>
</route></camelContext>
<util:list id="decoders" list-class="java.util.LinkedList"><bean class="org.apache.camel.component.netty.ChannelHandlerFactories"
factory-method="newLengthFieldBasedFrameDecoder"><constructor-arg value="1048576"/><constructor-arg value="0"/><constructor-arg value="4"/><constructor-arg value="0"/><constructor-arg value="4"/>
</bean><bean class="org.jboss.netty.handler.codec.string.StringDecoder"/>
</util:list>
ネットワークに近いHandlerから順番に記載
decoders=#frame-decoder,#userinfo-decoder
Network
encoders=#frame-encoder,#userinfo-encoder
","の前後にスペースは不要
Camelのルートでは、Exchangeから受信したオブジェクトを取得し、業務処理を行う。
レスポンスを返す場合は、オブジェクトをBODYに設定する。
public class UpdateUserInfoProcessor implements Processor {
@Overridepublic void process(Exchange exchange) throws Exception {
// メッセージをオブジェクトに変換したものを取得UserInfo info = (UserInfo) exchange.getIn().getBody(UserInfo.class);
// 業務処理UserInfo modified = updateUserInfo(info);
// 送信するオブジェクトをBODYに設定exchange.getOut().setBody(modified);
} (snip) }
オプション
sync (default: TRUE)
o one-way(false)、request-replay(true)
disconnect (default: FALSE)
o 送受信後、Channelを切断する場合はTRUE
transferExchange (default: FALSE) TCPのみo ExchangeのBODY、ヘッダ、プロパティなどをシリアライズして送受信
noReplyLogLevel (default: WARN)
o Producerで送信するメッセージがない場合や、sync=TRUEでレスポンスを返さない場合に出力するログレベル
orderedThreadPoolExecutor (default: TRUE)
o 同一Channelでの送受信をシーケンシャルに行う
textline (default: FALSE) TCPのみo ExchangeのBODYにあるオブジェクトを文字列に変換する
delimiter (default: LINE) TCPのみo textlineがTRUEの場合のみ有効。デリミタ(LINE、NULL)
decoderMaxLineLength (default: 1000) TCPのみo textlineがTRUEの場合のみ有効。1行の最大バイト数
autoAppendDelimiter (default: TRUE) TCPのみo textlineがTRUEの場合のみ有効。デリミタを自動でつけるかどうか。
encoding (default: null)
o Exchangeのエンコーディング。textlineがTRUEの場合は、文字列
生成時のエンコーディングとして使用される。指定されていない場合は、UTF-8。
allowDefaultCodec (default: TRUE)
o encoder(s)/decoder(s)が指定されていない場合、デフォルトのencoder/decoderを使用
o textlineがTRUEの場合
• stringDecoder/Encoder、DelimiterBasedFrameDecoder
o textlineがFALSEの場合
• ObjectDecoder/Encoder
reuseAddress (default: TRUE)o TCPの場合、ポートがTIME_WAITでも再利用可能とする
o UDPかつマルチキャストの場合に有効にする
keepAlive (default: TRUE) TCPのみo 確立したコネクションを保持
tcpNoDelay (default: TRUE) TCPのみo 連続する小さいパケットを即時に送信(TRUE)
backlog (default: OS依存) TCPのみo 接続待ちのコネクションの最大保持数
broadcast (default:FALSE)UDPのみo マルチキャストを許可
synchronous (default: FALSE)o Camelのルートを同期起動
connectTimeout (default: 10,000)
lazyChannelCreation (default: TRUE)o Producerが起動時に、接続先が立ち上がっていない場合に例外が発生する事象を避けるために、Channelを遅延起動する
producerPoolEnabled (default: TRUE)o producerのプールを有効にするかどうか。
producerPoolMaxActive (default: -1)o プールで保持するproducerのインスタンスの上限。-1は無制限。
producerPoolMin/MaxIdle (default: 0)o プールで保持するアイドル状態のproducerの最小/最大インスタンス数
producerMinEvictableIdle (default: 30,000)o プールでアイドル状態で存在可能な時間(ms)
おまけ
Spring XMLではなく、Nettyと同様にJavaコードでChannelPipelineを組み立てる
Camelが提供する抽象クラスを継承o ServerPipelineFactory
o ClientPipelineFactory
上記クラスを使用しない場合は、CamelはデフォルトのDefaultServerpipelineFactory/DefaultClientPipelineFactoryを使用
継承すると、sslなどの一部プ
ロパティが使用できなくなるので注意が必要
public class MyServerPipelineFactory extends ServerPipelineFactory {
private NettyConsumer consumer;
public MyServlerPipelineFactory() {//
}
public MyServlerPipelineFactory(NettyConsumer consumer) {this.consumer = consumer;
}
@Overridepublic ChannelPipeline getPipeline() throws Exception {
(snip)}
@Overridepublic ServerPipelineFactory createPipelineFactory(NettyConsumer consumer) {
return new MyServlerPipelineFactory(consumer);}
}
デフォルトコンストラクタ
ChannelPipelineの生成
インスタンスの生成
// スレッドセーフなHandlerはインスタンス変数として使いまわすprivate StringEncoder stringEncoder = new StringEncoder(CharsetUtil.UTF-8);private StringDecoder stringDecoder = new StringDecoder(CharsetUtil.UTF-8);
public ChannelPipeline getPipeline() throws Exception {ChannelPipeline channelPipeline = Channels.pipeline();// 送信channelPipeline.addLast("encoder-SD", stringEncoder);channelPipeline.addLast("decoder-DELIM",
new DelimiterBasedFrameDecoder(maxLineSize, true, Delimiters.lineDelimiter()));
// 受信channelPipeline.addLast("decoder-SD", stringDecoder);
// このHandlerでCamelのルートを実行channelPipeline.addLast("handler", new ServerChannelHandler(consumer));
return channelPipeline;}
これを追加しないとルートは実行されない
bean定義し、オプションに設定
<camelContext xmlns="http://camel.apache.org/schema/spring"><route>
<from uri="netty:tcp://localhost:5150?serverPipelineFactory=#myPipeline"/><to uri="mock:multiple-codec"/>
</route></camelContext>
<bean id=“myPipeline" class="com.buildria.netty.MyServerPipelineFactory" />