MAGELLAN について

MAGELLAN ってなに?

人気のオンラインゲームや普及している IoT を支えるためのサーバーは、これまでのシステムとは桁違いに大変なことに直面します。特定の企業のための仕組みではなく、普通の人が普通に使えるもの、しかもいつでも思い立った時に使えるようにすること。しかも一度動きはじめたら、途切れなく改善改良を繰り返さなければならない。スマホアプリの場合だったら、作ったアプリケーションをすぐにリリースできるわけではなく、Apple や Google などの審査を受けなければならない。。。つまりまとめると、

  • 圧倒的なトランザクション: 人気がぐーんと出てくると毎日利用者が数十万人ずつ増えたりとか。で、数百万人、数千万人が同時にアクセスしてくることになったりします。
  • 大量データ: 利用者が増えたらデータもどんどん増えてしまいます。
  • 止められない: 障害があっても止まらないことは当然かもしれないですが。
  • 頻繁なバージョンアップ: サーバー機能もクライアント機能もどんどん改善することになります。だからその度に止めるなんてできない。
  • 複数バージョンの混在: 複数バージョンを混在させることなんてしたくないでしょう。でも頻繁にバージョンアップするってことは、頻繁に Apple や Google に審査してもらうことになります。だから複数バージョン混在は想定しておかないと。これはクライアントバージョンが複数あるというだけじゃなく、サーバーバージョンも複数混在するかもしれないということを前提にシステムを考えなければならないということです。
  • ノード間通信: クライアントがサーバーと通信していればいいわけじゃなく、色々考えていくとクライアント間の通信。つまり利用者間でメッセージやりとりするとか、仲間同士で協力してモンスターをやっつけるとか。自動車同士で位置情報を交換しあうとか。しかもクライアント間通信にサーバー機能を介入させたいという要望も出てくるはずです。このような機能は今後かなり需要が高い部分ですが、実現するのは結構むずかしいことだったりもします。

サーバープログラムを作るのは簡単だったとしても、こうしたことを一つ一つ対応するのはかなり大変なことです。楽しいゲームや IoT サービスを実現するには、こうしたことは避けられないですが、いちいちやっていたら楽しいサービスどころじゃなくなって、結局なんか思ってたことがやれなくなったりします。

MAGELLAN は、人気のオンラインゲームや普及している IoT サービスに必要とされるこうした様々な課題を解決するためのクラウドサービス。オンラインゲーム開発者や IoT サービス開発者が、本当に自分のサービスに集中できるためのサービスです。

基本要素

MAGELLAN は、オンラインゲームや IoT サービスのサーバー側機能を手軽に実現できるようにしています。このサーバーで動くプログラムのことを、MAGELLAN では Worker と呼びます。頑張って働いてもらうので Worker です。この Worker がクライアントと連携しながら、様々な機能を実現していきます。ここではこの 2 大要素の Worker とクライアントについて説明します。

Worker

Worker は、HTTP 型通信と Publish/Subscribe 型通信をサポートしています。

HTTP 型通信というのは、クライアントからの要求をサーバーで処理し、なんらかの応答を返すようなもので、MAGELLAN では HTTP/HTTPS の 2 種類のプロトコルに対応しています。

Publish/Subscribe 型通信というのは、クライアントはサーバーにデータを送っても、応答を待たないで次のことをしてしまうようなものです。例えば車載器が、車の情報を定期的にサーバーに報告したり、農地の情報を報告するときなどに使います。ゲームで言うと、チャット機能や、MMO でプレーヤーの情報を交換しあうような場合などです。MAGELLAN では、Worker なしでクライアント同士が直接 Publish/Subscribe 型通信をできるようにもなっており、プロトコルとして MQTT に対応しています。

こうした複数の通信形態に対応した Worker を作成するために、MAGELLAN は特別なやり方をできるだけ覚えなくてもいいように作られています。そのため、Worker 開発者は慣れ親しんだ言語の慣れ親しんだ Web フレームワークを知っていればいいのです。

HTTP 型通信

