0

さまざまな処理マシンによって処理されるジョブを維持するデータベースがあります。したがって、その基本的なスキーマは次のとおりです。

+-------------+--------------+------+-----+---------+----------------+
| 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 はそれを処理するための最新の試みの開始を表し、EndTime は処理が正常に完了した時刻、GroupID は他のジョブを参照するための任意の文字列です。テーブル。

すべての加工機は、このテーブルを中心に同期して作業を取り込みます。すべての処理マシンが既存のレコードを更新できますが、新しいレコードは手動でのみ挿入されます。アイデアは、処理マシンが機能していないときはいつでも次のことを行うことでした。

  • それに属し (HostAddress = その IP)、まだ開始されていないジョブがあるかどうかを確認します。
  • 何もない場合は、まだ要求されていないジョブがあるかどうかを確認します (HostAddress IS NULL)。
  • 要求されていないジョブがある場合は、いくつか要求します (HostAddress をその IP に更新します)。
  • それに属するすべてのジョブを処理します (#3 で追加した可能性があることを除いて、#1 と同じチェック)。

私は、この一連の操作により、データベースが同じジョブでのさまざまなマシンの試行を同期させると考えていました。両方のマシンが同時に同じジョブを要求しようとしても、HostAddress 列に入る IP は 1 つだけなので、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().commit を実行すると結果が何らかの形でキャッシュされ、その直後に selectReady NamedQuery を実行すると、わざわざデータベースに相談せずにキャッシュされた結果が返されるということです。しかし、そうではないかもしれませんし、それを証明できるかどうかもわかりません. 私が見落としている、私のスキームに根本的な欠陥があるかもしれません。

では、実際に私の質問をするために、なぜこのデータベース同期ルーチンが失敗したのでしょうか? また、それを修正するにはどうすればよいでしょうか?

4

1 に答える 1

2

selectAvailable()複数のマシンがトランザクションを実行する前に呼び出すことができUPDATEます。その結果、彼らはそれぞれ同じ仕事が利用できると考えるかもしれません。

selectAvailable()呼び出しの前にトランザクションを開始する必要があります。これはSELECT ... FOR UPDATE、使用可能なジョブ レコードをロックして、トランザクションがコミットされるまで他のデータベース接続が読み取れないようにするために使用する必要があります。

于 2012-05-09T18:15:29.403 に答える