java - REST 服务前的请求队列

标签 java rest soa

在 REST 服务前设置请求队列的最佳技术解决方案(框架/方法)是什么?
这样我就可以增加 REST 服务实例的数量以获得更高的可用性,并通过将请求队列放在前面以形成服务客户端的服务/事务边界。

  • 我需要为请求队列 (java) 选择好的轻量级技术/框架
  • 使用它实现竞争消费者的方法。
  • 最佳答案

    这里有几个问题,这取决于你的目标。

    首先,它只促进后端资源的可用性。考虑是否有 5 个服务器在后端处理队列请求。如果其中一台服务器出现故障,则排队的请求应回落到队列中,并重新发送到其余 4 台服务器中的一台。

    然而,当这些后端服务器正在处理时,前端服务器会保留实际的发起请求。如果这些前端服务器之一出现故障,则这些连接将完全丢失,由原始客户端重新提交请求。

    前提可能是更简单的前端系统发生故障的风险较低,这对于与软件相关的故障来说当然是正确的。但是网卡、电源、硬盘驱动器等对人类的这种虚假希望完全不可知,并且一视同仁。因此,在谈论整体可用性时,请考虑这一点。

    就设计而言,后端是一个简单的过程,它等待一个 JMS 消息队列,并在每条消息到来时对其进行处理。有很多可用的示例,任何 JMS 服务器都适用于高级别。您所需要的只是确保消息处理是事务性的,这样如果消息处理失败,消息仍会保留在队列中,并且可以重新传送到另一个消息处理程序。

    您的 JMS 队列的主要要求是可集群化。 JMS 服务器本身是系统中的单点故障。失去了 JMS 服务器,您的系统几乎完全陷入困境,因此您需要能够对服务器进行集群并让消费者和生产者适本地处理故障转移。同样,这是特定于 JMS 服务器的,大多数都这样做,但它在 JMS 世界中很常见。

    前端是事情变得有点棘手的地方,因为前端服务器是从 REST 请求的同步世界到后端处理器的异步世界的桥梁。 REST 请求遵循典型的 RPC 模式,即使用来自套接字的请求有效负载、保持连接打开、处理结果并将结果传递回原始套接字。

    要证明这一点,您应该查看引入的处理 Servlet 3.0 的异步 Servlet,并且在 Tomcat 7、最新的 Jetty(不确定哪个版本)、Glassfish 3.x 和其他版本中可用。

    在这种情况下,您要做的是在请求到达时,使用 HttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response) 将名义上同步的 Servlet 调用转换为异步调用。 .

    这将返回一个 AsynchronousContext,并且一旦启动,就允许服务器释放处理线程。然后你做几件事。

  • 从请求中提取参数。
  • 为请求创建唯一 ID。
  • 根据您的参数创建新的后端请求负载。
  • 将 ID 与 AsyncContext 关联,并保留上下文(例如将其放入应用程序范围的 Map)。
  • 将后端请求提交到 JMS 队列。

  • 此时,初始处理完成,您只需从 doGet(或服务,或其他)返回。由于您尚未调用 AsyncContext.complete(),服务器不会关闭与服务器的连接。由于您通过 ID 在 map 中存储了 AsyncContext,因此暂时安全保存起来很方便。

    现在,当您向 JMS 队列提交请求时,它包含:请求的 ID(您生成的)、请求的任何参数以及发出请求的实际服务器的标识。最后一点很重要,因为处理的结果需要返回到它的原点。源由请求 ID 和服务器 ID 标识。

    当您的前端服务器启动时,它还启动了一个线程,该线程的工作是监听 JMS 响应队列。当它建立它的 JMS 连接时,它可以建立一个过滤器,例如“只给我一个服务器 ID 为 ABC123 的消息”。或者,您可以为每个前端服务器创建一个唯一的队列,后端服务器使用服务器 ID 来确定将回复返回到的队列。

    当后端处理器使用消息时,它们会获取请求 ID 和参数,执行工作,然后获取结果并将它们放入 JMS 响应队列。当它返回结果时,它将添加原始 ServerID 和原始请求 ID 作为消息的属性。

    因此,如果您最初收到前端服务器 ABC123 的请求,后端处理器会将结果寻址回该服务器。然后,该监听器线程将在收到消息时收到通知。监听器线程的任务是接收该消息并将其放入前端服务器的内部队列中。

    这个内部队列由一个线程池支持,该线程池的工作是将请求有效负载发送回原始连接。它通过从消息中提取原始请求 ID,从前面讨论的内部映射中查找 AsyncContext,然后将结果向下发送到与 AsyncContext 关联的 HttpServletResponse 来完成此操作。最后,它调用 AsyncContext.complete()(或类似的方法)告诉服务器你已经完成并允许它释放连接。

    对于内务管理,您应该在前端服务器上有另一个线程,它的工作是检测请求何时在 map 中等待的时间过长。原始消息的一部分应该是请求开始的时间。这个线程可以每秒唤醒一次,扫描 map 上的请求,对于任何已经存在太长时间(比如 30 秒)的请求,它可以将请求放到另一个内部队列中,由一组处理程序消耗,这些处理程序旨在通知客户端请求超时。

    您需要这些内部队列,以便主处理逻辑不会卡在等待客户端使用数据。这可能是连接缓慢或其他原因,因此您不想阻止所有其他未决请求来一一处理它们。

    最后,您需要考虑到您很可能会从响应队列中获得一条消息,用于请求不再存在于您的内部映射中。一方面,请求可能已超时,因此它不应再存在。另一方面,该前端服务器可能已停止并重新启动,因此挂起请求的内部映射将是空的。此时,如果您检测到对不再存在的请求的回复,您应该简单地丢弃它(好吧,记录它,然后丢弃它)。

    你不能重用这些请求,真的没有负载均衡器回到客户端这样的事情。如果客户端允许您通过已发布的端点进行回调,那么请确保您可以让另一个 JMS 消息处理程序发出这些请求。但这不是 REST 类型的事情,在这个级别的讨论中 REST 更像是客户端/服务器/RPC。

    至于哪个框架在比原始 Servlet 更高的级别上支持异步 Servlet(例如 JAX-RS 的 Jersey 或类似的东西),我不能说。我不知道在那个级别支持它的框架是什么。好像这是 Jersey 2.0 的一个特性,还没有出来。可能还有其他人,你必须环顾四周。另外,不要专注于 Servlet 3.0。 Servlet 3.0 只是一段时间(尤其是 Jetty)在单个容器中使用的技术的标准化,因此您可能需要查看 Servlet 3.0 之外的容器特定选项。

    但是概念是一样的。最大的收获是带有过滤的 JMS 连接的响应队列监听器、到 AsyncContext 的内部请求映射以及在应用程序中执行实际工作的内部队列和线程池。

    关于java - REST 服务前的请求队列,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14134086/

    相关文章:

    Java 流 : Grouping, 求和和计数

    Java Jackson 反序列化功能

    java - Spring Boot 和 Jackson : Define which fields to serialize on external class

    java - 我的 Rest API 层的 Vertx 模型绑定(bind)

    具有多个模块的 Grails/IntelliJ 设置

    java - Spring Roo - 未找到命令 'service'

    java - 使用 SwingWorker 向 jTable 添加行并更新 GUI

    java - 用于将多个请求参数转换为对象的 Spring 转换器或属性编辑器?

    从命令行进行 Rest API 测试

    agile - SOA 和敏捷方法论是互补的吗?