ページ

2010年6月28日月曜日

JGroupsを利用したクラスタ関連 ~その四 マージの仕組み ~

パーティションの結合(MERGE2)
~ MERGEは、通信障害によって分裂した複数のマルチキャストグループを
1つに結合する仕組みです。
FDやFD_SOCKにより障害検知されたメンバーはグループから除外される事に
なりますが、再度、通信可能な状態となると、本処理により再びグループに
参加することが出来ます。
ここでは、コーディネーターAからみた視点で記述しています。

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRDFTtHsIRolLtxFuvQq67qs5r7IERnDpGmrj1-TQTMFEV4zhfdDUAKZd5-GStRQ5L1L7KUEBItxZ_XS3D1WEO3OPwYkbe0Z38Oosr6RlexPEH7QE1OSLtz92PI0d45c88DT0Hevguri8/s1600/jgroups_merge_image1.JPG

コーディネーターであるメンバーAが通信不能となった場合、
マルチキャストグループは2つのパーティション([A],[B,C])
に分割されます。

この場合、メンバーAとメンバーBがコーディネーターとなり、
各パーティションのコーディネーターとなります。

この状態において、再度、通信可能な状態になると、
2つに分裂していたパーティションが1つになる為、
1つのパーティション内に2つのコーディネーターが
存在する事になります。

コーディネーターは、定期的にGET_MBRS_REQを送信して、
現在のコーディネーターを調査し、コーディネーターが
自分だけであるという事を確認しています。

GET_MBRS_RSPメッセージによって、自分以外のコーディネーターが
他に存在していると知ると、2つのコーディネーターを結合する為の
マージ処理が行われます。

この場合、メンバーAとBの2つのコーディネーターが存在する事になり、
マージリーダー(※1)に対してもうひとりのコーディネーターが
結合される事になります。

----------------------------------------------------------------
※1.マージリーダーとは
2つのコーディネーターが存在していた場合、「IPアドレス:ポート」
を昇順にソートした時の先頭になるコーディネーターが、マージリーダーとなり、
マージリーダー以外のコーディネーターは、マージリーダーのメンバーとなります。
----------------------------------------------------------------

マージリーダー(コーディネーターA)は、コーディネーターBに対して
MERGE_REQメッセージを送信します。
MERGE_REQメッセージを受信したコーディネーターBは、
自分のもっているメンバーリストをMERGE_RSPメッセージとしてメンバーAに
返信します。

メンバーリストを受信したメンバーAは、自分のメンバーリストとメンバーCが
持っていたメンバーリストを結合し、最新のメンバーリストを作成後、
すべてのメンバーに対してVIEWを送信します。

JGroupsを利用したクラスタ関連 ~その参 障害検知の仕組み ~

障害検知(FD)
~ FD(FailureDetection)は、ハートビートメッセージを利用して、
障害が発生したメンバーを検出する為の仕組みです。
メンバーは、HEARTBEATを受信すると、HEARTBEAT ACKを返信する事で
これに応答し、正常に疎通出来ることを定期的に確認しています。
各メンバーは、A→B→C→Aのようにリング状に、メンバーリストにいる
自分の次のメンバーに対して監視するようになっています。
ここでは、メンバーAからの視点で記述しています。

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhRDFTtHsIRolLtxFuvQq67qs5r7IERnDpGmrj1-TQTMFEV4zhfdDUAKZd5-GStRQ5L1L7KUEBItxZ_XS3D1WEO3OPwYkbe0Z38Oosr6RlexPEH7QE1OSLtz92PI0d45c88DT0Hevguri8/s1600/jgroups_fd_image1.JPG

メンバーAは、メンバーBに対して定期的にHEARTBEATメッセージを送信し、
メンバーBがこれに応答する HEARTBEAT ACKメッセージを返信する事で、
通信状態を確認しています。
同様に、メンバーBはメンバーCの障害を監視しており、メンバーCは
メンバーAの障害を監視しています。

メンバーBが通信不能な状態になると、メンバーBに対してHEARTBEATを
送信していたメンバーAは、一定時間以上、メンバーBからの応答がない為、
メンバーAの障害を検出します。(FDによる障害検知)

但し、この時点では障害が発生しているかもしれない(容疑者)の状況であり、
他メンバーから再確認させるの為の検証フェーズが開始されます。

メンバーAは、メンバーBの障害を検出した事を他メンバーに知らせる為に、
SUSPECTメッセージをマルチキャストで送信します。

メンバーA、メンバーCは、SUSPECTメッセージを受信し、メンバーBに障害が
検出された事を知る為、検証フェーズ(※1)が開始されます。

----------------------------------------------------------------
※1.VERIFY_SUSPECT
障害を検出した場合、本当にそのメンバーが通信不可能である事を
再確認する為に、「ARE YOU DEAD」メッセージによる再検証が行われます。
また、この検証する仕組みの事は「VERIFY_SUSPECT」と呼ばれ、
SUSPECTメッセージを受信すると開始されます。
「ARE YOU DEAD」メッセージの送信に対する「I AM NOT DEAD」
メッセージの応答によって障害を判定しています。
----------------------------------------------------------------

メンバーB、メンバーCは、メンバーAに対して、「ARE YOU DEAD」メッセージ
を送信後、「I AM NOT DEAD」メッセージの応答を待ちます。
この応答が一定時間返ってこない場合、メンバーAは障害が発生したと
判定されます。

また、メンバーAは、これまで監視していたメンバーBがいなくなった為、
自分の次のメンバーにあたるメンバーCの障害監視を始める事になります。

