mysql性能调优与架构设计_了解架构设计远远不够!一文拆解 Tomcat 高并发原理与性能调优

S9VHSWi3PaLyT0

来源 | 码哥字节

上帝视角拆解 Tomcat 架构设计,在了解整个组件设计思路之后。我们需要下凡深入了解每个组件的细节实现。从远到近,架构给人以宏观思维,细节展现饱满的美。关注「码哥字节」获取更多硬核,你,准备好了么?

在上文《追新求快的时代,别让 Java Web 开发必备工具 Tomcat 变成“熟悉的陌生人”!》中,我们站在上帝视角给大家拆解了 Tomcat 架构设计,分析 Tomcat 如何实现启动、停止,通过设计连接池与容器两大组件完成了一个请求的接受与响应。连接器负责对外交流,处理 socket 连接,容器对内负责,加载 Servlet 以及处理具体 Request 请求与响应。

RT4Gwk56bt5iOJ

高并发拆解核心准备

这回,再次拆解,专注 Tomcat 高并发设计之道与性能调优,让大家对整个架构有更高层次的了解与感悟。其中设计的每个组件思路都是将 Java 面向对象、面向接口、如何封装变与不变,如何根据实际需求抽象不同组件分工合作,如何设计类实现单一职责,怎么做到将相似功能高内聚低耦合,设计模式运用到极致的学习借鉴。

这次主要涉及到的是 I/O 模型,以及线程池的基础内容。

希望大家重视如下几个知识点,在掌握以下知识点再来拆解 Tomcat,就会事半功倍,否则很容易迷失方向不得其法。

一起来看 Tomcat 如何实现并发连接处理以及任务处理,性能的优化是每一个组件都起到对应的作用,如何使用最少的内存,最快的速度执行是我们的目标。

设计模式

?模板方法模式:抽象算法流程在抽象类中,封装流程中的变化与不变点。将变化点延迟到子类实现,达到代码复用,开闭原则。

?观察者模式:针对事件不同组件有不同响应机制的需求场景,达到解耦灵活通知下游。

?责任链模式:将对象连接成一条链,将沿着这条链传递请求。在 Tomcat 中的 Valve 就是该设计模式的运用。

I/O 模型

Tomcat 实现高并发接收连接,必然涉及到 I/O 模型的运用,了解同步阻塞、异步阻塞、I/O 多路复用,异步非阻塞相关概念以及 Java NIO 包的运用很有必要。本文也会带大家着重说明 I/O 是如何在 Tomcat 运用实现高并发连接。大家通过本文我相信对 I/O 模型也会有一个深刻认识。

Java 并发编程

实现高并发,除了整体每个组件的优雅设计、设计模式的合理、I/O 的运用,还需要线程模型,如何高效的并发编程技巧。在高并发过程中,不可避免的会出现多个线程对共享变量的访问,需要加锁实现,如何高效的降低锁冲突。因此作为程序员,要有意识的尽量避免锁的使用,比如可以使用原子类 CAS 或者并发集合来代替。如果万不得已需要用到锁,也要尽量缩小锁的范围和锁的强度。

对于并发相关的基础知识,如果读者感兴趣「码哥字节」后面也给大家安排上,目前也写了部分并发专辑,大家可移步到历史文章或者专辑翻阅,主要讲解了并发实现的原理、什么是内存可见性,JMM 内存模模型、读写锁等并发知识点。

RT7S2kzFTfre26

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 containerconnector.getService.getContainer.getPipeline.getFirst.invoke( request, response);

这个调用会触发 getPipeline 构成的责任链模式将请求一步步走入容器内部,每个容器都有一条 Pipeline,通过 First 开始到 Basic 结束并进入容器内部持有的子类容器,最后到 Servlet,这里就是责任链模式的经典运用。具体的源码组件是 Pipeline 构成一条请求链,每一个链点由 Valve 组成。如下图所示,整个 Tomcat 的架构设计重要组件清晰可见,希望大家将这个全局架构图深深印在脑海里,掌握全局思路才能更好地分析细节之美。

S9ms7b18uSqSQj

Tomcat 架构

启动流程:startup.sh 脚本到底发生了什么

S9ms8GoEckCjzv

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 流程

S9ms8HHGJijP7M

Tomcat Init

start 流程

S9ms8HXAoTs7sM

Tomcat start

读者朋友根据我的两篇内容,抓住主线组件去 debug,然后跟着该泳道图阅读源码,我相信都会有所收获,并且事半功倍。在读源码的过程中,切勿进入某个细节,一定要先把各个组件抽象出来,了解每个组件的职责即可。最后在了解每个组件的职责与设计哲学之后再深入理解每个组件的实现细节,千万不要一开始就想着深入理解具体一篇叶子。

