【问题标题】:How to handle race conditions in Web Service?如何处理 Web Service 中的竞争条件?
【发布时间】:2016-11-12 11:30:42
【问题描述】:

我用 Java Servlet 实现了一个 Web 服务。

我得到了以下设置: 有一个处理“工作”条目的数据库。每个作业都有一个状态,如“正在执行”或“队列中”或“已完成”。如果用户开始一项新工作,则会在数据库中创建一个包含工作且状态为“队列中”的条目。

仅当已执行的其他作业少于五个时才应执行该作业。如果有五个其他人已经在执行,则状态需要保持“排队”,Cronjob 将在稍后处理此作业的执行。

现在我只是想知道,如果当前执行的作业少于五个,我的脚本将执行此作业。但是,如果同时,在我的脚本询问数据库正在执行多少作业和脚本开始执行作业之间,来自另一个用户的另一个请求创建了一个作业,并且由于数据库。

然后会有一个竞争条件,将执行 6 个作业。

我怎样才能防止这样的事情发生? 有什么建议吗?非常非常感谢!

【问题讨论】:

  • 所以您没有使用 EJB(或者可能是 Spring)作为服务层?那些有内置的设施。这至少不是 servlet 的职责(它只是控制 HTTP 请求/响应)。
  • @BalusC 遗憾的是,我对整个 Java Servlets 编程非常陌生。我只有一个 Servlet,它从用户那里获取添加作业的请求。如果空闲槽可用,此 Sevlet 也会执行作业。
  • 你的流程不清楚。你说的是“脚本”。它是什么?作业执行者和作业创建者在同一个应用程序中?
  • @davidhxxx 抱歉用错了词。我只有一个 Servlet,当用户创建“作业”时会调用它。如果有可用的空闲槽,则同一个 Servlet 会执行“作业”。
  • @progNewbie,没问题。现在很清楚了:)

标签: java servlets race-condition


【解决方案1】:

如果我理解正确并且您可以控制向数据库发出请求的应用程序层,您可以使用信号量来控制谁在访问数据库。

信号量在某种程度上就像红绿灯。它们只允许访问 N 个线程的关键代码。因此,您可以将 N 设置为 5,并只允许关键代码中的线程将其状态更改为 executing 等。

Here 是一个很好的关于使用它们的教程。