このようにFDは、ハートビートを送信する事で、相手の通信状態を確認する
為の仕組みを担っています。

----------------------------------------------------------------
※ポイント
障害監視していた自分の隣のメンバーが除外された場合、次は、
その隣のメンバーに対して障害監視を始めることになります。
----------------------------------------------------------------

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjo5W-nifhDa4u4XVnBAGqhVh9vAel2CWAtb_HXdWVY5i2haCfbfejvFTx7wE8BxPiBKctTiqiIz6ftkmI8W4HrKdrc0IX4CexJ11xHCX_2o4cpBJ-WpdMxlWDavOGLw_9GtrhSxH4AcKE/s1600/jgroups_fd_heartbeat.JPG

障害検知(FD_SOCK)
~ FD_SOCKは、自分自身の障害を検知して、いつまでもメッセージを
送り続けないようにする仕組みです。
KEEP_ALIVE(トラフィックを2時間受信していないソケットにハートビート
を送信する)で、メンバーリストの隣のメンバーにソケット接続し、
LANを切断するなどしてコネクションが切断された場合に、障害を検出します。
また、FD_SOCKは、コネクションを閉じずにホスト (またはスイッチや
ルーター)がクラッシュすると、KEEP_ALIVEによるハートビートが送信される
2 時間後まで検出する事が出来ません。

各メンバーは、A→B→C→Aのようにリング状に、メンバーリストにいる自分
の次のメンバーに対して監視するようになっています。
ここでは、FDの処理と比較する為、メンバーBからの視点で記述しています。

https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjPwBu1zt9KFpTh_izEqHeaF9tuNxDdu1n5mWPTprxzrtxAFiFGhdMM0QIM4jDdpD8gKqvqIpupthgpetli-x-V9sultN6uxVyERfE1sRIpqRJN1U42YPlUMerXQw5JUKtdmMfPpDIuTy4/s1600/jgroups_fd_sock_image1.JPG

メンバーBは、自分の次のメンバーにあたるメンバーCに対して監視(ソケット接続)
しています。
(VIEWメッセージの受信によりメンバーリストが更新されると、
その度に接続しなおしています。)

メンバーBのLANが切断されると、そのタイミングでコネクションが閉じられ、
メンバーCの障害を検出します。
また、次のメンバーAに接続しようとしますが、接続できない為、
メンバーAの障害も検出します。

これにより、メンバーBは、メンバーAとメンバーCを自分のメンバーリストから除外して、
いつまでもメッセージを送信し続けないようにしています。

FDとFD_SOCKの関連性
FDは、他メンバーの障害を検出するのに対し、
FD_SOCKは、自分の障害はすぐに検出できますが、他メンバーの障害は、
KEEP_ALIVEが行われる最大2時間後まで検出する事が出来ません。
(上記の例の場合、メンバーBのLANを切断してもメンバーAはFD_SOCK
だけでは即座に検知する事が出来ない)
FDとFD_SOCKを組み合わせる事により、安定した障害検出を行うことが
出来るようになります。

----------------------------------------------------------------
※ FDとFD_SOCKの特性
FD
・過負荷の状態にあるマシンは、are-you-alive 応答の送信時に
パフォーマンスが低下する恐れがある。
・メンバーは、デバッガ/プロファイラでサスペンドしたときでも疑われてしまう。
・タイムアウトを小さくすると、間違った疑いの可能性が大きくなり、
ネットワークトラフィックが増大する。
・タイムアウトが大きいと、クラッシュされたメンバが一定の時間検出されず、
破棄されない。
FD_SOCK
・デバッガでサスペンドしてもTCP接続はまだオープンなので問題がない。
・同様の理由により、高負荷の状況でも問題とはならない。
・メンバーのTCP接続が中断された場合にのみ疑われ、
ハングしたメンバ検出されない。
・スイッチがクラッシュした場合は、TCPタイムアウトになるまで検出されない。
----------------------------------------------------------------

JGroupsを利用したクラスタ関連 ~その弐 参加・離脱の仕組み ~

参加(JOIN)
~ 参加(JOIN)とは、JGroupsリスナーの起動時に行われる
マルチキャストグループに加わる為の仕組みの事です。

・他メンバーが存在しない状態でリスナーを起動した場合



メンバーAは、グループ内に存在するコーディネーター(※1)を
見つける為に、GET_MBRS_REQメッセージをマルチキャストで送信します。
この場合、グループ内に他メンバーがいない為、GET_MBRS_REQメッセージ
に対する応答(GET_MBRS_RSPメッセージ)がありません。
メンバーAは、一定時間誰からも応答がない事を確認すると、
自分がグループ内の最初のメンバーであるとみなし、コーディネーターを
担当する事になります。

----------------------------------------------------------------
※1.コーディネーターとは
コーディネーターは、グループに存在するすべてのメンバー情報を、
グループ全体に通知して共有する役目をしています。
コーディネーターは、グループ内に1メンバーのみ存在しており、
新たにメンバーが参加したり、離脱すると、最新のメンバー情報を
全体に通知しています。
(このようにメンバーを管理する為の仕組みの事を、
「グループメンバーシップ(GMS)」と呼んでいます。)
また、コーディネーターは、後述するMERGE処理にて複数に分かれた
パーディションを結合する為に、定期的にGET_MBRS_REQメッセージ
を送信する事で、グループ内のコーディネーターが自分だけである事を
確認する役目もしています。
----------------------------------------------------------------

・既に1メンバー存在する状態でリスナーを起動した場合



