MQ#
jimble の MQ は DB のテーブルをキューにします。 別のミドルウェアは要りません。トランザクションを共有できるのが大きいところです。
書く#
public class NoticeExecutor extends MqExecutor {
@Override
public String queueName () { return "mq_blog"; }
@Override
public String key () { return "notice"; }
@Override
public MqExecuteType executeType () { return MqExecuteType.short_time; }
@Override
public MqStatus execute (DB db, Data row) {
Data data = row.getDataOptional("data");
// ... 処理する
return MqStatus.completed;
}
}
queueName()はテーブル名です。キューごとにテーブルを分けますkey()は種別です。1つのテーブルに複数の種類を混ぜられますexecuteType()はshort_time/long_time。取り出し方が変わります- 戻り値が
MqStatus.completed以外なら、maxRetry()まで再実行されます
積む#
try (Tx tx = db.begin()) {
long id;
try {
id = db.insertKey(
SQL.insert(Post.instance())
.value(Post.title, request.getString("title"))
.value(Post.body, request.getString("body"))
.value(Post.image_name, request.getStringOptional("image_name"))
.value(Post.published, request.getBoolean("published"))
.value(Post.created_at, new Date())
);
} catch (SqlExecuteException ex) {
return -1; // tx.commit() まで来ないので、抜けたら巻き戻る
}
new NoticeExecutor().put(db, new Data()
.putData("post_id", id)
.putData("title", request.getString("title")));
tx.commit(); // 記事とキューを一緒に確定して終わる
/*
* コミットしてから流す。
* 先に流すと、ロールバックしたときに
* 「入っていない記事のお知らせ」だけが届く。
*/
PostFeedHandler.notifyNewPost(request.getStringOptional("title"));
return id;
}
put() はトランザクションの中で呼びます。
同じ DB なので、ロールバックすれば積んだものも消えます。
「メールだけ飛んで注文が入っていない」が起きません。
時間を指定して積むこともできます。
new NoticeExecutor().put(db, data, scheduledAt);
二重に実行される#
同じメッセージが2回実行されることがあります。 処理の途中でプロセスが落ちたときなどです。
なので、何度やっても同じになるように書いてください。
- メールなら、送信済みの記録を見てから送る
- 集計なら、足すのではなく入れ直す
- 外部 API なら、冪等キーを付ける
「ちょうど1回」は分散システムでは作れません。jimble はそのふりをしません。
登録して回す#
// テーブルを用意する
new MqQueue(NoticeExecutor.QUEUE_NAME).install();
// Executor を登録する(クラスパス走査はしない)
MqRegistry.add(NoticeExecutor::new);
取り出して回すのはバッチです。MqWorkerBatch のようなバッチを1つ作り、
バッチ と同じように登録して cron で回します。
DB から取るのは、キューごとに1本の取り出し役だけです。取り出し役は、手の空いたワーカーがいるときだけ
SELECT ... FOR UPDATE SKIP LOCKED で手の空いたワーカーの数だけまとめて取り、その場でワーカーに渡します(先読みはしません。2.5.4 までは1回に1件でした)。
何台で回しても、1件のメッセージを取れるのは1台のワーカーだけです(2回処理されることがあるのは、上の「二重に実行される」の場合です)。
mq.thread_count.<種別> は並んで処理する数で、増やしても、何もしていないときに DB の接続を借りる数は増えません
(2.1.2 まではワーカーが1本ずつ自分で DB を見ていたので、DB の応答が遅れると、ワーカーの数だけ接続が同時に埋まりました)。