【讨论】:

    【解决方案2】:

    您可以使用记录锁定来控制并发性。一种方法是执行“选择更新”查询。

    您的应用程序必须有其他存储 worker_count 的表。然后您的 servlet 必须执行以下操作:

    1. 获取数据库连接

    2. 关闭自动提交

    3. 插入状态为“IN QUEUE”的作业

    4. 执行“select worker_cnt from ... for update”查询。

    (此时执行相同查询的其他用户将不得不等到我们提交)

    1. 读取worker_cnt值

    2. 如果 worker_cnt >= 5 提交并退出。

    (此时您获得了执行作业的票,但其他用户仍在等待)

    1. 将作业更新为“正在执行”

    2. 增加worker_cnt

    3. 提交。

    (此时其他用户可以继续查询,会得到更新的worker_cnt)

    1. 执行任务

    2. 将作业更新为“已完成”

    3. 递减worker_cnt

    4. 再次提交

    5. 关闭数据库连接

    【讨论】:

      【解决方案3】:

      编辑:我现在明白你的问题了。 我做另一个回应:)

      是的,你可以有竞争条件。 您可以使用数据库锁来处理它们。 如果记录不经常以并发方式访问,请查看悲观锁。 如果记录经常以并发方式访问,请查看乐观锁。

      【讨论】:

      • 我的问题是,可以从多个用户多次调用创建和执行作业的 Servlet。也许同时。所以我不知道系统上的进程是如何安排的。如果有两个不同的作业执行请求,难道他们都看到只有 4 个作业已经在执行并开始执行他们的作业,所以总共执行了 6 个作业吗?
      【解决方案4】:

      Guy Grin 是对的,您所要求的是可以通过semaphores 解决的互斥情况。 Dijkstra 的这个构造应该可以解决你的问题。

      此构造通常用于代码,一次只能由 一个 进程执行。示例情况正是您似乎面临的情况;例如需要确保您不会遇到丢失更新或脏读的数据库事务。为什么要同时执行 5 次?当您完全允许同时执行时,您确定不会遇到这些问题吗?

      基本思想是在您的代码中有一个所谓的关键部分,必须保护它免受竞争条件的影响。需要互斥处理。这部分代码被标记为关键,在它执行之前告诉其他方也想调用它到wait()。一旦它完成了它的魔法,它就会调用notify(),并且一个内部处理程序现在允许下一个进程执行关键部分。

      但是:

      • 我强烈建议不要自己实施ANY互斥处理方法。在几年前的理论计算机科学课程中,我们在操作系统级别分析了这些结构,并证明了可能出现的问题。即使乍一看看起来很简单,但它的含义远不止表面上所见,而且根据语言的不同,如果你自己做的话,真的很难把它做好。尤其是在 Java 和相关语言中,您无法控制底层 VM 正在做什么。取而代之的是预先实现的开箱即用的解决方案,这些解决方案已经过测试并被证明是正确的。

      • 在生产环境中处理互斥之前,请先阅读一些相关内容,并确保了解它的含义。例如。 The Little Book of Semaphores 写得很好,很好读。至少看一眼。

      我不太确定 Java Servlets,但 Java 确实有一个开箱即用的互斥解决方案,在一个名为 synchronized 的关键字中标记代码中不允许同时执行的关键部分几个过程。不需要外部库。

      this 之前关于 SO 的帖子中提供了一个很好的示例代码。虽然已经说明了,但让我提醒您,如果您处理多个生产者/消费者,请真正使用notifyAll(),否则会发生奇怪的事情,并且会在饥饿中旋转的疯狂过程会杀死您的猫。

      关于这个主题的另一个更大的教程可以找到here

      【讨论】:

        【解决方案5】:

        正如其他人所回应的那样,这种情况需要信号量或互斥量。我认为您可能需要小心的一个领域是,权威的 Mutex 位于何处。根据具体情况,您可能有几种不同的最佳解决方案(权衡安全性与性能/复杂性):

        a) 如果您只有一个服务器(非集群),并且修改数据库的唯一用例是通过您的 Servlet,那么您可以实现一个静态内存互斥锁(一些您可以同步的通用对象访问反对)。这将对性能的影响最小,并且最容易维护(因为所有相关代码都在您的项目中)。此外,它不依赖于您使用的特定数据库的特性。它还允许您锁定对非数据库对象的访问。

        b) 如果您有多个单独的服务器,但它们都是您的代码的实例,您可以实现一个同步服务,它允许特定实例在允许之前获得锁(可能有超时)更新数据库。这会稍微复杂一些,但所有逻辑仍将驻留在您的代码中,并且该解决方案将可跨数据库类型移植。

        c) 如果您的数据库可以由您的服务器或不同的后端进程(例如 ETL)更新,那么唯一的方法是在数据库中实现记录级锁定。如果您这样做,您将依赖于您的数据库提供的特定类型的支持,并且如果您碰巧移植到不同的数据库,则可能需要进行更改。在我看来,这是最复杂、最难维护的选项,只有在 c) 的条件明确为真时才应采用它。

        【讨论】:

          【解决方案6】:

          答案隐含在您的问题中:您的请求必须排队,因此与生产者和消费者建立一个先进先出队列。

          servlet 总是在队列中添加作业(可以选择检查是否已满),另外 5 个线程将每次提取一个作业,如果队列为空则休眠。

          不需要为此使用 cron 或 mutex,只需记住同步队列,否则消费者可能会提取相同的作业两次。

          【讨论】:

            【解决方案7】:

            在我看来,即使您不使用 ExecutorService,如果您始终更新数据库并从单线程启动作业,那么实现您的逻辑也是最容易的。您可以在队列中安排作业的执行,并让一个线程执行并将数据库状态更新为正确的形式。

            如果您想控制正在执行的作业的数量。一种方法是使用 ExecutorsService,FixedThreadPool 为 5。这样您就可以确定一次只会执行 5 个作业,不会再执行...所有其他作业都将在 ExecutorService 中排队。

            我的一些同事会向您指出低级并发 API。我相信这些并不是为了解决一般的编程问题。无论您决定做什么尝试使用更高级别的 API,不要深入研究细节。大多数低级的东西已经在现有的框架中实现了,我怀疑你会做得更好。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2019-06-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-02-04
              • 1970-01-01
              • 2011-04-01
              • 2014-02-23
              相关资源
              最近更新 更多