JAX-RS 2.0 异步服务器和客户端

一则或许对你有用的小广告

欢迎加入小哈的星球 ,你将获得:专属的项目实战 / Java 学习路线 / 一对一提问 / 学习打卡/ 赠书活动

目前,正在 星球 内带小伙伴们做第一个项目:全栈前后端分离博客项目,采用技术栈 Spring Boot + Mybatis Plus + Vue 3.x + Vite 4手把手,前端 + 后端全栈开发,从 0 到 1 讲解每个功能点开发步骤,1v1 答疑,陪伴式直到项目上线,目前已更新了 204 小节,累计 32w+ 字,讲解图:1416 张,还在持续爆肝中,后续还会上新更多项目,目标是将 Java 领域典型的项目都整上,如秒杀系统、在线商城、IM 即时通讯、权限管理等等,已有 870+ 小伙伴加入,欢迎点击围观

服务器端和客户端的异步处理是 JAX-RS 2.0 提供的新功能。乍一看,RESTful 服务中的异步处理似乎有点奇怪或非常规。通过考虑幕后真正发生的事情,将揭示 RESTful 异步处理的所有未知方面。异步处理模型是可扩展企业服务设计和实现的重要因素之一,这一点将变得更加清晰。

在同步请求/响应处理模型中,客户端连接由服务器在单个 I/O 线程中接受和处理。通常在服务器端有一个这样的 I/O 线程池。因此,当收到请求时,服务器会指定其中一个线程来接受和处理该请求。线程阻塞,直到处理完成并返回。当处理完成并将响应发送回客户端时,可以释放线程并将其发送回池中。此时为请求提供服务的容器,假设请求处理完成并且可以释放包括连接在内的所有关联资源。

如果请求处理不需要那么多时间,这个模型就可以完美地工作。如前所述,有一个线程池接受和处理传入的请求。当有大量请求并且处理繁重且耗时时,在某个时候我们会期望达到所有线程都在忙于处理并且池为空的点。此时没有更多线程可用于接受任何连接请求。

这就是异步处理模型发挥作用的时候了。异步处理模型背后的思想是分离连接接受和请求处理操作。从技术上讲就是分配两个不同的线程,一个接受客户端连接,一个处理繁重耗时的操作。在这个模型中,容器派出一个线程来接受客户端连接( acceptor ),将请求交给处理( worker )线程并释放acceptor。结果由工作线程发送回客户端。在这种机制中,客户端的连接保持打开状态。可能对性能影响不大,这样的处理模型对服务器的 吞吐量 可伸缩性 影响很大。

JAX-RS 2 async API 完美支持上述模型。考虑以下代码:


 @Stateless
@Path("/asyncresource")
public class AsynchronousResource {

@GET @Asynchronous public void asyncRestMethod(@Suspended final AsyncResponse asyncResponse) { String result = heavyLifting(); asyncResponse.resume(result); }

 private String heavyLifting() {
            return "RESULT";
  }

}

在“ AsynchronousResource ”类中定义了一个普通的 REST 资源“ asyncresource ”。此资源有一个方法“ asyncRestMethod ”,并使用“ @GET ”注释进行注释。 “ asyncRestMethod ”方法使用 @Suspended 注释注入一个“ AsyncResponse ”实例。 “ AsyncResponse ”和 @Suspended 都包含在 JAX-RS 异步 API 中。通过使用这些参数,JAX-RS 运行时被告知异步处理任何传入请求。值得一提的是 VOID 作为“asyncRestMethod”方法的返回类型。 VOID 关键字表示对于一个只接受客户端请求而不定义任何返回类型的接受线程来说是完全正常的。接受线程的唯一职责是将处理请求分派给工作线程。处理完成后,“ asyncResponse.resume(result) ”会将响应返回给客户端。

在前面的代码中,使用了 JAVA EE EJB 的异步特性。 @Asynchronous 注释告诉企业 bean 容器异步处理这个方法并充当工作线程。

所以场景如下:JAX-RS 运行时分派一个线程来接受连接。连接被接受并移交给工作线程进行后台处理。 JAX-RS 运行时释放接受器线程并将其返回到池中。然后它可以用它来接受更多的连接。

默认情况下,没有为这种所谓的繁重处理定义超时。这是“ TimeoutHandler ”事件处理程序起作用的地方。考虑以下代码:


 @Stateless
@Path("/asyncresource")
public class AsynchronousResource {

@GET @Asynchronous public void asyncRestMethod(@Suspended final AsyncResponse asyncResponse) { String result = heavyLifting(); asyncResponse.resume(result); }

 private String heavyLifting() {
            return "RESULT";
  }

}


在上面的代码中,每当繁重的操作处理时间超过 40 秒时,就会取消处理,释放资源并调用“ handleTimeout() ”方法。最后,将返回 Http 503 响应代码。为了展示 JAVA EE 为并发和异步处理支持提供的各种机制,这次使用了“ ManagedThreadFactory ”。只要在 JAVA EE 运行时环境中需要显式创建和使用线程,就会使用“ ManagedThreadFactory ”。如您所见,实际线程的创建与在普通 JAVA SE 程序中所做的一样。正如“ 托管 ”名称所示,JAVA EE 环境管理该线程的执行和生命周期。

JAX-RS 异步 API 提供的另一个有用特性是异步服务器端回调“ CompletionCallback / ConnectionCallback” 。通过使用“ CompletionCallback ”注册 AsyncResponse ,每当请求完成或失败时,都会调用 onComplete() 方法。类似地,通过使用“ ConnectionCallback ”注册 AsyncResponse ,每当与客户端的连接关闭或失败时,都会调用 onDisconnect() 方法。只要需要监视和记录运行时操作,这就会派上用场。考虑以下代码。


 @Stateless
@Path("/asyncresource")
public class AsynchronousResource {

@GET @Asynchronous public void asyncRestMethod(@Suspended final AsyncResponse asyncResponse) { String result = heavyLifting(); asyncResponse.resume(result); }

 private String heavyLifting() {
            return "RESULT";
  }

}


再次使用了一个新的并发实用程序。提交一个可运行对象,任务将异步执行。

除了服务器端异步 API 之外,JAX-RS 2.0 还支持客户端异步 API。客户端可以使用此 API 进行异步请求响应处理。考虑以下代码:



GET 在异步方法而不是请求上调用。这会将同步调用更改为异步调用。异步方法不是同步响应,而是返回一个 FUTURE 对象。通过调用 get 方法,调用会阻塞,直到响应准备就绪。只要响应准备就绪,就会返回 Future.get()

同样,在异步客户端中使用回调方法来完成异步场景。 InvocationCallback 接口提供了两个方法, completed() failed() 。只要处理完成并收到响应,就会调用 Completed 方法。如果您熟悉 FUTURE 对象,Completed 回调方法可以让您在响应准备好之前不断检查 isDone() 方法。相反,只要请求处理不成功,就会调用 Failed() 方法。考虑以下代码:


 @Stateless
@Path("/asyncresource")
public class AsynchronousResource {

@GET @Asynchronous public void asyncRestMethod(@Suspended final AsyncResponse asyncResponse) { String result = heavyLifting(); asyncResponse.resume(result); }

 private String heavyLifting() {
            return "RESULT";
  }

}


总而言之,JAX-RS 2.0 完美支持异步 API。在本文中,介绍了使用 JAX-RS 异步 API 的 各种方法和机制。异步系统设计提高了系统的可扩展性和资源划分。更高的 吞吐量 是遵循这种编程方法的显著成果之一。