Spring本身并不进行Web的处理,无论是TCP连接也好还是请求和响应也好,这些都是在Spring以外的部分完成的,例如Tomcat,默认的SpringBoot将会集成Tomcat。
就以Tomcat来说,一个Tomcat也就是一个服务器,服务器将会包含一个或多个Service,这也就是在Tomcat中运行的各个web服务,而组成web服务的组件,分别为Connector和Container,在Tomcat启动Service的时候就会初始化它们。
其中Connector的作用,顾名思义,用来处理网络的各类链接,对于Http服务,那当然是TCP连接了。
Connector的连接建立之后,是能够传输数据的,这些数据将会通过Servlet接口的具体实现来进行处理。Servlet中,可以得到输入流和输出流,如果使用HttpServlet,那么你可以直接得到Request和输出Response。
那么Spring在做什么呢?
SpringWeb定义了一个DispatcherServlet,这个东西会接收Request,并且根据用户的配置,转到Spring中的Controller进行处理。
DispatcherServlet不在乎这个Request的连接,甚至不在乎这个Request是谁提供的,只要有一个Web服务器,或者说web容器提供符合要求的Request,Spring的Web模块就能使用它,并且把它分发到自己容器内的Controller中。
通常来说,Request里面包含了InputStream和OutputStream,利用他们就足以完成响应了。
这些被Spring分发的Request,他们的连接也当然是被web容器管理的,这样的话,是不是一个请求使用一个线程,这个由web的容器决定而不是Spring。
那么Web容器会不会一个Tcp连接一个线程呢?绝对不会,因为这种模式存在性能上的瓶颈。
早期的Http是只能请求完毕后关闭tcp,下次重新连接的,那是Http1.0,而现在使用的是Http1.1,也就是同一个Tcp连接能够发送多个request,不需要请求完毕就关掉,这样就不用反复的进行TCP的连接,更加的方便快捷。
如果线程太多的话,那么系统将会花费更多的时间来切换它们,可以设想一种情况:如果线程可以无限制的多,那么会不会CPU只能在那里来回切换线程,而不能具体执行线程的代码呢?
(以下是简介,并不代表Tomcat也是这样做的)
当下Java里面的NIO是更优的做法:
使用主线程来接收链接,收到新的连接后就存到什么地方,例如存到ArrayList里面,然后把Socket注册到Selector上面。
然后利用NIO的Selector,获取到达服务器的数据,封装为Request塞给Servlet处理。
也就是主线程只需要不断地封装request,接收链接即可,不管多少个链接,这一个主线程就完全足够了。
如果每一个连接都有一个线程,假设有100000个链接,这些链接可以来自很多不同的用户,那么就有同等数量的线程出现,即使这些用户仅仅是打开了网页(实际上打开网页一般会有4-5个TCP连接)而没有进行任何操作。而使用上述的NIO,只需要一个。
自己设计的服务器当然可以不考虑这些,但是现在的类似于Tomcat的web容器,是需要考虑这种超级多的连接的,因为会有企业使用这种开源的web容器,所以对性能有更加严苛的要求,这也是我认为不应该使用“一个TCP连接一个线程”的原因。
1.
Spring Boot中线程的维护是由servlet容器或Netty负责,所以题主应该问的是servlet容器的线程模型。而Spring Boot是一个自动配置的框架。目前Spring Boot对web开发目前有两种解决方案:1. 传统的web框架基于Spring MVC + Tomcat,2. Spring 5新增的web-reactive框架基于Spring Webflux + Netty。这两个框架都支持大家熟用的注释,比如@Controller。技术栈可以看官网图,

先不谈web-reactive,通过分析Spring MVC和Tomcat的交互,来浅析一下Spring与servlet容器交互的原理——Spring MVC基于Java EE的Servlet API,Servlet API定义了Servlet容器和具体的servlet代码交互的约定,Spring MVC通过注册一个名为DispatcherServlet的servlet到servlet容器中处理请求,并把实际工作交给Spring提供的组件bean执行。
2.
了解了Spring和servlet容器的交互之后再回到问题。
2.1
首先题主可能对连接和请求的概念混淆了,这里强调一下连接(TCP)是传输层的,请求(HTTP)是应用层的。在像 HTTP 这样的Client-Server协议中,会话分为三个阶段:
客户端建立一条 TCP 连接(如果传输层不是 TCP,也可以是其他适合的连接)。
客户端发送请求并等待应答。
服务器处理请求并送回应答,回应包括一个状态码和对应的数据。
从 HTTP/1.1 开始,连接在完成第三阶段后不再关闭,客户端可以再次发起新的请求。这意味着第二步和第三步可以连续进行数次。
2.2
其次真的是一个线程处理一个HTTP请求吗?我觉得这个说法也不准确。
Tomcat支持三种运行模式(BIO, NIO, APR),大致流程均是:
当客户端向服务器建立TCP连接,发送请求,服务器操作系统将该连接放入accept队列,Tomcat在accept队列中接收连接;在连接中获取请求的数据,生成request;调用servlet容器处理请求;返回response,完成一次HTTP会话。在Tomcat 8.0前默认使用BIO,Tomcat在accept队列中接受TCP连接并获得HTTP Request,从线程池中取出空闲的线程来处理请求,如果无空闲线程则阻塞。Tomcat 8.0起默认启用NIO模式,在从accept获得request之后,注册到nio.Selector中后不阻塞继续获取连接,Tomcat遍历找到selector中可用的request,再从线程池中取出空闲的线程来处理请求。Tomcat相关的配置参数有:
acceptCount,当accept队列中连接的个数达到acceptCount时,队列满,进来的请求一律被拒绝。
maxConnections,当Tomcat接收的连接数达到maxConnections时,accept队列中的线程会一直阻塞着。
maxThreads,线程池线程的最大数量。
所以无论是BIO,还是NIO,当请求数量大于acceptCount,接收的连接数大于maxConnection时,Tomcat都不会分配线程服务。