グループ内にメンバーAのみが存在している状態で、メンバーBを起動すると、
メンバーBは、グループ内に存在するコーディネーターの存在を確認する為に、
GET_MBRS_REQメッセージをマルチキャストで送信します。
これを受信したメンバーAは、誰がコーディネーター(この場合はメンバーA)
なのかを知らせる為に、GET_MBRS_RSPメッセージを返信します。

その結果、メンバーBはグループ内のコーディネーターがメンバーAであると
知る為、メンバー管理をおこなっているコーディネーターに対して
グループへの参加依頼となるJOIN_REQメッセージを送信します。
コーディネーターは、JOIN_REQメッセージを受信すると、
最新のメンバーリストをJOIN_RSPメッセージとしてメンバーBに返信します。
これによって、メンバーBは、現在、グループ内に存在する全メンバーの情報
を知る事が出来ます。

----------------------------------------------------------------
※ポイント
他メンバーがGET_MBRS_REQメッセージを受信すると、
GET_MBRS_RSPメッセージとして自分やコーディネーターの情報を返信します。
これにより、グループ内に存在する現在のコーディネーターが誰なのか
を発見し、JOIN_REQメッセージを送信する事でグループに加わる事が出来ます。
また、このようにメンバーやコーディネーターの情報を取得する為の
仕組みを、「ディスカバリプロトコル」と呼ばれています。
----------------------------------------------------------------

----------------------------------------------------------------
ディスカバリプロトコルの種類
PING      ・・・ マルチキャストでGET_MBRS_REQメッセージを送信して、
コーディネーターが誰なのかを発見する。(デフォルト値)
TCPGOSSIP ・・・ コーディネーター役のメンバーを固定IPにする。
TCPPING   ・・・ 事前にすべてのメンバー情報を明示的にリスト化しておく。
MPING     ・・・ ソケットを開いてマルチキャストディスカバリ
メッセージを送受信する。
----------------------------------------------------------------

・既に2メンバーが連携されている状態でリスナーを起動した場合



メンバーA、メンバーBが既にマルチキャスト連携されている状態で
メンバーCを起動した場合、
リスナーを起動時に、GET_MBRS_REQメッセージをマルチキャストで送信します。
GET_MBRS_REQメッセージを受信したメンバーA、メンバーBは、
GET_MBRS_RSPメッセージを返信することで、コーディネーターがメンバーAで
ある事を知る事が出来ます。

メンバーCは、コーディネーターであるメンバーAに対して、
グループに参加する為にJOIN_REQメッセージを送信します。

コーディネーターは、JOIN_REQメッセージを受信後、
最新のメンバーリストをJOIN_RSPメッセージとしてメンバーCに返信します。

コーディネーターは、最新のメンバーリスト(VIEW)をマルチキャストで
送信する事で全メンバーに共有します。
これにより、すべてのメンバーは、[A,B,C]というリストを共有する事になります。

----------------------------------------------------------------
※ポイント
新たにメンバーが加わると、リストの最後尾に追加されます。
上記の場合、メンバーDが参加するとメンバーリストは、
[A,B,C,D]のようになります。
また、メンバーリストの先頭がコーディネーターを担当する事になっており、
メンバーAが離脱すると、[B,C,D]となる為、
メンバーBがコーディネーターとなります。
再度、メンバーAが参加しても、[B,C,D,A]となっている為、
コーディネーターは、メンバーBが継続する事となります。
----------------------------------------------------------------

離脱(REMOVE)
~ 離脱(REMOVE)とは、JGroupsリスナーの停止時にマルチキャストグループ
から抜ける為の仕組みの事です。

・コーディネーターが離脱した場合



離脱するメンバーは、コーディネーター(この場合は、メンバーA自身)に対して、
LEAVE_REQメッセージを送信します。

LEAVE_REQメッセージを受信したメンバーAは、LEAVE_RSPメッセージを
返信します。

コーディネーターは、離脱するメンバーを除いた最新のメンバーリスト
を作成し、マルチキャストでVIEWメッセージを全メンバーに送信します。

メンバーB、メンバーCは、VIEWメッセージを受信する事で最新のメンバー情報
を共有する事が出来ます。
また、この場合メンバーリストが、[B,C]になるので、メンバーBが
コーディネーターとなります。


・コーディネーターでないメンバーが離脱した場合



メンバーBは、コーディネーターであるメンバーAに対して、
LEAVE_REQメッセージを送信します。
これに対し、コーディネーターは、LEAVE_RSPメッセージを返信する
事でメンバーBの離脱が完了します。

また、コーディネーターであるメンバーAは、最新のメンバーリスト
を共有する為、マルチキャストでVIEWメッセージを送信します。

メンバーCは、VIEWメッセージを受信することで、今現在の最新メンバー
を共有する事が出来ます。

----------------------------------------------------------------
※ポイント
マルチキャストグループから離脱する際には、必ず、上記処理が必要になります。
その為、Jgroupsの終了処理を行わずにTomcatを停止しようとした場合、
Tomcatが停止しなくなるという問題が発生します。
(対応方法:ServletContextListenerのcontextDestroyedにて、
JGroupsの終了処理を実行させる。)
----------------------------------------------------------------

JGroupsを利用したクラスタ関連 ~その壱 JGroupsとは ~

JGroupsとは
JGroupsは、Javaで作成された複数のアプリケーション間において、
相互に通信させる為のネットワーク通信ライブラリです。
memcacheなどのキャッシュツールと組み合わせる事で、
クラスタ間でキャッシュのフラッシュをマルチキャスト連携させる事が
出来るようになります。

