さまざまな処理マシンで処理されるジョブを管理するデータベースがあります。その基本的なスキーマは、このようです:このデータベース同期ルーチンが失敗するのはなぜですか?
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| ID | int(11) | NO | PRI | NULL | auto_increment |
| EndTime | datetime | YES | | NULL | |
| GroupID | varchar(255) | NO | MUL | NULL | |
| HostAddress | varchar(15) | YES | | NULL | |
| StartTime | datetime | YES | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
IDが自動インクリメントされ、HostAddressは、この仕事を主張した加工機を表し、のStartTimeはそれを処理する時の最新の試みの始まりを表し、終了時間は、それの時間ですGroupIDは他のテーブルを参照するための任意の文字列です。
すべての処理マシンは、このテーブルの周りで同期して作業を行います。新しいレコードは手動でのみ挿入されますが、すべての処理マシンは既存のレコードを更新できます。
- ジョブが属しているかどうか(HostAddress =そのIP)、まだ開始されていないかどうかを確認してください。
- 存在しない場合は、まだジョブが要求されていないかどうかを確認します(HostAddress IS NULL)。
- 請求されていないジョブがある場合は、そのIPにHostAddressを更新してください。
- それに属しているすべてのジョブを処理します(#3でいくつか追加した可能性があることを除いて、#1と同じチェック)。
私は、この一連の操作によってデータベースが同じジョブで異なるマシンの試行を同期させると思っていました。両方のマシンが同じジョブを同時に要求したとしても、それらのIPのうちの1つだけがHostAddress列に終わるので、HostAddressのすべてのジョブを再度尋ねると、そのうちの1つだけがそのジョブを戻します。
しかし、これは当てはまりません。昨夜、35台の加工機械をほぼ同時に起動すると、同じジョブを処理する複数の機械の複数のケースが観察されました。それは私には、最後のチェックが正しく機能していないことを意味します。ここに私がやっていることのより具体的なバージョンがあります。データベース呼び出しはem.createNamedQueryを使用しています。簡潔にするために、それらの下に要約するつもりです。 JPAはHibernate 3.6.8によって提供され、データベースはMySQL 5.1.61です。
protected void poll(EntityManager em) {
List<JobRecord> candidates = null;
//Synchronized only for this machine. Others are running concurrently.
synchronized (em) {
//Check if anything is already claimed by us.
candidates = JobRecord.selectReady(em);
//SELECT record FROM JobRecord record WHERE HostAddress=[IP]
// AND StartTime IS NULL AND EndTime IS NULL;
if (candidates.isEmpty()) {
//None claimed. Check if any jobs aren't claimed by anyone.
candidates = JobRecord.selectAvailable(em);
//SELECT record FROM JobRecord record WHERE HostAddress IS NULL
// AND StartTime IS NULL AND EndTime IS NULL;
if (candidates.isEmpty()) {
//All jobs have been processed.
return;
}
//Claim these jobs we found for ourselves.
em.getTransaction().begin();
for (JobRecord job : candidates) {
job.setStartTime(null);
job.setEndTime(null);
job.setHostAddress([IP]);
em.merge(job);
}
em.getTransaction().commit;
//Only process what is actually claimed by us; could be nothing.
candidates = JobRecord.selectReady(em);
//(The first query again.)
}
//Do processing with candidates list.
}
頭に浮かぶ唯一の説明は、私がem.getTransactionを行うときに()。結果は何とかキャッシュされるコミットすることを、私はちょうどそれの後selectReady NamedQueryを行うときに、それがキャッシュされた結果を返すということですデータベースにご相談ください。しかし、それは事実ではないかもしれません、そして、私はそれを証明することができないと確信しています。私が見落としている私の計画に根本的に欠陥があるかもしれません。
実際に私の疑問を提起するために、なぜこのデータベース同期ルーチンが失敗し、それを修正するために何ができますか?
ああ!私は欠けていたビットがあると思う。私は複数のマシンが同じ仕事を選ぶことができ、複数のマシンがUPDATEしようとしていると思っていましたが、1つのHostAddressだけがDBで終わり、最終的なSELECTによって1つのHostAddressだけが読み返されるためです。しかし、一時的に実際にはDBに1つの値があり、その値を再読み込みして処理を開始し、次に別の処理が保留中の更新を行い、後で新しい値を再度読み込むことができることを忘れていました。 FOR UPDATEをもう少し調べますが、これはすべきことです。ありがとう。 –
私はそれを正しく理解すれば、SELECT ... FOR UPDATEとUPDATE自体が同じトランザクションの一部である必要があることに注意することが重要です。それ以外の場合は、行をロックし、何もしないで、更新する前にロックを解除します。その時点で、別のマシンが引き続きスワップインできます。 –
@HammerBro .:私の答えはそれを言いませんか? "*トランザクションをコミットするまでロックするために、' selectAvailable() 'コールの前にトランザクションを開始する*"; P – eggyal