ページ

2011年6月15日

RabbitMQのJavaクライアントをdisる

会社のプロダクトではRabbitMQを使い始めています。製品自体はJavaで作られているので、当然、RabbitMQへのアクセスはJavaのライブラリを使っています。幸い、本家でもJavaのライブラリは開発しているようで、ありがたく使わせてていただいています。でも、でもです。最初に製品にRabbitMQを組み込んだのは「いわがが」こと、id:kirisです。彼はまじめそうなそぶりを見せながら・・・。これ以上書くと怒られるので自重します。そして、このJavaのライブラリについてdisりまくります。挙げ句の果てに、「もう、こんなライブラリ、使っていられません!スクラッチから書き直します。nioで非同期処理して、結果はconcurrentのfutere使って取り出せるようにします。○×△※・・・」残念ながら、彼が言ったことをそのまま、インターネットで公開できないのが残念ですが、あまりにも・・・。さて、今回は彼がdisりまくったことのまとめです。

RabbitMQというか、MQのプロトコル自体のお話は以前書いたので、そっちを見てください。ある程度プロトコルを知っている、という前提です。RabbitMQのライブラリはcom.rabbitmq.clientのパッケージの中でインターフェースが定義されています。ネットワーク周りを中心に言うと、アプリケーションは最初にConnectionオブジェクトを作ります。ConnectionオブジェクトはTCP/IPのコネクションを管理するヒトです。仕様としてConnectionオブジェクトはスレッドセーフです。Connectionオブジェクトが実在するコネクションを管理するのに対して、ChannelオブジェクトはConnection上で論理的な接続を管理します。この辺りの概念はちょっとややこしいですね。実際にアプリケーションがMQとやりとりをするのはChannelオブジェクトを通してです。Channelオブジェクトもスレッドセーフということになっています。

アプリケーションに近いところから見ていくと、Channelの実装は、implの下にあるChannelNです。ネットワークへの送信だけにフォーカスすると、ChannelNの基底クラスのAMQChannelにある、transmitメソッドです。この中でchannelに対してsynchronizedしているので、channelがスレッドセーフと言うことになっています。さらにその先の、quiescingTrans内でネットワークの送受信が行われるので、channelを複数スレッドで使い回してそこそこの書き込みを行うと、ここの同期でブロックされます。なので、Channelって実は、書き込みがそこそこある場合は、スレッド間で共有しない方がいいです。

それじゃ、もう少しおっていきます。その後の処理はAMQCommandのtransmitメソッド中に入っていきます。このメソッドでAMQConnectionオブジェクト、一番最初に出てきたConnectionの実体ですね、その人のwriteFrameメソッドをコールして何回かに分けて書き込みを行っています。AMQCommandはAMQChannelのtransmitをコールするごとに作られたりするので、アトミックです。なので、AMQChannelのsynchronizedが本当に何を守ろうとしているのか、微妙です。しかもtransmit自体はvoidなので、送った後に何かする訳じゃないです。

で、面白いのはここじゃないのです。AMQConnectionの中で実際にTCPのコネクションを管理しているわけではなく、FrameHandlerが管理しています。正確にはこの人はインターフェースで、実体はSocketFrameHandlerです。たぶん、TCP/IPだけじゃなくUnixソケットとかをあつかう壮大な計画の一部なのでしょう。
複数のChannelで複数のスレッドからConnectionオブジェクトのwriteFrameが呼び出されます。なので、当然ネットワークへの書き込みは同期しないと悲しいことになります。SocketFrameHandlerのネットワークへの書き込みは、writeFrameです。このヒト、outputstreamをsynchronizedしています。えーと、ChannelでブロックするのでChannelを複数スレッドで使い回さないようにして、スレッドごとに一つのChannelで処理をしようとしても、悲しい現実につきあたります。結局複数のスレッドで書き込みを行っても、最終的に実際のネットワークへの書き込みでブロックされちゃいます。もっと悲しいことはvoidで結果を意識していないにもかかわらずです。これをサーブレットのRequstのスレッドの中でやると、負荷が集中し始めると世界がとまるんですねん。

本当にスループットを得たいなら、スレッドごとに一つのコネクションを管理する必要があります。でも、それはちょっとやり過ぎじゃ?僕たちの欲しいモノはブロッキングしないモノです。まあ、解の一つはスレッドを一つ作って、その中でゆっくりとデータを送信することです。そして、もう一つがid:kirisが作っているはず、僕はそう信じている高速でノンブロッキングなライブラリを待つことです。

でわでわ

0 件のコメント: