はじめに
フューチャーAdvent Calendar 2019 の4日目ですが、遅れて5日目に書いています。
この記事ではPub/Subを業務利用しようとして難しさを感じた点についてまとめ、どんな苦労をしたのかをちょっとだけ書きます。
背景としてはmosquittoやkafkaといったPub/SubブローカーからPubあるいはSubするアプリケーションをGoで作成してどのような形で業務利用できるかを考える機会がありました。
Pub/Subメッセージングモデルとは
以下の3つの要素で構成されるモデルです。
-
メッセージを仲介するBroker
メッセージをためている部分
Topicという論理的なチャンネルにメッセージを紐付けて管理する -
メッセージを発信するPublisher
Topicを指定してメッセージをBrokerに投げる -
メッセージを受信する(取りに行く)Subscriber
Topicを指定して(指定しないで取れるBrokerもある)メッセージを取りに行く
Pub/Subメッセージングモデルの利点
一般的なPub/Subメッセージングモデルの利点は以下の2点です。
-
疎結合
- プロトコルとTopicのみ知っていればPublisherとSubscriberは相互に知らなくてもやりとりが可能である
-
スケールがしやすい
- PublisherとSubscriberはそれぞれ別々に動的にスケール可能である
- Brokerの仕様次第ではあるが、1つのTopicを複数のパーティションに分けることによって並列にメッセージを処理できる
Pub/Subメッセージングモデルの難しさ
- 順序制御が難しい
スペックをあげるためにTopicのパーティションを分割すると、結構順番が乱れがちである。
業務利用においていえば、順序を制御しないといけないといけないケースは多々あり、FIFOのキューイングサービス使えば良いのではないかと思ってしまう。
- スケールが可能であるとはいえスケール可能な設計が難しい
Publisher・Subscriberともに動的に増やせるが業務では更に他のシステム・サービスと連携をすることが多く、実際のところスケール可能な点がどの程度活かせるかが設計にかかっており、その設計が非常に難しい
- PubとSubの間はPubからSubへの経路しかないため、リトライなどがしにくい
Subscriber側でエラーが出てもPublisher側に通知する機能はないため、単独では一方向のコミュニケーションしか行えない。
- 多くのPub/SubデフォルトのQos1だと重複してデータがBrokerに入る可能性がある
Subscriber側で重複を許容できるようにする必要がある
業務的にPub/Subメッセージングモデルを取り入れやすいケース
とはいえ業務利用ができないかと言われれば実際上記難しさを克服できるケースであれば利用可能であるとは考えられます。
-
順序制御がいらないケース
直前の状態に左右されない、例えば通知システムであれば利用は可能であると思われます。
0,1の状態を繰り返し直前の状態に左右されるようなシステムでは利用は難しいですが、例えばメールのPUSH通知など一つ一つのメッセージが独立して意味があるケースでは利用可能です。 -
メッセージの流量が多くないケース
ピークがあるようなシステムだと難しいです。
ただ流量が少なくパーティションが1でも処理できるようなケースであれば順序が乱れることも少なく、安定して運用することは可能ではないかと考えられます。
Pub/Subメッセージングモデルを外側から支える
- Publisherの前で順番をメッセージに振り、Subscriberで受け取ったメッセージを順序通り次処理に投げれるようにになるまで滞留させる
Sub側のシステムで待たせれば、順番通りにして次の処理に投げることも(試したことはないですが)できるとは思います。
ただ、Qos0だと無限に待つ可能性があるので、利用するBrokerのプロトコルや設定次第だと罠はありそうです。
- Subscriber側で流量を制限する
Subscribe側の処理が終わるまで次のメッセージを受けるのをBlockします。ただ注意が必要なのはPubの量が常にSubの量より多い場合どんどんPubしてからSub側の処理までの時間が伸びていきます。
- Publisherの前にAPIサーバを構える
セキュリティやバリデーションを考える場合、Brokerにユーザごとの権限の制御をもたせることはできないのもあり、またどのようなメッセージをも許容できてしまうため、外部システムとの連携ではAPIサーバを建てるということも考えられます。
- Subscriber側で重複許容処理をいれる
もともと重複しても問題ないような処理であれば良いですが、重複が許容されない場合はPub側でidを入れてSub側で同じidを排除するような機構をいれることが望ましいです。
実際にどんなところでハマったか
工場IoTで機器の故障を通知するようなシステムを作成するにあたって、私がプロジェクトに入る前に工場側でPubしてサーバ側でSubする方向になってました。
サーバ側の開発を私が受け持っていました。機能としては簡単で、機器が正常と異常の間で変化があった場合に通知をすると言ったものです。
ここで正常と異常を無限に繰り返すテストプログラムを流したところでハマりました。
サーバで受け取れるメッセージ(想定)
正常→異常→正常→異常→正常→異常→正常→異常→正常
出る通知(想定)
なし→通知→通知→通知→通知→通知→通知→通知→通知
サーバで受け取れたメッセージ(実際)
正常→異常→正常→異常→正常→正常→異常→異常→正常
出る通知(実際)
なし→通知→通知→通知→通知→なし→通知→なし→通知
ちなみに実際に想定の7割ぐらいしか通知は出ませんでした。
ただ、tpsを下げれば通知回数は想定数と一致したので、
高負荷でパーティションが詰まることがあることが大きな要因のようです。
そこでは実際想定される理論値で問題がなかったためOKが出ましたが、
負荷が大きいような状況だと厳しいことが伺われました。
まとめ
疎結合であるという点は間違いなく魅力的なので、うまく使えるシチュエーションを見つけて利用するにはPub/Subメッセージングモデルは有用です。
ただ、流行に乗っかってわけもわからず使うというには趣味ならばともかく、業務で利用するにはしっかりとした設計が必要であると感じました。
余談ですが、別の会社で働く友人はQueueですらあまり使いたくない、なるべく同期処理でやらないと怖いとのこと。
実際私も疎結合を意識しなくて良いのであればPub/Subは考えることが多いのであまり使いたくないとハマって思いました。
順序が乱れて宇宙の法則が乱れるので、、、