----------------------------------------------------------------
※ 通常、キャッシュは、各サーバー毎に保存されています。
その為、コンテンツが変更された際に、キャッシュが無効になるまでは、
古い内容のコンテンツが表示されてしまったり、各サーバー毎に
レスポンス内容が異なってしまうという問題が発生してしまいます。
そこで、JGroupsを利用して各サーバー間を連携させる事で、
この問題が解決できるようになります。
----------------------------------------------------------------

マルチキャスト通信とは
マルチキャスト通信は、特定のグループに対してのみデータを転送する
仕組みです。メッセージを受信する側がマルチキャストアドレス
(224.0.0.0~239.255.255.255)を設定することで、
マルチキャストアドレス宛に送信されたデータを、自分あてに
受信出来るようになります。




----------------------------------------------------------------
マルチキャストの利点
・パケットがマルチキャストグループあたりひとつで良いので
ネットワークが混雑しない。(ユニキャストの場合、受信者が増えると、
その数分だけパケットを送信するので、ネットワークが混雑する。)
・ネットワークインタフェースカードで所属していない
マルチキャストグループあてのパケットをフィルタできるので、
非受信者に負荷がかからない。(ブロードキャストだと、パケットは
ひとつで良いが、非受信者のコンピュータに対してもCPU負荷を
かけてしまう。)
----------------------------------------------------------------


JGroupsのパラメーターについて
JGroupsは、通信に利用する各プロトコルの設定値を変更する事が出来ます。
以下は、JGroupsで設定する主なパラメーターになります。

----------------------------------------------------------------
UDP
mcast_addr=231.12.21.132 ・・・ マルチキャストアドレスの指定
mcast_port=45566 ・・・ マルチキャストポートの指定
ip_ttl=4 ・・・ TTLの指定
mcast_send_buf_size=100000 ・・・ 送信するマルチキャストのバッファサイズ
mcast_recv_buf_size=500000 ・・・ 受信するマルチキャストのバッファサイズ
ucast_send_buf_size=100000 ・・・ 送信するユニキャストのバッファサイズ
ucast_recv_buf_size=64000 ・・・ 受信するユニキャストのバッファサイズ
PING
timeout=3000 ・・・ INITIAL_MEMBERSを見つけるために送信されるFIND_INITIAL_MBRSのタイムアウト合計(ミリ秒)。
num_initial_members=2 ・・・ GET_MBR_REQを送信してからtimeoutが経過するか、ここで指定した数のGET_MBR_RSPを受け取るまで待機する。
MERGE2
min_interval=5000 ・・・ FIND_INITIAL_MBRS(GET_MBR_REQを送信してGET_MBR_RSPを取得する処理)を送信する間隔の下限値を設定する。
max_interval=20000 ・・・ FIND_INITIAL_MBRS(GET_MBR_REQを送信してGET_MBR_RSPを取得する処理)を送信する間隔の上限値を設定する。
FD
shun=true ・・・ SUSPECTして除外したメンバーからHEARTBEATが送られてきた場合に、NOT MEMBERを送信して再JOINさせる。
timeout=5000 ・・・ HEARTBEATを送信する間隔。次のHEARTBEATを送信するまでに応答が返ってこない場合はタイムアウト
max_tries=3 ・・・ 指定した回数以上のタイムアウトが発生した場合にFD検知する。
FD_SOCK
VERIFY_SUSPECT
timeout=2000 ・・・ ARE YOU DEADメッセージを送信してから I AM NOT DEADを受け取るまでのタイムアウト
pbcast.NAKACK
retransmit_timeout=600,1200,2400,4800 ・・・ 再送要求までのタイムアウト時間。0.6秒→1.2秒→2.4秒→4.8秒の順に隔を空けて応答を待つ。
gc_lag=20 ・・・ GCラグ(ミリ秒)を設定する。
UNICAST
timeout=400,800,1600,3200 ・・・ ACKメッセージを受信するまでの最大待ち時間(ミリ秒)
pbcast.STABLE
desired_avg_gossip=20000 ・・・ STABLEメッセージを送信する平均時間
FRAG
frag_size=8192 ・・・ このサイズよりも大きなメッセージは、小さく断片化してから送信する。
STATS
pbcast.GMS
join_timeout=5000 ・・・ JOIN_REQを送信してからJOIN_RSPを取得するまでのタイムアウト(ミリ秒)を設定する。
leave_timeout=5000 ・・・ LEAVE_REQを送信してからLEAVE_RSPを取得するまでのタイムアウト(ミリ秒)を設定する。
merge_timeout=10000 ・・・ MERGE_REQを送信してからMERGE_RSPを取得するまでのタイムアウト(ミリ秒)を設定する。
shun=false ・・・ 受信したVIEWに自分が含まれていない場合は、自分をSHUNするか否か
print_local_addr=true ・・・ リスナー時起動時にローカルアドレスをコンソール表示するか否か
----------------------------------------------------------------

ルーティングとバインドアドレスについて注意点
~以下のように複数のネットワークインターフェースが存在する構成となっている場合、
先頭のインターフェースが優先してマルチキャスト通信に利用されます。
以下のように設定する事で、指定したインターフェースを経由して
マルチキャスト通信が利用できるようになります。



----------------------------------------------------------------
# route add -net 231.12.21.0 netmask 255.255.255.0 <インターフェース>
----------------------------------------------------------------

また、IPv6 を利用可能なOSでは、基本となるネイティブソケットが IPv6 になる為、
Javaでは、IPv4よりもIPv6を優先して利用するようになっています。
IPV4を優先するように設定する為には、以下のVMオプションを指定します。