每个核心类我在架构设计图以及泳道图都标识出来了,「码哥字节」给大家分享下如何高效阅读源码,以及保持学习兴趣的心得体会。

如何正确阅读源码

切勿陷入细节,不看全局:我还没弄清楚森林长啥样,就盯着叶子看 ,看不到全貌和整体设计思路。所以阅读源码学习的时候不要一开始就进入细节,而是宏观看待整体架构设计思想,模块之间的关系。

1.阅读源码之前,需要有一定的技术储备

比如常用的设计模式,这个必须掌握,尤其是:模板方法、策略模式、单例、工厂、观察者、动态代理、适配器、责任链、装饰器。大家可以看 「码哥字节」关于设计模式的历史文章,打造好的基础。

2.必须会使用这个框架/类库,精通各种变通用法

魔鬼都在细节中,如果有些用法根本不知道,可能你能看明白代码是什么意思,但是不知道它为什么这些写。

3.先去找书,找资料,了解这个软件的整体设计。

从全局的视角去看待,上帝视角理出主要核心架构设计,先森林后树叶。都有哪些模块?模块之间是怎么关联的?怎么关联的?

可能一下子理解不了,但是要建立一个整体的概念,就像一个地图,防止你迷航。

在读源码的时候可以时不时看看自己在什么地方。就像「码哥字节」给大家梳理好了 Tomcat 相关架构设计,然后自己再尝试跟着 debug,这样的效率如虎添翼。

4. 搭建系统,把源代码跑起来!

Debug 是非常非常重要的手段, 你想通过只看而不运行就把系统搞清楚,那是根本不可能的!合理运用调用栈(观察调用过程上下文)。

5.笔记

一个非常重要的工作就是记笔记(又是写作!),画出系统的类图(不要依靠 IDE 给你生成的), 记录下主要的函数调用, 方便后续查看。

文档工作极为重要,因为代码太复杂,人的大脑容量也有限,记不住所有的细节。文档可以帮助你记住关键点, 到时候可以回想起来,迅速地接着往下看。

要不然,你今天看的,可能到明天就忘个差不多了。所以朋友们记得收藏后多翻来看看,尝试把源码下载下来反复调试。

错误方式

  • 陷入细节,不看全局:我还没弄清楚森林长啥样,就盯着叶子看 ,看不到全貌和整体设计思路。所以阅读源码学习的时候不要一开始就进入细节,而是宏观看待整体架构设计思想,模块之间的关系。

  • 还没学会用就研究如何设计:首先基本上框架都运用了设计模式,我们最起码也要了解常用的设计模式,即使是“背”,也得了然于胸。在学习一门技术,我推荐先看官方文档,看看有哪些模块、整体设计思想。然后下载示例跑一遍,最后才是看源码。

  • 看源码深究细节:到了看具体某个模块源码的时候也要下意识的不要去深入细节,重要的是学习设计思路,而不是具体一个方法实现逻辑。除非自己要基于源码做二次开发,而且二次开发也是基于在了解整个架构的情况下才能深入细节。

RTJXJ1kBqzfCnu

组件设计-落实单一职责、面向接口思想

当我们接到一个功能需求的时候,最重要的就是抽象设计,将功能拆解主要核心组件,然后找到需求的变化与不变点,将相似功能内聚,功能之间若耦合,同时对外支持可拓展,对内关闭修改。努力做到一个需求下来的时候我们需要合理的抽象能力抽象出不同组件,而不是一锅端将所有功能糅合在一个类甚至一个方法之中,这样的代码牵一发而动全身,无法拓展,难以维护和阅读。

看看 Tomcat 如何实现将 Tomcat 启动,并且又是如何接受请求,将请求转发到我们的 Servlet 中。

Catalina

主要任务就是创建 Server,并不是简单创建,而是解析 server.xml 文件把文件配置的各个组件意义创建出来,接着调用 Server 的 init 和 start 方法,启动之旅从这里开始…,同时还要兼顾异常,比如关闭 Tomcat 还需要做到优雅关闭启动过程创建的资源需要释放,Tomcat 则是在 JVM 注册一个「关闭钩子」,源码我都加了注释,省略了部分无关代码。同时通过 await 监听停止指令关闭 Tomcat。

 
 /** * Start a new server instance. */ public void start { // 若 server 为空,则解析 server.xml 创建 if (getServer == ) { load; } // 创建失败则报错并退出启动 if (getServer == ) { log.fatal("Cannot start server. Server instance is not configured."); return; } // 开始启动 server try { getServer.start; } catch (LifecycleException e) { log.fatal(sm.getString("catalina.serverStartFail"), e); try { // 异常则执行 destroy 销毁资源 getServer.destroy; } catch (LifecycleException e1) { log.debug("destroy failed for failed Server 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值