大家好,欢迎来到IT知识分享网。
Tomcat系统架构(上): 连接器是如何设计的?
需求分析:
Tomcat需要实现的核心功能
架构设计:
连接器和容器,其中连接器负责外部交流,容器负责内部处理。具体来说就是,连接器处理 Socket 通信和应用层协议的解析,得到 Servlet请求;而容器则负责处理 Servlet 请求。
tomcat设计了两个核心组件
tomcat 连接器(Connector)
连接器(Connector):连接器负责对外交流
Tomcat container 内部容器
容器(Container):容器负责内部处理
Tomcat 支持的多种 I/O 模型;
Tomcat 支持的应用层协议有:
容器结构图 xml结构关系表示图
<Server> <Service> <Connector> </Connector> <Engine> <Host> <Context> </Context> </Host> </Engine> </Service> </Server>
Tomcat连接器(Connector):连接器负责对外交流
我们可以把连接器的功能需求进一步细化,比如:
1 监听网络端口。
2 接受网络连接请求。
3 读取请求网络字节流。
4 根据具体应用层协议(HTTP/AJP)解析字节流,生成统一的 Tomcat Request 对象。
将 Tomcat Request 对象转成标准的 ServletRequest。调用 Servlet 容器,得到 ServletResponse。将 ServletResponse 转成 Tomcat Response 对象。
组件之间通过抽象接口交互。这样做还有一个好处是封装变化。这是面向对象设计的精髓,将系统中经常变化的部分和稳定的部分隔离,有助于增加复用性,并降低系统耦合度。
网络通信的 I/O 模型是变化的,可能是非阻塞 I/O、异步 I/O 或者 APR。应用层协议也是变化的,可能是 HTTP、HTTPS、AJP。浏览器端发送的请求信息也是变化的。
如果要支持新的 I/O 方案、新的应用层协议,只需要实现相关的具体子类,上层通用的处理逻辑是不变的。
通过对 Tomcat 整体架构的学习,我们可以得到一些设计复杂系统的基本思路。首先要分
析需求,根据高内聚低耦合的原则确定子模块,然后找出子模块中的变化点和不变点,用接口和抽象基类去封装不变点,在抽象基类中定义模板方法,让子类自行实现抽象方法,也就是具体子类去实现变化点。(说起来简单。做起来并非易事)
两个问题?
问题:源码如何阅读效果好啊?现在源码一大堆,不知从何下手。
回复: 抓主线,抓主干,每个系统中都有一个关键的核心类,紧紧抓住这些类,先不要分散,在逐步看旁枝,等你学习弄明白一个经典的系统,很多套路你就明白了。
Tomcat系统架构(下):聊聊多层容器的设计
容器,顾名思义就是用来装载东西的器具,在 Tomcat 里,容器就是用来装载 Servlet
的。
那 Tomcat 的 Servlet 容器是如何设计的呢?
容器的层次结构
组合模式是啥 TODO
public interface Container extends Lifecycle {
public void setName(String name); public Container getParent(); public void setParent(Container container); public void addChild(Container child); public void removeChild(Container child); public Container findChild(String name); }
问题:请求定位 Servlet 的过程
首先,根据协议和端口号选定 Service 和 Engine。
我们知道 Tomcat 的每个连接器都监听不同的端口,比如 Tomcat 默认的 HTTP 连接器监
听 8080 端口、默认的 AJP 连接器监听 8009 端口。上面例子中的 URL 访问的是 8080 端
口,因此这个请求会被 HTTP 连接器接收,而一个连接器是属于一个 Service 组件的,这
样 Service 组件就确定了。我们还知道一个 Service 组件里除了有多个连接器,还有一个容器组件,具体来说就是一个 Engine 容器,因此 Service 确定了也就意味着 Engine 也确定了。
然后,根据域名选定 Host
Service 和 Engine 确定后,Mapper 组件通过 URL 中的域名去查找相应的 Host 容器,比
如例子中的 URL 访问的域名是user.shopping.com,因此 Mapper 会找到 Host2 这个容器。
之后,根据 URL 路径找到 Context 组件。
Host 确定以后,Mapper 根据 URL 的路径来匹配相应的 Web 应用的路径,比如例子中访
问的是 /order,因此找到了 Context4 这个 Context 容器。
最后,根据 URL 路径找到 Wrapper(Servlet)。
Context 确定后,Mapper 再根据 web.xml 中配置的 Servlet 映射路径来找到具体的
Wrapper 和 Servlet。
责任链模式是啥
public interface Valve {
public Valve getNext(); public void setNext(Valve valve); public void invoke(Request request, Response response) }
public interface Pipeline extends Contained {
public void addValve(Valve valve); public Valve getBasic(); public void setBasic(Valve valve); public Valve getFirst(); }
// Calling the container connector.getService().getContainer().getPipeline().getFirst().invoke(request, response)
精华
思考
几个问题:
1.Wrapper容器里有且只有一个Servlet,Context里可以有多个Servlet即可以有多个
Wrapper,这样理解对吗 ?
2.PepeLine负责维护链式Vavle,具体Vavle负责处理具体请求?
回复:
1, 对的
2,Vavle可以理解对请求进行“拦截”和“修正”,类似Filter,但是Valve用来扩展容器本身的
功能的,真正处理请求并产生响应的是Servlet。
几个问题:最外层是server,一个server对应一个Tomcat实例,server下面可以由很多service组件,service组件是区分协议和端口号(问题1:域名访问的话请求到达这里是已经被解析成port了吗?如果已经解析了后面为啥还能拿到二级域名)。每个service对应多个连接器和一个engine。(问题2:为啥要设置多个连接器对应一个engine,是为了提高连接器接受请求的并发吗?)
回复: 1,DNS解析是在客户端完成的,你需要在客户端把两个域名指向同一个IP,可以通过hosts配置,因此只要你使用的端口相同,两个域名其实访问的是同一个Service组件。而Tomcat设计通过请求URL中Host来转发到不同Host组件的。
2. 不同的连接器代表一种通信的路径,比如同时支持HTTP和HTTPS,不是为了并发。
容器结构图 xml结构关系表示图
<Server> <Service> <Connector> </Connector> <Engine> <Host> <Context> </Context> </Host> </Engine> </Service> </Server>
Tomcat如何实现一键式启停?
在我们实际的工作中,如果你需要设计一个比较大的系统或者框架时,你同样也需要考虑这几个问题:
今天我们就来解决上面的问题,在这之前,先来看看组件之间的关系。如果你仔细分析过这些组件,可以发现它们具有两层关系。
这两层关系决定了系统在创建组件时应该遵循一定的顺序。
创建组件时潜在的危险
因此,最直观的做法就是将图上所有的组件按照先小后大、先内后外的顺序创建出来,然后组装在一起。不知道你注意到没有,这个思路其实很有问题!因为这样不仅会造成代码逻辑混乱和组件遗漏,而且也不利于后期的功能扩展。
创建组件时解决方法
为了解决这个问题,我们希望找到一种通用的、统一的方法来管理组件的生命周期,就像汽车“一键启动”那样的效果。
一键式启停:LifeCycle 接口
我在前面说到过,设计就是要找到系统的变化点和不变点。这里的不变点就是每个组件都要经历创建、初始化、启动这几个过程,这些状态以及状态的转化是不变的。而变化点是每个具体组件的初始化方法,也就是启动方法是不一样的。因此,我们把不变点抽象出来成为一个接口,这个接口跟生命周期有关,叫作 LifeCycle。LifeCycle 接口里应该定义这么几个方法:init()、start()、stop() 和 destroy(),每个具体的组件去实现这些方法。
可扩展性:LifeCycle 事件
具体来说就是在 LifeCycle 接口里加入两个方法:添加监听器和删除监听器。除此之外,我们还需要定义一个 Enum 来表示组件有哪些状态,以及处在什么状态会触发什么样的事件。因此 LifeCycle 接口和 LifeCycleState 就定义成了下面这样。
从图上你可以看到,组件的生命周期有 NEW、INITIALIZING、INITIALIZED、
STARTING_PREP、STARTING、STARTED 等,而一旦组件到达相应的状态就触发相应的事件,比如 NEW 状态表示组件刚刚被实例化;而当 init() 方法被调用时,状态就变成
INITIALIZING 状态,这个时候,就会触发 BEFORE_INIT_EVENT 事件,如果有监听器在监听这个事件,它的方法就会被调用。
重用性:LifeCycleBase 抽象基类
有了接口,我们就要用类去实现接口。一般来说实现类不止一个,不同的类在实现接口时往往会有一些相同的逻辑,如果让各个子类都去实现一遍,就会有重复代码。那子类如何重用这部分逻辑呢?其实就是定义一个基类来实现共同的逻辑,然后让各个子类去继承它,就达到了重用的目的。
而基类中往往会定义一些抽象方法,所谓的抽象方法就是说基类不会去实现这些方法,而是调用这些方法来实现骨架逻辑。抽象方法是留给各个子类去实现的,并且子类必须实现,否则无法实例化。
下面是 LifeCycleBase 的 init() 方法实现。
@Override public final synchronized void init() throws LifecycleException {
//1. 状态检查 if (!state.equals(LifecycleState.NEW)) {
invalidTransition(Lifecycle.BEFORE_INIT_EVENT); } try {
//2. 触发 INITIALIZING 事件的监听器 setStateInternal(LifecycleState.INITIALIZING, null, false); //3. 调用具体子类的初始化方法 initInternal(); //4. 触发 INITIALIZED 事件的监听器 setStateInternal(LifecycleState.INITIALIZED, null, false); } catch (Throwable t) {
... } }
值得我们特别学习的是
课后思考
ContainerBase提供了针对Container接口的通用实现,所以最重要的职责包含两个:
- 维护容器通用的状态数据
- 提供管理状态数据的通用方法
Tomcat的“高层们”都负责做什么?
另一方面,软件系统中往往都有一些起管理作用的组件,你可以学习和借鉴 Tomcat是如何实现这些组件的。
Catalina
public void start() {
//1. 如果持有的 Server 实例为空,就解析 server.xml 创建出来 if (getServer() == null) {
load(); } //2. 如果创建失败,报错退出 if (getServer() == null) {
log.fatal(sm.getString("catalina.noServer")); return; } //3. 启动 Server try {
getServer().start(); } catch (LifecycleException e) {
return; } // 创建并注册关闭钩子 if (useShutdownHook) {
if (shutdownHook == null) {
shutdownHook = new CatalinaShutdownHook(); } Runtime.getRuntime().addShutdownHook(shutdownHook); } // 用 await 方法监听停止请求 if (await) {
await(); stop(); } }
所以当我们在设计这样的组件时,需要考虑两个方面:
其次还需要根据子组件依赖关系来决定它们的启动和停止顺序,以及如何优雅的停止,防止异常情况下的资源泄漏。这正是“管理者”应该考虑的事情。
理论上:
线程数=((线程阻塞时间 + 线程忙绿时间) / 线程忙碌时间) * cpu核数
如果线程始终不阻塞,一直忙碌,会一直占用一个CPU核,因此可以直接设置 线程数=CPU核数。
Tomcat 使用Java 编写 内存 CPU 在不改变业务代码的思路 :主要有下面三种思路
- 第一点 优化思路,增大线程池数量和增大请求队列大小
- 第二点 优化思路 减少业务线程的执行时间(内存 CPU 加大)
- 第三点优化思路,改变Tomcat的线程模型的优化
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/107188.html



