【问题标题】:How to handle asynchronous callbacks in a synchronous way in Java?如何在 Java 中以同步方式处理异步回调?
【发布时间】:2019-02-04 18:38:06
【问题描述】:

我有一个与架构相关的问题。这是一个与语言无关的问题,但由于我来自 Java 背景,如果有人以 Java 方式指导我,对我来说会更容易。

基本上,我正在编写的中间件与基于 SOAP 的第三方服务进行通信。调用是异步的——以某种方式,当调用服务时,它会返回响应 01——处理;表示第三方已成功收到请求。在原始的 SOAP 请求中,每次都必须提交一个回调 URL,由第三方实际发送结果。因此,调用特定服务实际上并不会立即返回结果。结果在中间件的一个单独的 HTTP 端点中接收。

现在在我们的前端,我们不想让用户体验复杂化。我们希望我们的用户调用一个中间件函数(通过菜单项/按钮),并立即得到结果;把脏活留给中间件。

请注意,从前端调用的中间件函数(比如说 X())和第三方推送结果的中间件端点 URL(我们称之为 Y)是完全分开的。 X() 必须等待然后获取 Y 中抓取的结果,然后将结果返回给前端。

如何构建稳健的解决方案来实现上述行为? 图片完美地描绘了我的情况。任何建议都将受到高度赞赏。

【问题讨论】:

  • 结果接收中间件如何将结果发送到原请求目标中间件? x() 和 y() 在同一个服务/应用程序中是不同的操作吗?
  • 未来和承诺。
  • @ErnestKiwele:X() 和 Y() 都是同一个中间件的一部分。 X() 接收来自 FE 的请求,并将异步调用发送给第三方。然后第三方处理请求并将结果(可以是 post 调用)返回给 Y()(这是一个端点 URL - 也在中间件中)

标签: java multithreading asynchronous architecture integration-patterns


【解决方案1】:

这个问题可能更多是关于集成模式,而不是关于多线程。但是可以使用异步调用和观察者模式的组合来编排同一应用程序/JVM 中的请求:

最好使用一个示例(利用您的 Java 知识)来完成。检查以下试图复制您的场景的简单组件:

第三方服务:它公开了一个返回相关ID的操作并开始长时间运行的执行

class ExternalService {
    public String send() {
        return UUID.randomUUID().toString();
    }
}

您的面向客户端的服务:它接收请求,调用第三方服务,然后在向结果接收器注册后等待响应:

class RequestProcessor {
    public Object submitRequest() {
        String correlationId = new ExternalService().send();

        return new ResultReceiver().register(correlationId).join();
    }
}

结果接收者:它将操作暴露给第三方服务,并维护一个内部关联注册表:

class ResultReceiver {

    Map<String, CompletableFuture<Object>> subscribers;

    CompletableFuture<Object> register(String responseId) {
        CompletableFuture<Object> future = new CompletableFuture<Object>();
        this.subscribers.put(responseId, future);

        return future;
    }

    public void externalResponse(String responseId, Object result) {
        this.subscribers.get(responseId).complete(result);
    }
}

在这种情况下,期货、承诺、回调很方便。同步由初始请求处理器完成,以强制客户端阻塞执行。

现在这可能会引发一些在这个简单的类集中没有解决的问题。其中一些问题可能是:

  • new ExternalService().send()new ResultReceiver().register(correlationId) 之间的竞争条件。这是可以在ResultReceiver 中解决的问题,如果它不知道某些响应可能非常快(可以说是 2 路等待)
  • 永远不会出现结果:结果可能需要很长时间或简单地出现错误。这些未来的 API 通常会提供超时来强制取消请求。例如:

    new ResultReceiver().register(correlationId)
        .get(10000, TimeUnit.SECONDS);
    

【讨论】:

  • 在你的例子中,Map&lt;String, CompletableFuture&lt;Object&gt;&gt; subscribers 应该被初始化并且是静态的吗?
  • @gbdcool 它肯定必须被初始化,但不需要是静态的(这取决于类的编写方式。例如,如果它是一个单例(我会这样做) ),该字段不需要是静态的)。
【解决方案2】:

那么,这样做到底有什么问题?您只需创建一个 API(中间件),它在第三方返回处理后的结果之前不会返回响应。前端向 X() 发送请求,X() 通过向 Y() 发送请求来处理该请求,然后继续轮询 Y() 以查看结果何时准备就绪,然后 X() 从 Y() 中获取结果并将其发送回前端。就像一个门面。

在使用第三方服务方面存在一些您无法控制的问题,您应该考虑这些问题。首先,您需要实现某种断路器或超时。因为第三方服务可能会挂起并且永远不会处理结果(或处理它们的时间太长以至于等待毫无意义)。此外,即使第三方服务不可用或已更新其 API 或其他原因阻止您使用它,您也应该考虑一些有意义的方式来保持网站运行。

最后只有一个想法。为什么要使已经实现异步同步的东西?这样做可能是因为它可能需要时间。长时间阻塞前端等待结果,让用户体验不愉快,UI 反应迟钝。通常最好坚持异步请求并显示他们正在处理的用户,但同时让他们做其他事情。

【讨论】:

  • 我同意,一开始让异步调用同步听起来不太对 - 但请这样想,并非所有功能都需要永远完成。第三方应该为简单的任务保留很少的同步调用,而他们没有。那么,我们为什么要让我们的客户为此受苦呢?一些琐碎的任务需要立即响应——我们实际上无法显示“您的请求正在处理中。请等待'每次。这就是问题所在。
  • 嗯,我就是这么说的。这就像外观设计模式——简化更复杂的界面。您可以这样做,但需要一些时间来考虑会出现什么问题。例如,至少为这些调用添加一些超时,如果它们没有在合理的时间内完成,则返回错误。
  • 如果我按原样处理异步功能,就会出现问题 - 我无法理解它。假设我让前端异步调用 X()。然后 X() 将调用第三方,第三方处理返回,X() 将其返回给前端。同时,3party 处理结果并将其返回给 Y。现在,没有人在听 Y,x() 已经将初始响应返回给前端。必须引入一些侦听器,以便 Y 将数据返回给实际客户端。顺便说一句,我所说的前端是指移动应用程序。
  • 是的,对于移动应用程序来说,最好是使用监听器而不阻塞界面。因为有人可能认为它崩溃了 :) 但是是的,您需要在一段时间后添加某种侦听器或轮询,或者保持某些连接打开以导致另一个线程或其他东西
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-08-22
  • 1970-01-01
  • 2012-05-14
  • 2018-09-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-17
相关资源
最近更新 更多