我刚开始使用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请求时处理长时间运行的操作最合适的选择是什么?
很抱歉发布了这么长时间。...:)
谢谢!
答案 0 :(得分:1)
这是一个大问题,我不确定是否可以完全解决。但是,让我们尝试一下:
在选项#1中,其实际含义是,如果使用多个相同类型的工作程序,则不应在工作程序顶点中使用ThreadLocal。仅使用一名工作人员就意味着您的请求将被序列化。
选项#2完全不正确。您不能将setInstances
与类的实例一起使用,而只能使用其名称。不过,您是对的,如果您选择使用类的名称,则Vert.x将实例化它们。
选项#3的并发性比使用Workers的并发性小,因此不应使用。
选项#4 executeBlocking
基本上是在执行选项#3,也很糟糕。