05.設計3: 信頼性設計
概要
信頼性設計って分かるでしょうか?
アプリの信頼性を上げるための設計ですが、主に以下のようなことを考えるようです。
・ログ設計 (監査ログ、変更ログ、エラー内容出力など)
・DB安全性 (デッドロック対策、排他ロックなど)
・大量アクセス時対策 (ロードバランスなど)
・サーバが落ちた時の対策
人によっても信頼性設計という定義は違うかと思いますが、いづれにしても上記のようなことは
設計しておかないといけないと思います。
ログ設計
ログにはどんなものを出力すればよいでしょうか?
それをここで考えてみましょう。
そのためには、なぜログを出力するのか?を考えるべきです。
そう考えてみると以下のような考えが浮かびます。
・監視部門にシステムに異常があることを伝えるため(ログ監視などをしているツールなどに異常を通知)
・故障があったときの原因調査
・監査ログ。情報流出などがあったときに、各ユーザが閲覧した情報を追跡する
目的に合った内容を出力しないと意味がありませんので、目的をはっきりさせて設計・実装しましょう。
故障解析のためのログなのに、「エラー発生」としか出力されなければ何も解析できません。
【一般的なログレベルの条件】
以下を参照いただければと思います。
自作でログを出力するときのレベルの参考になるかと思います。
参考: http://www.atmarkit.co.jp/ait/articles/0705/24/news133_2.html
ログ出力の設計例
【ログ監視】
まずは、監視について考えてみましょう。
ログを監視し、異常メールを出すツールがありますが、どのように異常を判断するんでしょうか?
たいていは以下のような感じだと思います。
・ログイン画面などを一定間隔でアクセスし、ステータスが200以外にならないか?
・Apacheのログを監視し、ステータス500系が出力されていないか?
・アプリのログを監視し、ログレベルがERROR以上のログが出力されていないか?
そうするとHTTPステータスの設計と、ログレベルの設計が重要だと分かります。
そこで、ログ監視については、異常時はステータス500を、ログレベルはERRORを出力する方針に
するのがよさそうです。
もし、ログインID/PWをミスした場合にもERRORやステータス500を返してしまうと、
誰かがログインミスするたびに、監視部門が大騒ぎしてしまうのでこの設計は重要です。
【監査ログ】
監査ログとは何でしょうか?
最近はどの企業でも誰がどんな情報を閲覧・編集したかを追跡できることを求められます。
それは個人情報保護の関連で、不正アクセスしたユーザがどんな情報を盗んでいったか?
などを追跡できないといけないからです。
どの情報を閲覧したか分かれば、あとで売られた情報と見比べて、一致すればその人が犯人だという
証拠のひとつになりますし、逆に他の会社のシステムから盗まれたかもしれない、などが分かるかも
知れません。
重要性が少し分かっていただけたでしょうか?
さて、もうひとつ設計上で重要なのは、どこまで情報をログに出力するか?です。
閲覧したすべての情報を出力するのは難しいです。
このサンプルでは、閲覧した画面のパスと、ログインユーザと、検索したキー文字列、
変更がある場合は更新の内容を出力する方針にします。
それぞれ、この情報を選択した理由を記述してみます。
・画面のパス ・・・URLが分かることでどの画面(情報)を閲覧したかが特定できます。
・ログインユーザ ・・・情報を閲覧した人が特定できます。
・検索したキー ・・・検索したキーが分かれば結果はおおよそ特定できます。
・更新ログ ・・・不正なユーザが変更した内容をログ出力。最悪元の状態に戻せるかもしれない。
また、ログレベルはINFOとし、後でログ出力をOFFにもできるように、logbackのマーカーの機能を使用します。
【故障ログ】
例外をトラップした個所で、ERRORレベルでログ出力する方針にします。
ソースの「AppHandlerExceptionResolver」クラスを参照してみてください。
ログ出力設計のまとめ
【補足】
上記の「アクセス認可エラー(Spring)」のように、フレームワークで出力しているログなのに、
ログレベルを変更しようとしている個所があります。
ふつう、こんなことはできません。
これをどうやって実装しようとしているかというと、logbackのフィルタを自作して解決します。
ソースの「com.sampletool.common.log.MapFilter」と設定ファイル「logback-ConsoleConf.xml」を
見てみてもらえればと思います。
お客様の中にはログレベルを変更してほしいといった要望をしてくる場合もあります。
(フレームワークの仕様なので変更は難しいと、なるべく断った方が良いとは思いますが)
大量アクセスやサーバダウン時のための設計
大量アクセスやサーバダウンに対する一般的対策
このことは、実戦として考えなければならないことです。
ここはお客様の方針を良くヒアリングしなければなりません。
この方針が設計を大きく変える可能性があるからです。
⇒基本的には、複数の系を立ち上げるのが通常です。
「じゃあ、tomcat用とApache用のサーバを複数買って、立ち上げればいいじゃない!それだけでしょ?」
って思うかもしれません。
しかし、複数のサーバを立ち上げて連携するのは意外と難しく、最初からアプリの設計に組み込んでいなければ
対応できるものではありません。
簡単にどのような構成になるのか以下で見てみましょう。
【考慮しなければならない点】
WEBアプリは、セッションIDとセッションに保管した情報が存在しなければ正しく動作しません。
複数のサーバを立ち上げると言うことは、複数のサーバ間でセッションIDとセッション情報を
共有させなければなりません。
追記2016.02.03:
セッションの共有する方法にはSpring Sessionを使う方法があり、まだ調べきれてないですがかなり有用そうです。
一度ご検討されてはどうでしょうか?
複数サーバ間を連携する一般的なパターン
同じセッションIDは同じ系のtomcatで処理させるパターン
上図のように、負荷分散することで異常終了する確率を低くします。
また、1系が落ちても2系は生きているので、2系に流されていたユーザは救えます。
ただ、1系は救えない弱点はありますが、このレベルで良いと言うお客様もいます。
そして、WEBアプリを作る上で系を意識することなく設計・作成ができるので開発費が安くなります。
また、これに加えてtomcat1系と2系の同期を取るという方法もあります(tomcatのdistributableなどの機能を使用)。
そうすることで、1系が落ちた場合でも、1系に流れていたセッションを救うことができます。
さらに、通常は以下のパターンのようにセッションの同期のタイミングを考慮してプログラムを設計しないと
いけませんが、このやり方ですとプログラム設計上の考慮点がかなり減ると思います。
すべての系にセッション情報をコピーしながら運用するパターン
こちらの方法では、常にセッションの情報を同期しているのでどちらの系に流されても問題なく動作します。
具体的には同期をとるには、tomcatのdistributableの機能を使用します。
この機能自体はそれほど理解が難しくないのですが、別の問題が出ます。
セッションに保存する容量が大きくなると同期に負荷や時間がかかります。
同期がとれる前に同じユーザ(セッションID)からアクセスされた場合など
さまざまなシチュエーションで問題ないことを検討しなければならず、その設計を考えるだけで膨大になります。
回避方法としては、セッションには最低限の情報のみ保存し、できれば1度保存したらセッション削除するまで
変更しないとかですかね。
いずれにしてもこちらの方法は検討に時間がかかるため開発費は上がりそうです。
このパターンではもう1つアイデアがあります。
ロードバランサに、いつでも1系に流させる方法です。
そして、1系に障害があって落ちてしまったときは、2系に流すようにします。
つまり2系はずっと待機していて、困ったときに使用されると言うアイデアです。
この場合も、1系と2系のセッションはdistributableなどで同期をとっておく必要がありますが
上記のアイデアのように検討することはそれほど多くなく、開発費は下がりそうです。
ただ、負荷分散のためにサーバの数を2つ以上に増やしたいなどの要望には応えられません。
このアイデアが、どちらかの系しかアクティブになっていないことを前提としているから、当然ですね。
おまけ: tomcat間のセッションの同期をとる、他のアイデア
上記で、tomcatのdistributableでセッション情報の同期をとる方法を紹介しました。
他にもないかとGoogleなどで検索してみて、ヒントになりそうな情報をメモしておこうと思います。
【保存先をDBに変更する方法】
最初に言っておくと、この方法は現時点では実践では使えなさそうです。
1系、2系ともに、DBにセッション情報を保管するアイデアです。
実はtomcatには、セッションの保存先をメモリではなく、DBに変更する機能があります。
この方法ではDBを介して1系、2系のセッション情報を共有できるのでうまく行きそうです。
しかし、私がtomcat7で試したところでは、DBに保存されるのはリアルタイムではありませんでした。
リクエスト処理が終わってから何十秒か経過してからDBに書き込まれるため、
DB書き込み前に別の系にリクエストがあると、正しく同期しなかったのです。
ですので、この方法は現状ではあきらめた方がよさそうです。
【GemFireを使用する方法】
上記と似ているのですが、GemFireというインメモリ上にセッションを保持すると言う方法もあるようです。
GemFireには複製機能(レプリケーション)があるので、どちらかの系がGemFireにセッション情報を保存すれば
自動的に同期がとられるはずです。
このあたりしっかり調べていないので、ご興味があれば1つの選択肢として調べてみると良いかも知れません。
参考: http://static.springsource.com/projects/tc-server/2.5/admin/html/cluster.html
このサンプルでの設計方針
いろいろ書いてきましたが、このサンプルでは初級ということで簡単な1番目のパターンを方針にします。
ですので、2番目のパターンを検討される方はサンプルを見るときにご注意願います。
とはいえ、気を付けるのはセッションの操作なのでそれ以外はそれほど変わらないと思います。
実際には、お客様にそれぞれのメリット・デメリットを説明して、お客様の運用チームとも合意をとって
方針決定した方が良いと思います。
Created Date: 2015/03/28