GS2 Blog

Game Server Services(https://gs2.io/) の最新情報をお届けします

【新機能】マイクロサービス間の連携を補助するためのサービスを追加します

みなさんこんにちは。GS2 CEO の丹羽です。

近日公開予定の新機能のお知らせをしたいと思います。
今回お知らせするサービスは2つで、どちらもGS2のマイクロサービス間を協調動作させるために重要な役割を果たす事になる機能です。

GS2-Lock

これは名前の通り、排他処理を実現するための機能となっています。
機能としては非常に単純で、『特定のリソースに対してロックを取得する』という宣言と『特定のリソースのロックを解除する』という宣言の2つが主な機能です。

用途例

たとえば、GS2-Stamina でスタミナを回復するために GS2-Money で 課金通貨を消費する。というプロセスを例に考えてみましょう。
GS2-Lock が無い状態でこれを実現すると、『GS2-Stamina でスタミナを回復する』『GS2-Money で 課金通貨を消費する』という2つの操作はそれぞれ独立した操作になります。
そのため、『GS2-Stamina でスタミナを回復する』と『GS2-Money で 課金通貨を消費する』のわずかな間に他の処理で GS2-Money の消費が割り込んでくる可能性があります。
その結果、スタミナは回復したにも関わらず、GS2-Money の残高が不足していて減らせない。ということが起こりえます。

このような問題を避けるためには GS2-Lock で GS2-Money の残高を増減する前後に、GS2-Money のウォレットのロックの取得/解除を行うようにします。
そうすることで、『GS2-Stamina でスタミナを回復する』と『GS2-Money で 課金通貨を消費する』のわずかな間に他の処理で GS2-Money の消費が割り込んできたとしても
割り込んできた側の処理がロックが取れずに失敗することになり、このような問題を避けることが出来るようになります。

そのほかの特徴

GS2-Lock ではロックを取得する際に『トランザクションID』というものを指定します。
そして、同一トランザクションからのロック取得リクエストだった場合はロック参照カウンターを増加させ、ロック解除リクエストはロック参照カウンターを減算し、実際のロック解除は参照カウンターが0になるまで行わないようになっています。
こうすることで、一つの処理系の中で同じリソースのロックを取ろうとしてデッドロックする。というような問題を避けることが出来るようになっています。

また、GS2-Lock のリリースにあわせて『リクエストID』という機能が導入されます。
リクエストIDとは、APIリクエスト毎にユニークなIDで、さらに APIリクエストによって発火した GS2-Script の中で GS2-SDK を使用して通信をした場合は同じリクエストIDが使用される。という特徴があります。
つまり、GS2-Lock のトランザクションIDにこのリクエストIDを指定することによってピタゴラ装置的に起動するGS2-Script全てで共通のトランザクションIDを使用することが出来ます。

f:id:kazutomo:20180210013805p:plain

ちなみに、ロックの解放を忘れてしまってどうにもならなくなってしまうことを避けるために、ロック取得時にTTLを秒単位で指定することが出来ます。
TTLの長さはロック取得時に指定出来ますので、この後の処理にかかりそうな時間に対して十分な時間でTTLを指定してロックを取得することで何らかの事故によってロックを解除出来なかったとしてもデッドロックすることは無くなります。

GS2-JobQueue

GS2-Lock は複数のマイクロサービスを超えて一貫性のある排他処理を実現するための機能でしたが、GS2-JobQueue は複数のマイクロサービスを超えて結果整合の処理を実現するための機能です。

名前の通り、このサービスはユーザ毎にジョブキューを提供します。
ジョブには GS2-Script のスクリプト名と引数、最大リトライ回数 を指定出来ます。

ジョブキューはFIFO先入れ先出し)を保証しており、1回のAPIリクエストで最大10件のジョブを一気に登録出来ます。
一気にジョブを登録する際には、リクエスト時に指定した順番に優先度が付いて登録が行われます。
ジョブキューの実行は明示的にAPIを通して実行リクエストを出す必要があり、API実行は排他処理が行われているため同時に複数のジョブを走らせることは出来ません。
ジョブには最大リトライ回数を指定したことから推測出来るように、GS2-Script が 200 以外のステータスコードを返した場合は、次回ジョブ実行リクエストの際にリトライを行います。
ジョブキューの実行リクエストのレスポンスにはジョブとして動いた GS2-Script の実行結果が返るほか、ジョブキューの終端まで処理を終えたかも取得出来ます。

用途例

現在のGS2の機能としては提供していませんが、クエストをクリアした際に、あたらしいユニットを入手し、新しいユニットを入手したことでユニット図鑑を解放する。という処理を実現するとしましょう。
エストをクリアした報酬としてユニットを入手する箇所に関しては、UX的に同期的に処理した方がよさそうですが、図鑑の解放はプレイヤーがすぐに目にすることは無いでしょうから、結果整合でよさそうです。

ユニットを入手した際に GS2-Script を発火するように設定し、そのスクリプトでは GS2-JobQueue に対して『ユニット入手というスクリプトを呼び出すジョブ』を登録します。
『ユニット入手というスクリプト』では、入手したユニットが図鑑で解放されているかを確認し、解放されていなければ解放するというスクリプトにします。
あとはゲーム内で余裕があるときに GS2-JobQueue のジョブを実行すれば結果整合で図鑑の解放が実現出来ます。
図鑑の解放済み確認や解放処理をユニットを入手した際に発火するスクリプトで記述する方が楽じゃない?と感じられるかもしれませんが、そのようにしてしまうと、処理エラーやクオーターリミットによるエラーでユニットの入手処理まで巻き添えになってしまいますが、GS2-JobQueue を通すことで非同期処理でリトライ付で実行出来ることによってリスクを最小限に抑えることが出来ます。

そのほかの特徴

最大リトライ回数を超えてエラーが発生した場合はデッドレターキューに追加されます。
デッドレターキューには GS2-Script の実行時エラーの内容も一緒に保存されますので、問題の解決や復旧にも役立つでしょう。

定期的に GS2-JobQueue のジョブを実行すればいい。って言われても常にポーリングするのはちょっと…。と思われた方には GS2-InGamePushNotification との連携機能をご紹介します。
この連携機能を使うことで、ジョブキューに新しいジョブが登録されたときにプッシュ通知を受けることが出来ます。
これを使えば、ジョブキューが空になるまで実行したら、GS2-InGamePushNotification からの通知が来るまではジョブキューは触らず、通知を受けたらもう一度ジョブキューが空になるまで実行する。という風にすることで無駄なポーリングを回避出来ます。


利用料金や実装例については実際に機能をご利用いただけるようになった際にこのブログで告知します。
GS2 ではマイクロサービスの数も増えてきて、これからは1回の操作で複数のマイクロサービスのデータを読み書きすることが増えてくることになります。
今回は、そのようなケースで活用出来る機能のご紹介でした。

それでは、また。

(C) Game Server Services, Inc.