-Djava.net.preferIPv4Stack=true

また、JGroupsは、リスナー起動時に自分のIPアドレスをOSから取得します。
その際も先頭のインターフェースに割り当てられたIPアドレスを利用してしまう為、
マルチキャスト通信で利用するインターフェースに割り当てられたIPアドレスを
バインドアドレスとして指定する為に、以下のVMオプションを指定して下さい。

-Djgroups.bind_addr=

JGroupsの仕組み
~JGroupsは、多くの処理(プロトコル)から構成されており、
それぞれの仕組みが互いに組み合わされる事で1つの機能を担っています。
JGroupsの特徴を理解しておく事は、トラブル発生時に問題解決に役立つ為、
非常に重要です。
次回からは、JGroupsで主要となる以下の機能の特徴について記述しています。

・参加/離脱(JOIN/REMOVE)
・障害検知(FD、FD_SOCK)
・パーティションの結合(MERGE)

2010年6月16日水曜日

JAVAヒープサイズ・GCチューニングのまとめ

■ 前提条件
-----------------------------------------------
JVMは、Sun Java (JDK 1.5-1.6)を想定。


■ 目標
-----------------------------------------------
・マイナーGC、フル GCがそれぞれ頻発しないこと。

・フル GCの実行時間が1秒未満であること。

・マイナーGCの実行時間が0.1秒未満であること。

・連続した負荷状態(想定されるピークアクセス)でもOutOfMemoryErrorが発生しないこと。

・理想的な状態は、上記に加えて、フル GCの発生が低頻度であること。

具体的には、できるだけマイナーGCで短命オブジェクト(1回使ったらもう使わないようなオブジェクト。逆にセッションオブジェクト等は長命オブジェクトとなる)を破棄させて、短命オブジェクトが、TenuringThresholdを超えるマイナーGCを経てNew領域からOld領域へ移動することをできるだけ抑えることがチューニング目標となる。

※Old領域に行ってしまうとフル GCでしか掃除されないため、できるだけOld領域に移る前にNew領域で掃除させる。

※実際のTenuringThresholdが、-XX:MaxTenuringThresholdで指定した値にできるだけ近い状態を維持する。


■ 運用環境で是非設定しておくべきJava起動オプション
-----------------------------------------------
-server ・・・ サーバーモードを有効化
-Xms ・・・ Javaヒープ初期サイズ。一般的に、-Xmxと同値にする
-Xmx ・・・ Javaヒープ最大サイズ
-Xmn(-XX:NewSize) ・・・ New領域初期サイズ。一般的に、-XX:MaxNewSizeと同値にする
-XX:MaxNewSize ・・・ New領域最大サイズ
-XX:PermSize ・・・ Permanent領域初期サイズ。一般的に、-XX:MaxPermSizeと同値にする
-XX:MaxPermSize ・・・ Permanent領域最大サイズ
-verbose:gc(-Xloggc:path_to_file) ・・・ GCログ出力を有効化
-XX:+PrintGCTimeStamps ・・・ GCログにタイムスタンプ(Java起動時からの経過時間)を出力
-XX:+PrintGCDetails ・・・ GCログを詳細に出力(New領域とJavaヒープそれぞれが
どれだけ減ったか出力される)
-XX:+PrintClassHistogram*1 ・・・ SIGQUITシグナル受信時にヒープ統計情報を出力(出力時に強制的にフル GCが発生)。-verbose:gc(-Xloggc:path_to_file)と併用必須。
なお、SIGQUITシグナルを送信するには、kill -3 を実行すればよい
-XX:+HeapDumpOnOutOfMemoryError 
・・・ OutOfMemoryError発生時にヒープダンプを出力


■ 運用環境で設定しておいた方がいいJava起動オプション
-----------------------------------------------
-XX:SurvivorRatio ・・・ Eden領域のサイズをS0またはS1領域のサイズで割った値(S0とS1領域は同じサイズ)


-XX:MaxTenuringThreshold 
・・・ New領域において、オブジェクトがマイナーGCを何回超えて生き残ると、Old領域に移動するかのしきい値の最大値(実際のしきい値は、1からMaxTenuringThresholdの範囲で動的に決定される)

-XX:TargetSurvivorRatio ・・・ Survivor領域がいっぱいと判断される使用率


■ チューニング(トラブル対応)のためのコマンド、ツール
-----------------------------------------------
1.GC発生状況、メモリリーク状況

 -verbose:gc(-Xloggc:path_to_file)
 -XX:+PrintGCTimeStamps
 -XX:+PrintGCDetails

 を設定しておくことでGCログが取得できる。取得したGCログは、侍やGCViewer等でグラフ化できる。


2.ヒープ使用状況

 jstatコマンドでリアルタイムに確認可能。
 (-gcutilオプションで、S0、S1、Eden、Old、Permanent領域ごとの使用率やマイナーGC、フル GCの発生回数、GCの実行時間累計が確認可能)

 jstat -gcutil -h10 5000

 jconsoleコマンドを使用すれば、GUIでリアルタイムに確認することも可能。


3.ヒープ統計情報

 以下の方法で出力できる。

 -verbose:gc(-Xloggc:path_to_file)、-XX:+PrintClassHistogramを設定しておき、kill -3 を実行
 jmap -histo

 ヒープ中のオブジェクトのインスタンス数、サイズ一覧が出力される。
 ヒープダンプの出力に比べて、取得時の負荷は小さいが、分析は大変。