HTTP 型通信を行うプログラムは、普通のウェブアプリ上のプログラムと全く同じです。 DB は、好きなデータベースを利用できますが、Google Cloud SQL を用意しました。つまり MySQL です。

HTTP 型通信のプログラムは、以下のようになります(Ruby on Rails でのコード例)。

class UsersController < ApplicationController

  def index
    users = User.all
    respond_to do |format|
      format.xml  { render xml:  users }
      format.json { render json: users }
    end
  end

  def show
    user = User.find(params[:id])
    respond_to do |format|
      format.xml  { render xml:  user }
      format.json { render json: user }
    end
  end
  .
  .
end

ソースでわかるように、MAGELLAN 独自のものは何もありません。クライアントとのやりとりは、通常の Web クライアントのようにマッピングされるので、普通のウェブアプリのようにプログラミングをしてください。

Publish/Subscribe 型通信

Publish/Subscribe という通信は、複数の人が参加する仮想的なチャンネルを設定し、そこに参加しているノード同士で同報通信が行えます。MAGELLAN の場合、複数のクライアントと Worker を一つのグループとして通信しあうこともできますし、Worker を絡ませずクライアント同士で通信しあうこともできるようになっています。

クライアントとの通信は MQTT を基本としていますが、Worker 開発者は、プロトコルのことを意識する必要はありません。

Publish/Subscribe 型通信のプログラムは、以下のようになります。

まずは Publish です。Publish は、トピックを購読しているユーザーにデータを送信するものです。MAGELLAN が提供する Publisher クラスの publish メソッドを使って送信します。送信時には、トピックと送信するデータを指定すれば、データがトピックを Subscribe している全員に送信されます。

  • Publish のコード例(Ruby on Rails でのコード例)
Magellan::Publisher.publish("worker/friendship/#{friendship.id}", "#{username} has arrived at the destination.")

次に Subscribe です。Subscribe はデータを受け取るものですが、受け取るデータがあると MAGELLAN がプログラムを呼び出してくれます。データが到着したら呼び出して欲しいメソッドを登録しておけば、あとは MAGELLAN が対応してくれます。

  • Subscribe のコード例(Ruby on Rails でのコード例)
class RoomsSubscriber < Magellan::Subscriber::Base
  def receive_automobile_data
    ...
  end
end

クライアント

Worker と通信するクライアントに制限はありません。サーバーやスマートフォン、据え置き型ゲーム機、そしてウエラブル端末や IoT 機器などの組み込み機器など、あらゆる装置をクライアントにできます。

HTTP 型通信の場合は、HTTP/HTTPS を使って RESTful 通信をすればやりとりができます。Pulish/Subscribe 型の場合は、MQTT でやりとりする必要があります。MQTT のライブラリについては、MQTT サイトlibraries で紹介されているので参考にしてください。

Unity や Cocos2d-x を使ったクライアント開発のために、Client SDK も提供します。Client SDK については、後述しますので参考にしてください。

また、Worker 開発者が簡単にクライアントからの接続を試せるように、libmagellan というライブラリも提供しています。このライブラリを使えば、Ruby の irb によりインタラクティブに動作確認をすることができます。

libmagellan

libmagellan を利用するには、以下の通りです。 ここでは、irb を使った例となっています。

まず irb を libmagellan ライブラリを指定して起動します。

$ irb -r libmagellan

Libmagellan.new で、Libmagellan オブジェクトを生成します。

irb> c = Libmagellan.new("https://nebula-001a-web.magellanic-clouds.net", consumer_key: "MyOrg.MyProject", consumer_secret: "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", mqtt_host: "nebula-001a-mqtt.magellanic-clouds.net", mqtt_port: 1883, client_version: "0.0.1")
=> #<Libmagellan::Core:0x007f338d638108>

これで MAGELLAN と接続ができたので、ping メソッドで MAGELLAN との疎通確認をしてみましょう。

irb> c.ping
=> #<Net::HTTPOK 200 OK readbody=true>

次に request メソッドで、MAGELLAN 上の Worker にリクエストを送信します(ここでは適切な Worker が動作していることを前提としています)。

