「Google App Engine」の編集履歴(バックアップ)一覧に戻る
Google App Engine - (2012/04/26 (木) 02:24:08) の編集履歴(バックアップ)
Slim3
appcfg
- My Applicationsに作成した、GAEアプリケーションの管理を行うコマンドラインツールです。
- Google Plugin for Eclipseの場合、SDKがplugins/com.google.appengine.eclipse.sdkbundle_x.x.x.../appengine-java-sdk-x.x.xに存在するので、そのbinフォルダ内にappcfg.cmdがあります。
- 通常は、環境変数GAE_HOMEに上記のplugins/com.google.appengine.eclipse.sdkbundle_x.x.x.../appengine-java-sdk-x.x.xを設定し、%GAE_HOME%\binをPATHに追加します。
インスタンス
- GAEでいう「インスタンス」とは、計算リソースのことです。「仮想的なCPU」と考えてもいいでしょう。
- Javaクラスのインスタンスとは全然別の概念なので、そこを取り違えないように。
- GAEのインスタンスの属性としてclassという用語が出てくるけど、やっぱりJavaクラスとは何にも関係ないので注意。
- 何かしらのJavaクラスのコードを実行する際には、(GAEの)インスタンスが割り当てられて実行します。
- このとき、コードの起動方法と設定によって、割り当てられるインスタンスが決定されます。
Frontend InstanceとBackend Instance
参考
Properties of Backends(公式サイトのBackends Java API Overviewより)
※Default(Frontend) InstanceとBackend Instanceの比較表です。
Properties of Backends(公式サイトのBackends Java API Overviewより)
※Default(Frontend) InstanceとBackend Instanceの比較表です。
- 通常の、ブラウザからのHTTPリクエストによってアクセスされるServletは、Frontend Instanceが割り当てられます。
- CronやTask QueueからアクセスされるServletは、FrontendかBackendが選択できる?
- 多分、publicのBackendにすれば、ブラウザからのHTTPリクエストに、Backend Instanceを割り当てることができるのかな?
Cron
- 定期的に、Servletを自動実行する仕組みです。
- 主に、Webクローラー等の、UIを伴わないバックグラウンド処理に利用されます。
- 具体的には、定期実行したい処理を記述したServletに対応するURLに対して、設定ファイル(cron.xml)に設定されたタイミングでリクエストを送出します。
- 例外発生時にcatchしなかった場合でも、リトライは発生しません。
- Cronから呼び出されることを想定しているURLは、外部から勝手に呼び出されないように、admin以外はアクセスできないようにしておきましょう(後述の「認証」を参照)。
- 逆に言えば、管理者の場合は、ブラウザからURLを指定してリクエストを送出すれば、Cronからの起動を待たずに強制起動が可能です。
- まぁ、結局は単なるServletなのですから、当たり前ですが…
スケジュールの指定
- 指定可能なのは、下記2パターン。混在は出来ないようです。
- 間隔を指定して実行
- 例:1時間毎に実行
- 指定日の特定の時刻に実行
- 例:毎日12:00に実行
- 間隔を指定して実行
間隔を指定して実行
- 下記の形式で指定
every N (hours|mins|minutes) ["from" (time) "to" (time)]
- 2時間毎に実行の場合
every 2 hours
- 10:00~14:00の間に、30分毎に実行の場合
every 30 minutes from 10:00 to 14:00
指定日の特定の時刻に実行
- 下記の形式で指定
("every"|ordinal) (days) ["of" (monthspec)] (time)
- 毎日10:00に実行の場合
every day 10:00
Task Queue
- Servletを非同期実行する仕組みです。
- 主に、UIを伴わないバックグラウンド処理に利用されます。処理を非同期実行可能な単位に分割し、その単位をTaskとして実行させます。
- 具体的には、非同期実行したい処理を記述したServletに対応するURLを、設定ファイル(queue.xml)で定義したqueueにキューイングします。
- キューイングされたURLは、とあるタイミングで取り出されて、リクエストが送出されます。
- キューからの取り出しをApp Engineに任せるのがPush Queue
- キューからの取り出しをアプリで指定(Task Queue APIまたはTask Queue REST API)するのがPull Queue
- 例外発生時にcatchしなかった場合、リトライされます。
- とは言え、無限にリトライすると、一気にインスタンス時間を消費するので、必ずqueue.xmlの<retry-parameters>でリトライの上限設定を行いましょう。
- Task Queueから呼び出されることを想定しているURLは、外部から勝手に呼び出されないように、admin以外はアクセスできないようにしておきましょう(後述の「認証」を参照)。
- 逆に言えば、管理者の場合は、ブラウザからURLを指定してリクエストを送出すれば、強制起動が可能です。
- まぁ、これも結局は単なるServletなのですから、当たり前ですが…
リトライ
- queue.xmlの<retry-parameters>でリトライの設定
<queue-entries>
<queue>
<name>testQueue</name>
<retry-parameters>
<task-retry-limit>5</task-retry-limit>
</retry-parameters>
<rate>1/s</rate>
</queue>
</queue-entries>
- 上記のtestQueueでは、タスクで例外が発生した場合、最大5回のリトライが実施されます。
- 最大で、最初の1回+リトライ5回=6回実行されます。
- <task-retry-limit>
- リトライ回数を設定します。5と指定すると、5回のリトライを試みます。
- <task-age-limit>
- リトライ期間を設定します。例えば、"5d"と指定すると、最初のタスク起動から5日間、リトライを試みます。
- <task-retry-limit>と<task-age-limit>を同時に指定した場合、両方のリミットに達しするまでリトライする、とありますが、実質<task-retry-limit>が優先ですね。例え5日間経過しても、5回に達していなければリトライし続けるので。
- <min-backoff-seconds>と<max-backoff-seconds>と<max-doublings>
- 次のリトライまでのインターバルを決定します。
- インターバルは、リトライの度に増加します。1回目は10秒、2回目は20秒、3回目は40秒…、という感じです。
- 詳しい計算方法は、なんだか面倒くさそうなので割愛…。
- かいつまんで言えば、最初のリトライまでのインターバルは<min-backoff-seconds>で、そこから<min-backoff-seconds> * (2 ^ (リトライ回数 - 1))ずつ増加していきます。でいいのかな?
- <max-backoff-seconds>が指定されている場合は、インターバルの最大値は<max-backoff-seconds>となります。
- <max-doublings>を指定することで、インターバルの増分の最大値をコントロールできますが、これが何とも説明するのがややこしいです。気が向いたらちゃんと調べます…。
CronとTask Queueの使い分け
- サンプルとして、Webクローラを想定します。
- Webクローラは、サイトA、サイトB、サイトCを巡回します。
- サイトA、サイトB、サイトCは、それぞれ独立に巡回することが可能とします。
- サイトA、サイトB、サイトCの巡回処理をタスクとして、それぞれの巡回処理を行うServletのURLを/crawlSiteA、/crawlSiteB、/crawlSiteCとします。
- 各巡回処理のタスクをキューイングする処理を行うServletのURLを/execCrawlersとします。
- 具体的な/execCrawlersの処理は、/crawlSiteA、/crawlSiteB、/crawlSiteCをTask Queueにキューイングします。
- Cronで、/execCrawlersを定期起動するようにします。
説明
- もし、上記のような構成ではなく、全処理を/execCrawlersのみで構成した場合
- サイトAでエラーが発生した場合、サイトBやサイトCの巡回が実行されません。
- Cronではリトライが無いので、エラーが発生しても、そのままです。
- 上記構成の場合は、
- /crawlSiteAでエラーが発生しても、/crawlSiteBや/crawlSiteCは独立して実行されます。
- さらに、エラー発生したタスクについては、リトライが実施されます。
- とはいえ、クローリングの無制限のリトライは、DoS攻撃に等しいので、リトライの上限を設けておきましょう。
Backends
- Backend Instanceを用いて、コードを実行させます。
backends.xml
- Backendsの設定をbackends.xmlに記述します。
- backends.xmlはWEB-INF直下に配置します。
<backends>
<backend name="test-instance">
<class>B1</class>
<options>
<dynamic>true</dynamic>
</options>
</backend>
</backends>
- 上記の場合、test-instanceというnameのBackend Instanceを定義します。
- インスタンスの名前は全て小文字じゃないと、deploy時に怒られます。
- 複数のBackend Instanceを定義したい場合は、<backend>を並べていきます。
- インスタンスのclassとしてB1を指定してます。
- classの種類についてはInstance Classesを参照
- まぁ、課金しなければ、ほぼB1ですね。
- インスタンスのタイプとしてDynamicを指定しています。
- インスタンスには、Resident(常駐型)とDynamic(動的起動型)の2種類があります。
- 詳しくはTypes of Backendsを参照
- これもまぁ、課金しなければ、ほぼDynamicですね。
- ようするに、上の設定は、一番ケチな設定です。
もうすこし詳しく
- Backendsの無料枠は9インスタンス時間
- B1を1時間稼動させる単位を1インスタンス時間とします。
- classが1つあがる毎に、消費単位が2倍となります。
- B2で1時間稼動させれば2インスタンス時間、B4の場合は4インスタンス時間となります。
- Residentの場合、一度起動すると、手動でシャットダウンさせるまでずっと常駐します
- そのため、リクエストに対してすぐに応答することができます。
- そのため、インスタンス時間をどんどん消費していきます。
- 無料枠でB1の場合、初期化される日本時間16:00から、9インスタンス時間後の25:00の間しか稼動しません。その後、再び初期化される16:00までは、そのインスタンスを利用することができません。
- Dynamicの場合、リクエストがあったときに起動され、しばらくして不要になれば除去されます。
- そのため、リクエストがあったときにインスタンスが起動されていなければ、起動に時間がかかってしまいます。
- そのため、インスタンス時間の消費量は必要な分だけになります。
試してみる
- Backendsだろうが、実行するコードはServlet(或いは、Servletベースのフレームワーク)です。
- 今回はSlim3のControllerでサンプルを作ってみます。
- 今回は、/testInstanceというパスで作ってみました。
package jp.fujiyan.test.controller;
import org.slim3.controller.Controller;
import org.slim3.controller.Navigation;
import com.google.appengine.api.backends.BackendService;
import com.google.appengine.api.backends.BackendServiceFactory;
public class TestInstanceController extends Controller {
private BackendService service = BackendServiceFactory.getBackendService();
@Override
public Navigation run() throws Exception {
System.out.println(service.getCurrentBackend());
return null;
}
}
- 今回は、Cronで実行させるのが目的で、レスポンスを返す必要が無いので、run()の戻り値は不要です。
- Slim3 Plug-inの、build.xmlのgen-controller-without-viewで作れば手っ取り早いです。
- BackendService#getCurrentBackend()は、コードを実行しているBackendsのインスタンスの名前を返します。
- コードがFrontendで実行されている場合はnullを返します。
- ブラウザから、/testInstanceでアクセスした場合、Frontendsで上記Controllerが実行されます。
- Logには、下記の様に出力されます。
- Frontendsなので、nullが出力されています。
2012-01-01 01:23:45.678 [xxxxx/1.358322054626724163].<stdout>: null
Cronを使って、Backendsで実行
- Cronで、Backendsで実行させる場合は、<target>で実行させるBackendsのインスタンス名を指定します。
<?xml version="1.0" encoding="UTF-8"?>
<cronentries>
<cron>
<url>/testInstance</url>
<description>Backends Instance Test</description>
<schedule>every day 18:00</schedule>
<timezone>Asia/Tokyo</timezone>
<target>test-instance</target>
</cron>
</cronentries>
- 上記の場合、日本時間18:00に、test-instanceで/testInstanceに対応するControllerが実行されます。
- Logには、下記の様に出力されます。
- 今度は、Backend Instanceの名前である、test-instanceが出力されています。
2012-01-01 18:00:07.752 [xxxxx/test-instance.358322297746672704].<stdout>: test-instance
Backend Instance実行のCronからのTask Queue呼び出し
- Task Queue呼び出しの際のInstanceは、キューイング元のInstanceになるっぽいです。
- なので、targetを指定せずに、Backend Instance実行のCronからTask Queueを呼び出した場合は、同じBackend Instanceでタスクが実行されます。
認証
- 認証については、Googleアカウントを利用する。
- よって、基本的にアプリ側で認証を実装する必要は無い。但し、後述の3種類の権限よりも細かく制御したい場合には実装が必要。
- アクセス権限については、次の3種類
- A)Googleアカウントへのログイン不要(全公開)
- B)Googleアカウントへのログイン必要(一般権限)
- C)Googleアカウントへのログイン必要(管理者権限)
- 上記A)については、特に設定は不要。
- 上記B)またはC)については、web.xmlの<security-constraint>に記述を行う。
- B)またはC)でアクセス制限したいURLパターンを<url-pattern>に記述する。
- B)に対して許可する場合は<role-name>*</role-name>と記述する。
- C)に対して許可する場合は<role-name>admin</role-name>と記述する。
- Googleアカウントに対して管理者権限を与えるには、アプリの管理メニューで指定する。
- 下記は、URLが/member/で始まるコンテンツは、Googleアカウントにログインした人のみアクセス可能で、/admin/で始まるコンテンツは、Googleアカウントにログインした人の内、管理者権限を持っている人のみがアクセス可能となる。
<security-constraint> <web-resource-collection> <url-pattern>/member/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>*</role-name> </auth-constraint> </security-constraint> <security-constraint> <web-resource-collection> <url-pattern>/admin/*</url-pattern> </web-resource-collection> <auth-constraint> <role-name>admin</role-name> </auth-constraint> </security-constraint>
ローカルのDatastoreのデータを削除
- /war/WEB-INF/appengine-generated/local_db.binを削除して、同名のファイルを新規作成する。
- 削除直後のアクセスで"Failed to load from the backing store"というログとともにスタックとレースがでるけど気にしない
ローカルテスト環境での管理コンソール
- http://localhost:8888/_ah/adminで、ローカルテスト環境の管理コンソールにアクセスできます。
- 主に、Datastore Viewerで、ローカルのDatastoreが閲覧したい場合に。
- つうか、つい最近までコイツの存在を知りませんでした…。
/work/は使わないほうがいい
- パス/work/を実現しようとして、/war/work/というディレクトリを作ると、Jettyがテンポラリファイルをそこに作成してしまい、何かと面倒くさいです。
ロギング
- まぁ、Log4Jは使えるけど、たとえばwarn()でログ出力しても、Administration Console上ではWarningとしては扱ってくれません。
- Info扱いですね。
- ということで、管理面を考えると、Log4Jは使わずに、おとなしく標準ロギングAPIを使ったほうが良いのですかね。
- なので、下記の記事は、参考までに…
Log4Jを使うには
参考
I can't
I can't
- commons-loggingのjarファイルをwar/WEB-INF/libにコピー
- log4Jのjarファイルをwar/WEB-INF/libにコピー
- war/WEB-INF/appengine-web.xmlの<system-properties>にorg.apache.commons.logging.Logを追加する
<system-properties> ... <property name="org.apache.commons.logging.Log" value="org.apache.commons.logging.impl.Log4JLogger"/> ... </system-properties>
- srcにlog4j.propertiesまたはlog4j.xmlを置き、内容を利用したい設定に変更する
- 新規プロジェクト作成時には、デフォルトでlog4j.propertiesが作成されるが、DataNucleusのログ設定しかないので、修正が必要
- war/WEB-INF/logging.propertiesは不要なので削除してもよい。
- 削除の際には、war/WEB-INF/appengine-web.xmlの<system-properties>のjava.util.logging.config.fileも削除する
JDOを使わない場合
- プロジェクトのプロパティダイアログを開き(プロジェクトを選択して、右クリック→[Properties])、[Builders]の[Configure the builders for the project]で、[Enhancer]のチェックをOFFにする
- JDO向けのモデル拡張を行うプロセス。チェックしていると処理が実行されるので外しておく。
- src/META-INF/jdoconfig.xmlを削除
- JDOの設定ファイル。不要なので削除する。