ニッポン一億総マイクロサービス化が進む昨今ですが、皆様いかがお過ごしでしょうか?ヌーラボでテックリードを務める馬場です。サービス間の通信はマイクロサービスを支える動脈とも言える、とても重要なファクターです。サービス間の通信手段としてヌーラボで利用している「 ジョブキュー 」の基本と、利用例をご紹介します。
ふるえるぞハート!燃えつきるほどヒート!!おおおおおっ刻むぞ血液のビート!
ジョブキュー とは何か?
ジョブキューと聞いてなんだか難しいものと感じる方や、何が嬉しいのかわからない方もいらっしゃるのではないでしょうか?
簡単に言ってしまうと、ジョブキューとはキューに登録されたジョブを逐次実行するものです。
これはメッセージキュー(MQ / Message Queue) やタスクキューと呼ばれることもあります。
ジョブキューを構成する要素には以下のようなものがあります。
- ジョブ : 何らかの処理
- キュー : 先入れ先出し (FIFO) のリスト構造を持つ、コンピュータの基本的なデータ構造の一つ
- ワーカー : キューに登録されたジョブを逐次実行していくもの
たとえば、 Web アプリケーションにおいて、ユーザーからの入力を受け付けた後にサーバーサイドで重い処理をする必要があっても、その処理をジョブとして切り出してキューに登録するようにすれば、アプリケーションはその重い処理を非同期で実行でき、処理が完了する前にユーザーにレスポンスを返すことができます。
代表的なジョブキューの実装
ジョブキューには、ライブラリやミドルウェア、クラウドサービスなど様々な実装があります。代表的なものを以下にあげます。
- ライブラリ
- TheSchwartz
- ミドルウェア
- ActiveMQ
- RabbitMQ
- クラウドサービス
- Amazon SQS
- GAE Task Queue
ヌーラボでの利用例
ヌーラボでは Backlog、 Cacoo、 Typetalk という3つのサービスを提供しています。各サービスはひとつのアカウント「ヌーラボアカウント」でご利用いただけます。
Cacoo チームプランや Typetalk では、アカウントの集まりを「組織」としてサービス間で共有でき、複数人で便利にご利用いただけるようになっています。今後はBacklogも、この「組織」を共有できるようにするために、2017 年 7 月現在、急ピッチで作業が進んでいます*。この「組織」ではお客様との契約なども管理しています。(詳しくは「クレジット払いにも対応!Backlogのユーザー管理、契約、支払いがCacoo for ビジネスやTypetalkと共通になります」をご覧ください)
社内ではこれらのサービスをまとめて Nulab Apps と呼んでおり、apps.nulab.com というドメインに配備しています。ここに、独自に実装したジョブキューも配備されています。
なぜジョブキューが必要なのか?
Nulab Apps はアカウントや組織のデータを管理しており、それらが変更された場合、各サービスへ変更を通知する必要があります。ただ、単純な HTTP API 呼び出しでは、以下のような都合が悪いことが想像できます。
●通知先のサービスが障害やメンテナンスなどで一時的に通知を受け付けられないことがある
通知先がリクエストを受け付けられないときは、レスポンスを返すまでに非常に時間がかかります。普通の HTTP API 呼び出しでは各サービスからレスポンスが返ってくるまでユーザーへリクエストを返せません。通知先の状態によらず、ユーザーへレスポンスを返すためには HTTP API 呼び出しを非同期に実行すればよさそうです。
また、データの整合性を保つために、いちど通知に失敗しても成功するまでリトライする必要があります。
これらのことを考慮してジョブキューを導入することにしました。
●BacklogやCacooはオンプレミスのサーバーへのインストールも提供している (エンタープライズ版)
オンプレミスの環境では外部ネットワークとの接続が制限されることも多いため、クラウドサービスは利用できません。
Active MQ など出来合いのミドルウェアは機能も豊富ですが、複雑ですし、ジョブキューの導入を決めた時点では、自分たちで運用した経験がありませんでした (現在 Cacoo では RabbitMQ を使っていたりします)。私たちが必要としていた機能は、非常に単純なものでした。すでにBacklogの内部でも独自に実装したジョブキューを使っていたので、出来合いのミドルウェアの学習コストと実装のコストを天秤にかけ、独自に実装することを選びました。
現在ではこのジョブキューは外部システムとの連携だけではなく、定期的なバッチ処理の実行にも利用しています。
ジョブキュー の実装
このジョブキューは Spring Framework を用いて実装されています。
キューのデータがロストすることは避けたいため、ストレージには RDB (Amazon Aurora) を利用しています。テーブルがキューを、その列がジョブを表します。テーブルには状態を表す列を用意して、ジョブの状態を管理しています。
プロフィールが変更されるなどの通知が必要なイベントが発生したときには、「実行待ち」の状態として新しいジョブをキューに登録します(テーブルへの INSERT)。
キューのポーリングは Spring Framework のスケジューリング機能によって実装され、データベースを定期的に検索して「実行待ち」のジョブを取得します。このとき状態列を「処理中」に更新 (テーブルの UPDATE) することで行ロックを取得して、同時に同じジョブが実行されることを防ぎます。
ここで実際の処理を行うエンドポイントへ HTTP でリクエストするワーカースレッドが起動されます。リクエストの結果、レスポンスのステータスコードが正常だった場合は、さらに状態列を「処理済み」に書き換えて終了します。
レスポンスのステータスコードがエラーを示していた場合 ( 4xx や 5xx )は、ステータスは再度、待ち状態に更新されます。このとき、通知先のサービスが長時間回復しないことも考慮し、何回目のリトライかによって、次回実行までの待ち時間が計算されます。
その他にも動的なパラメータの指定や実行順序の重みづけ、定期的な処理済みのジョブの削除などを実装しています。
ジョブキューの運用
ジョブの実行に失敗したときには、そのことを Typetalk へポストするようにしているので、通知先のサービスを含め、異常をすばやく検知することができます。また、Mackerel を利用して、キューに登録されたジョブの数が異常な状態になっていないかをモニタリングしています。
おわりに
弊社では現在、社内システムの整備も進めています。ここでも外部システムとの連携やバッチ処理が必要なため、同じようなジョブキューが必要になりました。
このジョブキューはリリース当初から特に問題もなく、3年以上にわたって運用されてきましたが、Nulab Appsとデータベースを共有していたり、INSERT文でジョブを投入していたり、独立性がそれほど高くありませんでした。
そのため、独立性を高め、再構築する作業を進めています。ジョブも HTTP API を通して投入できるように検討しています。また、Spring Cloudを通してEurekaを中心とした、Netflixが提供するミドルウェアやライブラリを利用し、インフラをメンテナンスするコストを抑える施策も進めています。
ヌーラボでは、このようなバックエンドのシステムや社内システムに興味があるエンジニアを絶賛募集中です。