springboot启动时会启动tomcat的原因

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

maven中添加 Web 的依赖,Spring Boot 就帮我们内置了 Servlet 容器,默认使用的是 Tomcat

因为内置了启动容器,应用程序可以直接通过 Maven 命令将项目编译成可执行的 jar 包,通过 java -jar 命令直接启动,不需要再像以前一样,打包成 War 包,然后部署在 Tomcat 中。

具体步骤

SpringBoot的启动主要是通过实例化SpringApplication来启动的,启动过程主要做了如下几件事情:

  1. 配置系统属性
  2. 获取监听,发布应用开始启动时间
  3. 初始化输入参数
  4. 配置环境,输出banner
  5. 创建上下文
  6. 预处理上下文
  7. 刷新上下文
  8. 再次刷新上下文
  9. 发布应用已经启动事件
  10. 发布应用启动完成事件

启动tomcat步骤

而启动 Tomcat 是在第7步 刷新上下文 这一步。

从整个流转过程中我们知道了 Tomcat 的启动主要是实例化两个组件:Connector、Container。

  1. Spring Boot 创建 Tomcat 时,会先创建一个根上下文,将 WebApplicationContext 传给 Tomcat;
  2. 启动 Web 容器,需要调用 getWebserver(),因为默认的 Web 环境就是 TomcatServletWebServerFactory,所以会创建 Tomcat 的 Webserver,这里会把根上下文作为参数给 TomcatServletWebServerFactory 的 getWebServer();
  3. 启动 Tomcat,调用 Tomcat 中 Host、Engine 的启动方法。

tomcat线程关系

不同线程做不同的事情

img

tomcat中的nio

在 Tomcat 中,NIO(New I/O)代表使用非阻塞 IO 的 Java NIO API。Tomcat 7 使用了一个基于 NIO 的连接器(Connector),它支持 HTTP/1.1 协议的长连接(keep-alive)和异步 IO。

相比传统的阻塞 IO 模型,NIO 提供了更高的并发处理能力和更低的资源消耗。NIO 基于事件驱动模型,通过 Selector 监听多个通道的状态变化,当通道就绪时,执行读写操作。这种模型避免了线程阻塞的问题,减少了线程切换的开销,提高了系统的吞吐量。

在 Tomcat 中,NIO 连接器使用了 Java NIO API 中的 ServerSocketChannel 和 SocketChannel,通过分配缓冲区来实现数据的读写操作。同时,Tomcat 还提供了一些针对 NIO 连接器的优化配置,例如调整最大请求头大小、设置线程池大小等,以充分发挥 NIO 的性能优势。

netty nio原理

Netty 是一个基于 NIO 的网络通信框架,它通过事件驱动模型和异步非阻塞 IO 实现高性能的网络通信。下面是 Netty NIO 的工作原理:

  1. Selector:Netty 基于 Java NIO 中的 Selector 组件实现事件驱动。Selector 监听多个 Channel 的状态变化,当某个 Channel 就绪时,将其加入到就绪集合中,供后续处理。
  2. Channel:Netty 封装了 Java NIO 中的 SocketChannel 和 ServerSocketChannel,并提供了更加易用的 API。用户可以通过 ChannelPipeline 添加多个 ChannelHandler 来处理数据读写、协议解析等操作。在数据读写时,Channel 会把数据传递给 ChannelPipeline 中的第一个 ChannelHandler 进行处理,最终经过所有的 ChannelHandler 后输出到网络上。
  3. Buffer:Netty 使用 ByteBuffer 缓冲区来存储数据,以减少数据复制和传输开销。Buffer 可以被分配为 Direct Buffer 或 Heap Buffer,前者是直接分配内存,后者则是使用 JVM 堆内存。Direct Buffer 适合长时间存在的数据,如文件传输;Heap Buffer 则适合短暂存在的数据,如请求响应。
  4. EventLoop:Netty 中的 EventLoop 是一个线程,在运行期间负责处理 Channel 上的事件。每个 Channel 都被分配到一个 EventLoop 上,并在该线程中执行 Channel 的事件处理。由于每个 EventLoop 都是单线程的,因此不需要进行线程同步和并发控制,可以避免多线程竞争的问题,提升了性能。