4.ヒープダンプ

 以下の方法で出力できる。

 -XX:+HeapDumpOnOutOfMemoryErrorを設定しておき、OutOfMemoryErrorが発生
 -XX:+HeapDumpOnCtrlBreak*2を設定しておき、kill -3 を実行
 jmap -heap:format=b (Java 1.5の場合)
 jmap -dump:format=b,file=filename (Java 1.6の場合)

 出力に時間もかかる(ヒープサイズ512MBで5-10分程度)し、負荷も大きいが、詳細なヒープ情報を取得可能。ダンプファイルはバイナリなので、以下のツールで分析する。

 ・Eclipse Memory Analyzer
 ・HeapAnalyzer
 ・HAT


5.プロファイラ

 オブジェクトの生成、GC等、JVMの挙動をトレースする。アプリケーションを動作させつつ、増加していくオブジェクトを探したり、スタックトレースを確認したり等の調査が可能。
 パフォーマンスが悪化するため、運用環境では通常使用できない。
 メモリリークやメモリの大量消費の調査の場合、まず問題の再現方法を突き止めた上で、開発/検証環境でプロファイラを使用して、再現方法を実施して、増加したオブジェクトを解析する。

 ・NetBeans Profiler


6.スレッドダンプ

 ヒープ、GCチューニングの観点ではないが、アプリケーションの応答がとまってしまったらとりあえず取得するのがよい。kill -3 で出力される。
 スレッドのデッドロックが発生していないか、大量の待ち行列が発生していないか等がわかる。


■ ケース別チューニング(トラブル対応)方法
-----------------------------------------------
1.OutOfMemoryError: PermGen space と出る場合

 Permanent領域不足によるOutOfMemoryErrorが発生している。

 対応としては、-XX:PermSize、-XX:MaxPermSizeを指定し、OutOfMemoryErrorが発生しないようにPermanent領域を広げてやればよい。
 ただし、基本的にPermanent領域は、クラス情報やメソッド情報が格納される領域であり、アプリケーション起動後はほとんど増減しない。
 jstatやjconsole等でPermanent領域の使用状況を確認し、もし増加が続くようであれば、Permanent領域のメモリリークが発生している可能性がある。

 リーク箇所を発見するには、ヒープ統計情報やヒープダンプを数回取得して比較したり、アプリケーションログ等からOutOfMemoryError発生直前に実行されている処理がいつも同じでないか等を確認する。

 例えば、DIコンテナは通常1アプリケーションで1つしか使用しないが、とある処理で新規にコンテナを生成するようになっていて、その処理が実行されるたびに大量のPermanent領域を消費し、ついにはOutOfMemoryErrorが発生した事例がある。

2.OutOfMemoryError: Java Heap Space と出る場合(OutOfMemoryErrorとしか出ないこともある)

 ヒープ不足によるOutOfMemoryErrorが発生している。

 とりあえずの対応としては、-Xms、-Xmxを指定し、OutOfMemoryErrorが発生しないようにヒープを広げてやればよい。

 根本対応としてはまずは、GCログを取得し、ヒープでのメモリリークが発生していないか確認する必要がある。
 GCログを侍やGCViewer、Excel等でグラフ化して確認する。
 グラフがフル GCを跨って、右肩上がりになっている(右肩上がりになってフル GCでいったん下がるが、スタート時ほど下がっていない。複数回のフル GC発生時の下限値を結ぶ線が右肩上がりになっている)場合は、メモリリークが発生している可能性が高い。
 リーク箇所を発見するには、ヒープ統計情報やヒープダンプを数回取得して比較して、フル GCを経ても増加し続けているオブジェクトを探す。
 怪しいオブジェクトが見つかれば、NetBeans Profiler等を使用して、オブジェクトの使用箇所を探し、オブジェクト参照を追跡する。

3.ヒープチューニング方法

 運用環境で、jstat -gcutilやjconsoleを使用してヒープの各領域の使用状況を確認しながら、フル GC実行時間を最小にするために、OutOfMemoryErrorが発生しないサイズでなるべく小さいサイズを-Xms、-Xmxに指定し、また、フル GC発生頻度を抑えるために、

 -Xmn(-XX:NewSize)
 -XX:MaxNewSize
 -XX:SurvivorRatio
 -XX:MaxTenuringThreshold
 -XX:TargetSurvivorRatio

 を調整して、なるべくNew領域でのマイナーGCでメモリを解放させて、Old領域へ移動される頻度、サイズを減らすようにする。

 マイナーGCが発生していないのに、いきなりOld領域の使用率が上がった場合や、S0、S1、Eden領域がいきなり100%になった場合は、オブジェクトのサイズがS0、S1、Eden領域サイズよりも大きい。
 アクセスログと突き合わせて、使用率のあがるURLを特定し、コードを見たり、URLアクセス前後のヒープ統計情報、ヒープダンプを比較することで、サイズの大きいオブジェクトを特定する。
 もし、そのオブジェクトが短命オブジェクトの場合は、-Xmn(-XX:NewSize)、-XX:MaxNewSizeを増加させて、Old領域に行かないように調整する。
 とりあえずは、以下のような値からはじめて調整していく。

 -Xmn(-XX:NewSize)
 -XX:MaxNewSize
 -Xmxサイズの1/4 - 1/3程度
 -XX:SurvivorRatio 2 - 8程度
 -XX:MaxTenuringThreshold 32程度
 -XX:TargetSurvivorRatio 80 - 90程度

