Vert.x:如何使用阻止操作处理HttpRequest

时间:2019-02-03 01:08:32

标签: spring multithreading rest vert.x

我刚开始使用Vert.x,并且想了解在处理REST HttpRequest的过程中处理潜在的长(阻塞)操作的正确方法是什么。该应用程序本身是Spring应用程序。

这是我到目前为止拥有的简化的REST服务:

public class MainApp { 
   // instantiated by Spring
   private AlertsRestService alertsRestService;

   @PostConstruct
   public void init() {
       Vertx.vertx().deployVerticle(alertsRestService);
   }
}

public class AlertsRestService extends AbstractVerticle {
  // instantiated by Spring
    private PostgresService pgService;
    @Value("${rest.endpoint.port:8080}")
    private int restEndpointPort;

    @Override
    public void start(Future<Void> futureStartResult) {
        HttpServer server = vertx.createHttpServer();
        Router router = Router.router(vertx);
        //enable reading of the request body for all routes 
        router.route().handler(BodyHandler.create());

        router.route(HttpMethod.GET, "/allDefinitions")
              .handler(this::handleGetAllDefinitions);

        server.requestHandler(router)
            .listen(restEndpointPort, 
                result -> {
                    if (result.succeeded()) {
                        futureStartResult.complete();
                    } else {
                futureStartResult.fail(result.cause());
                    }
                }
            );
    }

  private void handleGetAllDefinitions( RoutingContext routingContext) {
        HttpServerResponse response = routingContext.response();
        Collection<AlertDefinition> allDefinitions = null;
        try {
            allDefinitions = pgService.getAllDefinitions();
        } catch (Exception e) {
            response.setStatusCode(500).end(e.getMessage());
        }           
        response.putHeader("content-type", "application/json")
            .setStatusCode(200)
            .end(Json.encodePrettily(allAlertDefinitions));
    }

}

Spring配置:

    <bean id="alertsRestService" class="com.my.AlertsRestService"
      p:pgService-ref="postgresService"
      p:restEndpointPort="${rest.endpoint.port}"
    />
    <bean id="mainApp" class="com.my.MainApp"
      p:alertsRestService-ref="alertsRestService"
    />

现在的问题是:如何正确处理对我的postgresService的(阻止)调用,如果有很多要获取/返回的项目,则可能需要更长的时间?

在研究并查看了一些示例之后,我看到了一些实现方法,但是我不完全理解它们之间的区别:

选项1 。将我的AlertsRestService转换为 Worker Verticle 并使用worker线程池:

public class MainApp { 
   private AlertsRestService alertsRestService;
        
   @PostConstruct
   public void init() {
       DeploymentOptions options = new DeploymentOptions().setWorker(true);
       Vertx.vertx().deployVerticle(alertsRestService, options);
   }
}

这里让我感到困惑的是Vert.x文档中的以下声明:“ Worker verticle实例永远不会由多个线程同时由Vert.x执行,但是可以[在不同时间]由不同线程执行“

这是否意味着对我的alertRestService的所有HTTP请求将被有效地限制为一次由一个线程依次执行?那不是我想要的:该服务是完全无状态的,应该能够很好地处理并发请求...。

所以,也许我需要看看下一个选项:

选项2。通过执行类似于文档中示例的操作将我的服务转换为多线程Worker Verticle

public class MainApp { 
   private AlertsRestService alertsRestService;
        
   @PostConstruct
   public void init() {
     DeploymentOptions options = new DeploymentOptions()
	  	.setWorker(true)
	  	.setInstances(5) // matches the worker pool size below
	  	.setWorkerPoolName("the-specific-pool")
	  	.setWorkerPoolSize(5);
     Vertx.vertx().deployVerticle(alertsRestService, options);
   }
}

因此,在此示例中-究竟会发生什么?据我了解,“ .setInstances(5)”指令意味着将创建5个“ alertsRestService”实例。我将此服务配置为Spring bean,其依赖关系由Spring框架连接。但是,在这种情况下,在我看来,这5个实例不是由Spring创建的,而是由Vert.x创建的-是这样吗?以及如何将其改为使用Spring?

选项3。使用'blockingHandler'进行路由。代码中唯一的更改是在AlertsRestService.start()方法中,该方法是我为路由器定义处理程序的方式:

boolean ordered = false;
router.route(HttpMethod.GET, "/allDefinitions")
	.blockingHandler(this::handleGetAllDefinitions, ordered);

据我了解,将“ ordered”参数设置为TRUE意味着可以同时调用该处理程序。这是否意味着该选项等效于带有多线程工作程序端点的选项#2? 有什么区别?异步多线程执行仅与一个特定的HTTP请求有关(与/ allDefinitions路径有关),而不是整个AlertsRestService Verticle?

选项4。,我发现的最后一个选项是显式使用'executeBlocking()'指令以仅在工作线程中运行包含的代码。我找不到许多如何使用HTTP请求处理实现此操作的示例,因此以下是我的尝试-可能不正确。此处的区别仅在于处理程序方法handleGetAllAlertDefinitions()的实现-但它涉及到...:

private void handleGetAllAlertDefinitions(RoutingContext routingContext)    {
  vertx.executeBlocking(
      fut -> { fut.complete( sendAsyncRequestToDB(routingContext)); },
      false,
      res -> { handleAsyncResponse(res, routingContext); }
  );
}

public Collection<AlertDefinition> sendAsyncRequestToDB(RoutingContext routingContext) {
  Collection<AlertDefinition> allAlertDefinitions = new LinkedList<>();
  try {
      alertDefinitionsDao.getAllAlertDefinitions();
  } catch (Exception e) {
      routingContext.response().setStatusCode(500)
        .end(e.getMessage());
  }
  return allAlertDefinitions;
}

private void handleAsyncResponse(AsyncResult<Object> asyncResult, RoutingContext routingContext){
  if(asyncResult.succeeded()){
      try { 
          routingContext.response().putHeader("content-type", "application/json")
            .setStatusCode(200)
            .end(Json.encodePrettily(asyncResult.result()));
      } catch(EncodeException e) {
           routingContext.response().setStatusCode(500)
             .end(e.getMessage());
      }
   } else {
      routingContext.response().setStatusCode(500)
        .end(asyncResult.cause());
   }
}

与其他选项有何不同?以及选项4是否提供了并发执行处理程序或像选项1一样的单线程?

最后,回到最初的问题:在处理REST请求时处理长时间运行的操作最合适的选择是什么?

很抱歉发布了这么长时间。...:)

谢谢!

1 个答案:

答案 0 :(得分:1)

这是一个大问题,我不确定是否可以完全解决。但是,让我们尝试一下:

在选项#1中,其实际含义是,如果使用多个相同类型的工作程序,则不应在工作程序顶点中使用ThreadLocal。仅使用一名工作人员就意味着您的请求将被序列化。

选项#2完全不正确。您不能将setInstances与类的实例一起使用,而只能使用其名称。不过,您是对的,如果您选择使用类的名称,则Vert.x将实例化它们。

选项#3的并发性比使用Workers的并发性小,因此不应使用。

选项#4 executeBlocking基本上是在执行选项#3,也很糟糕。