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 ,然后跟着该泳道图阅读源码 ,我相信都会有所收获 ,并且事半功倍。在读源码的过程中,切勿进入某个细节 ,一定要先把各个组件抽象出来,了解每个组件的职责即可 。最后在了解每个组件的职责与设计哲学之后再深入理解每个组件的实现细节,千万不要一开始就想着深入理解具体一篇叶子。
每个核心类我在架构设计图以及泳道图都标识出来了,「码哥跳动」给大家分享下如何高效阅读源码 ,以及保持学习兴趣的心得体会。
如何正确阅读源码
切勿陷入细节,不看全局 :我还没弄清楚森林长啥样 ,就盯着叶子看 ,看不到全貌和整体设计思路。所以阅读源码学习的时候不要一开始就进入细节 ,而是宏观看待整体架构设计思想,模块之间的关系。
1.阅读源码之前,需要有一定的技术储备
比如常用的设计模式,这个必须掌握 ,尤其是 :模板方法、策略模式、单例 、工厂 、观察者 、动态代理 、适配器 、责任链 、装饰器。大家可以看 「码哥字节」关于设计模式的历史文章 ,打造好的基础 。
2.必须会使用这个框架/类库 ,精通各种变通用法
魔鬼都在细节中 ,如果有些用法根本不知道 ,可能你能看明白代码是什么意思 ,但是不知道它为什么这些写。
3.先去找书,找资料 ,了解这个软件的整体设计。
从全局的视角去看待,上帝视角理出主要核心架构设计 ,先森林后树叶。都有哪些模块 ? 模块之间是怎么关联的?怎么关联的 ?
可能一下子理解不了 ,但是要建立一个整体的概念,就像一个地图 ,防止你迷航 。
在读源码的时候可以时不时看看自己在什么地方 。就像「码哥字节」给大家梳理好了 Tomcat 相关架构设计,然后自己再尝试跟着 debug,这样的效率如虎添翼 。
4. 搭建系统 ,把源代码跑起来 !
Debug 是非常非常重要的手段, 你想通过只看而不运行就把系统搞清楚,那是根本不可能的!合理运用调用栈(观察调用过程上下文)。
5.笔记
一个非常重要的工作就是记笔记(又是写作!),画出系统的类图(不要依靠 IDE 给你生成的), 记录下主要的函数调用 , 方便后续查看。
文档工作极为重要,因为代码太复杂,人的大脑容量也有限 ,记不住所有的细节。 文档可以帮助你记住关键点 , 到时候可以回想起来,迅速地接着往下看。
要不然,你今天看的,可能到明天就忘个差不多了 。所以朋友们记得收藏后多翻来看看,尝试把源码下载下来反复调试。
错误方式陷入细节,不看全局 :我还没弄清楚森林长啥样,就盯着叶子看