irb> r = c.request("/rooms")
=> #<Net::HTTPOK 200 OK readbody=true>

レスポンス(request メソッドの戻り値)の body を確認すると、Worker が返してきた情報を見ることができます。

irb> r.body
=> "[{\" .... \"}]"
irb>

Client SDK

クライアント開発を効率化するために、Client SDK も用意しています。現状は、Unity と Cocos2d-x の 2 つに対応しています。

Client SDK では HTTP 型通信と、Publish/Subscribe 型通信を利用する為の API を提供しており、決まった手順を踏むだけで簡単に利用することができます。

具体的な方法は、以下のようになります。

  1. クライアント作成: 割り当てられたホスト、ポート、ConsumerKey、ConsumerSecret を指定して MAGELLAN クライアントを作成します。
  2. Worker リクエスト用インスタンス作成: API の URL のディレクトリ部、リクエストメソッド、プロトコル、スキームを指定して、Worker インスタンスを作成します。
  3. リクエストコールバック設定: Worker インスタンスには、レスポンス受信時に実行されるコールバックをデリゲートとして設定することができます。
  4. Worker リクエスト API コール: Worker 利用する為に SDK で用意された Worker リクエストAPIを呼び出します。
  5. Worker リクエスト結果受信(コールバック受信): レスポンスを受信すると、コールバック呼び出しと Worker インスタンスに受信したデータ格納が行われます。

たとえばアプリケーションで画面上に配置された「Send」ボタンを押した際に Worker を利用する場合のプログラムは Unity と Cocos2d-x でそれぞれ、以下のようになります。

Client SDK - Unity

Unity で Client SDK を利用する場合は、以下のようになります。

using UnityEngine;
using System.Collections;
using Magellan;

public class Sample_Worker : MonoBehaviour
{
  void Start()
  {
    //1. クライアント作成(※パラメータに接続先指定)
    Manager.Instance.CreateClient("example1.com",           //接続先ホスト指定
    443,                      //接続先ポート指定
    "CONSUMER_KEY",           //認証用ConsumerKey指定
    "CONSUMER_SECRET",        //認証用ConsumerSecret指定
    true);                    //デフォルトのクライアントとして設定するか
  }

  void OnGUI()
  {
    if (GUILayout.Button("Send", GUILayout.Width(120), GUILayout.Height(120)))
    {
      SendRequest();
    }
  }

  void SendRequest ()
  {
    //2. Workerリクエスト用インスタンス作成
    Worker worker = new Worker("sample");  //URLのディレクトリパス指定
    worker.Method = BaseRequest.MethodType.Post;        //リクエストのメソッドタイプ指定
    worker.Protocol = BaseRequest.ProtocolType.General; //プロトコル指定
    worker.Scheme = BaseRequest.SchemeType.HTTPS;       //リクエストのスキーム指定

    //3. リクエストコールバック設定
    worker.ResponseCallback = (req) =>
    {
      //5. Workerリクエスト結果受信(コールバック)
    };

    //4. WorkerリクエストAPIコール
    Manager.Instance.SendWorkerRequest(worker);

    //5. Workerリクエスト結果受信
    int code = worker.Code;
    string header = Encoding.UTF8.GetString(worker.ResponseHeader);
    string data = Encoding.UTF8.GetString(worker.Response);
  }
}
Client SDK - Cocos2d-x

Cocos2d-x の場合は、以下のようなコードになります。

using namespace Network;
using namespace Magellan;

namespace{
  void httpCallback(HTTPRequest *req)
  {
    //5. Workerリクエスト結果受信(コールバック)
  }
}

