「Google App Engine」の編集履歴(バックアップ)一覧はこちら
Google App Engine - (2012/06/17 (日) 00:08:21) の1つ前との変更点
追加された行は緑色になります。
削除された行は赤色になります。
|BGCOLOR(#eff):&bold(){WebコミックLibrary}[[http://web-comi.appspot.com/>>http://web-comi.appspot.com/]]&br()GAE/JとSlim3で作成してみた、各出版社から配信されているWebコミックをまとめて閲覧できるサイトです。只今、実験運用中…|
*参考サイト
[[公式サイト(Java)>>http://code.google.com/intl/ja/appengine/docs/java/overview.html]]
----
*Slim3
[[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に追加します。
----
*Datastore ~ Operations
-Quotaの中でも、世の中の頭を悩ませる最大の壁
--Datastore Read Operations
--Datastore Write Operations
--Datastore Small Operations
-Writeはどうしようもないし、そんなに問題もなってませんが、ReadとSmallはどうにかして減らしたい。
-そもそも、数値の算出方法が公開されてないので、対策も立てにくい。以下推測。
--おそらく、各Datastore ~ Opsを、カテゴライズしたものっぽいです。
--Datastore Read Operations ≒ Datastore Entity Fetch Ops + Datastore Query Ops
---Entityのフェッチ数とクエリ数の合計っぽいです
--Datastore Small Operations ≒ Datastore Key Fetch Ops
---Keyのフェッチ数っぽいです。ひょっとしたら、Datastore Id Allocation Opsも勘定に入っているかもしれませんが、そんな大きな数になるもんじゃないのでキニシナイ。
-「Entityのフェッチ」と「Keyのフェッチ」の何が違うの、という点については、
--Query.setKeysOnly()を指定して取得した場合は、Keyのフェッチになる模様。そうでなければEntityのフェッチになる模様。
-結論から言えば、Memcacheでキャッシュしまくるしかないです。
--Entity自体をキャッシュする
---Keyを指定してEntityを取得する場合は、(Datastoreの)Keyを(Memcacheの)Keyにして、Entity自体をキャッシュ
---Entityを更新するタイミングで、キャッシュも更新してあげる
---Dao層でサポートすれば、比較的容易に対応できます。
--Queryの結果をキャッシュする。
---検索条件をうまいこと(Memcacheの)Keyにして、Query結果のリストをキャッシュ
---これについては、Entityが更新された場合どうすんの、というのが難しい
---Entityが更新するたびにキャッシュをクリアすると、結局意味が無い。
---なので、あまり頻繁にEntityが更新されないクエリについて有効な手法かと。
---「Entityの値の変更はあるが、Entityの増減は無い」という場合は、クエリ結果のKeyのリストのみをキャッシュしておけば、実際に利用する際はそのキーを使ってEntityのキャッシュを参照する、という方法で多少はマシかも?
----
*インスタンス
参考
[[GAE/PでのBackendsの使い方 - Tari Lari Run>>http://bygzam.seesaa.net/article/226558849.html]]
-GAEでいう「インスタンス」とは、計算リソースのことです。「仮想的なCPU」と考えてもいいでしょう。
--Javaクラスのインスタンスとは全然別の概念なので、そこを取り違えないように。
--GAEのインスタンスの属性としてclassという用語が出てくるけど、やっぱりJavaクラスとは何にも関係ないので注意。
-何かしらのJavaクラスのコードを実行する際には、(GAEの)インスタンスが割り当てられて実行します。
--このとき、コードの起動方法と設定によって、割り当てられるインスタンスが決定されます。
**Frontend InstanceとBackend Instance
参考
[[Properties of Backends>>https://developers.google.com/appengine/docs/java/backends/overview#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
参考
[[Scheduled Tasks With Cron for Java>>https://developers.google.com/appengine/docs/java/config/cron]]
-定期的に、Servletを自動実行する仕組みです。
--主に、Webクローラー等の、UIを伴わないバックグラウンド処理に利用されます。
-具体的には、定期実行したい処理を記述したServletに対応するURLに対して、設定ファイル(cron.xml)に設定されたタイミングでリクエストを送出します。
-例外発生時にcatchしなかった場合でも、リトライは発生しません。
-Cronから呼び出されることを想定しているURLは、外部から勝手に呼び出されないように、admin以外はアクセスできないようにしておきましょう(後述の「認証」を参照)。
--逆に言えば、管理者の場合は、ブラウザからURLを指定してリクエストを送出すれば、Cronからの起動を待たずに強制起動が可能です。
--まぁ、結局は単なるServletなのですから、当たり前ですが…
**スケジュールの指定
参考
[[The Schedule Format - Scheduled Tasks With Cron for Java>>https://developers.google.com/appengine/docs/java/config/cron#The_Schedule_Format]]
-指定可能なのは、下記2パターン。混在は出来ないようです。
--間隔を指定して実行
---例:1時間毎に実行
--指定日の特定の時刻に実行
---例:毎日12:00に実行
***間隔を指定して実行
-下記の形式で指定
#pre2(black){{
every N (hours|mins|minutes) ["from" (time) "to" (time)]
}}
-2時間毎に実行の場合
#pre2(black){{
every 2 hours
}}
-10:00~14:00の間に、30分毎に実行の場合
#pre2(black){{
every 30 minutes from 10:00 to 14:00
}}
***指定日の特定の時刻に実行
-下記の形式で指定
#pre2(black){{
("every"|ordinal) (days) ["of" (monthspec)] (time)
}}
-毎日10:00に実行の場合
#pre2(black){{
every day 10:00
}}
----
*Task Queue
参考
[[Java Task Queue Configuration>>https://developers.google.com/appengine/docs/java/config/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>でリトライの設定
#highlight(xml){{
<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秒…、という感じです。
--イマイチ、インターバルの計算方法がわかりません。実際に動かすと、予想通りの時間にならない…。
----
*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
[[Java Backends Configuration>>https://developers.google.com/appengine/docs/java/config/backends]]
-Backendsの設定をbackends.xmlに記述します。
-backends.xmlはWEB-INF直下に配置します。
#highlight(xml){{
<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>>https://developers.google.com/appengine/docs/java/config/backends#Instance_Classes]]を参照
--まぁ、課金しなければ、ほぼB1ですね。
-インスタンスのタイプとしてDynamicを指定しています。
--インスタンスには、Resident(常駐型)とDynamic(動的起動型)の2種類があります。
--詳しくは[[Types of Backends>>https://developers.google.com/appengine/docs/java/config/backends#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というパスで作ってみました。
#highlight(java){{
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が出力されています。
#pre2(black){{
2012-01-01 01:23:45.678 [xxxxx/1.358322054626724163].<stdout>: null
}}
***Cronを使って、Backendsで実行
-Cronで、Backendsで実行させる場合は、<target>で実行させるBackendsのインスタンス名を指定します。
#highlight(xml){{
<?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が出力されています。
#pre2(black){{
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でタスクが実行されます。
**明示的にBackend Instanceそ指定して、Task Queue呼び出し
-例えば、通常のWebブラウザからのリクエスト(Frontend Instance)から、Backend InstanceでTask Queueを実行したい場合は、URLのホスト名で、Backend Instanceを指定します。
-通常、リクエストの際のURLは、http://[アプリ名].appspot.com/~ですが、これをhttp://[Backend Instance名].[アプリ名].appspot.com/~とすれば、指定したBackend Instanceでリクエストを処理します。
--例えば、http://test-instance.appname.appspot.com/testInstanceとすれば、test-instanceで/testInstanceに対応するControllerが実行されます。
--つまり、通常のリクエストにおける、http://[アプリ名].appspot.com/~の形式は、インスタンスを指定しない→Default(Frontend) Instanceということですね。
----
*LocaleとTimeZone
-GAEのデフォルトでは、Localeはen_US、TimeZoneはUTCです。
--但し、ローカルの開発環境では、LocaleはOSの設定のようです。日本ならja_JP。でも、TimeZoneはUTCです。
-Locale.setDafault()は、アクセス制御により使用が禁止されていますが、TimeZone.setDefault()は利用可能なようです。
-ということで、TimeZoneはFilterでsetDefault()でJSTにしておいたほうが、何かとシアワセかもしれません。
----
*URL Fetch APIでキャッシュされてしまう
参考
[[Disable URLFetchService cache - Google App Engine for Java | Google グループ>>http://groups.google.com/group/google-appengine-java/browse_thread/thread/a8c8a6cb9fbe652f?pli=1]]
-下記のように、Cache-ControlとPragmaを設定して、キャッシュを無効にしてしまう
#highlight(java){{
String url = …;
URLConnection connection = new URL(url).openConnection();
connection.addRequestProperty("Cache-Control", "no-cache,max-age=0");
connection.addRequestProperty("Pragma", "no-cache");
BufferedInputStream in = new BufferedInputStream(openConnection().getInputStream());
try {
…
} finally {
in.close();
}
}}
----
*認証
参考
[[北海道を愛するプログラマの覚書>>http://d.hatena.ne.jp/tetsuya_odaka/20090917/1253177052]]
-認証については、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アカウントにログインした人の内、管理者権限を持っている人のみがアクセス可能となる。
#pre2(black){{
<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のデータを削除
参考[[プログラマ的京都生活>>http://d.hatena.ne.jp/mtoyoshi/20090506/1241590403]]
-/war/WEB-INF/appengine-generated/local_db.binを削除して、同名のファイルを新規作成する。
-削除直後のアクセスで"Failed to load from the backing store"というログとともにスタックとレースがでるけど気にしない
----
*ローカルテスト環境での管理コンソール
-&nolink(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>>http://fall-ken.at.webry.info/201003/article_2.html]]
+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を追加する
#pre2(black){{
<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を使わない場合
参考
[[Song of Cloud>>http://songofcloud.gluegent.com/2009/11/slim3-datastore2.html]]
-プロジェクトのプロパティダイアログを開き(プロジェクトを選択して、右クリック→[Properties])、[Builders]の[Configure the builders for the project]で、[Enhancer]のチェックをOFFにする
--JDO向けのモデル拡張を行うプロセス。チェックしていると処理が実行されるので外しておく。
-src/META-INF/jdoconfig.xmlを削除
--JDOの設定ファイル。不要なので削除する。
----
*GAEでStruts2
[[GAEでStruts2>Google App Engine/Struts2]]
----
|BGCOLOR(#eff):&bold(){WebコミックLibrary}[[http://web-comi.appspot.com/>>http://web-comi.appspot.com/]]&br()GAE/JとSlim3で作成してみた、各出版社から配信されているWebコミックをまとめて閲覧できるサイトです。只今、実験運用中…|
*参考サイト
[[公式サイト(Java)>>http://code.google.com/intl/ja/appengine/docs/java/overview.html]]
----
*Slim3
[[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に追加します。
----
*Datastore ~ Operations
-Quotaの中でも、世の中の頭を悩ませる最大の壁
--Datastore Read Operations
--Datastore Write Operations
--Datastore Small Operations
-Writeはどうしようもないし、そんなに問題もなってませんが、ReadとSmallはどうにかして減らしたい。
-そもそも、数値の算出方法が公開されてないので、対策も立てにくい。以下推測。
--おそらく、各Datastore ~ Opsを、カテゴライズしたものっぽいです。
--Datastore Read Operations ≒ Datastore Entity Fetch Ops(Entityのフェッチ数) + Datastore Query Ops(クエリ数)
---Entityのフェッチ数とクエリ数の合計っぽいです
--Datastore Small Operations ≒ Datastore Key Fetch Opss(Keyのフェッチ数)
---ひょっとしたら、Datastore Id Allocation Opsも勘定に入っているかもしれませんが、そんな大きな数になるもんじゃないのでキニシナイ。
-「Entityのフェッチ」と「Keyのフェッチ」の何が違うの、という点については、
--Query.setKeysOnly()を指定して取得した場合は、Keyのフェッチになる模様。そうでなければEntityのフェッチになる模様。
-結論から言えば、Memcacheでキャッシュしまくるしかないです。
--Entity自体をキャッシュする
---Keyを指定してEntityを取得する場合は、(Datastoreの)Keyを(Memcacheの)Keyにして、Entity自体をキャッシュ
---Entityを更新するタイミングで、キャッシュも更新してあげる
---Dao層でサポートすれば、比較的容易に対応できます。
--Queryの結果をキャッシュする。
---検索条件をうまいこと(Memcacheの)Keyにして、Query結果のリストをキャッシュ
---これについては、Entityが更新された場合どうすんの、というのが難しい
---Entityが更新するたびにキャッシュをクリアすると、結局意味が無い。
---なので、あまり頻繁にEntityが更新されないクエリについて有効な手法かと。
---「Entityの値の変更はあるが、Entityの増減は無い」という場合は、クエリ結果のKeyのリストのみをキャッシュしておけば、実際に利用する際はそのキーを使ってEntityのキャッシュを参照する、という方法で多少はマシかも?
----
*インスタンス
参考
[[GAE/PでのBackendsの使い方 - Tari Lari Run>>http://bygzam.seesaa.net/article/226558849.html]]
-GAEでいう「インスタンス」とは、計算リソースのことです。「仮想的なCPU」と考えてもいいでしょう。
--Javaクラスのインスタンスとは全然別の概念なので、そこを取り違えないように。
--GAEのインスタンスの属性としてclassという用語が出てくるけど、やっぱりJavaクラスとは何にも関係ないので注意。
-何かしらのJavaクラスのコードを実行する際には、(GAEの)インスタンスが割り当てられて実行します。
--このとき、コードの起動方法と設定によって、割り当てられるインスタンスが決定されます。
**Frontend InstanceとBackend Instance
参考
[[Properties of Backends>>https://developers.google.com/appengine/docs/java/backends/overview#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
参考
[[Scheduled Tasks With Cron for Java>>https://developers.google.com/appengine/docs/java/config/cron]]
-定期的に、Servletを自動実行する仕組みです。
--主に、Webクローラー等の、UIを伴わないバックグラウンド処理に利用されます。
-具体的には、定期実行したい処理を記述したServletに対応するURLに対して、設定ファイル(cron.xml)に設定されたタイミングでリクエストを送出します。
-例外発生時にcatchしなかった場合でも、リトライは発生しません。
-Cronから呼び出されることを想定しているURLは、外部から勝手に呼び出されないように、admin以外はアクセスできないようにしておきましょう(後述の「認証」を参照)。
--逆に言えば、管理者の場合は、ブラウザからURLを指定してリクエストを送出すれば、Cronからの起動を待たずに強制起動が可能です。
--まぁ、結局は単なるServletなのですから、当たり前ですが…
**スケジュールの指定
参考
[[The Schedule Format - Scheduled Tasks With Cron for Java>>https://developers.google.com/appengine/docs/java/config/cron#The_Schedule_Format]]
-指定可能なのは、下記2パターン。混在は出来ないようです。
--間隔を指定して実行
---例:1時間毎に実行
--指定日の特定の時刻に実行
---例:毎日12:00に実行
***間隔を指定して実行
-下記の形式で指定
#pre2(black){{
every N (hours|mins|minutes) ["from" (time) "to" (time)]
}}
-2時間毎に実行の場合
#pre2(black){{
every 2 hours
}}
-10:00~14:00の間に、30分毎に実行の場合
#pre2(black){{
every 30 minutes from 10:00 to 14:00
}}
***指定日の特定の時刻に実行
-下記の形式で指定
#pre2(black){{
("every"|ordinal) (days) ["of" (monthspec)] (time)
}}
-毎日10:00に実行の場合
#pre2(black){{
every day 10:00
}}
----
*Task Queue
参考
[[Java Task Queue Configuration>>https://developers.google.com/appengine/docs/java/config/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>でリトライの設定
#highlight(xml){{
<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秒…、という感じです。
--イマイチ、インターバルの計算方法がわかりません。実際に動かすと、予想通りの時間にならない…。
----
*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
[[Java Backends Configuration>>https://developers.google.com/appengine/docs/java/config/backends]]
-Backendsの設定をbackends.xmlに記述します。
-backends.xmlはWEB-INF直下に配置します。
#highlight(xml){{
<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>>https://developers.google.com/appengine/docs/java/config/backends#Instance_Classes]]を参照
--まぁ、課金しなければ、ほぼB1ですね。
-インスタンスのタイプとしてDynamicを指定しています。
--インスタンスには、Resident(常駐型)とDynamic(動的起動型)の2種類があります。
--詳しくは[[Types of Backends>>https://developers.google.com/appengine/docs/java/config/backends#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というパスで作ってみました。
#highlight(java){{
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が出力されています。
#pre2(black){{
2012-01-01 01:23:45.678 [xxxxx/1.358322054626724163].<stdout>: null
}}
***Cronを使って、Backendsで実行
-Cronで、Backendsで実行させる場合は、<target>で実行させるBackendsのインスタンス名を指定します。
#highlight(xml){{
<?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が出力されています。
#pre2(black){{
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でタスクが実行されます。
**明示的にBackend Instanceそ指定して、Task Queue呼び出し
-例えば、通常のWebブラウザからのリクエスト(Frontend Instance)から、Backend InstanceでTask Queueを実行したい場合は、URLのホスト名で、Backend Instanceを指定します。
-通常、リクエストの際のURLは、http://[アプリ名].appspot.com/~ですが、これをhttp://[Backend Instance名].[アプリ名].appspot.com/~とすれば、指定したBackend Instanceでリクエストを処理します。
--例えば、http://test-instance.appname.appspot.com/testInstanceとすれば、test-instanceで/testInstanceに対応するControllerが実行されます。
--つまり、通常のリクエストにおける、http://[アプリ名].appspot.com/~の形式は、インスタンスを指定しない→Default(Frontend) Instanceということですね。
----
*LocaleとTimeZone
-GAEのデフォルトでは、Localeはen_US、TimeZoneはUTCです。
--但し、ローカルの開発環境では、LocaleはOSの設定のようです。日本ならja_JP。でも、TimeZoneはUTCです。
-Locale.setDafault()は、アクセス制御により使用が禁止されていますが、TimeZone.setDefault()は利用可能なようです。
-ということで、TimeZoneはFilterでsetDefault()でJSTにしておいたほうが、何かとシアワセかもしれません。
----
*URL Fetch APIでキャッシュされてしまう
参考
[[Disable URLFetchService cache - Google App Engine for Java | Google グループ>>http://groups.google.com/group/google-appengine-java/browse_thread/thread/a8c8a6cb9fbe652f?pli=1]]
-下記のように、Cache-ControlとPragmaを設定して、キャッシュを無効にしてしまう
#highlight(java){{
String url = …;
URLConnection connection = new URL(url).openConnection();
connection.addRequestProperty("Cache-Control", "no-cache,max-age=0");
connection.addRequestProperty("Pragma", "no-cache");
BufferedInputStream in = new BufferedInputStream(openConnection().getInputStream());
try {
…
} finally {
in.close();
}
}}
----
*認証
参考
[[北海道を愛するプログラマの覚書>>http://d.hatena.ne.jp/tetsuya_odaka/20090917/1253177052]]
-認証については、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アカウントにログインした人の内、管理者権限を持っている人のみがアクセス可能となる。
#pre2(black){{
<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のデータを削除
参考[[プログラマ的京都生活>>http://d.hatena.ne.jp/mtoyoshi/20090506/1241590403]]
-/war/WEB-INF/appengine-generated/local_db.binを削除して、同名のファイルを新規作成する。
-削除直後のアクセスで"Failed to load from the backing store"というログとともにスタックとレースがでるけど気にしない
----
*ローカルテスト環境での管理コンソール
-&nolink(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>>http://fall-ken.at.webry.info/201003/article_2.html]]
+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を追加する
#pre2(black){{
<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を使わない場合
参考
[[Song of Cloud>>http://songofcloud.gluegent.com/2009/11/slim3-datastore2.html]]
-プロジェクトのプロパティダイアログを開き(プロジェクトを選択して、右クリック→[Properties])、[Builders]の[Configure the builders for the project]で、[Enhancer]のチェックをOFFにする
--JDO向けのモデル拡張を行うプロセス。チェックしていると処理が実行されるので外しておく。
-src/META-INF/jdoconfig.xmlを削除
--JDOの設定ファイル。不要なので削除する。
----
*GAEでStruts2
[[GAEでStruts2>Google App Engine/Struts2]]
----
表示オプション
横に並べて表示:
変化行の前後のみ表示: