【问题标题】:Why does this database synchronization routine fail?为什么这个数据库同步例程会失败?
【发布时间】:2012-05-09 17:40:24
【问题描述】:

我有一个数据库,用于维护要由各种处理机器处理的作业。它的基本架构是这样的:

+-------------+--------------+------+-----+---------+----------------+
| 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 代表处理这个 Job 的机器,StartTime 代表最近一次处理它的开始时间,EndTime 是它成功完成处理的时间,GroupID 是一个任意字符串用于引用其他表。

所有的处理机器都围绕这张桌子进行同步以抓取工作。新记录只能手动插入,尽管所有处理机器都可以更新现有记录。这个想法是让一台处理机器在它不工作时执行以下操作:

  • 查看是否有任何作业属于它(HostAddress = 它的 IP)并且尚未启动。
  • 如果没有,请查看是否有任何作业尚未申请(HostAddress IS NULL)。
  • 如果有无人认领的作业,请认领一些(将 HostAddress 更新为其 IP)。
  • 处理属于它的所有作业(与 #1 相同的检查,除了我们可能通过 #3 添加了一些)。

我原以为这一系列操作会导致数据库为我同步不同机器对同一作业的尝试;即使两台机器试图同时申请同一个工作,也只有其中一个 IP 最终会出现在 HostAddress 列中,因此当他们再次要求其 HostAddress 上的所有工作时,只有其中一个会得到该工作。

但情况似乎并非如此。昨晚几乎同时启动 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 时,它返回缓存的结果 麻烦查阅数据库。但情况可能并非如此,我不确定我能否证明这一点。我的计划甚至可能存在根本性的缺陷,而我忽略了。

那么,实际上提出我的问题,为什么这个数据库同步例程会失败,我可以做些什么来纠正它?

【问题讨论】:

    标签: java mysql jpa concurrency synchronization


    【解决方案1】:

    多台机器可以在任何一台机器执行UPDATE 事务之前调用selectAvailable()。因此,他们可能都认为有相同的工作机会。

    您需要在selectAvailable() 调用之前开始事务,该调用应使用SELECT ... FOR UPDATE 来锁定可用的作业记录,以便在事务提交之前没有其他数据库连接可以读取它们。

    【讨论】:

    • 啊!我想我已经得到了我所缺少的东西。我期待多台机器可以选择同一个作业,并且多台机器会尝试更新它,但我假设因为只有一个 HostAddress 最终在数据库中,所以最终 SELECT 只会读回一个 HostAddress。但是我忘记了它实际上可以暂时在数据库中具有一个值,重新读取该值并开始处理,然后另一个可以进行挂起的更新,然后重新读取新值。我将进一步研究 FOR UPDATE ,但这应该可以解决问题。谢谢。
    • 我认为另一个需要注意的重要事情是,如果我理解正确的话,SELECT ... FOR UPDATE 和 UPDATE 本身需要是同一个事务的一部分。否则它将锁定行,不对它们做任何事情,并在更新之前解锁它们,此时另一台机器仍然可以突袭。
    • @HammerBro.:我的回答不是这么说的吗? "selectAvailable() 调用之前开始事务...以便锁定...直到事务提交" ;P
    • 哈哈,原来如此。我一直在从几个不同的角度考虑这个问题,所以我的脑袋有点晕。我实际上认为我找到了一种纯粹在 JPA 中执行此操作的方法(这可能翻译为 FOR UPDATE 但更合适一些);如果看起来可行,我可以在我的问题上编辑该解决方案。
    • @HammerBro.:一定要这样做。我不是 Java 开发人员,所以很可能有更多 Java 风格的方法来实现这一点。不过,原则是合理的。
    猜你喜欢
    • 2014-05-15
    • 2015-09-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-15
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多