非阻塞端点:将操作ID返回给调用者 - 想获得您对我的实现的意见吗?

时间:2017-08-11 18:11:45

标签: spring spring-boot

Boot Pros,

我最近开始在春季开机时编程,我偶然发现了一个问题,我希望得到你的意见。

我尝试实现的目标:

  • 我创建了一个Controller,它公开了一个名为GET的{​​{1}}端点。这个nonBlockingEndpoint执行一个非常长的操作,资源很重,可以运行20到40秒。(在附加的代码中,它被nonBlockingEndpoint模仿)
  • 每当调用Thread.sleep()时,spring应用程序应该注册该调用并立即将操作ID返回给调用者。
  • 然后,调用者可以使用此ID在另一个端点nonBlockingEndpoint上查询此操作的状态。在开始时它将被启动,并且一旦控制器完成服务reuqest,它将是诸如queryOpStatus的代码。然后,调用者知道他的请求已在服务器上成功完成。

我找到的解决方案:

  • 我有以下控制器(请注意,它显然用@Async标记)
  • 使用SERVICE_OK注册新操作已启动
  • 我使用APIOperationsManager java构造使用CompletableFuture
  • 将长时间运行的代码作为新的异步流程提供
  • 我会立即向调用者返回一个响应,告诉操作正在进行中
  • 异步任务完成后,我使用CompletableFuture.supplyAsync(() -> {}通过API Operations Manager更新操作状态

以下是代码:

cf.thenRun()

这里也是用于completness的APIOperationsManager.java:

    @GetMapping(path="/nonBlockingEndpoint")
public @ResponseBody ResponseOperation nonBlocking() {

    // Register a new operation
    APIOperationsManager apiOpsManager = APIOperationsManager.getInstance();
    final int operationID = apiOpsManager.registerNewOperation(Constants.OpStatus.PROCESSING);
    ResponseOperation response = new ResponseOperation();

    response.setMessage("Triggered non-blocking call, use the operation id to check status");
    response.setOperationID(operationID);
    response.setOpRes(Constants.OpStatus.PROCESSING);

    CompletableFuture<Boolean> cf = CompletableFuture.supplyAsync(() -> {

        try {
            // Here we will 
            Thread.sleep(10000L);
        } catch (InterruptedException e) {}

        // whatever the return value was
        return true;
    });
    cf.thenRun(() ->{ 
        // We are done with the super long process, so update our Operations Manager
        APIOperationsManager a = APIOperationsManager.getInstance();
        boolean asyncSuccess = false;

        try {asyncSuccess = cf.get();} 
        catch (Exception e) {}

        if(true == asyncSuccess) {
            a.updateOperationStatus(operationID, Constants.OpStatus.OK);
            a.updateOperationMessage(operationID, "success: The long running process has finished and this is your result: SOME RESULT" );
        } 
        else {
            a.updateOperationStatus(operationID, Constants.OpStatus.INTERNAL_ERROR);
            a.updateOperationMessage(operationID, "error: The long running process has failed."); 
        }
    });

    return response;
}

我提出的问题

  • 这个概念是否也是一个有效的概念?有什么可以改进的?
  • 我会遇到并发问题/竞争条件吗?
  • 有没有更好的方法在靴子弹簧中达到同样的效果,但我还没有找到呢? (也许使用@Async指令?)

我很乐意收到您的反馈。

非常感谢, 彼得P

1 个答案:

答案 0 :(得分:1)

使用一个请求提交长时间运行的任务是一种有效的模式,返回一个允许客户端稍后询问结果的ID。

但我建议重新考虑一些事情:

  • 不要将Integer用作id,因为它允许攻击者猜测id并获取这些id的结果。而是使用随机UUID。
  • 如果您需要重新启动应用程序,则所有ID及其结果都将丢失。您应该将它们保存到数据库中。
  • 您的解决方案无法在包含许多应用实例的群集中运行,因为每个实例只会知道其自己的应用程序。 ids和结果。这也可以通过将它们持久保存到数据库或Reddis商店来解决。
  • 使用CompletableFuture的方式使您无法控制用于异步操作的线程数。可以使用标准Java执行此操作,但我建议使用Spring配置线程池
  • 使用@Async注释控制器方法不是一个选项,这不起作用。而是将所有异步操作放入一个简单的服务中,并使用@Async对其进行注释。这有一些优点:
    • 您也可以同步使用此服务,这样可以更轻松地进行测试
    • 您可以使用Spring配置线程池
  • / nonBlockingEndpoint不应返回id,而是返回queryOpStatus的完整链接,包括id。客户端可以直接使用此链接而无需任何其他信息。

此外,还有一些您可能还想更改的低级别实施问题:

  • 不要使用Vector,它会在每次操作时同步。请改用列表。迭代List也更容易,你可以使用for循环或流。
  • 如果您需要查找值,请不要迭代Vector或List,而是使用Map。
  • APIOperationsManager是一个单例。这在Spring应用程序中没有任何意义。使它成为普通的PoJo并创建它的bean,将其自动装入控制器。默认情况下,Spring bean是单例。
  • 您应该避免在控制器方法中执行复杂的操作。而是将任何内容移动到服务中(可以使用@Async注释)。这使得测试更容易,因为您可以在没有Web上下文的情况下测试此服务

希望这有帮助。

我是否需要对数据库进行事务处理?

只要您只编写/更新一行,就不需要进行此事务处理,因为这确实是&#39; atomic&#39;。

如果您一次编写/更新多行,您应该使其成为事务性保证,所有行都被更新或者没有。

但是,如果两个操作(可能来自两个客户端)更新同一行,则最后一个操作将获胜。