每个连接的线程与每个请求的线程有啥区别?

Posted

技术标签:

【中文标题】每个连接的线程与每个请求的线程有啥区别?【英文标题】:What is the difference between thread per connection vs thread per request?每个连接的线程与每个请求的线程有什么区别? 【发布时间】:2013-02-19 11:36:20 【问题描述】:

您能否解释一下已在各种 servlet 实现中实现的两种方法:

    每个连接的线程 每个请求的线程

以上两种策略中哪一种更适合扩展?为什么?

【问题讨论】:

***.com/questions/7457190/… - 重复? 【参考方案1】:

以上两种策略中哪一种更适合扩展?为什么?

每个请求的线程比每个连接的线程更好。

Java 线程相当昂贵,通常每个使用 1Mb 内存段,无论它们是活动的还是空闲的。如果您为每个连接提供自己的线程,则该线程通常会在连接上的连续请求之间处于空闲状态。最终,框架需要停止接受新连接(因为它无法创建更多线程)或开始断开旧连接(如果/当用户醒来时,这会导致连接中断)。

由于 TCP/IP 的工作方式,HTTP 连接需要的资源比线程堆栈少得多,尽管每个 IP 地址有 64K 个打开连接的限制。

相比之下,在每个请求线程模型中,线程仅在处理请求时关联。这通常意味着服务需要更少的线程来处理相同数量的用户。而且由于线程使用大量资源,这意味着服务将更具可扩展性。

(请注意,thread-per-request 并不意味着框架必须关闭 HTTP 请求之间的 TCP 连接...)


话虽如此,当每个请求的处理过程中存在长时间的停顿时,每个请求的线程模型并不理想。 (当服务使用comet 方法时,这尤其不理想,这涉及长时间保持回复流打开。)为了支持这一点,Servlet 3.0 规范提供了一种“异步 servlet”机制,它允许 servlet 的请求方法来挂起它与当前请求线程的关联。这会释放线程去处理另一个请求。

如果 Web 应用程序可以设计为使用“异步”机制,那么它可能比每个请求线程或每个连接线程更具可扩展性。


跟进

让我们假设一个包含 1000 张图片的网页。这会产生 1001 个 HTTP 请求。进一步让我们假设使用了 HTTP 持久连接。使用 TPR 策略,这将导致 1001 个线程池管理操作 (TPMO)。使用 TPC 策略,这将产生 1 个 TPMO...现在根据单个 TPMO 的实际成本,我可以想象 TPC 可能比 TPR 更好地扩展的场景。

我认为有些事情你没有考虑到:

Web 浏览器需要获取大量 URL 以完成一个页面,很可能会打开多个连接。

使用 TPC 和持久连接,线程必须等待客户端接收响应并发送下一个请求。如果网络延迟很高,则此等待时间可能很长。

服务器无法知道何时可以关闭给定(持久)连接。如果浏览器不关闭它,它可能会“徘徊”,束缚 TPC 线程,直到服务器超时连接。

TPMO 开销并不大,尤其是当您将池开销与上下文切换开销分开时。 (您需要这样做,因为 TPC 会在持久连接上引发上下文切换;见上文。)

我的感觉是,这些因素可能会超过每个连接专用一个线程所节省的 TPMO。

【讨论】:

您写了“(请注意,每个请求的线程并不意味着框架必须在每个请求后关闭 HTTP 连接......)”。所以当稍后重用连接时,确实同一个线程连接到它还是不同的线程? "Thread-per-request 比 thread-per-connection 更好地扩展。" ...如果有一个显着数字单个 HTTP 连接中提供的 HTTP 请求?对于这种情况,我可以想象,每个请求线程策略由于线程池管理开销而导致大量服务器负载,而每个请求连接策略不会有这个开销。 我不明白你在说什么。这个问题是关于每个请求的线程与每个连接的线程。如果您限制一个连接只能发出一个请求(即非持久连接),那么每个请求的线程和每个连接的线程具有相同的特征。 @StephenC 假设一个网页包含 1000 张图片。这会产生 1001 个 HTTP 请求。进一步假设使用了 HTTP 持久连接。使用 TPR 策略,这将导致 1001 个线程池管理操作 (TPMO)。使用 TPC 策略,这将导致 1 个 TPMO... 现在根据单个 TPMO 的实际成本,我可以想象 TPC 可能比 TPR 更好地扩展的场景。但也许我错过了一点? @serg.nechaev - 这是一个具体的参考:oracle.com/technetwork/java/javase/tech/… ... 并寻找“ThreadStackSize”【参考方案2】:

HTTP 1.1 - 支持持久连接,这意味着可以使用同一个 HTTP 连接接收/发送多个请求/响应。 因此,要并行运行使用相同连接接收到的请求,会为每个请求创建一个新的Thread

HTTP 1.0 - 在这个版本中,使用连接只接收到一个请求,并且在发送响应后连接被关闭。所以只为一个连接创建了一个线程。

【讨论】:

这是真的,但持久连接并不意味着每个连接都有线程。【参考方案3】:

Thread per connection 是从multiple requests(keep-alive) 重用相同HTTP Connection 的概念。

Thread per request将从client创建一个each request的线程。服务器可以根据request创建多个threads

【讨论】:

只是为了澄清其他人,keep-alive 不一定会为整个连接分配一个线程,例如在针对 servlet 容器进行时。这就是 Keep-Alive 和 TPC 之间的区别。【参考方案4】:

每个请求的线程将为服务器收到的每个 HTTP 请求创建一个线程。

每个连接的线程将重用来自多个请求的相同 HTTP 连接(保持活动状态).AKA HTTP persistent connection 但请注意,这只支持HTTP 1.1

每个请求的线程更快,因为大多数 Web 容器使用线程池。

您应该在服务器上的核心数上设置的最大并行连接数。 更多内核 => 更多并行线程。

在这里查看如何配置... 雄猫 6:http://tomcat.apache.org/tomcat-6.0-doc/config/executor.html

Tomcat 7:http://tomcat.apache.org/tomcat-7.0-doc/config/executor.html

例子

【讨论】:

【参考方案5】:

每个请求的线程应该更好,因为它重用线程,而某些客户端可能处于空闲状态。如果您有很多并发用户,则可以使用较少数量的线程为他们提供服务,并且具有相同数量的线程会更昂贵。还有一个考虑因素——我们不知道用户是否仍在使用应用程序,所以我们不知道何时销毁线程。对于每个请求一个线程的机制,我们只使用一个线程池。

【讨论】:

它是使用线程池还是产生新线程并加入旧线程? 产生一个新线程是非常昂贵的操作。我假设任何适当的实现都会通过池重用线程。 我知道它很贵,这就是我问的原因。在没有线程池的情况下,每个请求使用一个线程比每个连接使用一个线程要昂贵得多。假设很糟糕,这就是我问的原因。 你说的是哪个具体实现,javax.servlet.* 只是一个 API,它没有指定服务请求的方式。 是的,但通常API是由一些软件实现的。

以上是关于每个连接的线程与每个请求的线程有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

NIO与传统IO的区别(形象比喻)[转]

NIO与传统IO的区别

NIO与传统IO的区别

Java中IO与NIO的区别和使用场景

Apache 两种mpm prefork 和 worker 的区别

Netty相关面试题