总之,Netty 的 NIO 实现通过 Selector、Channel、Buffer 和 EventLoop 组成了一个基于事件驱动的高性能网络通信框架,可以支持大量并发连接和高吞吐量的数据传输。

netty nio原理图

img

以下是 Netty NIO 的工作原理图示:

Copy Code              +---------------------+
              |       Selector      |
              |                     |
              |     wait for I/O    |
              |                     |
              +----------+----------+
                         |
                         |
             +-----------+------------+
             |                        |
      +------+--------+      +--------+------+
      |  Channel A    |      |   Channel B   |
      |               |      |              |
      |  +---------+  |      |  +---------+ |
      |  |Handler 1|  |      |  | Handler1| |
      |  +---------+  |      |  +---------+ |
      |  +---------+  |      |  +---------+ |
      |  |Handler 2|  |      |  | Handler2| |
      |  +---------+  |      |  +---------+ |
      |  +---------+  |      |  +---------+ |
      |  |Handler 3|  |      |  | Handler3| |
      |  +---------+  |      |  +---------+ |
      +---------------+      +--------------+

                       Figure 1. Netty NIO Architecture

如上图所示,Netty 基于 NIO 实现了事件驱动的网络通信框架。在这个框架中,Selector 负责监听多个 Channel 的状态变化并将其加入到就绪集合中;Channel 封装了 Java NIO 中的 SocketChannel 和 ServerSocketChannel,并通过 ChannelPipeline 来处理数据读写、协议解析等操作;Buffer 缓冲区用于存储数据,以减少数据复制和传输开销;EventLoop 是一个线程,在运行期间负责处理 Channel 上的事件。

在 Netty 中,每个 Channel 都被分配到一个 EventLoop 上,并在该线程中执行 Channel 的事件处理。由于每个 EventLoop 都是单线程的,因此不需要进行线程同步和并发控制,可以避免多线程竞争的问题,提升了性能。同时,通过使用 Selector、Channel 和 Buffer 这些 NIO 组件,Netty 可以支持大量并发连接和高吞吐量的数据传输。

tomcat能处理高并发的原因

Tomcat 能够处理高并发的原因主要有以下几个:

  1. 多线程机制:Tomcat 使用线程池技术对请求进行处理,每个请求都会通过独立的线程来处理,从而实现高并发。Tomcat 提供了多种线程池类型,包括传统的线程池和 NIO 线程池等,可以根据实际情况选择不同的线程池类型。
  2. 非阻塞 IO:Tomcat 支持使用 NIO 技术进行网络通信,可以实现非阻塞 IO,从而更好地处理高并发请求。在 NIO 模式下,Tomcat 使用少量的线程来处理大量的请求,避免了传统模式下每个连接都需要一个线程的问题。
  3. 缓存机制:Tomcat 提供了缓存机制,可以将常用的资源缓存在内存中,减少了磁盘 I/O 操作,从而提高了并发处理能力。
  4. 异步处理:Tomcat 支持异步 Servlet 和异步 IO,在处理大量请求时能够更高效地处理请求。异步 Servlet 可以让 Servlet 的处理线程在返回响应之前释放,从而让 Tomcat 更快地处理下一个请求,而异步 IO 则可以让 Tomcat 在等待数据到达时不会被阻塞,从而更好地处理高并发请求。
  5. 负载均衡机制:Tomcat 支持负载均衡机制,可以将负载均衡到多个 Tomcat 服务器上,从而分担每个服务器的并发请求压力,提高整个系统的处理能力。

