Skip to content

Instantly share code, notes, and snippets.

@voluntas
Last active April 17, 2019 16:41
Show Gist options
  • Star 23 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save voluntas/cae671638cd104d05719 to your computer and use it in GitHub Desktop.
Save voluntas/cae671638cd104d05719 to your computer and use it in GitHub Desktop.
時雨堂 BOT サーバー (すごいErlangをゆかいに学ぶ会)

時雨堂 BOT サーバー (歌舞伎座.tech#5)

@voluntas

バージョン

0.3.3

url

http://voluntas.github.io/

2014 年 10 月 30 日に行われる 歌舞伎座.tech#5「すごいErlangをゆかいに学ぶ会」 の発表資料です

ベースとなっている gumiStudy #20 版は こちら

概要

サーバ BOT を動かすためのサーバーです。BOT はシンプルな言語である Lua で書くことが出来ます。

サーバー自体は Erlang/OTP を使用して書かれており、 Lua は Erlang VM 上で動作しています。

で、だれ?

時雨堂から来ました。

Twitter も GitHub も ID は @voluntas です。

最近は Erlang/OTP と Lua を書いてご飯を食べています。

なにはなすの?

メインのサーバとは完全に疎結合になっている、BOT サーバーの概要をお話しします。

さらにサーバのメトリクスの重要性と Erlang/OTP と Lua の可能性を話します。

なにはなさないの?

ゲーム内部のロジック的な話は一切しません。あくまで BOT サーバーの話をします。

目次

  • BOT サーバーの概要
    • BOT の定義
    • BOT の役割
    • BOT の具体的な役割
    • BOT サーバーに期待される役割
    • BOT サーバーを投入した結果
  • BOT サーバーの内部
    • 開発理由
    • Lua
    • スケールする Lua
    • 1 台での性能
    • 疎結合
  • BOT サーバの機能
    • BOT サーバ用 Lua
    • BOT の動作
    • 基本 HTTP API
    • 応用 HTTP API
  • 組み込んだ Lua のデバッグ
    • WebSocket を使ったデバッグ
    • Lua のプリントデバッグ
    • Lua のエラーアウトプット
    • WS でのデバッグを投入した結果
  • メトリクス
    • BOT サーバの統計機能
    • メトリクスの重要性
    • 試験時の指標
    • 運用時の指標
    • メトリクスの出力
    • Graphite の紹介
    • InfluxDB (Graphite Plugin) + Grafana
    • 統計情報を投入した結果
  • 今後
    • タイマーへの加算
    • 統計情報の WebSocket 化
    • WebUI による管理
    • 認証 API への対応
    • 強制評価 API
    • 興味がある人いますか?
  • Erlang/OTP + Lua
    • 可能性
    • 現実性
    • 未来性

BOT サーバーの概要

BOT の定義

今回お話しするのはサーバBOT と呼ばれる、運営側の BOT です。クライアント側の BOT ではありません。

BOT の役割

BOT はゲームを楽しくするのが役割です。

実際は人間が判断しているように感じられるためにはリアルタイムに状況を判断してリアルタイムに処理を行う必要があります。

BOT の具体的な役割

株式会社 gumi 様が提供している ドラゴンジェネシス 聖戦の絆傀儡虚神ゴリアテ を動かしています。

image

画像は こちら から (非公式)

動画みたい人は こちら から実況ですが見れました(非公式)

ゴリアテ(コンピュータ) vs ギルド(人間) で人間は最大 18 名という仕様です。つまり、ギルドの数だけゴリアテを動かす必要があります。

たとえばギルドが 1 万あったら、1 万のゴリアテをリアルタイムで動かす必要があります。

さらにゴリアテは状態を持っているため、1 万の状態を管理する必要があります。

また、リアルタイムゲームは速度が大事になるため、サクサク動く必要があります。

これらをどう実現したのかは BOT サーバの内部の部分でお話しします。

BOT サーバーに期待される役割

実際に使った現場の意見

  • とにかく落ちないこと
  • 期待通りに動作すること
  • 完全に疎結合になること
  • サーバ台数を減らせること
  • Lua スクリプトのデバッグが簡単であること
  • BOT サーバ自体の設置が簡単であること
  • 簡単に使い始められること
  • 統計情報が見れること

BOT サーバーを投入した結果

実際本番ではノーエラーで安定稼働しました。大量の Box が作られましたが 2 台の小さな AWS インスタンスでサクサク動作したとのことです。

トラブルが一切無かったこと、完全に疎結合になっていたため開発効率が良いこと、汎用的な仕組みのためいろいろな組み合わせが考えられることなど、高評価を得ました。

余談ですが、このサーバの影響でこのサーバを使ったプロジェクトの開発者から Erlang/OTP を学びたいとの声が上がってきているそうです。

BOT サーバーの内部

我々が開発した BOT サーバーでは BOT を動かす箱を Box と定義します。

Box には様々なデータや Lua スクリプトを入れ込めます。また、 Box に対して情報を送信し、Box の持つ情報を更新することが出来ます。

Box はそれぞれ独立しており、独立の状態を保持します。お互いに影響は受けません。

開発理由

もともと Erlang/OTP でサーバーを実装し、プラグインを Lua で開発するというシステムを開発しておりました。それがベースとなったシステムです。

リアルタイムなイベントを体験が出来るためのシステムとして、イベント駆動ではないタイマー駆動のシステムを開発しました。

Lua

url

http://ja.wikipedia.org/wiki/Lua

  • 通常の Lua はとても仕様が小さく、覚えるコストも少なくてすみます
  • 別の言語を覚えていれば、すぐに書き始められます

Lua が独特なのはテーブルと呼ばれるリストとマップが混在した仕組みです。ここでは説明は割愛します。

LuaJIT と呼ばれる Lua はかなり高速で動作します。

Lua は調べれば色々出てくると思うのでここでは省略します。

スケールする Lua

BOT サーバでは Erlang VM 上で動く Lua 実装である luerl を使用しています。

実際は rebar フレンドリーにしたり、小さなバグを修正したりしているのでオレオレ luerl を使っています。

そのため BOT サーバで動く Lua はマルチコアでスケールします。これが最大の魅力です。

すべて Erlang で実装されていることもあり、Lua <-> Erlang/OTP の連携もとてもスムーズに出来ます。テーブルは proplist になって返ってきます。

1 台での性能

BOT の実装に依存はしますが 4 コア / 8 GB のサーバで 10000 の Box を動作させることが可能です。

今のところポート枯渇を回避する予定が無いため、エフェメラルポートに収まる 10000 Box を最大としています。

ステータスの更新は 1 台で秒間 10000 前後は処理出来ます。

複雑なロジックであれば CPU を使用しますが、マルチコアで処理されるためマシンの性能を上げることで簡単に性能を上げることが可能となります。

疎結合

BOT サーバは投入される Lua に一部制限がある以外、定数や戻される JSON 、さらにはステータスとして投入されてくる JSON には全く制限がありません。

つまりタダの箱を提供するだけです。後は Lua でゴリゴリ BOT を書いておもしろいゲームを作るだけです。

BOT サーバは仕組みを提供するだけです。そのためとても汎用的です。

注意

BOT サーバは作成した Box がそれぞれリクエストをゲームサーバに投げます。そのため大量のリクエストがゲームサーバに送られることになります。

そのためゲームサーバ側で実装するよりも負荷は高くなります。事前にある程度負荷試験を行っておくのが良いでしょう。特にロードバランスする必要があります。

また状態のアップデートは非同期で処理はしていますが、間に MQ などを挟むことで明示的に非同期にする事をオススメします。

BOT サーバの機能

BOT サーバ用 Lua

BOT サーバに組み込むための Lua スクリプトにはいくつか条件があります

まずはサンプルコードです。

counter = 0

function box(uuid, constants, current_status)

    if current_status.counter ~= nil then
        counter = counter + current_status.counter
        return "action", {uuid = uuid, counter = counter}

    else
        counter = counter + 1
        print(counter)
        return nil
    end
end
  • ライブラリは Erlang/OTP 側に用意されているスタンダードなもののみ
  • グローバル変数や関数の定義は自由に出来る
  • 呼び出される関数の名前、引数は決まっている
    • box という関数である
    • 引数は 4 つである
      • uuid は box につける ID です
      • constants は定数です
      • current_status はゲームサーバから送られてくる状態です
  • 戻り値は引数を二つ return で戻す
    • 一つ目は 文字列
      • HTTP でゲームサーバに送信する時の URL の PATH です
    • 二つ目は テーブル
      • HTTP でゲームサーバに送信する JSON です
    • nil を戻せる
      • 何もしません
  • print は後ほど説明します

上のコードは、 current_status の counter が nil じゃなければグローバル変数の counter と足して、uuid と counter を action という PATH に送るという意味です。

逆に current_status が nil だった場合は counter に +1 して終わります。

BOT サーバの Lua コードはグローバル変数の状態を BOT サーバが終了するまで保持し続けます。

つまり counter をインクリメントしておけば、次に Lua を評価するとき前回の値が入っています。

このグローバル変数は Box 単位のため他の Box に影響はしません。

BOT の動作

BOT は投入された Lua コードを評価して出力された JSON を指定したサーバに HTTP で送信します

  1. HTTP API 経由で BOT の Box を作り Lua コードなどを流し込む
  2. BOT の Box は指定された時間から Lua の評価を開始する
  3. BOT は Lua を評価し、送り先 URL と JSON の二つを戻す
  4. BOT は送り先 URL に JSON を送信する

後は 2-4 をループし、最後に指定した時間になると Box は削除されます。

基本 HTTP API

BOT サーバを操作するには HTTP API を使用します。

API はいくつかありますが、主に使う API を紹介していきます。

  • CreateBox
  • UpdateStatus

この二つだけです。

CreateBox

  • BOT の設定を Box に流し込みます
{
    "uuid": "f35644f9-d353-4d84-9bd2-62f91c046550",

    "start": "2014-10-10T10:10:00",
    "stop": "2014-10-10T10:15:00",

    "timer_interval": 10,

    "host": "127.0.0.1",
    "port": 5000,
    "base_path": "/spam",

    "constants": {
    },

    "init_status": {
    },

    "lua_script": "",

}
  • uuid はこの Box の ID です。この uuid を使って Box に UpdateStatus を送信します

UpdateStatus

  • BOT に対して、情報を送りつけます

ここでいう情報とは、たとえば戦場の状態だったり、個々のキャラクターのパラメーターだったりします。

HP の状態などはすべてこの UpdateStatus から取得します。これば box の Lua コードに渡されてきて、Lua コードで判断するという仕組みです。

応用 HTTP API

基本的には使いませんが、何かしら意図的に使いたい場合のための API も用意してあります。

GetBox

Box の状態を取得できます。ただし Lua の状態を取得することは出来ません。

DeleteBox

Box を強制的に削除することが出来ます。ただし Box はタイマーで動作するため意図的に削除する以外は放置して問題ありません。

組み込んだ Lua のデバッグ

WebSocket を使ったデバッグ

組み込みスクリプトはデバッグがかなりツライです。特にサーバーに組み込まれている場合はローカルでデバッグ出来る間はいいのですが、実際色々と動作させてテストする場合はデバッグ情報をログから取得するのはツライと判断しました。

そこでいろいろな情報を WS 経由で送信出来るようにする機能を追加しています。

WS を見るには二つのツールをオススメしています。

  • wscat
    • node.js の WS クライアントでターミナル好きの人はこちら
  • Simple WebSocket Client
    • Chrome から簡単に使うことが出来るクライアントです

これらを特定のデバッグで立ち上げているウェブサーバに接続します。そうすることで様々な情報が送られてくるようになります。

Lua のプリントデバッグ

counter = 0

function box(uuid, constants, current_status)

    if current_status.counter ~= nil then
        counter = counter + current_status.counter
        return "action", {uuid = uuid, counter = counter}

    else
        counter = counter + 1
        print(counter)
        return nil
    end
end

これでもし counter が 10 だった場合は以下のように表示されます。ここでは wscat を使って例を表示しています。

$ wscat -c ws://127.0.0.1:5555/debug
connected (press CTRL+C to quit)
  < {"datetime":"2014-08-28T16:22:22.651505Z","hostname":"example.com","print":"10 "}

この機能を使うことで組み込んだ Lua の print をターミナル環境で見ることが出来ます。組み込み状態になると print 情報をどこに出力すべきなのか?となったりします。

print デバッグですが、stdout に表示されるものをどうやって WebSocket 経由で表示するのか?という疑問が生まれてくると思います。

実は難しい話ではありません。特定の状況だけ IO Protocol の投げ先を変更しています。

Erlang の IO 部分は実はメッセージパッシングになっていて、そこは Protocol として定義されています。つまりオレオレ IO Protocol が簡単に作れるようになっています。

そのため、IO の向き先を変更してしまえば、あとは好きに表示できるのです。Lua の print を WebSocket に表示する場合も向き先を変更し、Cowboy の WebSocket ハンドラーに送りつけています。

ちなみに Cowboy と WebSocket については詳しい日本語記事があるので こちら をオススメします。

こうすることで Lua での print の IO の向き先を変更し、WS で伝えられるようにしています。

Lua のエラーアウトプット

組み込んでいる Lua スクリプトがエラーをはくと、プロセス事クラッシュに巻き込まれてしまうため例外を取得して、WS に流すという仕組みを追加しました。

どんなエラーがどんな状況で起きているかがよくわかります。

$ wscat -c ws://127.0.0.1:5555/debug
connected (press CTRL+C to quit)
  < {"datetime":"2014-08-28T16:29:03.721296Z","hostname":"example.com","uuid":"aecebb67-12c2-438c-86cd-7b715070977e","constants":{"matching_id":"02522dbe-c10e-4ce6-b7a5-88f58a30ec81"},"current_status":{"counter":10,"uuid":"aecebb67-12c2-438c-86cd-7b715070977e"},"error_reason":"{undef_function,nil}"}

WS でのデバッグを投入した効果

最初は Erlang の Crash ログを読むという苦行になっていましたが、この WS でのデバッグを投入することですぐにエラーの箇所がわかるようになったそうです。

開発効率も格段に変わったと、この BOT サーバでボスの AI を Lua で書いたエンジニアから報告を受けました。

BOT サーバのような組込系スクリプトがメインのサーバの場合はデバッグはとても重要です。今後も開発者が少しでも楽が出来るようなデバッグ機能を追加していくつもりです。

メトリクス

メトリクスの重要性

メトリックス を日本語でいうならば 測定基準 です。つまり数値化されたデータを使って判定を行うということです。簡単にいえばメモリーの量が増大していけばそれはメモリーリークと 判定 することができます。

そのためには様々な情報が数値化されている必要があります。

ここで言うメトリクスとは統計情報を使って判断することをメトリクスとします。また統計情報とは数値化されたデータとします。

BOT サーバの統計機能

BOT サーバは統計出力機能を二つもっています。

一つは HTTP API の統計で、Box がゲームサーバに対して投げる HTTP API の統計も含まれます。

Box の生成、Box に対する情報更新、後は Box 自体がアプリサーバへのリクエスト、レスポンス。さらにはそれらのエラーの数が取得できます。

たとえば Box からのリクエストが 200 が帰ってこない場合はそもそもゲームサーバ側が不調の可能性があります。

{
    "box_bad_response": 0,
    "box_request": 4159,
    "box_request_error": 3381,
    "box_response": 0,
    "create_box": 1000,
    "update_status": 0
}

もう一つは組み込み Lua 関連の統計情報です。Lua の呼ばれた回数、エラー数、nil を戻した数、さらには想定外のエラーが起きた場合の数なども取得できます。

{
    "lua_call_function": 5354,
    "lua_error": 0,
    "lua_nil_return": 0,
    "lua_unexpected_return": 0
}

これらをゲームサーバ側の管理画面などに組み込むことで気軽に BOT サーバの動作状況を監視することが出来ます。

WebSocket によるリアルタイムな統計情報の取得などを実装していく予定でが、取得側が定期的に取りに来ても問題ないと思います。

この BOT サーバはアプリと連動するという事もあり、HTTP で取得できるのはアプリ側の管理画面を作ったりして確認出来る状況を作ることも出来ます。

試験時の指標

試験といっても色々ありますが、自分が重視しているテストは負荷試験とロングラン試験の二つです。

セキュリティ試験もありますが、これはまた別の話で長くなるので省略します。

  • どのくらいのリクエストが処理されているのか
  • どのくらいのメモリーが使用されているのか
  • どのくらいの CPU が使用されているのか

この辺の統計情報を取得する必要があります。これらをログから解析するのも良いですが、リアルタイムに表示できるようにしておくと良いでしょう。もちろんメトリクスによるアラートも欲しいです。

運用時の指標

メトリクスは試験時だけではなく、運用時にも必要です。何かしらの統計情報が一定数を超えた場合は問題とするといった事ができます。

運用側が気軽に判断出来るように、統計情報を気軽に取得できるようにしておくのも重要です。

さらに、最近ではサーバ自体が統計情報をプッシュで出力しデータベースに保存するという手段も積極的に使用していくべきです。

メトリクスの出力

  • HTTP API による出力
  • GraphiteStatsD などの専用のプロトコルでの出力

PULL 型と PUSH 型の二つを用意しておくのが良いでしょう。HTTP API は必要なときだけ、Graphite などの専用プロトコルではリアルタイムに送信、といった感じです。

Graphite の紹介

url

http://graphite.wikidot.com/

Graphite は Python で書かれたリアルタイムグラフツールです。

簡単に説明すると決められたメッセージフォーマットで統計データを送れば時系列でデータを保存しておいてくれます。

そして、それをグラフで気軽に見ることが出来ます。

http://graphite.wikidot.com/screen-shots

InfluxDB (Graphite Plugin) + Grafana

InfluxDB

http://influxdb.com/

Grafana

http://grafana.org/

最近のはやりである InfluxDB が Graphite に対応しているのでそちらがオススメです。さらに Grafite の Web UI はちょとオサレとは言えないので、それをオサレにする Grafana が InfluxDB に対応しているのでそちらもオススメです。

ここでは Erlang VM のメモリーを表示している例です。erlang:memory/0 で取得できるデータを流し込んでいます。

image

統計情報を投入した結果

統計情報があることでどこがボトルネックなのか、どこがエラーなのかがすぐに判断出来るようになりました。負荷試験や実際の本番でのエラーなどもすべて判断できます。

実際に本番時ではこの BOT サーバの統計情報がリアルタイムで見れることでどのくらいの Box が立ち上がっているのか、今実際にどのくらい動いているのかということがすぐわかりました。

性能との兼ね合いもありますが、統計情報はあればあるだけ困りません。たくさんの情報を取得できるようにしましょう。

今後

タイマーへの加算

戻り値にタイマー加算を用意することで毎回等間隔で評価している Lua コードを Lua 側が自由に設定出来るようにする予定です。

統計情報の WebSocket 化

管理側がリアルタイムに監視できるようにする予定です。

WebUI による管理

JS オンリーで認証が無い管理画面を用意する予定です。

認証 API への対応

ゲームサーバーへの通信を今は HTTP ですが、HTTPS や認証の仕組みを用意する予定です。

ただしインターナルに保持するシステムのため IP 制限だけでも問題はないと考えているため実装の優先順位は低いです。

強制評価 API

強制的に Lua を評価する API を追加予定です。コレを使うことでゲームサーバが無理矢理情報を取得することが出来ます。

同期、非同期の両方を用意する予定です。同期的なのは API の戻り値にアクションと JSON が含まれます。非同期は通常通り HTTP 経由で JSON を投げ込んできます。

興味がある人いますか?

紹介してきたBOT サーバがパッケージとしてすぐ使える状態で販売可能です、ご興味があればご相談ください。

contact at shiguredo.jp

Erlang/OTP + Lua

おまけ

多くの言語で Lua は組込スクリプトとして使われているようです。自分の Lua のイメージは早くて小さいというイメージでした。実際使ってみると覚えることも少なくサクサク書ける感じがしました。

今回 Erlang/OTP と Lua を組み合わせて実際に本番システムで動作させた事を踏まえて、色々考えてみました。

可能性

Erlang/OTP 自体がとてもスケール出来る VM 上に作られているため、設計を間違えなければうまくスケールします。今回使用したのはその Erlang VM 上で動く Lua です、そのため Lua 自体がスケールする仕組みになっています。

一番の強みは気軽に書いた Lua がスケールするという事でしょう。組込言語がスケールしてくれる安心感はかなり魅力があると思います。

さらに Lua スクリプト一つが何かしらバグがあったとしても他には一切影響がありません。これも Erlang/OTP 独特の世界に Lua が包み込まれているからです。

スケール+耐障害性を持った Lua は自分が想定したよりもかなりの可能性を感じました。

現実性

Erlang 自体がネットワークサーバの DSL です、その上で動く何かしらの簡単なロジックを Lua で書くというのは、実際 Lua を書いた側に聞いてみましたがデバッグ環境さえ充実していれば特に苦にはならないようでした。

むしろ最初はデバッグ環境が無かったので相当苦労していたようです。ただプリントとエラーを WebSocket 経由で見れるようにしてからは、ほぼ不満が無くなったとの事です。

スクリプト言語を組み込むときはデバッグ環境の整備がとても重要ですね。

さらに、今回は Lua が呼ばれる回数やエラー回数などをすべて統計情報として取得できるようにしたことから、負荷試験などでどのくらい呼ばれているのかなどが見えるようになりました。

サーバ開発者からもこの統計情報は重宝されました。

道具を用意することで現実的に使えるところまで落とせたと思っています。

未来性

実際 Erlang/OTP 部分を書くのは設計が重要です。組込だけなら良いですが実際はコアとなるサーバー部分が大半になるでしょう。それにうまく Lua を組み込むのは難しいと思います。

ただ、プラグイン部分を Lua にするのはとても未来があると思います。難しい処理をするのでは無く、ちょっとした処理をコアのコードを変更せずに実現出来るようになるのです。

弊社は今後も Erlang/OTP と Lua の組み合わせを積極的に使ったプロダクトを開発していく予定です。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment