Tomcat 架构深度剖析:为什么它能扛住亿级流量?
很多人以为 Tomcat 只是架构级流一个普通 Servlet 容器,但当流量洪水涌来,深度它依旧能稳稳扛住高并发。剖析这背后到底依靠什么?住亿线程模型?I/O 模式?还是隐藏的架构设计 ?别再猜了,今天我们就来拆解 Tomcat 如何在 10 万并发下保持稳定 ,架构级流让你彻底看懂它的深度底层逻辑 !
上回「码哥跳动」站在上帝视角给大家拆解了 Tomcat 架构设计 ,剖析分析 Tomcat 如何实现启动、住亿停止 ,架构级流通过设计连接池与容器两大组件完成了一个请求的深度接受与响应 。连接器负责对外交流 ,剖析处理 socket 连接 ,服务器租用住亿容器对内负责,架构级流加载 Servlet 以及处理具体 Request 请求与响应。深度
高并发拆解核心准备
这回 ,剖析再次拆解,专注 Tomcat 高并发设计之道与性能调优 ,让大家对整个架构有更高层次的了解与感悟 。其中设计的每个组件思路都是将 Java 面向对象、面向接口 、如何封装变与不变 ,如何根据实际需求抽象不同组件分工合作,如何设计类实现单一职责,怎么做到将相似功能高内聚低耦合,设计模式运用到极致的亿华云学习借鉴 。
这次主要涉及到的是 I/O 模型,以及线程池的基础内容。
在学习之前,希望大家积累以下一些技术内容,很多内容「码哥字节」也在历史文章中分享过。大家可爬楼回顾…… 。希望大家重视如下几个知识点,在掌握以下知识点再来拆解 Tomcat ,就会事半功倍,否则很容易迷失方向不得其法 。
一起来看 Tomcat 如何实现并发连接处理以及任务处理,性能的源码下载优化是每一个组件都起到对应的作用 ,如何使用最少的内存,最快的速度执行是我们的目标 。
I/O 模型
Tomcat 实现高并发接收连接 ,必然涉及到 I/O 模型的运用,了解同步阻塞、异步阻塞 、I/O 多路复用,异步非阻塞相关概念以及 Java NIO 包的云计算运用很有必要 。本文也会带大家着重说明 I/O 是如何在 Tomcat 运用实现高并发连接。大家通过本文我相信对 I/O 模型也会有一个深刻认识 。
Java 并发编程
实现高并发 ,除了整体每个组件的优雅设计 、设计模式的合理、I/O 的运用 ,还需要线程模型,如何高效的并发编程技巧。在高并发过程中 ,不可避免的建站模板会出现多个线程对共享变量的访问,需要加锁实现 ,如何高效的降低锁冲突。因此作为程序员 ,要有意识的尽量避免锁的使用,比如可以使用原子类 CAS 或者并发集合来代替。如果万不得已需要用到锁 ,也要尽量缩小锁的范围和锁的强度。
对于并发相关的基础知识,高防服务器如果读者感兴趣「码哥字节」后面也给大家安排上,目前也写了部分并发专辑 ,大家可移步到历史文章或者专辑翻阅,
Tomcat 总体架构
再次回顾下 Tomcat 整体架构设计,主要设计了 connector 连接器处理 TCP/IP 连接,container 容器作为 Servlet 容器,处理具体的业务请求 。对外对内分别抽象两个组件实现拓展。
一个 Tomcat 实例默认会有一个 Service,而一个 Service 可以包含多个连接器。连接器主要有 ProtocalHandler 和 Adapter 两个组件共同完成连接器核心功能。ProtocolHandler 主要由 Acceptor 以及 SocketProcessor 构成 ,实现了 TCP/IP 层 的 Socket 读取并转换成 TomcatRequest 和 TomcatResponse,最后根据 http 或者 ajp 协议获取合适的 Processor 解析为应用层协议,并通过 Adapter 将 TomcatRequest、TomcatResponse 转化成 标准的 ServletRequest 、ServletResponse。通过 getAdapter().service(request, response);将请求传递到 Container 容器。adapter.service()实现将请求转发到容器 org.apache.catalina.connector.CoyoteAdapter 复制// Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke( request, response);1.2.3.这个调用会触发 getPipeline 构成的责任链模式将请求一步步走入容器内部 ,每个容器都有一条 Pipeline,通过 First 开始到 Basic 结束并进入容器内部持有的子类容器,最后到 Servlet ,这里就是责任链模式的经典运用。具体的源码组件是 Pipeline 构成一条请求链,每一个链点由 Valve 组成 。「码哥字节」在上一篇Tomcat 架构解析到工作借鉴 已经详细讲解 。如下图所示,整个 Tomcat 的架构设计重要组件清晰可见,希望大家将这个全局架构图深深印在脑海里,掌握全局思路才能更好地分析细节之美。

Tomcat 架构
启动流程 :startup.sh 脚本到底发生了什么
Tomcat 启动流程
Tomcat 启动流程
Tomcat 本生就是一个 Java 程序 ,所以 startup.sh 脚本就是启动一个 JVM 来运行 Tomcat 的启动类 Bootstrap 。Bootstrap 主要就是实例化 Catalina 和初始化 Tomcat 自定义的类加载器 。热加载与热部署就是靠他实现 。Catalina: 解析 server.xml 创建 Server 组件 ,并且调用 Server.start() 方法 。Server :管理 Service 组件,调用 Server 的 start() 方法。Service :主要职责就是管理简介器的顶层容器 Engine,分别调用 Connector 和 Engine 的 start 方法 。Engine 容器主要就是组合模式将各个容器根据父子关系关联,并且 Container 容器继承了 Lifecycle 实现各个容器的初始化与启动 。Lifecycle 定义了 init()、start() 、stop() 控制整个容器组件的生命周期实现一键启停 。
这里就是一个面向接口、单一职责的设计思想 ,Container 利用组合模式管理容器,LifecycleBase 抽象类继承 Lifecycle 将各大容器生命周期统一管理这里便是,而实现初始化与启动的过程又 LifecycleBase 运用了模板方法设计模式抽象出组件变化与不变的点,将不同组件的初始化延迟到具体子类实现 。并且利用观察者模式发布启动事件解耦。
具体的 init 与 start 流程如下泳道图所示:这是我在阅读源码 debug 所做的笔记,读者朋友们不要怕笔记花费时间长,自己跟着 debug 慢慢记录 ,相信会有更深的感悟。
init 流程

Tomcat Init
start 流程

Tomcat start
读者朋友根据我的两篇内容 ,抓住主线组件去 debug ,然后跟着该泳道图阅读源码,我相信都会有所收获 ,并且事半功倍。在读源码的过程中,切勿进入某个细节 ,一定要先把各个组件抽象出来 ,了解每个组件的职责即可 。最后在了解每个组件的职责与设计哲学之后再深入理解每个组件的实现细节 ,千万不要一开始就想着深入理解具体一篇叶子。
每个核心类我在架构设计图以及泳道图都标识出来了,「码哥跳动」给大家分享下如何高效阅读源码,以及保持学习兴趣的心得体会 。
如何正确阅读源码
切勿陷入细节 ,不看全局:我还没弄清楚森林长啥样,就盯着叶子看