Contenu connexe
Similaire à appengine java night #3 (20)
appengine java night #3
- 1. appengine
java night #3
実際に作ってわかった
App Engine の困ったところ
source: http://www.flickr.com/photos/katemonkey/122489910/
- 9. 制約その1
1 リクエストは30秒以内に処理すべし
HardDeadlineExceededError
http://code.google.com/intl/ja/appengine/docs/whatisgoogleappengine.html
- 10. App Engineではバッチ
処理も30秒以内
天気予報を取得するために次のようにした。
Cronで1分毎に実行
各地域の天気予報を取得
取得済みの地域はスキップ
天気予報をDatastoreへ保存
※当時はTaskQueueがリリースされていなかった。
- 13. TaskQueueで高速化
TaskQueueを使って並列処理
1地域毎に1タスク、142のTaskで実行する
Cronは指定時間にQueueを追加するだけ
for (Location location : Location.getAll()) {
QueueFactory.getDefaultQueue().
add(TaskOptions.Builder
.url("/crawler")
.param("locationID",location.getId()));
}
- 16. App Engineでの
バッチ処理
バッチ処理でも30秒以内に処理
結果的にTaskQueueを使う必要あり
キューを使うことで非同期、並列処理となる
非同期、並列処理の知識と経験が必要
既存プログラムをApp Engineに移行する場合
にバッチ処理は処理方式を変更する必要に迫
られる
- 17. こんなバッチの場合は
どうする?
1Taskが30秒以内に終わらない
バッチが終わったことを知りたい
- 18. 1Taskが30秒以内に
終わらない
機能を分割する。1機能を30秒以内に分割
当該アプリの例だと1Taskの機能は下記
Fetch
Parse
Insert
機能別にTaskを実行するようにする
- 19. TaskQueueを
チェインする
Fetch処理の最後にParseのキューを追加
Parse処理の最後にInsertのキューを追加
Insert処理を実行してDatastoreに登録する
- 21. バッチが終わったことを
知りたい
処理件数で把握する
複数リクエスト(TaskQueue)間で連番を作
成する
連番の処理件数がキューを追加した件数
と同じだったらバッチ終了と判断する
- 22. カウンター
Sharding Counter
書き込みが集中しないように複数のエン
ティティに分散して書き込みし集計する
Memcache Counter
Memcacheを用いた簡易カウンター
Memcache Counterを紹介
- 24. APIの使用例
MemcacheService s =
MemcacheServiceFactory.getMemcacheService();
if (!s.contains("MemcacheCounter")) {
s.put("MemcacheCounter", 1); // 初期化は1
} else {
// 2回目以降は値に+1する
s.increment("MemcacheCounter", 1);
}
// 実行のたびに1,2,3,4,5になる
System.out.println(s.get("MemcacheCounter"));
http://d.hatena.ne.jp/bluerabbit/20091008/1255007854
- 37. (案5) 補償トランザクション
トランザクションをプログラムで補償する
Insert時
Userの登録は正常終了
MailQueueが異常終了
異常を検知してUserをロールバックする
(Userを削除する)
Update時
Userを更新する前にバックアップを作成
する(Userをシリアライズして保存)
失敗した場合はバックアップから戻す
※30秒制限があるため実装は困難です。しかし、タスクキューを使えば出来なくもありません。
- 39. Entity Groupって何?
全てのEntityはEntity Groupに所属
Entity Group内ではトランザクションをサポート
全ての操作が成功か失敗かになる
Entityを作成するときに、別のEntityを新しいEntity
の「親」に指定することができる
新しいEntityに対して親を指定することで、その新し
いEntityは親Entityと同じEntity Groupに入る
親を持たないEntityはルートエンティティとなる
Entityの親はEntityの作成時に定義され、後で変更
することはできない
Entity Group全体に対してトランザクションの排他処
理は実行される
- 40. ルートエンティティ
String kind = "User";
Key userKey = KeyFactory.createKey(kind, 1);
Entity user = new Entity(userKey);
DatastoreService ds = DatastoreServiceFactory.
getDatastoreService();
ds.put(user);
KEY Kind
User(1) User
- 41. UserにMailQueueを追加
String kind = "MailQueue";
Key mailKey =
KeyFactory.createKey(userKey, kind, 1);
Entity mail = new Entity(mailKey);
DatastoreService.put(mail);
KEY Kind
User(1) User
User(1)/MailQueue(1) MailQueue
- 42. EntityGroupはKeyで構成
KEY Kind
User(1) User
User(1)/MailQueue(1) MailQueue
User(1)/MailQueue(2) MailQueue
User(1)/Book(8) Book
※ルートエンティティが子エンティティ
を保持している訳ではない
- 43. 同一Kindでも構成可能
KEY Kind
Bank(1) Bank
Bank(1)/Bank(2) Bank
Bank(1)/Bank(3) Bank
Bank(1)/Bank(4) Bank
※注意:排他はEntityGroup全体
- 44. EntityGroupの排他
tx = ds.beginTransaction() ;
口座A -1000円
口座B +1000円 tx = ds.beginTransaction() ;
口座C = ds.get(tx, keyC);
tx.commit();
口座C -2000
口座D +2000
// ConcurrentModificationException
tx.commit();
※口座A、B、C、DはEntityGroupです。
- 45. トランザクション内の分離レベルは
SERIALIZABLE
tx = ds.beginTransaction() ;
口座A = ds.get(tx, keyA);
口座A 残高照会 1000円
tx = ds.beginTransaction() ;
口座A -1000円
口座B +1000円
tx.commit();
口座B = ds.get(tx, keyB);
口座B 残高照会 0円
※リクエスト前は口座Bの残高は0円です。
- 50. 処理イメージ(1)
// キーを作成する。
DatastoreService service =
DatastoreServiceFactory.getDatastoreService();
KeyRange keys = service.allocateIds("Kind", 1);
String key = KeyFactory.keyToString(keys.getStart());
);
// キューのパラメータにキーを設定する
QueueFactory.getDefaultQueue().
add(TaskOptions.Builder.url("/insert").
param("key", key));
DatastoreServiceのjava doc
- 51. 処理イメージ(2)
// DatastoreService#get(Key)で登録有無をチェック
String keyString = (String) request.getAttribute("key");
Key key = KeyFactory.stringToKey(keyString);
try {
DatastoreService service =
DatastoreServiceFactory.getDatastoreService();
Entity e = service.get(key);
// 登録済み
} catch (EntityNotFoundException e) {
// 未登録
// → 登録処理を行う
}
- 53. 処理イメージ
// Keyを作成
String keyName = "001" + "20091204";
Key key =
KeyFactory.createKey("Kind", keyName);
DatastoreService ds =
DatastoreServiceFactory.getDatastoreService();
try {
ds.get(key);
} catch (EntityNotFoundException e) {
Entity entity = new Entity(key); // 作成キーで登録
ds.put(entity); // 存在しないときにのみ登録
}
KeyFactoryのjava doc