9

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

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

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

我在虚拟机和 Tomcat7 上使用 spring 运行它。我会提前道歉并提到我对Tomcat很陌生

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

我一直在阅读很多关于服务中的异步请求处理的内容,尤其是在 Tomcat 中,但我有一些我仍然不清楚的事情:

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

我很抱歉就同样的事情问了这么多问题,但我和我的同事们为这些事情争论了一个多星期,没有任何具体的答案。

我有 1 个更普遍的问题:您认为使我描述的服务可扩展的最佳方式是什么?(暂时不考虑添加更多机器),您能否发布任何示例或参考以用于特定的解决方案?

我会发布更多我一直在查看的链接链接,但我目前的声誉不允许这样做。对于任何可以理解的参考资料或具体示例,我将不胜感激,我显然很乐意澄清任何相关问题

干杯!

4

1 回答 1

4

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

异步 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,使用 a Callablejust 在工作发生时移动(Callable稍后在某个线程或其他线程上执行,但仍有望同步返回结果); DeferredResult听起来更像您需要的,因为这允许您的代码执行它想要的任何工作,然后在需要设置时设置结果(触发响应的完成)。

Honestly, I think if you want to implement this really efficiently, you'd be better off staying away from the Java EE stack - so much of it has baked in assumptions that I/O is synchronous that trying to do async stuff with it is swimming upstream (for example, JDBC has synchronous I/O baked into its bones - if you really want this to scale and you want to use an SQL database, you'd be better off with something like this ).

For another example of using Netty for this sort of thing, see the tiny-maven-proxy project - the code is less pretty, but it shows an example of doing an HTTP proxy where the response body is fed to the client chunk-by-chunk, as it arrives - so you never actually pull the full response body into memory, meaning even requests with huge responses won't run the proxy out of memory. Tiny-maven-proxy also caches on the filesystem. I didn't do those things in the demo because it would have made the code more complicated.

于 2015-02-07T05:01:43.253 回答