4.マイナーGC実行時間が1秒を超える場合

 New領域が大きすぎる可能性があるので、-Xmn(-XX:NewSize)、-XX:MaxNewSizeを減らす。

 CPUが複数ある(マルチコアを含む)場合は、-XX:+UseParallelGC オプションでパラレルGCを有効にする。
 -XX:+UseParallelGC ・・・ マイナーGCをマルチスレッドで実行

5.フル GC実行時間が1秒を超える場合

 まずは、メモリリークが発生していないか確認する。
 確認方法は、2.OutOfMemoryError: Java Heap Space と出る場合 を参照。

 メモリリークが発生していない場合は、Old領域が大きすぎる可能性があるので、-Xms、-Xmxを減らす。減らしすぎて、OutOfMemoryErrorを発生させないように注意する。やり方は、3.ヒープチューニング方法 を参照。

 メモリリークが発生しておらず、これ以上減らすとOutOfMemoryErrorが発生するOld領域サイズなのに、フル GC実行時間が1秒を超える場合は、以下の対応案が考えられる。

 (1)アプリケーションでメモリを使いすぎていないか確認し、使いすぎている場合は修正する

  メモリを多く消費する処理を特定するには、運用環境で、jstat -gcutilやjconsoleを使用してヒープの各領域の使用状況を監視し、使用率がドカッとあがったときに実行された処理を、アクセスログから突き止める。(ある程度絞れれば、あとは開発/検証環境で突き止める)あとは、コード解析を行い、修正する。

 (2)セッションタイムアウト時間の確認

  セッションタイムアウト時間が不必要に長すぎないか確認し、長い場合は短くする。

 (3)コンカレントGCの使用

  フル GCをできるだけアプリケーションをとめずに並行実行するコンカレントGCを使用する。以下のJava起動オプションを設定する。

  -XX:+UseConcMarkSweepGC ・・・ コンカレントGCの有効化
  -XX:+CMSParallelRemarkEnabled 
・・・ フル GCのRemarkフェイズをマルチスレッドで実行
  -XX:+UseParNewGC ・・・ マイナーGCをマルチスレッドで実行

6.アプリケーションの応答が停止(フリーズ)した場合

 スレッドダンプを取得する。

7.StackOverflowError と出る場合

 StackTraceを確認し、メソッドの不要な再帰呼び出し等が行われていないか確認する。あるいは、以下のJava起動オプションを設定し、スタックサイズを増やしてみる。

 ・HotSpot VMの場合
  -Xss 
(Java/ネイティブ)スレッドのスタックサイズ
  -Xoss 効果なし

 ・非HotSpot VMの場合
  -Xss ネイティブスレッドのスタックサイズ
  -Xoss Javaスレッドのスタックサイズ


2010年6月14日月曜日

Apache RewriteRule設定 サンプル

Apache RewriteRule設定 サンプル


■/hoge/ を /fuga/ に rewrite(リダイレクト)する。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/$ /fuga/
-------------------------------------------------------


■/hoge/ 以下を /fuga/ 以下にまとめて rewrite(リダイレクト)する。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/(.*)$ /fuga/$1
-------------------------------------------------------


■/hoge/ 以下で末尾が .jpg のリクエストのみを /fuga/ 以下に rewrite(リダイレクト)する。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/(.*\.jpg)$ /fuga/$1
-------------------------------------------------------


■/hoge/ 以下で末尾が .jpg か .gif のリクエストのみを /fuga/ 以下に rewrite(リダイレクト)する。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/(.*)\.(jpg|gif)$ /fuga/$1.$2
-------------------------------------------------------


■/cgi-bin/hoge/fuga を /cgi-bin/example.cgi?q=hoge&opt=fuga に rewrite(リダイレクト)(いわゆる、動的アドレスを静的アドレスに変換するってやつ)
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/cgi-bin/([0-9A-Za-z]+)/([0-9A-Za-z]+)$ /cgi-bin/example.cgi?q=$1&opt=$2
-------------------------------------------------------

■リダイレクト時のブラウザのURL欄
mod_rewrite で rewrite(リダイレクト)処理を行ったとき、以下のようにサーバパスで rewrite(リダイレクト)させると、ブラウザのURL欄は書き換わらない。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/$ /fuga/
-------------------------------------------------------


例えば、
http://www.ise-web.com/hoge/ にアクセスすると、
http://www.ise-web.com/fuga/ の中身が、
URL欄は http://www.ise-web.com/hoge/ のまま表示される。


しかし、以下の例では、リダイレクトと同時にURL欄が書き換わる。
-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/$ /fuga/ [R=301]
-------------------------------------------------------

-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/$ /fuga/ [R=302]
-------------------------------------------------------

-------------------------------------------------------
RewriteEngine on
RewriteRule ^/hoge/$ http://www.ise-web.com/fuga/
-------------------------------------------------------

最後の URL にリダイレクトさせるパターンは、同じサーバ内の URL であっても、飛び先を URL で記述するとブラウザのURL欄が書き換わります。
同一サーバ内であれば、URLにリダイレクトさせると無駄にログも増えるのであまりオススメしません。
■%2F問題

Apache1.X 系で mod_rewrite を使う場合、URLに「%2F」が含まれると思い通りに動作しない問題があります。
(Apache2.X 系でも同様ですが、Apache2.0.46 以降では「AllowEncodedSlashes On」により回避できます。)


例えば、以下のような書き換えを記述したとします。
RewriteEngine on
RewriteRule ^/keyword/(.*)$ /cgi-bin/script.cgi?k=$1

想定としては、
http://www.ise-web.com/keyword/hogefuga というアクセスに対して
http://www.ise-web.com/cgi-bin/script.cgi?k=hogefuga の結果を返します。


