使用tomcat和spring处理异步http请求

Gid*_*eon 8 java multithreading tomcat spring-mvc asynccontroller

这是我的第一个问题所以请耐心等待我:)

我正在尝试创建一个服务:

  1. 接收包含要查询的URL的HTTP GET请求
  2. 对于单个GET请求,服务将提取URL
  3. 查询有关URL的本地数据库
  4. 如果在DB中找到了结果,它会将其返回给客户端,如果没有,则需要查询一些外部服务(可能需要相对较长的时间才能响应)
  5. 将URL的结果返回给客户端

我在虚拟机和Tomcat7上使用spring运行它.我会事先道歉并提到我对Tomcat很新

无论如何,我期待很多并发GET请求到这个服务(成千上万的同时请求)我基本上想要实现的是尽可能地使这个服务可扩展(如果这是不可能的那么至少一个可以处理数十万个并发请求的服务)

我一直在阅读A LOT关于服务中的异步请求处理,特别是在Tomcat中,但我有些事情对我来说仍然不清楚:

  1. 从官方tomcat网站看来,Tomcat似乎包含许多接受者线程和工作线程数.如果是这样,我为什么要使用AsyncContext?什么是释放Tomcat的工作线程并在我的应用程序中占用不同的线程以执行完全相同的操作的好处?(系统中仍有1个活动线程)
  2. 有点类似于第一个问题,但创建AsyncContext并将其与不同的线程一起使用有什么好处?(来自我的应用程序中创建的线程池的线程)
  3. 关于同样的问题,我在这里看到我也可以返回一个Callable或DeferredResult,并用Tomcat的一个线程或我自己的一个线程处理它.返回Callable或使用DeferredResult而不仅仅是处理来自请求的AsyncContext有什么好处吗?
  4. 另外,如果我决定返回一个可调用的,Tomcat从哪个线程池获取线程来处理我的callable?这里使用的线程是否与我之前提到的Tomcat相同的工作线程?如果是这样,我发布一个Tomcat工作线程并使用另一个工作线程可以获得哪些好处?
  5. 我从Oracle的文档中看到,我可以向AsyncContext传递一个将同时处理的Runnable对象,用于执行此Runnable的线程来自何处?我有控制权吗?另外,将AsyncContext传递给Runnable而不仅仅是将AsyncContext传递给我的一个线程有什么好处?

我为同样的问题提出了很多问题而道歉,但是我和我的同事们争论这些事情一个多星期没有任何具体的答案.

我还有一个更普遍的问题:您认为使我描述的服务具有可扩展性的最佳方法是什么?(暂时不要添加更多机器),您是否可以针对目标解决方案发布任何示例或参考?

我发布了更多我一直在关注的链接链接,但我目前的声誉不允许.我将非常感谢任何可以理解的参考文献或具体的例子,我很乐意澄清任何相关问题

干杯!

Tim*_*eau 5

这里面有很多问题,但我会尝试解决其中的一些问题。

异步 I/O 是一件好事,尤其是在服务大量请求的服务器上 - 它允许您使用更少的线程来处理更多的请求。对于您正在编写的代理,您确实希望 HTTP 客户端(向外部 URL 发出请求)也是异步的,这样处理请求或接收远程响应都不会涉及阻塞 I/O。

也就是说,与使用像Netty这样从头开始异步的框架相比,使用 Tomcat 或 Java EE 服务器做这些事情可能会更困难,因为这些服务器事后才添加了异步 I/O 。作为构建在 Netty 之上的框架的作者,我有点偏见。

为了演示您只需要很少的代码即可完成您所描述的操作,我编写了一个小型服务器,它在 3 个 Java 源文件中执行您在此处描述的操作,并将其放在 github 上- 它构建了一个独立的 JAR,您可以运行java -jar它来尝试一下,我试图清晰地评论它。

归根结底,网络应用程序大部分时间都在等待 I/O 发生。特别是在代理的情况下,使用传统的线程 I/O,您将​​收到一个请求,并且接收请求的线程将负责同步应答它- 这意味着,如果它必须向在另一台服务器上,该线程被阻塞,等待来自远程服务器的答案。这意味着该线程不能用于其他任何用途。因此,如果您有 10 个线程,并且所有线程都在等待响应,则您的服务器将无法再应答任何请求,直到其中一个线程完成并释放线程为止。使用异步 I/O,当某些 I/O 完成时您会收到回调。换句话说,当有事情要做时(例如从代理请求到达的响应),您的代码不会停滞不前,直到操作系统将数据刷新到套接字并从网卡中取出。当您的代码等待该 HTTP 请求完成时,发送代理请求的线程可以自由地用于处理另一个请求,这意味着一个线程可以对一个请求做一些工作,对另一个请求做一些工作,然后再对另一个请求做一些工作。 ,最终完成第一个请求。由于线程是操作系统提供的有限资源,因此这允许您用更少的硬件做更多的事情。

至于Callablevs. DeferredResult,使用 aCallable只是在工作发生时移动(Callable稍后在某个线程或其他线程上执行,但仍然期望同步返回结果); DeferredResult听起来更像是您需要的,因为这允许您的代码运行并执行它想要的任何工作,然后在需要设置某些内容时设置结果(触发响应的完成)。

老实说,我认为如果你想真正有效地实现这一点,你最好远离 Java EE 堆栈 - 其中很多内容都假设 I/O 是同步的,因此尝试用它做异步事情是很困难的向上游游(例如,JDBC 的骨子里就是同步 I/O - 如果你真的希望它能够扩展并且你想使用 SQL 数据库,那么你最好使用这样的东西)。

有关使用Netty进行此类操作的另一个示例,请参阅tiny-maven-proxy项目 - 代码不太漂亮,但它显示了一个执行 HTTP 代理的示例,其中响应正文按块馈送到客户端 -块,当它到达时 - 所以你永远不会真正将完整的响应主体拉入内存,这意味着即使具有巨大响应的请求也不会耗尽内存来运行代理。Tiny-maven-proxy 还在文件系统上缓存。我在演示中没有做这些事情,因为这会让代码变得更加复杂。