【问题标题】:How to prevent concurrency in web service API?如何防止 Web 服务 API 中的并发性?
【发布时间】:2010-12-19 03:37:52
【问题描述】:

我们有三个 Web 服务(/a/b/c),其中每个服务映射到一个单独的 Java 类(ClassAClassBClassC)中的一个方法(go()) )。

只能同时运行一项服务(即:/b 不能在 /a 运行时运行)。然而,由于这是一个 REST API,因此没有什么可以阻止客户端请求同时运行的服务。

在服务器上强制服务同时运行的最好和最简单的方法是什么?


更新:这是一个内部应用,我们不会有很大的负载,只有一个应用服务器。

更新:这是一个主观问题,因为您可以对影响最终答案的一般应用程序设计提出不同的论点。接受了过度思考的答案,因为我发现它最有趣且最有帮助。

【问题讨论】:

  • @Marcus:它很可能是一个内部应用程序,但在这个阶段设计具有内置限制的东西是一个坏主意。您可能认为它永远不需要扩展,但您能确定吗?以后不用再头疼了,坚持一些最佳做法!

标签: java web-services api rest concurrency


【解决方案1】:

首先,在不了解您的架构的情况下,如果您必须对 WebService 层实施并发限制,您可能会遇到问题。虽然您可以使用传统的锁等来对两个服务之间的请求进行序列化,但是当您添加第二个 Web 层来扩展您的解决方案时会发生什么?如果锁是 web 层的本地锁,它们将几乎没用。

我猜想在 Web 服务下面可能有某种类型的层,在这里您需要强制执行这些限制。如果客户端 B 在客户端 A 发出冲突请求后进来,则后端应在发现状态已更改时拒绝该请求,然后您应向第二个客户端返回 409。最后竞争条件仍然可能,但你必须让你的最低公共层保护你免受冲突请求。

【讨论】:

    【解决方案2】:

    您可以使用某种信号量来保持跨服务串行访问。

    【讨论】:

      【解决方案3】:

      假设只强制 Web 服务器只有一个侦听线程服务请求是不行的......我想我只是使用静态锁(ReentrantLock 可能,为了清楚起见,虽然你可以同步任何共享对象,真的):

      public class Global {
        public static final Lock webLock = new ReentrantLock();
      }
      
      public class ClassA {
          public void go() {
              Global.webLock.lock()
              try {
                  // do A stuff
              } finally {
                  Global.webLock.unlock()
              }
          }
      }
      
      public class ClassB {
          public void go() {
              Global.webLock.lock()
              try {
                  // do B stuff
              } finally {
                  Global.webLock.unlock()
              }
          }
      }
      
      public class ClassC {
          public void go() {
              Global.webLock.lock()
              try {
                  // do C stuff
              } finally {
                  Global.webLock.unlock()
              }
          }
      }
      

      【讨论】:

      • @overthink:当他想要更多服务层级时会发生什么?如果在这些层上实施锁,此解决方案将无法扩展。
      • @overthink:所以你是说你也可以使用synchronized(sharedObject) {...} 块而不是Global.webLock.lock()/unlock() 语句?
      • @Marcus:是的,您可以使用同步而不是锁定/解锁。例如在我上面的示例中,您可以将 webLock 设为简单的 Object 并改用 synchronized(webLock) {...}
      • @jkp:我不是想给出设计建议;只是回答被问到的问题。没错,如果涉及多个 Web 服务器,这将不起作用。
      【解决方案4】:

      你的设计有缺陷。服务应该是幂等的。如果您拥有的类不支持,请重新设计它们,直到它们支持为止。听起来这三种方法中的每一种都应该是服务的基础,而不是类。

      【讨论】:

      • 我能看到阻止 A、B 和 C 类中的 go() 方法同时运行的唯一原因是它们共享某些东西。如果那是类数据或数据库数据,您应该尝试重新设计,以便它们不共享任何内容。这将允许它们同时运行而不必担心干扰。如果这不可能,那么您应该有一项服务以正确的顺序调用它们,以确保它们不会被乱序调用。无论哪种情况,我都认为你的设计有缺陷,治疗会比疾病更糟糕。
      • 如果可以的话+100。设计中某处存在大问题,信号量、锁等不是解决这个问题的方法。
      • 要点..整个过程涉及财务计算。 C 是一天结束时运行的最终计算。 A 上传一些可以随时完成的设置,B 是中间的一组计算,在中午完成。因此,这三个功能的正常操作永远不会同时发生。但为了彻底起见,我们希望确保它们不会同时发生。
      • 在我看来,A 是这里唯一的服务。另外两个听起来更像是在特定时间启动的批处理作业。您对这种方法的唯一关注是确保他们在指定的窗口内完成。如果这是一个公平的总结,我建议查看 Spring Batch:static.springsource.org/spring-batch
      • "...REST API..." - 不要将无 SOAP 的 Web 服务与真正的 REST 方法混淆。如果 REST 是根据无状态 HTTP 协议建模的,那么您的服务就不值得称谓——它们不是无状态的。
      【解决方案5】:

      为什么不使用超媒体来限制访问?

      使用类似的东西,

      POST /A
      

      启动第一个进程。完成后,结果应提供一个链接以启动第二个过程,

      <ResultsOfProcessA>
        <Status>Complete</Status>
        <ProcessB href="/B"/>
      </ResultsOfProcessA>
      

      按照链接启动第二个进程,

      POST /B
      

      然后重复 C 部分。

      可以说,行为不端的客户端可能会缓存到步骤 B 的链接,并尝试在将来的某个请求中重新使用它以规避该序列。但是,在执行步骤 A 时分配某种令牌并要求将令牌传递给步骤 B 和 C 以防止客户端手动构造 URL,这并不难。

      进一步阅读您的 cmets,您似乎遇到了一种情况,即 A 可以在 B 之前或之后运行。在这种情况下,我建议创建一个资源 D 来表示整个进程集的状态(A,B和 C)。当客户端检索 D 时,它会显示允许它遵循的 URI。一旦客户端启动了 A 进程,则 D 资源应在处理期间移除 B 链接。当 B 在 A 之前启动时,应该会发生相反的情况。

      这种技术的另一个优点是,如果 A 或 B 已经运行了一天,因为状态可以显示在 D 中,所以很明显。一旦 A 和 B 已经运行,那么 D 可以包含 C 的链接。

      超媒体不是 100% 万无一失的解决方案,因为您可能有两个客户端具有相同的 D 副本,并且两者都可能认为进程 A 尚未运行,并且两者都可能尝试同时运行它。这可以通过在 D 上设置某种“上次修改”时间戳来解决,并且只要 D 的状态发生变化,您就可以更新该时间戳。这可以允许稍后的请求被拒绝。根据您对场景的描述,这似乎更像是一种边缘情况,超媒体会捕获大多数并行运行进程的尝试。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-04-02
        • 1970-01-01
        • 1970-01-01
        • 2021-12-07
        • 2018-04-20
        • 2019-03-06
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多