hogefuga の部分が色々と変化するわけです。


この際、
http://www.ise-web.com/keyword/hogefuga/hage は
http://www.ise-web.com/cgi-bin/script.cgi?k=hogefuga/hage となりますが、


http://www.ise-web.com/keyword/hogefuga%2Fhage は
http://www.ise-web.com/cgi-bin/script.cgi?k=hogefuga%2Fhage とならず、404エラーになります。


直接、
http://www.ise-web.com/cgi-bin/script.cgi?k=hogefuga%2Fhage にアクセスするとこの問題は起きません。


先にも書きましたが、Apache2.0.46 以降では httpd.conf に「AllowEncodedSlashes On」を記述することにより回避できます。
しかし、Apache1.X の環境ではなかなか回避できずにはまる要素だと思います。
■アクセスを拒否する
どこかにリダイレクトするのではなく、特定のアクセスにエラーを返せます。


■.htaccess へのアクセスを拒否する。
-------------------------------------------------------
RewriteEngine On
RewriteRule \.htaccess - [F]
-------------------------------------------------------


■/hoge/ 以下へのアクセスに 403 Forbidden を返します。
-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hoge/.* [F]
-------------------------------------------------------


■/hage/ 以下へのアクセスに 410 Gone を返します。
-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hage/.* [G]
-------------------------------------------------------

■複数の RewriteRule
RewriteRule は複数かけます。


■/hoge/ 以下を /fuga/ 以下にリダイレクとした上に /fuga/hage/ 以下のものは /foo/ 以下にリダイレクトする。
-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hoge/(.*) /fuga/$1
RewriteRule ^/fuga/hage/(.*) /foo/$1
-------------------------------------------------------


上の例で、/hoge/ 以下を /fuga/ 以下にリダイレクとさせた時点で処理を終わらせる、つまり次のリダイレクトを実行させないようにするには [L] を付加します。


-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hoge/(.*) /fuga/$1 [L]
RewriteRule ^/fuga/hage/(.*) /foo/$1
-------------------------------------------------------

■RewriteRule のオプション
これ以外にもいろいろありますが。


■HTTPステータスコードを吐く [R=ステータスコード]
-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hoge/(.*) /fuga/$1 [L,R=301]
RewriteRule ^/fuga/hage/(.*) /foo/$1
-------------------------------------------------------


■大文字、小文字を区別しない [NC]
-------------------------------------------------------
RewriteEngine On
RewriteRule ^/hoge/(.*) /fuga/$1 [NC,L]
RewriteRule ^/fuga/hage/(.*) /foo/$1
-------------------------------------------------------

■ある条件が揃ったらリダイレクト
RewriteCond を使えば、ある条件に合致したときだけリダイレクトするということも、もちろん可能です。


■HTTP_HOST が www.ise-web.com だったら RewriteRule を適用する。
-------------------------------------------------------
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.ise-web.com$
RewriteRule ^/(.*) /$1
-------------------------------------------------------


■HTTP_HOST が www.ise-web.com じゃなかったら RewriteRule を適用する。
-------------------------------------------------------
RewriteEngine On
RewriteCond %{HTTP_HOST} !^www.ise-web.com$
RewriteRule ^/(.*) /$1
-------------------------------------------------------


■HTTP_HOST が www.ise-web.com で HTTP_USER_AGENT に MSIE が含まれていたら RewriteRule を適用する。
-------------------------------------------------------
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.ise-web.com$
RewriteCond %{HTTP_USER_AGENT} MSIE
RewriteRule ^/(.*) /$1
-------------------------------------------------------


RewriteCond を複数並べると AND でつながっていく。OR にしたい場合は [OR] をつける。


■HTTP_HOST が www.ise-web.com であるか、または HTTP_USER_AGENT に MSIE が含まれていたら RewriteRule を適用する。
-------------------------------------------------------
RewriteEngine On
RewriteCond %{HTTP_HOST} ^www.ise-web.com$ [OR]
RewriteCond %{HTTP_USER_AGENT} MSIE
RewriteRule ^/(.*) /$1
-------------------------------------------------------


RewriteCond の条件で大文字、小文字を区別しない場合は [NC] をつける。


■HTTP_USER_AGENT に MSIE や msie や MsIe や MSiE などが含まれていたら RewriteRule を適用する。
-------------------------------------------------------
RewriteEngine On
RewriteCond %{HTTP_USER_AGENT} MSIE [NC]
RewriteRule ^/(.*) /$1
-------------------------------------------------------


RewriteCond で対象となる変数には以下のようなものがあります。


HTTP_USER_AGENT / HTTP_REFERER / HTTP_COOKIE / HTTP_FORWARDED / HTTP_HOST / HTTP_PROXY_CONNECTION / HTTP_ACCEPT
REMOTE_ADDR / REMOTE_HOST / REMOTE_USER / REMOTE_IDENT / REQUEST_METHOD / SCRIPT_FILENAME / PATH_INFO / QUERY_STRING / AUTH_TYPE
DOCUMENT_ROOT / SERVER_ADMIN / SERVER_NAME / SERVER_ADDR / SERVER_PORT / SERVER_PROTOCOL / SERVER_SOFTWARE
TIME_YEAR / TIME_MON / TIME_DAY / TIME_HOUR / TIME_MIN / TIME_SEC / TIME_WDAY / TIME
API_VERSION / THE_REQUEST / REQUEST_URI / REQUEST_FILENAME / IS_SUBREQ


以上、かんたんな mod_rewrite サンプル集でした。