【问题标题】:Request Queue in front of a REST ServiceREST 服务前的请求队列
【发布时间】:2012-12-17 12:44:06
【问题描述】:

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

  1. 我需要为请求队列 (java) 选择良好且轻量级的技术/框架
  2. 用它实现竞争消费者的方法。

【问题讨论】:

  • 你可以使用负载均衡器吗?
  • 到目前为止你发现了什么?
  • 负载均衡器保证更高的周转率/吞吐量,但我的意图是提高可用性,正如@Subin 正确提到的那样

标签: java rest soa


【解决方案1】:

我们使用的设计是一个 REST 接口接收所有请求并将它们分派到消息队列(即 Rabbitmq)

然后工作人员听取消息并按照一定的规则执行它们。如果一切都失败了,您仍然会在 MQ 中收到请求,如果您有大量请求,您可以添加工作人员...

查看这个主题演讲,它展示了这个概念的力量!

http://www.springsource.org/SpringOne2GX2012

【讨论】:

  • 但是请求/响应模型如何适合这里?它是否适合同步 req/resp 模型(从客户端的角度来看)?或者客户端需要轮询等待/侦听(事件)以获取响应?
  • 在 REST 中有多个选项。即如果我调用服务并且请求还不能被处理,服务器返回:202 Accepted。这意味着«请求已被接受处理,但处理尚未完成。»
【解决方案2】:

如果您放宽了必须使用 Java 的要求,您可以考虑使用 HAProxy。它非常轻量级,非常标准,并且做了很多好事(请求池/keepalives/排队)。

不过,在实施请求队列之前请三思。除非您的流量非常突发,否则只会损害系统在负载下的性能。

假设您的系统每秒可以处理 100 个请求。您的 HTTP 服务器有一个有限的工作线程池。请求池可以提供帮助的唯一方法是每秒接收超过 100 个请求。在您的工作线程池已满后,请求开始堆积在您的负载均衡器池中。由于它们到达的速度比您处理它们的速度快,因此队列变得越来越大……越来越大……越来越大。最终要么这个池也被填满,要么你的 RAM 用完,负载均衡器(以及整个系统)严重崩溃。

如果您的网络服务器太忙,请开始拒绝请求并在线获取一些额外容量。

如果您能够及时获得额外的容量来处理请求,请求池肯定会有所帮助。它也会对你造成非常严重的伤害。在您的 HTTP 服务器的工作线程池之前打开辅助请求池之前,请考虑后果。

【讨论】:

    【解决方案3】:

    这里有几个问题,具体取决于您的目标。

    首先,它只提升后端资源的可用性。考虑是否有 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,一旦启动,服务器就可以释放处理线程。然后你做几件事。

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

    此时,初始处理已完成,您只需从 doGet(或服务或其他)返回。由于您没有调用 AsyncContext.complete(),因此服务器不会关闭与服务器的连接。由于您通过 ID 将 AsyncContext 存储在地图中,因此暂时可以方便地进行安全保存。

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

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

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

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

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

    对于内务管理,您应该在前端服务器上设置另一个线程,该线程的工作是检测请求何时在映射中等待太久。原始消息的一部分应该是请求开始的时间。该线程可以每秒唤醒一次,扫描地图中的请求,对于任何存在时间过长(例如 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 的内部请求映射,以及在应用程序中执行实际工作的内部队列和线程池。

    【讨论】:

    • 如果我必须运行 5 个连接到队列的微服务(嵌入式容器/码头),以便它们相互竞争(以获得更好的吞吐量)并且我的客户端库只负责直到队列(请求/响应)
    • 简而言之,必须通过使用随每个服务提供的客户端库来隐藏异步交互,我正在为该客户端库寻找一种技术选择,以调解来自服务客户端的 HTTP 请求/响应。
    • 我不明白。进行 REST 调用的客户端不会是异步的,REST 不是基于异步的系统。
    • 不,客户端正在进行 httpcall,但处理是异步的(即队列),客户端库需要通过轮询/回调隐藏这种异步行为 - 这就是我的意思
    • 一直在寻找更具体的解决方案,但可以理解问题的性质以提供多种方法和答案。谢谢
    猜你喜欢
    • 2010-09-17
    • 2019-09-06
    • 2012-12-12
    • 1970-01-01
    • 1970-01-01
    • 2015-04-19
    • 2016-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多