在REST服务之前拥有请求队列的最佳技术解决方案(框架/方法)是什么? 这样我就可以增加REST服务实例的数量以获得更高的可用性,并将Request队列放在前面以形成服务客户端的服务/事务边界。
- 我需要Request Queue(java)的优秀轻量级技术/框架选择
- 用它实现竞争消费者的方法。
在REST服务之前拥有请求队列的最佳技术解决方案(框架/方法)是什么? 这样我就可以增加REST服务实例的数量以获得更高的可用性,并将Request队列放在前面以形成服务客户端的服务/事务边界。
根据您的目标,这里有几个问题。
首先,它只会提高后端资源的可用性。考虑是否有5台服务器在后端处理队列请求。如果其中一个服务器出现故障,则排队的请求应回退到队列中,并重新传送到其余4个服务器中的一个。
但是,当这些后端服务器正在处理时,前端服务器将继续执行实际的启动请求。如果其中一个前端服务器出现故障,那么这些连接将完全丢失,并且由原始客户端重新提交请求。
前提可能是更简单的前端系统的故障风险更低,而且对于与软件相关的故障也是如此。但网络卡,电源,硬盘驱动器等对于人类的这种虚假希望是非常不可知的,并且平等地惩罚所有人。因此,在谈论整体可用性时请考虑这一点。
至于设计,后端是一个等待JMS消息队列的简单过程,并在它们到来时处理每个消息。有很多可用的例子,任何JMS服务器都适合高级别。您所需要的只是确保消息处理是事务性的,这样,如果消息处理失败,消息将保留在队列中,并可以重新传递给另一个消息处理程序。
您的JMS队列的主要要求是可群集的。 JMS服务器本身是系统中的单点故障。丢失了JMS服务器,您的系统几乎已经死了,所以您需要能够集中服务器并让消费者和生产者适当地处理故障转移。同样,这是JMS服务器特定的,大部分是这样做的,但它在JMS世界中非常常见。
前端是事情变得有点棘手的地方,因为前端服务器是从REST请求的同步世界到后端处理器的异步世界的桥梁。 REST请求遵循典型的RPC模式,即从套接字消耗请求有效负载,保持连接打开,处理结果,并将结果传回原始套接字。
为了表明这一点,你应该看看处理Servlet 3.0的Asynchronous Servlet,它可以在Tomcat 7,最新的Jetty(不确定是什么版本),Glassfish 3.x和其他版本中使用。
在这种情况下,您要做的是当请求到达时,您将名义上同步的Servlet调用转换为异步调用 HttpServletRequest.startAsync(HttpServletRequest request, HttpServletResponse response)
。
这将返回一个AsynchronousContext,一旦启动,就允许服务器释放处理线程。然后你做几件事。
此时,初始处理完成,您只需从doGet(或服务,或其他)返回。由于您尚未调用AsyncContext.complete(),因此服务器不会关闭与服务器的连接。由于您通过ID在地图中拥有AsyncContext存储,因此暂时可以安全地保存。
现在,当您将请求提交给JMS队列时,它包含:请求的ID(您生成的),请求的任何参数以及发出请求的实际服务器的标识。最后一点非常重要,因为处理结果需要返回原点。原点由请求ID和服务器ID标识。
当您的前端服务器启动时,它还启动了一个线程,它的工作就是监听JMS响应队列。当它设置其JMS连接时,它可以设置一个过滤器,例如“仅为ABC123的ServerID提供消息”。或者,您可以为每个前端服务器创建一个唯一的队列,后端服务器使用服务器ID来确定要返回答复的队列。
当后端处理器使用消息时,它们会获取请求ID和参数,执行工作,然后获取结果并将它们放到JMS响应队列中。当它返回结果时,它会将原始ServerID和原始请求ID添加为消息的属性。
因此,如果您最初收到前端服务器ABC123的请求,后端处理器将把结果发送回该服务器。然后,当收到消息时,将通知该侦听器线程。侦听器线程任务是获取该消息并将其放到前端服务器内的内部队列中。
此内部队列由线程池支持,该线程池的作用是将请求有效负载发送回原始连接。它通过从消息中提取原始请求ID,从前面讨论的内部映射查找AsyncContext,然后将结果发送到与AsyncContext关联的HttpServletResponse来完成此操作。最后,它调用AsyncContext.complete()(或类似方法)来告诉服务器您已完成并允许它释放连接。
对于内务处理,您应该在前端服务器上有另一个线程,它的工作是检测请求在地图中等待的时间太长。部分原始邮件应该是请求开始的时间。这个线程可以每秒唤醒,扫描地图以查找请求,对于那些已经存在太长时间(例如30秒)的线程,它可以将请求放到另一个内部队列中,由一组处理程序使用,这些处理程序旨在通知请求超时的客户端。
您需要这些内部队列,以便主处理逻辑不会在客户端等待消耗数据。它可能是一个缓慢的连接或其他东西,所以你不想阻止所有其他待处理的请求逐个处理它们。
最后,您需要说明您可能会从响应队列中获取内部地图中不再存在的请求的消息。首先,请求可能已超时,因此它不应再存在。另一方面,该前端服务器可能已经停止并重新启动,因此挂起请求的内部映射将只是空的。此时,如果您检测到您对不再存在的请求有回复,则应该简单地丢弃它(好吧,记录它,然后丢弃它)。
你不能重复使用这些请求,负载均衡器不会回到客户端。如果客户端允许您通过已发布的端点进行回调,那么,确保您可以让另一个JMS消息处理程序发出这些请求。但这不是REST类的东西,在这个级别的讨论中REST更多的是客户端/服务器/ RPC。
至于哪个框架支持比原始Servlet更高级别的异步Servlet(例如Jersey for JAX-RS或类似的东西),我不能说。我不知道在那个级别支持它的框架是什么。看起来像这是泽西2.0的一个功能,还没有出来。可能还有其他人,你必须环顾四周。另外,不要注意Servlet 3.0。 Servlet 3.0只是个别容器中使用的技术的标准化一段时间(Jetty值得注意),因此您可能希望查看Servlet 3.0之外的容器特定选项。
但概念是一样的。最重要的是具有过滤的JMS连接的响应队列侦听器,到AsyncContext的内部请求映射,以及在应用程序中执行实际工作的内部队列和线程池。
如果您放松了它必须使用Java的要求,您可以考虑使用HAProxy。它非常轻巧,非常标准,可以很好地完成很多好事(请求汇集/保持活动/排队)。
但是,在实现请求排队之前请三思而后行。除非您的流量非常突发,否则它只会损害系统在负载下的性能。
假设您的系统每秒可处理100个请求。您的HTTP服务器有一个有界的工作线程池。请求池可以提供帮助的唯一方法是每秒接收超过100个请求。工作线程池已满后,请求开始堆积在负载均衡器池中。由于它们到达的速度超过了你能够处理它们的速度,因此队列变得更大......而且更大......更大。最终要么这个池填满,要么你的RAM耗尽,负载均衡器(以及整个系统)也很难崩溃。
如果您的Web服务器太忙,请开始拒绝请求并在线获得一些额外容量。
如果您能够及时获得额外的容量来处理请求,请求池肯定会有所帮助。它也会伤害你。在HTTP服务器的工作线程池前打开辅助请求池之前,请考虑后果。
我们使用的设计是一个REST接口,接收所有请求并将它们分派到消息队列(即Rabbitmq)
然后工作人员听取消息并按照某些规则执行它们。如果一切都失败了,你仍然会在MQ中得到请求,如果你有大量的请求,你可以添加工作人员......
检查这个主题演讲,它显示了这个概念的力量!