blog読者プレゼントキャンペーンTwitterフォロー&条件付きツイートで「バリーくんぬいぐるみ」を抽選で20名にプレゼント!応募期間は2019/11/29~2019/12/31まで。詳細はこちらをご覧ください。12/22(日)の記事です】こんにちは。IoTサービスを開発している近藤です。これまでのエンジニアブログの記事をご覧いただくとお分かりの通り、IIJはIoT事業にも積極的に取り組んでいます。IoTサービスは、その取り組みの一つである、エンタープライズ向けのIoTプラットフォームサービスです。IoTサービスとは何なのかを説明せねばなりません。IoTのシステムというものを少しだけ想像すると、「取得したセンサーデータを、クラウドサービスに送る」といった具合でしょうか。この“クラウドサービスに送る”の部分、さらに考えを進めると、結構面倒なんです。そもそもクラウドサービス使うのに認証が必要なのか。げ、これ認証キーを変更したら、1,000台のデバイス一つ一つにログインして認証キーの変更しなきゃならないのか...」などなど。IoTのシステムを検討されたことのある管理者の方はうんうんと頷いていただけるのではと思います。IoTサービスで提供しているSIMをデバイスにさして、センサーデータをとにかくプラットフォームに送信していただくだけで完了です。SIMとプラットフォームの間はプライベートネットワークになっているため、デバイス(の情報)が直接インターネットに晒される状態にはなりません。同じ理由で通信の暗号化を考える必要がないため、HTTPでもUDPでも軽量なプロトコルで送っていただいて結構です。送信先のクラウドサービスの認証などの設定はWeb上で管理し、そこで設定を一度変更するだけで、1,000台全てのデバイスに対してその変更が適用され、適切にクラウドサービスへセンサーデータが送信されます。他にも、プライベートネットワーク上にあるデバイスの管理画面に、Web上でアクセスできるといったリモートメンテナンス管理機能や、センサーデータ群をファイルにして半永久的にオブジェクトストレージに保存する機能など沢山ありますが、閑話休題、サービスの説明は終わりです。IoTサービスは、大量のデバイスからプラットフォームを目掛けてセンサーデータが送信されてきます。それはもう、休み無く、絶え間無く。PoC目的である場合を除き、一人のお客様がご利用になるデバイス数は、10〜1,000台と、非常に数が多いです。それら全てのお客様のデバイスから受け取るセンサーデータを、プラットフォームは高速に、損失することなく、適切に処理をしなければなりません。プラットフォームの負荷は高い。それに耐えねばなりません。システムを一から考えるこうした高負荷に耐えられるシステムを、一度頭をまっさらにして、一から考えてみます。データハブには、受け取ったセンサーデータが本当にお客様の契約したSIM経由で送信されているかを確かめる認証機能、またセンサーデータをお客様のサーバへ届ける転送機能が必要です。以下はその模式図です。一つのアプリケーションが、受け取ったセンサーデータに対して認証を行い、その処理の後に転送を行なっています。ここでは話を簡単にするために、センサーデータの送信はHTTPプロトコルのPOSTメソッドを使って行われているとします。デバイスから送信されるセンターデータが多くなり、とうとう一つのアプリケーションでは処理できなくなりました。処理すべきセンサーデータの量が多くなったのならば、こちらもアプリケーションの数を多くしましょう。次に考えられる構成は以下です。前段にある「LB」は「LoadBalancer」の略で、役割は字のまま「負荷分散」です。Serverといったソフトウェアが有名です。こいつは賢くて、デバイスから送信されたセンサーデータを、後ろに控えているアプリケーションに対して均等に割り振ってくれます。後ろに控えているアプリケーションの一つが死んでいたら、そのアプリケーションを避けて、生きているアプリケーションだけに割り振る機能も持っています。今、後ろに3つのアプリケーションがあるため、単純計算でこのプラットフォームが処理できるセンサーデータの量は、以前の3倍になりました。この後ろに控えているアプリケーションを増やせば増やすほど、プラットフォーム全体の処理性能は理論的には何倍にもなりそうです。ここまではよくあるシステム構成です。それに一つ観点を加えます。アプリケーションの保守性です。一般的に、一つのアプリケーションに沢山の機能を詰め込むほど、そのアプリケーションの改修には時間がかかります。いうのも、機能のコードを一部変更した際に、改修予定になかった機能にまで、予期せぬ形でその変更が影響してしまいバグに繋がることがあるからです。この例であれば、転送機能のコードを改修した際に、その変更が認証機能にまで波及していないかと心配になります。した心配から、転送機能の改修なのに、認証機能が正常に動くことを確認するテストにも時間を費やしてしまいます。そこで、この一つのアプリケーションを二つに分解し、認証機能だけを持ったアプリケーション(認証アプリケーション)と、転送機能だけを持ったアプリケーション(転送アプリケーション)を作ってそれぞれ動かします。APIで行われているものとします。(microservice!?)だいぶ良くなりました。認証機能と転送機能がアプリケーション単位で異なるため、それぞれのアプリケーションは自分の役割に専念すればOKです。認証アプリケーションは「認証を確認して、OKなら次へ渡す、NGならドロップする」、転送アプリケーションは「受け取ったセンサーデータをお客様のサーバへ転送する」ということに集中すればよく、これは開発者にとって、改修するハードルを下げてくれます。Well“を体現しているようです。アプリケーションの個数を増やす単位も、より最適なものになりました。転送アプリケーションの負荷が高い一方で、認証アプリケーションの負荷がそうでもない場合、転送アプリケーションの個数のみを増やす、という戦略をとることができます。これにより、限りあるリソースをより効率的に利用できるようになりました。「デバイスは、お客様のサーバへセンサーデータを送信した時のレスポンスを受け取らなければならない」といった、デバイスとお客様サーバ間の同期的な協調が要件としてあるならば、これは一つの完成形だと思います。LBをNginxなどで自前で構築すると、アプリケーション数に増減がある度にLBに設定の変更を加えねばならず、だいぶ手間がかかります。Kubernetesやその周辺の技術は、こうしたLBの設定の煩わしさを吸収し、またアプリケーションのロケーションすらも意識させない仕組みを提供してくれます。アプリケーションが乱立し、大量のLBが存在し得るような上記の構成を取る際は、Kubernetesの導入を見据えたほうがよいでしょう。ここで各アプリケーションが同期して動いているとします。POSTでデータを送信してからレスポンスが返るまでの間に行われている通信に、順に番号をつけました。この時の認証アプリケーションの処理時間を図にすると以下のようになります。上の図からわかるように、認証アプリケーションに、転送アプリケーションがサーバに転送した結果を待つ時間が生じています。認証アプリケーション処理時間は、転送アプリケーションの処理時間に大きく影響を受けます。これでは、仮に認証アプリケーションがせっかく高速に処理をできたとしても、自分とは関係ないところ(=転送処理)で時間がかかっているせいで、処理を終えるまでに結局時間がかかってしまいます。極端な例として、動画エンコーディングを考えてみます。動画ファイルを受け取るだけの受信アプリケーションと、受信アプリケーションから受け取った動画をエンコーディングするだけのアプリケーションの2つを考えると、後者のアプリケーションのほうが前者の受信アプリケーションよりもうんと時間がかかります。この場合、二つのアプリケーションが同期する構成をとろうとは考えないでしょう。これを解決したければ非同期な仕組みを導入する必要がありそうです。メッセージングアーキテクチャ認証アプリケーションと転送アプリケーションの間のLBがMessageBrokerというものに変わりました。これは、この記事でメッセージングアーキテクチャと呼んでいるものの一つの構成例です。メッセージングアーキテクチャとは、メッセージ(=データ)という概念を基調にして、中間層であるMessageBrokerを介し、コンポーネント間が非同期でメッセージをやりとりする構成パターンを指します。イベントドリブンアーキテクチャと呼ばれることのほうが多いです。これはよく知られるPub/Subモデルです。Pub/Subモデルでは、メッセージをMessageBrokerに保存する役割(今の場合認証アプリケーション)をPublisher、メッセージをMessageBrokerから取得する役割(今の場合転送アプリケーション)をSubscriberと呼びます。MessageBrokerは、Publisherから受け取ったメッセージを保存し、またSubscriberから要求されたメッセージを渡す役割を持っています。PublisherがMessageBrokerにメッセージを保存することをpublish、SubscriberがMessageBrokerからメッセージを取得することをsubscribeと言います。MessageBrokerで有名なソフトウェアとして私がすぐに思いつくのは、Kafka、RabbitMQ、またRedis(のPub/Sub機能)です。他にも多くのソフトウェアがあるようですし、AzureやAWSがサービスとして提供しているMessageBrokerもあります。再度私たちが作ろうとしているアーキテクチャ図に戻りましょう。認証アプリケーションは、そのレスポンスを、MessageBrokerから受け取っています。このとき、MessageBrokerは転送アプリケーションの処理の完了を待たずに、認証アプリケーションにレスポンスを返します。認証アプリケーションは転送アプリケーションの処理時間に影響を受けません。自分のペースで処理ができるため、本来の自分の力を発揮することができ、一つの認証アプリケーションでより多くのセンサーデータを処理できるようになりました。転送アプリケーションは、データが来るのを待つのではなく、自らMessageBrokerにセンサーデータを取りにいっています。転送機能アプリケーションもまた、自分のペースで処理をしています。これまで処理速度に注目してきましたが、今度はアプリケーション間の機能的な関係に注目します。認証アプリケーションは、転送アプリケーションの存在を知らずとも動きます。処理をした結果をMessageBrokerにただただpublishしているだけです。転送アプリケーションもまた、受け取ったデータが誰から送信されたものかを知りません。PublisherとSubscriberそれぞれに対する依存関係がより小さくなっています。PublisherとSubscriber間の関係が疎結合になりました。この疎結合性に注目すると、機能の追加がより容易になります。クラウドアダプタを新しく機能追加したいとします。すると以下のような構成を考えられます。新しく追加されたクラウド転送アプリケーションは、MessageBrokerからsubscribeしているだけです。認証アプリケーションと転送アプリケーションは、この機能追加による影響をより受けにくくなっています。IoTサービスでは、これを基本的な考え方として設計しています。運用的な観点でこの設計に助けられたのは、メンテナンスの時です。IoTサービスも、ローリングアップデートなど、工夫を凝らして無停止のメンテナンスに努めていますが、それでも、止むを得ずサービスを停止しなければならないメンテナンスが存在します。した場合も、例えば転送アプリケーションだけ停止してしまっても、コンポーネント間が疎結合であるため認証アプリケーションは全く影響を受けず、認証アプリケーションはセンサーデータを受け取り続けることができます。サービスを停止する単位をより小さくすることもでき、同時にお客様への影響をより小さくすることにつながります。IoTサービスで用いている構成パターンであるメッセージングアーキテクチャについて説明しました。メッセージングアーキテクチャはアプリケーション間を疎結合にします。観点で多くの利益をもたらします。今回、具体的にどのように実装すればメッセージングアーキテクチャを実現できるのかまでは触れませんでした。Integrationなどで実装しています。Flowを使っています。IoTサービス)メッセージングアーキテクチャにもデザインパターンがあります。Patterns、いわゆるEIPです。EIPにも大変助けられました。私がイベントドリブンではなくメッセージングと頑なに呼び続ける理由は、私自身このEIPからこのアーキテクチャを学んだからです。IoTサービスには、データストレージという、ある一まとまりのセンサーデータを一つのファイルにまとめ、IIJのオブジェクトストレージへアップロードする機能がありますが、その設計はEIPのAggregatorパターンを大いに参考にしました。他にもKafkaを使ったアプリケーションのスケールアウトやPropagateの仕組みなど、お話したいことがまだまだ沢山ありますが、それを書くには余白があまりに狭すぎる。ここまでお読みいただき、ありがとうございました。2015年に新卒として入社して以来IoT事業に従事しています。2018年も、気が付けばあっという間に師走です。アドベントカレンダーも今日で最後になりますね。