综上所述,Tomcat 通过多种技术手段来提高处理并发请求的能力,可以满足大规模应用程序的需求。

异步 Servlet 是什么

异步 Servlet 是指在处理 Servlet 请求时,Servlet 容器可以让请求线程在响应之前立即返回,然后通过回调机制等方式通知应用程序响应已经准备好了。这种方式相比传统的同步 Servlet 处理方式,可以更好地处理长时间运行的任务和高并发请求。

具体来说,异步 Servlet 的处理流程如下:

  1. 当一个请求到达 Servlet 容器时,容器会创建一个请求对象和一个响应对象。
  2. 如果该请求被标记为异步请求,则容器会将该请求挂起,并把请求对象和响应对象交给一个独立的线程来处理。
  3. 在独立的线程中,应用程序可以执行一些长时间运行的操作,比如发送网络请求、读取大文件等。
  4. 当响应准备好时,应用程序可以调用响应对象的 complete() 方法,表示异步请求已经完成。
  5. 此时,Servlet 容器会将响应返回给客户端,请求线程不再阻塞,可以处理其他请求。

使用异步 Servlet 可以提高服务器的并发能力,从而更好地处理高并发请求。同时,它还可以用于处理需要长时间运行的任务,比如批量数据导入、大文件上传等。

需要注意的是,在使用异步 Servlet 时,应用程序需要负责控制线程的数量和生命周期,避免线程泄漏和资源浪费。

子线程怎么把处理的结果返回主线程

在多线程编程中,子线程和主线程是并行执行的,因此如果需要将子线程的处理结果返回给主线程,可以通过以下方法实现:

  1. 使用共享内存:可以在主线程和子线程之间建立一个共享内存区域,让子线程将处理结果写入该区域,主线程再从该区域读取结果。
  2. 使用队列:主线程可以创建一个队列,将需要进行处理的数据放入队列中,子线程从队列中获取数据进行处理,并将处理结果再放回队列中,主线程再从队列中获取结果。
  3. 使用回调函数:在主线程中定义一个回调函数,子线程在处理完数据后调用该函数,将处理结果作为参数传递给主线程。
  4. 使用信号量:可以使用信号量来同步主线程和子线程,在子线程完成处理后发送一个信号量通知主线程,主线程再从共享内存或者其他方式获取结果。

无论使用哪种方法,都需要注意线程安全问题,保证多个线程对共享资源的访问不会出现竞争和冲突。

springboot网络请求中子线程怎么把处理结果返回主线程

在 Spring Boot 中,可以使用 CompletableFuture 来进行异步编程,实现子线程将处理结果返回给主线程的功能。具体实现方法如下:

  1. 创建 CompletableFuture 对象,使用 supplyAsync() 方法来执行异步操作,并返回一个 CompletableFuture 对象。
  2. 在 supplyAsync() 方法中传入需要执行的操作,比如发送网络请求等操作。
  3. 可以在 thenApply() 方法中对操作结果进行转换和处理,然后返回一个新的 CompletableFuture 对象。
  4. 最后通过 join() 方法获取最终的处理结果。

以下是示例代码:

javaCopy CodeCompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
    // 执行异步操作,比如发送网络请求等操作
    return doSomething();
}).thenApply(result -> {
    // 对操作结果进行转换和处理
    return processData(result);
});

// 获取最终的处理结果
String result = future.join();

在上述代码中,supplyAsync() 方法执行了一个异步操作,并返回一个 CompletableFuture 对象。接着在 thenApply() 方法中对操作结果进行转换和处理,并返回一个新的 CompletableFuture 对象。最后,通过 join() 方法获取最终的处理结果。

需要注意的是,如果在子线程中发生异常,可以使用 exceptionally() 方法来处理异常情况,比如返回一个默认值。

参考链接:
https://blog.csdn.net/qq_53318060/article/details/129782017
https://blog.csdn.net/niceyoo/article/details/109954382

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