void WorkerScene::onEnter()
{
  CCScene::onEnter();
  CCSize visibleSize = CCDirector::sharedDirector()->getVisibleSize();
  CCPoint origin = CCDirector::sharedDirector()->getVisibleOrigin();

  mSendItem = CCMenuItemImage::create("Send.png",
  NULL,
  this,
  menu_selector(WorkerScene::menuSendCallback));
  mSendItem->setPosition(ccp(origin.x + visibleSize.width / 2 ,
    origin.y + visibleSize.height / 5  ));


    CCMenu* pMenu = CCMenu::create(mSendItem, NULL);
    pMenu->setPosition(CCPointZero);
    this->addChild(pMenu, 1);

    //1. クライアント作成(※パラメータに接続先指定)
    MagellanManager *mng = MagellanManager::getInstance();
    mHandle = mng->createClient("example1.com",       //接続先ホスト指定
    443,                  //接続先ポート指定
    "CONSUMER_KEY",       //認証用ConsumerKey指定
    "CONSUMER_SECRET");   //認証用ConsumerSecret指定
  }

  void WorkerScene::menuSendCallback(CCObject* pSender)
  {
    MagellanManager *mng = MagellanManager::getInstance();
    WorkerRequest *req = NULL;

    //2. Workerリクエスト用インスタンス作成
    req = new WorkerRequest("sample",                   //URLのディレクトリパス指定
    HTTP_METHOD_POST,            //リクエストのメソッドタイプ指定
    HTTP_PROTOCOL_GENERAL,      //プロトコル指定
    BASE_REQUEST_SCHEME_HTTPS); //リクエストのスキーム指定

    //3. リクエストコールバック設定
    req->setResponseCallback(httpCallback);

    //4. WorkerリクエストAPIコール
    mng->sendWorkerRequest(mHandle, req);
  }

  void WorkerScene::update(float delta)
  {
    CCScene::update(delta);
  }

  void WorkerScene::onExit()
  {
    MagellanManager *mng = MagellanManager::getInstance();
    mng->releaseInstance();

    CCScene::onExit();
  }

MAGELLAN の構造

MAGELLAN は、無停止運用や大規模トランザクションを捌くために、以下のような 4 つの構造のうえで Worker を動かしています。

  • コンテナ:コンテナは Docker イメージが Worker として動くための入れ物です。アクセスの増加が見込まれる場合などは、コンテナの数を増やし、Worker の数を調整します。また、Worker のバージョンアップをする場合などは、新しいバージョンの Docker イメージを用意し、コンテナに Worker の入れ替えを指示することで、無停止でのバージョン切り替えが行えます。
  • ステージ:ステージは、複数のコンテナが動く場所で、異なるバージョンの Worker を動かす場合などに効果を発揮します。例えば開発用のステージと本番用のステージ、それに Apple や Google へ新しいバージョンの審査を申請している場合の審査対応用ステージなどです。クライアントのバージョンが混在する場合などは、バージョンごとにステージを分けて対応などが行えます。
  • プロジェクト:プロジェクトは、複数のステージをまとめたもので、プロジェクト単位に使うことを想定しています。
  • 組織:組織は、複数のプロジェクトが動く場所で、通常はなんらかの組織により管理される単位です。

structure

Worker 開発の流れ

最後に、Worker 開発の基本的な流れを見てみましょう。

  1. アプリケーションを作成する

    Worker は、普通のアプリケーションの形式で開発できるようになっています。

    アプリケーションが開発できたら、HTTP 型通信の場合は、そのまま curl やブラウザーなどの HTTP クライアントを使ってローカルでテストを行ってください。

    Publish/Subscribe 型通信の場合は、今のところ MAGELLAN に登録してからのテストとなります。

  2. Docker イメージを作成する

    アプリケーションが作成できたら、Docker コマンドを使って Docker イメージを作成してください。これによってプログラムの実行に必要な環境がパッキングされ、MAGELLAN で実行させる前準備は完了です。

  3. Docker Hub に登録する

    Docker イメージを作成したら、Docker Hub に登録してください。MAGELLAN は、利用する Worker を Docker Hub 経由で受け取り実行します。

  4. MAGELLAN 環境を準備する

    Worker を動かすためには、すでに説明した 4 つの構造「組織、プロジェクト、ステージ、コンテナ」を準備する必要があります。アクセスボリュームや、サービスの運営方針に従って、適切な準備をしてください。

  5. MAGELLAN にデプロイ/実行

    Docker Hub に登録し、MAGELLAN 環境の準備が終わったら、いよいよ Worker をデプロイして実行します。

overview