Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

PHPからJavaへ乗り換えた。そんな昔話をしよう

2,080 views

Published on

ゲームサーバ勉強会第七回(SIG-NetworkSystem)での発表資料です

Published in: Technology
  • Be the first to comment

PHPからJavaへ乗り換えた。そんな昔話をしよう

  1. 1. PHPからJavaへ乗り換えた。 そんな昔話をしよう 黒河 優介
  2. 2. 今回の講演について • 2012年ごろに開発していたモバイルタイトルでの話になります。 • プロトタイプまでは PHP 5系で作成し、本制作をJava(Tomcat 6)で 行ったため、PHPでは最後まで作成しきれていません。 • そのタイトルでは初期のプロトタイプ時からサービスクローズ まで関わっていました。
  3. 3. モバイルのゲーム通信ざっくりおさらい クライアント APIサーバー DBサーバー サーバーへ通信するアプリケーションが 動いている
  4. 4. モバイルのゲーム通信ざっくりおさらい クライアント APIサーバー DBサーバー クライアントから受け取ったデータを 処理して返す部分 PHP / Tomcat / node.js 等
  5. 5. モバイルのゲーム通信ざっくりおさらい クライアント APIサーバー DBサーバー ゲームの設定に関するマスターデータの格納及び、 各プレイヤーのセーブデータの格納先 MySQL / DB2 / MongoDB等
  6. 6. モバイルゲームでの通信処理の流れ1 クライアント APIサーバー DBサーバー 1. 「ログインする」「ガチャを引いた」「ダンジョンに入る」「ダンジョンクリア」等の アクション時にサーバーに送信。 アクションに紐づくデータをHTTP経由で送信
  7. 7. モバイルゲームでの通信処理の流れ2 クライアント APIサーバー DBサーバー 2. APIサーバーがクライアントから受け取ったアクション・データを元に処理。 APIサーバーがDBサーバー上のデータを読み書きして処理を行う。
  8. 8. モバイルゲームでの通信処理の流れ3 クライアント APIサーバー DBサーバー 3.最後に処理した結果を APIサーバーから クライアントへ返す
  9. 9. 今回の話 クライアント APIサーバー DBサーバー 今回の講演は、 ここを PHP実装→Java実装へ乗り換えた話
  10. 10. もう少し俯瞰視点での話 プレイヤー APIサーバー 運営者 ※変更したのはAPIサーバーのみでお知らせ等のWebViewや 管理画面(運営向けのWebページ)はPHPのまま実装。 (赤枠がPHP->Javaで、青枠はPHPのままの部分) HTML表示する類は PHPが楽だった WEBサーバー DBサーバー 運営向けサーバー
  11. 11. なんでJavaに変えたの? • ボス(偉いプログラマ)からの提案でした • プロトタイプ開発中にゲーム仕様も膨らみ、予定より規模が大きく なったので PHPだと厳しそうだった • PHPよりはJavaのプログラマーの方が多い部署だった • ログイン機能と、ゲームプレイに入るときのデータ取得通信位までし か作っていなかったので、そこまで未練はなかった • Java自体は フィーチャーフォン時代とAndroid周りのクライアント コード書いてたので、何とかなるだろうという算段があった
  12. 12. というか、何でPHPを選択したの? • 何となくです • PHPでWebサービスのアップデート/メンテナンスの経験があった • 勉強会等の資料等を見ていても当時はPHPが多かったように見えた
  13. 13. で、結局Javaに変えてよかったと思う? • 変えて本当に良かった。 • このタイトルだけでなく、次のプロジェクトでもJavaを採用した • 後日PHP実装のAPIサーバーを少し見る事になりますが…。 やっぱりあの時Javaにしてよかったと思った
  14. 14. Javaに変えてよかった点 • 「コンパイル時に怒られる」って素晴らしい! • 静的解析によるコード品質の担保 • 「キャッシュサーバー」なしで運用出来た
  15. 15. Javaに変えて面倒だった点 • DBへのアクセスで、オブジェクト変数への適応は自前で行う 必要があった → Reflectionを使って回避 • リソースリークが発生しうる → ちゃんと組めば問題ない.
  16. 16. 「コンパイル時に怒られる」って 素晴らしい
  17. 17. コンパイルについて • PHP インタプリンタ形式なので、コンパイルせずにサーバーにソース ファイル(.php)を置くだけで実行可能な状況になる • Java コンパイル形式なので、コンパイルした結果(.class/.war)をサーバーに 置くことで実行可能になる
  18. 18. コンパイルについて • PHP → サーバーにエラーとなるプログラムがアップされてしまい、 サーバーが全く動かない状態が起こりえる。 開発中でも他スタッフの手が止まってしまうのでヨロシクない • Java → 少なくともコンパイルが通ったプログラムなので、最低限の 保証はされている(実行時エラーが起きないとは言っていない)
  19. 19. 変数・型宣言 • PHP 型付け、変数宣言なしでも いきなり変数が使える • Java 変数を型とセットで宣言しないとソモソモ使えない
  20. 20. 変数・型宣言 • PHP → 変数名のスペルミス等で実行時に未初期化変数等のエラー等が 定期的に発生する。 int型のつもりが気付くとstring型になっててトラブル • Java → 変数名をスペルしたら、コンパイル通らないし、別の型で宣言された 変数には別の型の値は入れられない
  21. 21. PHPでありがちなエラー(極端にした例) for( $counter = 0 ; $counter < 10; $count++ ){ // 何かを処理する… } 変数名が間違っているので、ループ条件である 「$counter」がインクリメントされず別変数が インクリメントされてしまう。 その結果、無限ループしてしまう。 問題なのは、実行して この処理を通るまではエラーがわからない事
  22. 22. やっぱり 「コンパイル時にエラーが見つかる」って 素晴らしい
  23. 23. 静的解析によるコード品質の担保
  24. 24. 静的解析ツールの利用 • Javaには、コードの静的解析ツール「CheckStyle」と 「FindBugs」がある • Jenkinsでの自動ビルドに、これらのツールを組み込み活用し た
  25. 25. CheckStyleについて • ソース本体(.javaファイル)に対して行う静的解析ツール • 主な目的は、コーディング規約が守られているかチェックする ツール • 特定のパスだけは例外等の細かい指定をxmlで出来る • 実際のプロジェクトでもいくつか部分的にチェック対象から外すよう にしていた
  26. 26. CheckStyleで出来ること • 変数やクラス名の命名規則チェック • 変数やメソッドは小文字スタートになっているか?等 • 行数の多いメソッドチェック • コメントをちゃんと書いているかチェック
  27. 27. FindBugsについて • コンパイル結果(.classファイル)に対して行う静的解析ツール • 主な目的は、バグになりそうな箇所のチェック • null ポインタアクセスのチェック等が出来る
  28. 28. PHPの頃は、こういった静的解析ツールを使用し てなかった…
  29. 29. Jenkinsで構築したフロー(PHP環境) 開発用の APIサーバー ソースコードの 自動取得バージョン管理 (svn/git等) Jenkinsマシンの処理 コミット 何のチェックもないので、サーバーにあげて実行 してからエラーが発覚することがちょくちょく あった。 クライアントサイドから怒られる事もちょくちょ くあった… プログラムを開発用 のサーバーにアップ 開発者
  30. 30. しかし、Javaでは違う!!!
  31. 31. Jenkinsで構築したフロー(Java環境) 開発用の APIサーバー CheckStyleによる コードチェック 実際のビルド (Javaファイルのコンパイル) FindBugsによる チェック ソースコードの 自動取得バージョン管理 (svn/git等) Jenkinsマシンの処理 コミット Jenkins内の全ての チェックを通ると サーバーにプログラ ムをアップロード プログラム更新開発者
  32. 32. Jenkinsで構築したフロー(Java環境) CheckStyleによる コードチェック 実際のビルド (Javaファイルのコンパイル) FindBugsによる チェック ソースコードの 自動取得バージョン管理 (svn/git等) Jenkinsマシンの処理 プログラム更新 どこかに引っかかるとJenkinsから お叱りメールが飛んでくる。 開発サーバーのプログラム更新は 行われない。 ※CheckStyle/FindBugsはエラー0運用はさすがに厳 しかったので多少のエラー数10位は許容していた。 エラーが少し増えたタイミングで警告メールだけ出す ようにしていた 開発者 エラーが起きると メールが来る
  33. 33. Jenkinsで構築したフロー(Java環境) 開発用の APIサーバー 開発者が直接「開発サーバー」に プログラムをアップロードするのは 全面的に禁止! 開発者
  34. 34. 静的解析ツールを自動ビルドのシステムに組 み込む事で、コードの質をある程度保つこと が出来た また「Jenkinsからの警告」という体にしたことで、コード修正をメンバーに依頼しやすかった。
  35. 35. 「キャッシュサーバー」なしで運用できた
  36. 36. キャッシュサーバー??? APIサーバー DBサーバー DBサーバーは、検索等が高機能なため、データを ただ読むだけの用途では遅くなりがち
  37. 37. キャッシュサーバー??? APIサーバー DBサーバー キャッシュサーバー (memcached等) DBサーバーから得た結果を キャッシュサーバーに入れて、 そちらから優先的に読むようにする
  38. 38. 何故キャッシュサーバーなしで出来た? • PHPでは、プロセスベース • 1アクセス毎にプログラム自体がリセットされてしまうので、次のアク セスがあった時も再びデータを取り直すところからスタート • 対して、Java(tomcat)はスレッドベース • 1アクセス毎にスレッドを割り当てて実行するので、アクセス毎にデー タはリセットされない • 次回以降のアクセスのためのデータを何らかの形で保持してあげれば 再びデータを取り直す必要がない
  39. 39. PHPでは… クライアント APIサーバー DBサーバー ユーザーからのアクセス毎にPHP実行のためのプロセスを立てているので、 DBサーバーから取得してきたデータは消えてしまう。
  40. 40. Java(tomcat)では… クライアント APIサーバー DBサーバー ユーザーからのアクセスにプログラム中のスレッドを割り当てるだけなので、 staticな変数(グローバル変数)に置いた変数は消えずに残せる。 DBから取得したデータをstatic変数に入れることで、次回以降使いまわせる
  41. 41. Java(tomcat)はスレッドベースなので 自身のプログラム自体にキャッシュすることが出来た
  42. 42. どういうデータにどう使った? • static変数にキャッシュしたのはマスターデータ • DBのテーブル(種類)毎にそれぞれキャッシュの有効時間があり、一定 時間を過ぎると強制的に取り直していた • データ更新があった場合は、キャッシュをクリアするために開発者し か叩けないキャッシュクリア専用のAPIを用意した • ユーザーのデータは、アクセス毎にDBへアクセスしていた ※マスターデータとは、ゲームのカード設定等のデータ。 運営以外が書き換えることはない
  43. 43. その他にスレッドベースでのテクニック • static変数に アクセスしたユーザーIDの履歴を100件程用意し て、フレンド以外のパートナーユーザー紹介に使った • 外部サーバーにチュートリアル突破率等を渡す必要があったが、 別スレッドを立てて裏で外部サーバーへの通信を行う事でレス ポンスの向上を測った
  44. 44. この結果、 「キャッシュサーバー」なしで運用も問題なく出来た
  45. 45. 良いところは聞き飽きたんで、 悪いところを…
  46. 46. DBへのアクセスで、オブジェクト変数へ の適応は自前で行う必要があった
  47. 47. DBの取得周りについて • PHPでは連想配列にデータを格納出来たので、あまり面倒な事 はせず丸ごとデータをとってこれた • Javaでは一つ一つのカラム(項目)に対してデータを取得して回 る必要があった
  48. 48. PHPでのDBの取得周りについて id name lv 1 セイバー 10 2 アーチャー 9 3 ランサー 8 DBテーブル 「servant」$dbObj = GetFromDB(“SELECT * FROM servant”); $data = array(); for( $i = 0 ; $i < $dbObj->count ; ++$i ){ $data[i] = $dbObj->GetRow($i); } // データへのアクセスは下記のようになる // $data[0] で一件目のデータにヒット // $data[0][‘id’] <= ここには1 // $data[0][‘name’] <= ここには”セイバー” // $data[0][‘lv’] <= ここには10 // $data[1] で2件目のデータにヒット // $data[1][‘id’] <= ここには2 // $data[1][‘name’] <= ここには”アーチャー” // … DB上に上記のようなテーブルがあったと 仮定して、それを処理するPHPプログラム は左のような仮コードになる
  49. 49. JavaでのDBの取得周りについて id name lv 1 セイバー 10 2 アーチャー 9 3 ランサー 8 DBテーブル 「servant」 dbObj = GetFromDB(“SELECT * FROM servant”); Servant[] data = new Servant[dbObj.GetCount()]; for( int i = 0; i < dbObj.GetCount(); ++i ){ data[i] = new Servant(); data[i].id = dbObj.GetInt( i , “id” ); data[i].name = dbObj.GetString( i , “name” ); data[i].lv = dbObj.GetInt( i , “lv” ); } // 各カラムごとに個別に取ってくる必要があり面倒 DB上に上記のようなテーブルがあったと 仮定して、それを処理するJavaプログラム は左のような仮コードになる // どこかでクラス定義している class Servant{ public int id; public string name; public int lv; }
  50. 50. JavaでのDBの取得周りについて id name lv 1 セイバー 10 2 アーチャー 9 3 ランサー 8 DBテーブル 「servant」 dbObj = GetFromDB(“SELECT * FROM servant”); Servant[] data = new Servant[dbObj.GetCount()]; for( int i = 0; i < dbObj.GetCount(); ++i ){ data[i] = new Servant(); data[i].id = dbObj.GetInt( i , “id” ); data[i].name = dbObj.GetString( i , “name” ); data[i].lv = dbObj.GetInt( i , “lv” ); } // 各カラムごとに個別に取ってくる必要があり面倒 カラムが増えれば増えるほど、ここを書く 量が増えてきて面倒だし、ミスも増える // どこかでクラス定義している class Servant{ public int id; public string name; public int lv; }
  51. 51. カラムが増えれば増えるほど 書くのが大変になるのでツライ・・・
  52. 52. が、これをReflectionを使って回避した
  53. 53. Reflectionについて • JavaのReflectionを使うと、string型で変数名を経由してオブ ジェクトの値をいじることが出来るようになる • これを使えば、DBのカラム名からオブジェクトに直接代入が出来る
  54. 54. Reflectionについて id name lv 1 セイバー 10 2 アーチャー 9 3 ランサー 8 DBテーブル 「servant」 // DBからデータを引くとき、各public変数の名前に勝手に代入させる class Servant{ public int id; public string name; public int lv; } DB名と変数名で紐づけして、 勝手に値が入るようにする
  55. 55. JavaでのDBの取得周りについて id name lv 1 セイバー 10 2 アーチャー 9 3 ランサー 8 DBテーブル 「servant」 dbObj = GetFromDB(“SELECT * FROM servant”); Servant[] data = dbObj.GetCount(); for( int i = 0 ; i < dbObj.GetCount();++i){ data[i] = new Servant(); dbObj.Read<Servant>( i , data[i] ); } // どこかでクラス定義している class Servant{ public int id; public string name; public int lv; }
  56. 56. これで、DBアクセスするときに面倒だった 問題も解消! 同様にクライアントから送られてきたJsonを読み込むときにも 利用して、オブジェクトを一発で取得することに成功
  57. 57. まとめ • PHPからJavaに乗り換え時に、静的解析ツールを導入したので コードの品質を保った状態で開発できた • Java(tomcat)環境下では、スレッドベースだったのを利用して static変数(global領域)にデータをキャッシュする事が出来た • PHPでは連想配列という便利な奴がいたので、DBへのデータ アクセスが楽だったが、JavaではReflectionを使って楽にした
  58. 58. おまけ…2タイトル目の話 プレイヤー APIサーバー 運営者 次のタイトルではWebView部分からも、PHPを抹消した。 WebViewからもJavascriptでAPIを叩いて、テンプレート適用を Javascript側ですることによって、Jsonを返すだけのAPIサーバーと 化したため WEBサーバー DBサーバー 運営向けサーバー
  59. 59. 変更前の流れ Webサーバー Webサーバー内で、プレイヤーに合わせた HTMLを生成(プレイヤー名埋め込み等)を して、そのHTMLを表示している
  60. 60. 変更後流れ Webサーバー Webサーバー まずは、ただのHTMLを表示 その後に、そのページ内に仕込んだ JavascriptでWebサーバーへJsonデータを 問合せ、受け取ったJsonデータで自身の HTMLを書き換える
  61. 61. また一つ、Java化してしまった… このJavascriptで取得する仕組みによって、サーバーサイドプログ ラマーから 「Webデザイナーから来たHTMLからテンプレート専 用のファイルを作る」という手間を一つ省くことが出来たのだが、 これはまた別の機会があれば…

×