Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Netty & Apache Camel

1,986 views

Published on

Netty 3.Xベースの概略とApache Camel Nettyコンポーネントの簡単な説明です

Published in: Technology
  • Be the first to comment

Netty & Apache Camel

  1. 1.   ソケット接続をApache Camelに統合する 日本Apache Camelユーザ会 @ssogabe
  2. 2. Netty
  3. 3.  Javaで非同期、イベント駆動のサーバ・クライアントアプリケー ションを構築するためのフレームワーク  高パフォーマンス、高スケーラビリティ o NIOをラッピング o 処理をレイヤーごとに分離  TCP、UDP以外にも、HTTP、WebSocket、RTSPなどもサポート  Akka、Play!、HornetQなどで使用されている
  4. 4.  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ベースを対象とする
  5. 5. アーキテクチャ
  6. 6.  発生したイベントを、Handlerが処理を行う  Nettyでは、22個のイベントをサポート  主なイベント No. イベント 説明 1 channelOpen チャネルがオープンしたが、まだ、ポートにバインドも、接続もして いない 2 channelConnected 接続先とのコネクションが確立した 3 writeComplete チャネルに何か書き込まれた 4 messageReceived メッセージを受信した 5 channelDisconnected 接続先とのコネクションが切断された 6 channelClosed チャネルがクローズした 7 exceptionCaught I/OスレッドやChannelHandlerが例外をスローした 8 channelIdle チャネルが一定期間アイドル状態になった
  7. 7.  メッセージをHandlerを組み合わせたChannelPipelineで 処理 o “データ受信”などのイベントが、ChannelPipelineを流れる o Handlerが処理するイベントを受け取り、イベントからデータを取得 o Nettyが提供する、ロギング、メッセージのフレーム処理などの Handlerや、メッセージの処理を行うユーザ実装のHandlerを組み 合わせて処理を実装 o サーバ、クライアントいずれも同じ仕組み
  8. 8. 実際には1つのライン上に配置
  9. 9.  大きく分けて5種類のHandlerをサポート  送信用、受信用、送受信共用の3種類 No. カテゴリ 説明 代表的なHandler 1 フレーム ストリームからメッセージを切り出しや、電 文長の追加を行う LengthFieldBasedFrameDecoder LengthFieldPrepender 2 変換 メッセージ⇔文字列、オブジェクトや、SSL、 暗号化処理を行う StringDecoder/Encoder ZlibDecoder/Encoder OneToOneDecoder/Encoder 3 イベント メッセージ受信などイベントに対するユー ザ実装の処理を行う SimpleChannelHandler IdleStateAwareChannelHandler 4 アイドル・ タイムアウト 無通信時の処理や、タイムアウトを検出す る IdleStateHandler Read/WriteTimeoutHandler 5 その他 ロギングなどの処理 LoggingHandler
  10. 10.  スレッドセーフではない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 送受信
  11. 11.  多くの場合、次の順序でHandlerを設定 Network フレーム (ストリーム⇒メッセージ) 変換 (メッセージ⇒オブジェクト) 変換 (オブジェクト⇒メッセージ) フレーム (電文長の追加) イベント (業務処理、レスポンス作成) UpStream(受信) DownStream(送信)
  12. 12.  実装例 ServerBootstrap bootstrap = new ServerBootstrap(factory); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public 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; } });
  13. 13.  ChannelPipelineでは、受信したバイト列を ChannelBufferで受け取る  ChannelBufferを操作して、オブジェクト⇔メッセージ変換 を行う  JavaのNIOが提供するByteBufferより使い勝手が良い o ByteBufferは、position()/limit()/flip()などを使用して、書 き込み位置を正確に把握する必要がある o 多くの場合、ByteBufferより高速に動作  デフォルトのバイトオーダーは、ビッグエンディアン
  14. 14.  読み込み位置と書き込み位置を管理する  readXXX()でバイト列を取得、writeXXX()でバイト列を書 き込むと、readerIndex/writerIndexが増加  getXXX()、setXXX()は、reader/writerIndexは増加し ないので注意が必要  基本は、readXX()、writeXX()を使用 0 readerIndex writerIndex 読み書き不可バイト列 読み込み可能バイト列 書き込み可能バイト列 capacity
  15. 15. Handler フレーム処理
  16. 16.  TCP/IPのストリームベースの通信では、2つのメッセージを 送信した場合、OSは2つのメッセージではなく、1つのバイト列 として扱う(UDPは不要)  受信APでは、バイト列をフレーム分けする必要がある  ObjectDecoder/Encoderを使用する場合は不要 ABC DE ストリーム通信 AB CD E ABC DE フレーム処理 送信AP 受信AP TCP/IP
  17. 17.  3つのクラスを標準で提供、独自実装も可 o DelimiterBasedFrameDecoder • NUL(0x00)や改行文字などの1つ以上のデリミタで分ける o FixedLengthFrameDecoder • 固定のバイト長で分ける o LengthFieldBasedFrameDecoder • メッセージのあるフィールドから電文長を取得し分ける  動的にバイト長を算出し、先頭に付加するクラスも提供 o LengthFieldPrepender • LengthFieldBasedFrameDecoderと組み合わせることが多い o 先頭以外にバイト長を設定する場合は、自前で行う
  18. 18. ServerBootstrap bootstrap = new ServerBootstrap(factory); bootstrap.setPipelineFactory(new ChannelPipelineFactory() { @Override public 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; } });
  19. 19.  メッセージに含まれるあるフィールドから電文長を取得し、必 要な部分をフレーム分けする 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~ペイロードをフレーム分けする
  20. 20.  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
  21. 21.  電文長は、ペイロードのみの場合、メッセージ全体の場合が あるため、何らかの補正が必要  算出例1 HDR1 電文長 HDR2 Payload 00 C0 00 00 00 04 0A 00 C0 11 FF EF lengthFieldOffset(=2) initialBytesToStrip(=8) バイト長= 電文長フィールドの 値 4バイト 2ba2バイト足すとメッセージ長 lengthAdjustment=2
  22. 22.  算出例2 HDR1 電文長 HDR2 Payload 00 C0 00 00 00 0C 0A 00 C0 11 FF EF lengthFieldOffset(=2) initialBytesToStrip(=0) 12バイト 2ba6バイト削除するとメッセージ長 lengthAdjustment=-6 バイト長=電文長フィールドの値
  23. 23.  フレーム処理を実装する場合は、FrameDecoderを拡張 1. 必要なバイト列を読めない場合は、nullを返す。 • nullを返すと、再度decode()が呼ばれる 2. bufferから必要なバイト列を取得 public class TimeDecoder extends FrameDecoder{ @Override protected Object decode( ChannelHandlerContext ctx, Channel channel, ChannelBuffer buffer) { if (buffer.readableBytes() < 4) { ・・・ (1) return null; } return buffer.readBytes(4); ・・・ (2) } }
  24. 24. Handler 変換処理
  25. 25.  フレーム処理で切り出されたChannelBufferを変換  よく使われるSSL、圧縮などを標準で提供  イベント処理で扱いやすいように、ChannelBuffer(バイト 列)を、文字列やユーザ定義のオブジェクトに変換 o イベント処理では、バイト列を意識しないようにする
  26. 26.  標準で提供する変換処理 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をサポート
  27. 27. public class UserInfoEncoder extends OneToOneEncoder { @Override protected 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); // Age buffer.writeInt(info.getAge()); return buffer; } }
  28. 28. public class UserInfoDecoder extends OneToOneDecoder { @Override protected 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); // age int age = buffer.readInt(); return new UserInfo(name, age); } }
  29. 29. Handler イベント処理
  30. 30.  発生したイベントに対するユーザ処理の実装 o メッセージを受信時にレスポンスを返す o 例外が発生したら、コネクションを切断する など  SimpleChannelHandlerか、 IdleStateAwareChannelHandlerを拡張 o 通常は前者を使用 o アイドル時の処理も実装する場合は後者
  31. 31. public class UserInfoServerHandler extends SimpleChannelHandler { private final Logger log = LoggerFactory.getLogger(UserInfoServerHandler.class); @Override public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { UserInfo info = (UserInfo) e.getMessage(); log.debug("電文を受信しました {}", info.toString()); log.debug("電文を送信します"); e.getChannel().write(info); } @Override public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { log.warn("例外が発生しました", e.getCause()); log.debug("コネクションを切断します"); e.getChannel().close(); } }
  32. 32. Camel Netty
  33. 33.  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]
  34. 34.  Camel Nettyコンポーネントが、メッセージを受信し、 Exchangeを生成、ルート実行 o messageReceivedイベントのみ実装するため、他のイベント発生時 の処理は実装不可  イベント処理以外のHandlerは、オプションで設定 Network Camel Netty Component Component Component オプションで設定 ユーザ実装
  35. 35.  request-replyタイプ、one-wayタイプをサポート o request-reply • Out Bodyに設定されたオブジェクトをレスポンスとして返す o one-way • レスポンスは返さない  syncオプションで設定 o デフォルトはRequest-Reply
  36. 36.  次のオプションのいずれかでHandlerを指定 o encode/decoder o encoders/decoders o serverPipelineFactory/clientPipelineFactory  encoder/decoder、encoders/decodersを使用する場合、ssl は別オプションで指定  encoders/decodersを推奨 o encoder/decoderは、1つしか指定できない o serverPipelineFactory/clientPipelineFactoryは、オプション の一部が使えなくなる可能性 ⇒ 詳細は「おまけ」参照
  37. 37.  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が 用意されているわけで はない
  38. 38.  Handlerを独自実装した場合は、ChannelHandleFactory を返すクラスを実装 public final class MyHandlerFactories { private MyHandlerFactories() { } public static ChannelHandlerFactory newUserInfoEncoder() { return new ShareableChannelHandlerFactory(new UserInfoEncoder()); } public static ChannelHandlerFactory newUserInfoEncoder() { return new ChannelHandlerFactory() { @Override public ChannelHandler newChannelHandler() { return new UserInfoEncoder(); } }; } } スレッドセーフの場合 スレッドセーフでない場合
  39. 39.  Handlerをカンマ区切りで複数設定 <camelContext xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="netty:tcp://localhost:5150?decoders=#length-decoder,#string- decoder&amp;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> 順番が重要!
  40. 40.  Handlerをutil:listで設定 <camelContext xmlns="http://camel.apache.org/schema/spring"> <route> <from uri="netty:tcp://localhost:5150?decoders=#decoders&amp;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>
  41. 41.  ネットワークに近いHandlerから順番に記載 decoders=#frame-decoder,#userinfo-decoder Network encoders=#frame-encoder,#userinfo-encoder ","の前後にスペース は不要
  42. 42.  Camelのルートでは、Exchangeから受信したオブジェクトを 取得し、業務処理を行う。  レスポンスを返す場合は、オブジェクトをBODYに設定する。 public class UpdateUserInfoProcessor implements Processor { @Override public void process(Exchange exchange) throws Exception { // メッセージをオブジェクトに変換したものを取得 UserInfo info = (UserInfo) exchange.getIn().getBody(UserInfo.class); // 業務処理 UserInfo modified = updateUserInfo(info); // 送信するオブジェクトをBODYに設定 exchange.getOut().setBody(modified); } (snip) }
  43. 43. オプション
  44. 44.  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での送受信をシーケンシャルに行う
  45. 45.  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。
  46. 46.  allowDefaultCodec (default: TRUE) o encoder(s)/decoder(s)が指定されていない場合、デフォルトの encoder/decoderを使用 o textlineがTRUEの場合 • stringDecoder/Encoder、DelimiterBasedFrameDecoder o textlineがFALSEの場合 • ObjectDecoder/Encoder
  47. 47.  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のルートを同期起動
  48. 48.  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)
  49. 49. おまけ
  50. 50.  Spring XMLではなく、Nettyと同様にJavaコードで ChannelPipelineを組み立てる  Camelが提供する抽象クラスを継承 o ServerPipelineFactory o ClientPipelineFactory  上記クラスを使用しない場合は、Camelはデフォルトの DefaultServerpipelineFactory/DefaultClientPip elineFactoryを使用 継承すると、sslなどの一部プ ロパティが使用できなくなるの で注意が必要
  51. 51. public class MyServerPipelineFactory extends ServerPipelineFactory { private NettyConsumer consumer; public MyServlerPipelineFactory() { // } public MyServlerPipelineFactory(NettyConsumer consumer) { this.consumer = consumer; } @Override public ChannelPipeline getPipeline() throws Exception { (snip) } @Override public ServerPipelineFactory createPipelineFactory(NettyConsumer consumer) { return new MyServlerPipelineFactory(consumer); } } デフォルトコンストラクタ ChannelPipelineの生成 インスタンスの生成
  52. 52. // スレッドセーフな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; } これを追加しないとルー トは実行されない
  53. 53.  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" />

×