Spring Boot Tomcat 架构及参数优化

1. Springboot Tomcat 架构及参数优化

1.1. 版本说明

构件 版本
spring-boot 2.7.18
tomcat-embed-core 9.0.83

1.2. SpringBoot Tomcat 架构分析

1.2.1. Tomcat 核心组件类图

classDiagram direction LR class Tomcat { #Server server +void start() +void stop() } class Connector { #ProtocolHandler protocolHandler } class Container { <<Interface>> } class Engine { <<Interface>> } class StandardEngine class Host { <<Interface>> } class StandardHost class Lifecycle { <<Interface>> void start() void stop() } class Server { <<Interface>> } class StandardServer { -Service[] services } class Service { <<Interface>> } class StandardService { #Connector[] connectors -Engine engine } class AbstractHttp11JsseProtocol~S~ class AbstractHttp11Protocol~S~ class AbstractProtocol~S~ { -AbstractEndpoint endpoint } class Http11NioProtocol class Http11Nio2Protocol class ProtocolHandler { <<Interface>> void start() void stop() } class AbstractEndpoint~S, U~ { -Executor executor -LimitLatch connectionLimitLatch -Handler~S~ handler +void bind() #U serverSocketAccept() +void createExecutor() +boolean processSocket(SocketWrapperBase~S~> socketWrapper, SocketEvent event, boolean dispatch) +void start() +void stop() } class AbstractJsseEndpoint~S, U~ class Nio2Endpoint class NioEndpoint { -ServerSocketChannel serverSock } class Executor { <<Interface>> } class ThreadPoolExecutor note for ThreadPoolExecutor "Tomcat 线程池在 AbstractEndpoint 创建,server.tomcat.threads.max、\n server.tomcat.threads.min-spare 参数作用于此,\n 用于指定最大、最小线程数,线程池其他参数默认值为:\n 是否守护线程:是;\n 线程优先级:5;\n 空闲线程存活时间:60 秒;\n 任务队列为:TaskQueue,容量为 Integer.MAX_VALUE" class VirtualThreadExecutor class LimitLatch { -AtomicLong count -long limit +long countDown() +void countUpOrAwait() } note for LimitLatch "LimitLatch 用于限制连接数量,基于 AQS 实现,\n server.tomcat.max-connections 参数作用于此,\n 接受一个连接前判断是否达到最大连接数 limit,\n 否则自旋等待直至成功并 count 加 1;\n 关闭连接后 count 减 1" class Handler { <<Interface>> } class ConnectionHandler~S~ class Poller note for Poller "Poller 线程不停从已连接的 socket 读取事件,\n 最终封装成 SocketProcessorBase 交给 ThreadPoolExecutor 处理" class Acceptor note for Acceptor "Acceptor 线程不停接收新的客户端连接,\n 直至达到 server.tomcat.max-connections" class SocketProcessor class SocketProcessorBase~S~ note for SocketProcessorBase "SocketProcessorBase 线程将请求经过层层传递最终给到\n DispatcherServlet,DispatcherServlet 再分派到对应的\n Spring Controller 中处理具体的业务逻辑" class Runnable { <<Interface>> } Tomcat "1" *--> "1" Host Tomcat "1" *--> "1" Server Host --|> Container StandardHost ..|> Host StandardServer ..|> Server StandardServer "1" *--> "n" Service Server --|> Lifecycle Service --|> Lifecycle StandardService ..|> Service StandardService "1" *--> "1" Engine StandardService "1" *--> "n" Connector StandardEngine ..|> Engine Engine --|> Container Container --|> Lifecycle Connector ..|> Lifecycle Connector "1" *--> "1" ProtocolHandler Http11NioProtocol --|> AbstractHttp11JsseProtocol Http11Nio2Protocol --|> AbstractHttp11JsseProtocol AbstractHttp11JsseProtocol --|> AbstractHttp11Protocol AbstractHttp11Protocol --|> AbstractProtocol AbstractProtocol ..|> ProtocolHandler AbstractProtocol "1" *--> "1" AbstractEndpoint AbstractJsseEndpoint --|> AbstractEndpoint Nio2Endpoint --|> AbstractJsseEndpoint NioEndpoint --|> AbstractJsseEndpoint AbstractEndpoint "1" *--> "1" Executor AbstractEndpoint "1" *--> "1" LimitLatch AbstractEndpoint "1" *--> "1" Handler AbstractEndpoint ..> SocketProcessorBase ThreadPoolExecutor ..|> Executor VirtualThreadExecutor ..|> Executor ConnectionHandler ..|> Handler Poller ..|> Runnable Acceptor ..|> Runnable Poller ..> AbstractEndpoint Acceptor ..> AbstractEndpoint SocketProcessor --|> SocketProcessorBase~S~ SocketProcessorBase~S~ ..|> Runnable

1.2.2. Tomcat 核心组件架构图

1.3. SpringBoot Tomcat 工作流程

1.3.1. SpringBoot 初始化 Tomcat 流程

flowchart TD flow1("SpringApplication#run(Class<?> primarySource, String... args)") --> flow2("SpringApplication#run(Class<?>[] primarySources, String[] args)") flow2 --> flow3("SpringApplication#run(String... args)") flow3 --> flow4("SpringApplication#refreshContext(ConfigurableApplicationContext context)") flow4 --> flow5("SpringApplication#refresh(ConfigurableApplicationContext applicationContext)") flow5 --> flow6("ServletWebServerApplicationContext#refresh()") flow6 --> flow7("AbstractApplicationContext#refresh()") flow7 --> flow8("ServletWebServerApplicationContext#onRefresh()") flow8 --> flow9("ServletWebServerApplicationContext#createWebServer()") subgraph flow10["TomcatServletWebServerFactory#getWebServer(ServletContextInitializer... initializers)"] direction LR flow10_1("Tomcat#Tomcat()") flow10_1 --> flow10_2("Tomcat#start()") end flow9 --> flow10

1.3.2. Tomcat 启动流程

flowchart TD flow1["Tomcat#start()"] flow2["StandardServer#start()"] flow3["StandardService#start()"] flow4["StandardEngine#start()"] flow5["Connector#start()"] flow6["Http11NioProtocol#start()"] subgraph flow7["NioEndpoint#start()"] flow8["NioEndpoint#bind() \n 初始化 ServerSocketChannel,绑定端口"] flow9["NioEndpoint#createExecutor() \n 创建工作线程池"] flow10["NioEndpoint#initializeConnectionLatch() \n 初始化限流组件"] flow11["Poller#Poller() \n 启动 socket 事件监听线程"] flow12["Acceptor#Acceptor() \n 启动 socket 连接线程"] end flow1 --> flow2 flow2 --> flow3 flow3 --> flow4 flow3 --> flow5 flow5 --> flow6 flow6 --> flow7 flow7 --> flow8 flow8 --> flow9 flow9 --> flow10 flow10 --> flow11 flow11 --> flow12

1.3.2.1. 初始化 ServerSocketChannel

核心源码:

serverSock = ServerSocketChannel.open();
InetSocketAddress addr = new InetSocketAddress(getAddress(), getPortWithOffset());
serverSock.bind(addr, getAcceptCount());
  1. 打开 ServerSocketChannel。
  2. 绑定端口,指定 backlog

其中端口由 server.port 配置参数指定,backlog 由 server.tomcat.accept-count 配置参数指定,默认值为 100,客户端与服务端完成 TCP 三次握手之后,连接放入等待队列中,ServerSocketChannel 调用 accept() 方法从队列中取出连接。因此,当 Tomcat 达到 max-connections 指定的最大连接数后,还能继续接收 accept-count 数量的连接。

1.3.2.2. 初始化工作线程池

核心源码:

TaskQueue taskqueue = new TaskQueue();
TaskThreadFactory tf = new TaskThreadFactory(getName() + "-exec-", daemon, getThreadPriority());
executor = new ThreadPoolExecutor(getMinSpareThreads(), getMaxThreads(), 60, TimeUnit.SECONDS,taskqueue, tf);
taskqueue.setParent( (ThreadPoolExecutor) executor);
  1. TaskQueue 任务队列继承自 LinkedBlockingQueue,这里无法指定容量,默认容量为 Integer.MAX_VALUE,即无限大。预计未来将支持指定容量,详见 github issues
  2. TaskThreadFactory 线程工厂指定了线程名称前缀为 http-nio-端口-;线程为守护线程;线程优先级为默认值:5。
  3. 线程池核心线程数由 server.tomcat.threads.min-spare 配置参数指定,默认值为 10;线程池最大线程数由 server.tomcat.threads.max 配置参数指定,默认值为 200;空闲线程存活时间 60 秒。

TaskQueue 重写了 offer 方法,使得 Tomcat 线程池与 JDK 线程池创建线程的时机不一样,具体表现为:

  1. 如果线程池里的线程数量等于最大线程数,说明无法再创建新线程,任务加入队列中,等待空闲线程处理。
  2. 如果已提交的任务数小于等于线程池里的线程数量,说明有空闲线程,任务加入队列中,由空闲线程处理。
  3. 如果线程池里的线程数量小于最大线程数,任务无法加入队列,强制线程池新建线程去处理。
  4. 如果以上都不是,任务加入队列,等待空闲线程处理。

核心源码:

@Override
public boolean offer(Runnable o) {
  //we can't do any checks
    if (parent==null) {
        return super.offer(o);
    }
    //we are maxed out on threads, simply queue the object
    if (parent.getPoolSizeNoLock() == parent.getMaximumPoolSize()) {
        return super.offer(o);
    }
    //we have idle threads, just add it to the queue
    if (parent.getSubmittedCount() <= parent.getPoolSizeNoLock()) {
        return super.offer(o);
    }
    //if we have less threads than maximum force creation of a new thread
    if (parent.getPoolSizeNoLock() < parent.getMaximumPoolSize()) {
        return false;
    }
    //if we reached here, we need to add it to the queue
    return super.offer(o);
}

1.3.2.3. 初始化限流组件 LimitLatch

核心源码:

protected LimitLatch initializeConnectionLatch() {
    if (maxConnections==-1) {
        return null;
    }
    if (connectionLimitLatch==null) {
        connectionLimitLatch = new LimitLatch(getMaxConnections());
    }
    return connectionLimitLatch;
}

最大连接数由 server.tomcat.max-connections 配置参数指定,默认值为 8192,表示同一时间 Tomcat 能够接受的最大连接数量。接受一个新连接 LimitLatch 计数加 1,处理完请求断开连接,LimitLatch 计数减 1。

1.3.3. Acceptor 线程工作流程

flowchart TD flow1["线程启动"] flow2{"停止?"} flow3["尝试 LimitLatch 计数加 1"] flow4{"成功?"} flow6["SocketChannel socket = endpoint.serverSocketAccept() \n 接收新连接"] flow7["SocketChannel 封装为 NioSocketWrapper"] flow8["NioSocketWrapper 封装为 PollerEvent"] flow9["PollerEvent 注册到 Poller 的 SynchronizedQueue 队列,Poller 线程处理队列里的事件"] flow1 --> flow2 flow2 --> |no|flow3 flow3 --> flow4 flow4 --> |no \n 自旋-等待-重试|flow3 flow4 --> |yes|flow6 flow6 --> flow7 flow7 --> flow8 flow8 --> flow9 flow9 --> flow2

1.3.4. Poller 线程工作流程

flowchart TD flow1["线程启动"] flow2{"while(true)"} flow3["Poller#events() \n 处理 SynchronizedQueue 队列里的 PollerEvent 事件"] flow4["Selector#selectedKeys() \n 监听 socket 事件"] flow5["Poller#processKey(SelectionKey sk, NioSocketWrapper socketWrapper) \n 处理监听到的事件"] flow6["AbstractEndpoint#processSocket(SocketWrapperBase socketWrapper, SocketEvent event, boolean dispatch) \n 封装 SocketProcessor 多线程任务,提交到线程池处理"] flow1 --> flow2 flow2 --> |yes|flow3 flow3 --> flow4 flow4 --> flow5 flow5 --> flow6 flow6 --> flow2

1.3.5. SocketProcessor 线程处理请求工作流程

flowchart TD flow1["SocketProcessor#doRun()"] flow2["ConnectionHandler#process(SocketWrapperBase socket, SocketEvent status)"] flow3["Http11Processor#process(SocketWrapperBase socketWrapper, SocketEvent status)"] flow4["CoyoteAdapter#service(Request req, Response res)"] subgraph subgraph1["Connector"] subgraph subgraph1_1["Service"] subgraph subgraph1_1_1["Engine"] subgraph subgraph1_1_1_1["Pipeline"] flow5["StandardEngineValve#invoke(Request request, Response response)"] end end end end subgraph subgraph2["Host"] subgraph subgraph 2_1["Pipeline"] flow6["StandardHostValve#invoke(Request request, Response response)"] end end subgraph subgraph3["Context"] subgraph subgraph 3_1["Pipeline"] flow7["StandardContextValve#invoke(Request request, Response response)"] end end subgraph subgraph4["Wrapper"] subgraph subgraph 4_1["Pipeline"] flow8["StandardWrapperValve#invoke(Request request, Response response)"] end end flow9["FilterChain#doFilter(ServletRequest request, ServletResponse response)"] flow10["DispatcherServlet#service(ServletRequest req, ServletResponse res)"] flow11["RequestMappingHandlerAdapter#handle(HttpServletRequest request, HttpServletResponse response, Object handler)"] flow12["ServletInvocableHandlerMethod#(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs)"] flow13["Spring Web Controller 层,处理具体业务"] flow1 -->|NioSocketWrapper| flow2 flow2 -->|NioSocketWrapper| flow3 flow3 -->|Request, Response| flow4 flow4 -->|Request, Response| flow5 flow5 -->|Request, Response| flow6 flow6 -->|Request, Response| flow7 flow7 -->|Request, Response| flow8 flow8 -->|ServletRequest, ServletResponse| flow9 flow9 -->|ServletRequest, ServletResponse| flow10 flow10 -->|ServletRequest, ServletResponse, HandlerMethod| flow11 flow11 --> flow12 flow12 --> flow13

1.4. 配置参数优化

1.4.1. Tomcat 相关 Server Properties

  • server.tomcat.accept-count: Maximum queue length for incoming connection requests when all possible request processing threads are in use. default: 100.
  • server.tomcat.accesslog.buffered: Whether to buffer output such that it is flushed only periodically. default: true.
  • server.tomcat.accesslog.check-exists: Whether to check for log file existence so it can be recreated if an external process has renamed it. default: false.
  • server.tomcat.accesslog.condition-if: Whether logging of the request will only be enabled if "ServletRequest.getAttribute(conditionIf)" does not yield null.
  • server.tomcat.accesslog.condition-unless: Whether logging of the request will only be enabled if "ServletRequest.getAttribute(conditionUnless)" yield null.
  • server.tomcat.accesslog.directory: Directory in which log files are created. Can be absolute or relative to the Tomcat base dir. default: logs.
  • server.tomcat.accesslog.enabled: Enable access log. default: false.
  • server.tomcat.accesslog.encoding: Character set used by the log file. Default to the system default character set.
  • server.tomcat.accesslog.file-date-format: Date format to place in the log file name. default: .yyyy-MM-dd.
  • server.tomcat.accesslog.ipv6-canonical: Whether to use IPv6 canonical representation format as defined by RFC 5952. default: false.
  • server.tomcat.accesslog.locale: Locale used to format timestamps in log entries and in log file name suffix. Default to the default locale of the Java process.
  • server.tomcat.accesslog.max-days: Number of days to retain the access log files before they are removed. default: -1.
  • server.tomcat.accesslog.pattern: Format pattern for access logs. default: common.
  • server.tomcat.accesslog.prefix: Log file name prefix. default: access_log.
  • server.tomcat.accesslog.rename-on-rotate: Whether to defer inclusion of the date stamp in the file name until rotate time. default: false.
  • server.tomcat.accesslog.request-attributes-enabled: Set request attributes for the IP address, Hostname, protocol, and port used for the request. default: false.
  • server.tomcat.accesslog.rotate: Whether to enable access log rotation. default: true.
  • server.tomcat.accesslog.suffix: Log file name suffix. default: .log.
  • server.tomcat.additional-tld-skip-patterns: Comma-separated list of additional patterns that match jars to ignore for TLD scanning. The special '?' and '*' characters can be used in the pattern to match one and only one character and zero or more characters respectively.
  • server.tomcat.background-processor-delay: Delay between the invocation of backgroundProcess methods. If a duration suffix is not specified, seconds will be used. default: 10s.
  • server.tomcat.basedir: Tomcat base directory. If not specified, a temporary directory is used.
  • server.tomcat.connection-timeout: Amount of time the connector will wait, after accepting a connection, for the request URI line to be presented.
  • server.tomcat.keep-alive-timeout: Time to wait for another HTTP request before the connection is closed. When not set the connectionTimeout is used. When set to -1 there will be no timeout.
  • server.tomcat.max-connections: Maximum number of connections that the server accepts and processes at any given time. Once the limit has been reached, the operating system may still accept connections based on the "acceptCount" property. default: 8192.
  • server.tomcat.max-http-form-post-size: Maximum size of the form content in any HTTP post request. default: 2MB.
  • server.tomcat.max-keep-alive-requests: Maximum number of HTTP requests that can be pipelined before the connection is closed. When set to 0 or 1, keep-alive and pipelining are disabled. When set to -1, an unlimited number of pipelined or keep-alive requests are allowed. default: 100.
  • server.tomcat.max-swallow-size: Maximum amount of request body to swallow. default: 2MB.
  • server.tomcat.mbeanregistry.enabled: Whether Tomcat's MBean Registry should be enabled. default: false.
  • server.tomcat.processor-cache: Maximum number of idle processors that will be retained in the cache and reused with a subsequent request. When set to -1 the cache will be unlimited with a theoretical maximum size equal to the maximum number of connections. default: 200.
  • server.tomcat.redirect-context-root: Whether requests to the context root should be redirected by appending a / to the path. When using SSL terminated at a proxy, this property should be set to false. default: true.
  • server.tomcat.relaxed-path-chars: Comma-separated list of additional unencoded characters that should be allowed in URI paths. Only "< > [ \ ] ^ ` { | }" are allowed.
  • server.tomcat.relaxed-query-chars: Comma-separated list of additional unencoded characters that should be allowed in URI query strings. Only "< > [ \ ] ^ ` { | }" are allowed.
  • server.tomcat.remoteip.host-header: Name of the HTTP header from which the remote host is extracted. default: X-Forwarded-Host.
  • server.tomcat.remoteip.internal-proxies: Regular expression that matches proxies that are to be trusted. default: 10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|100\\.6[4-9]{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.[7-9]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.1[0-1]{1}\\d{1}\\.\\d{1,3}\\.\\d{1,3}|100\\.12[0-7]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|0:0:0:0:0:0:0:1|::1.
  • server.tomcat.remoteip.port-header: Name of the HTTP header used to override the original port value. default: X-Forwarded-Port.
  • server.tomcat.remoteip.protocol-header: Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
  • server.tomcat.remoteip.protocol-header-https-value: Value of the protocol header indicating whether the incoming request uses SSL. default: https.
  • server.tomcat.remoteip.remote-ip-header: Name of the HTTP header from which the remote IP is extracted. For instance, 'X-FORWARDED-FOR'.
  • server.tomcat.resource.allow-caching: Whether static resource caching is permitted for this web application. default: true.
  • server.tomcat.resource.cache-ttl: Time-to-live of the static resource cache.
  • server.tomcat.threads.max: Maximum amount of worker threads. default: 200.
  • server.tomcat.threads.min-spare: Minimum amount of worker threads. default: 10.
  • server.tomcat.uri-encoding: Character encoding to use to decode the URI. default: UTF-8.
  • server.tomcat.use-relative-redirects: Whether HTTP 1.1 and later location headers generated by a call to sendRedirect will use relative or absolute redirects. default: false.

1.4.2. 参考配置

服务器配置:

CPU 核心 内存
4 核 8G
server:
  tomcat:
    threads:
      max: 1000
      min-spare: 200
    accept-count: 1000
    max-connections: 10000
posted @ 2024-05-24 15:26  Jason207010  阅读(314)  评论(0编辑  收藏  举报