【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)

简介: 【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)

Tomcat系统架构分析

Tomcat 的结构很复杂,但是Tomcat也非常的模块化,找到了Tomcat 最核心的模块,就抓住Tomcat的“七寸”。

Tomcat 整体结构

Tomcat的总体结构从外到内进行分布,最大范围的服务容器是Server组件,Service服务组件(可以有多个同时存在),Connector(连接器)、Container(容器服务),其他组件:Jasper(Jasper解析)、Naming(命名服务)、Session(会话管理)、Logging(日志管理)、JMX(Java 管理器扩展服务)、Websocket(交互服务)。

Tomcat总体结构图

从上图中可以看出 Tomcat 的心脏是两个组件:Connector 和 Container,关于这两个组件将在后面详细介绍。

Connector 组件是可以被替换,这样可以提供给服务器设计者更多的选择,因为这个组件是如此重要,不仅跟服务器的设计的本身,而且和不同的应用场景也十分相关,所以一个 Container 可以选择对应多个 Connector。 多个 Connector 和一个 Container 就形成了一个 Service。

Service的概念大家都很熟悉了,有了 Service 就可以对外提供服务了,但是 Service 还要一个生存的环境,必须要有人能够给她生命、掌握其生死大权,那就非 Server 莫属了。所以整个 Tomcat 的生命周期由Server 控制。

以 Service 作为“婚姻”

我们将 Tomcat 中 Connector、Container 作为一个整体比作一对情 侣的话,Connector 主要负责对外交流,可以比作为Boy,Container 主要处理 Connector 接受的请求,主要是处理内部事务,可以比作 为 Girl。那么这个 Service 就是连接这对男女的结婚证了。是 Service 将它们连接在一起,共同组成一个家庭。当然要组成一个家 庭还要很多其它的元素。

说白了,Service 只是在 Connector 和 Container 外面多包一层, 把它们组装在一起,向外面提供服务,一个 Service 可以设置多个 Connector,但是只能有一个 Container 容器。这个 Service 接口的 方法列表如下:


1) Service 接口
方法列表

从 Service 接口中定义的方法中可以看出,它主要是为了关联Connector 和 Container,同时会初始化它下面的其它组件,注意接口中它并没有规定一定要控制它下面的组件的生命周期。所有组件的生命周期在一个 Lifecycle 的接口中控制,这里用到了一个重要的设计模式,关于这个接口将在后面介绍。

Tomcat 中 Service 接口的标准实现类是 StandardService 它不仅 实现了 Service 借口同时还实现了 Lifecycle 接口,这样它就可以控制它下面的组件的生命周期了。StandardService 类结构图如下

2) StandardService 的类结构图
方法列表

从上图中可以看出除了 Service 接口的方法的实现以及控制组件生命周期的 Lifecycle 接口的实现,还有几个方法是用于在事件监听的方法的实现,不仅是这个 Service 组件,Tomcat中其它组件也同样有这几个方法,这也是一个典型的设计模式,将在后面介绍。

下面看一下 StandardService 中主要的几个方法实现的代码,下面是 setContainer 和 addConnector 方法的源码:

3) StandardService. SetContainer

java

复制代码

public void setContainer(Container container) {
  Container oldContainer = this.container;
  if ((oldContainer != null) && (oldContainer instanceof Engine))
     ((Engine) oldContainer).setService(null);
    this.container = container;
     if ((this.container != null) && (this.container instanceof Engine))
       ((Engine) this.container).setService(this);
       if (started && (this.container != null) && (this.container instanceof Lifecycle){
         try {
             ((Lifecycle) this.container).start();
         } catch (LifecycleException e) {
             ;
        }
       }
      synchronized (connectors) {
         for (int i = 0; i < connectors.length; i++)
            connectors[i].setContainer(this.container);
         }
         if (started && (oldContainer != null) && (oldContainer instanceof 
                                  Lifecycle)) {
           try {
           ((Lifecycle) oldContainer).stop();
           } catch (LifecycleException e) {
           ;
           }
           }
       support.firePropertyChange("container", oldContainer, this.container);
    }

这段代码很简单,其实就是先判断当前的这个 Service 有没有已经关联了 Container,如果已经关联了,那么去掉这个关联关系——oldContainer.setService(null)。如果这个 oldContainer 已经被启动了,结束它的生命周期。然后再替换新的关联、再初始化并开始这个新的 Container 的生命周期。最后将这个过程通知感兴趣的事件监 听程序。这里值得注意的地方就是,修改 Container 时要将新的Container 关联到每个 Connector,还好 Container 和 Connector 没有双向关联,不然这个关联关系将会很难维护。

4) StandardService. addConnector

java

复制代码

public void addConnector(Connector connector) {
  synchronized (connectors) {
    connector.setContainer(this.container);
    connector.setService(this);
    Connector results[] = new Connector[connectors.length + 1];
    System.arraycopy(connectors, 0, results, 0, connectors.length);
    results[connectors.length] = connector;
    connectors = results;
    if (initialized) {
       try {
       connector.initialize();
       } catch (LifecycleException e) {
       e.printStackTrace(System.err);
       }
    }
    if (started && (connector instanceof Lifecycle)) {
       try {
        ((Lifecycle) connector).start();
       } catch (LifecycleException e) {
       ;
       }
    }
    support.firePropertyChange("connector", null, connector);
   } 
}

上面是 addConnector 方法,这个方法也很简单,首先是设置关联关系,然后是初始化工作,开始新的生命周期。这里值得一提的是,注意 Connector 用的是数组而不是 List 集合,这个从性能角度考虑可以理解,有趣的是这里用了数组但是并没有向我们平常那样,一开始就分配一个固定大小的数组,它这里的实现机制是:重新创建一个当前大小的数组对象,然后将原来的数组对象 copy 到新的数组中,这种方式实现了类似的动态数组的功能,这种实现方式,值得我们以后拿来借鉴。


最新的 Tomcat6 中 StandardService 也基本没有变化,但是从Tomcat5 开始 Service、Server 和容器类都继承了MBeanRegistration 接口,Mbeans 的管理更加合理。

以 Server 为“居”

前面说一对情侣因为 Service 而成为一对夫妻,有了能够组成一个家庭的基本条件,但是它们还要有个实体的家,这是它们在社会上生存之本,有了家它们就可以安心的为人民服务了,一起为社会创造财富。

Server 要完成的任务很简单,就是要能够提供一个接口让其它程序能够访问到这个 Service 集合、同时要维护它所包含的所有 Service 的生命周期,包括如何初始化、如何结束服务、如何找到别人要访问的 Service。

还有其它的一些次要的任务,如您住在这个地方去登记啊、可能还有要配合当地机关日常的安全检查什么 的。

Server 的类结构图如下:

1) Server 的类结构图


它的标准实现类 StandardServer 实现了上面这些方法,同时也实现LifecycleMbeanRegistration 两个接口的所有方法,下面主要看一下 StandardServer 重要的一个方法 addService 的实现:

2) StandardServer.addService

java

复制代码

public void addService(Service service) {
  service.setServer(this);
  synchronized (services) {
  Service results[] = new Service[services.length + 1];
  System.arraycopy(services, 0, results, 0, services.length);
  results[services.length] = service;
  services = results;
    if (initialized) {
       try {
       service.initialize();
       } catch (LifecycleException e) {
       e.printStackTrace(System.err);
       }
    }
     if (started && (service instanceof Lifecycle)) {
           try {
           ((Lifecycle) service).start();
           } catch (LifecycleException e) {
           ;
           }
    }
    support.firePropertyChange("service", null, service);
 }
}

从上面第一句就知道了 Service 和 Server 是相互关联的,Server也是和 Service 管理 Connector 一样管理它,也是将 Service 放在一个数组中,后面部分的代码也是管理这个新加进来的 Service 的生命周期。Tomcat6 中也是没有什么变化的。


组件的生命线“Lifecycle”

arduino

复制代码

前面一直在说 Service 和 Server 管理它下面组件的生命周期,那它们是如何管理的呢?

Tomcat 中组件的生命周期是通过 Lifecycle 接口来控制的,组件只要继承这个接口并实现其中的方法就可以统一被拥有它的组件控制了,这样一层一层的直到一个最高级的组件就可以控制 Tomcat 中所有组件的生命周期,这个最高的组件就是 Server,而控制 Server 的是 Startup,也就是您启动和关闭 Tomcat

下面是 Lifecycle 接口的类结构图:

1) Lifecycle 类结构图

除了控制生命周期的 Start 和 Stop 方法外还有一个监听机制,在生命周期开始和结束的时候做一些额外的操作。这个机制在其它的框架中也被使用,如在 Spring 中。关于这个设计模式会在后面介绍。

Lifecycle 接口的方法的实现都在其它组件中,就像前面中说的,组件的生命周期由包含它的父组件控制,所以它的 Start 方法自然就是调用它下面的组件的 Start 方法,Stop 方法也是一样。如在 Server 中 Start 方法就会调用 Service 组件的 Start 方法,Server 的Start 方法代码如下:

2) StandardServer.Start

java

复制代码

public void start() throws LifecycleException {
  if (started) {
        log.debug(sm.getString("standardServer.start.started"));
              return;
  }
  lifecycle.fireLifecycleEvent(BEFORE_START_EVENT, null);
  lifecycle.fireLifecycleEvent(START_EVENT, null);
  started = true;
  synchronized (services) {
    for (int i = 0; i < services.length; i++) {
      if (services[i] instanceof Lifecycle)
        ((Lifecycle) services[i]).start();
      }
    }
  lifecycle.fireLifecycleEvent(AFTER_START_EVENT, null);
}

监听的代码会包围 Service 组件的启动过程,就是简单的循环启动所有 Service 组件的Start方法,但是所有 Service 必须要实现Lifecycle 接口,这样做会更加灵活。 Server 的 Stop 方法代码如下:

3) StandardServer.Stop

java

复制代码

public void stop() throws LifecycleException {
  if (!started)
    return;
  lifecycle.fireLifecycleEvent(BEFORE_STOP_EVENT, null);
  lifecycle.fireLifecycleEvent(STOP_EVENT, null);
  started = false;
  for (int i = 0; i < services.length; i++) {
    if (services[i] instanceof Lifecycle)
      ((Lifecycle) services[i]).stop();
    }
    lifecycle.fireLifecycleEvent(AFTER_STOP_EVENT, null);
}

它所要做的事情也和 Start 方法差不多。

相关文章
|
3月前
|
消息中间件 Java Kafka
Java 事件驱动架构设计实战与 Kafka 生态系统组件实操全流程指南
本指南详解Java事件驱动架构与Kafka生态实操,涵盖环境搭建、事件模型定义、生产者与消费者实现、事件测试及高级特性,助你快速构建高可扩展分布式系统。
232 7
|
1月前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
172 1
|
4月前
|
应用服务中间件 Linux 测试技术
Tomcat启动错误:组件启动失败StandardEngine[Catalina].StandardHost[localhost].StandardContext[]
解决此类问题需要系统地排查和分析,有时候甚至需要根据应用的具体情况定制化解决方案。注意的是,如果问题涉及到复杂的应用逻辑或第三方库,那么可能需要深入到具体的应用代码和库文档中查找原因。最后,保持软件环境的更新也是预防这类问题的好习惯,因为新版本的软件通常会修复旧版本中的已知错误。
616 12
|
6月前
|
NoSQL Java Redis
分布式锁—6.Redisson的同步器组件
Redisson提供了多种分布式同步工具,包括分布式锁、Semaphore和CountDownLatch。分布式锁包括可重入锁、公平锁、联锁、红锁和读写锁,适用于不同的并发控制场景。Semaphore允许多个线程同时获取锁,适用于资源池管理。CountDownLatch则用于线程间的同步,确保一组线程完成操作后再继续执行。Redisson通过Redis实现这些同步机制,提供了高可用性和高性能的分布式同步解决方案。源码剖析部分详细介绍了这些组件的初始化和操作流程,展示了Redisson如何利用Redis命令和
|
消息中间件 存储 Java
RocketMQ(一):消息中间件缘起,一览整体架构及核心组件
【10月更文挑战第15天】本文介绍了消息中间件的基本概念和特点,重点解析了RocketMQ的整体架构和核心组件。消息中间件如RocketMQ、RabbitMQ、Kafka等,具备异步通信、持久化、削峰填谷、系统解耦等特点,适用于分布式系统。RocketMQ的架构包括NameServer、Broker、Producer、Consumer等组件,通过这些组件实现消息的生产、存储和消费。文章还提供了Spring Boot快速上手RocketMQ的示例代码,帮助读者快速入门。
|
12月前
|
监控 前端开发 数据可视化
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
@icraft/player-react 是 iCraft Editor 推出的 React 组件库,旨在简化3D数字孪生场景的前端集成。它支持零配置快速接入、自定义插件、丰富的事件和方法、动画控制及实时数据接入,帮助开发者轻松实现3D场景与React项目的无缝融合。
772 9
3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目,实现数字孪生
|
11月前
|
设计模式 存储 算法
分布式系统架构5:限流设计模式
本文是小卷关于分布式系统架构学习的第5篇,重点介绍限流器及4种常见的限流设计模式:流量计数器、滑动窗口、漏桶和令牌桶。限流旨在保护系统免受超额流量冲击,确保资源合理分配。流量计数器简单但存在边界问题;滑动窗口更精细地控制流量;漏桶平滑流量但配置复杂;令牌桶允许突发流量。此外,还简要介绍了分布式限流的概念及实现方式,强调了限流的代价与收益权衡。
476 12
|
11月前
|
设计模式 监控 Java
分布式系统架构4:容错设计模式
这是小卷对分布式系统架构学习的第4篇文章,重点介绍了三种常见的容错设计模式:断路器模式、舱壁隔离模式和重试模式。断路器模式防止服务故障蔓延,舱壁隔离模式通过资源隔离避免全局影响,重试模式提升短期故障下的调用成功率。文章还对比了这些模式的优缺点及适用场景,并解释了服务熔断与服务降级的区别。尽管技术文章阅读量不高,但小卷坚持每日更新以促进个人成长。
298 12
|
SQL 数据采集 分布式计算
【赵渝强老师】基于大数据组件的平台架构
本文介绍了大数据平台的总体架构及各层的功能。大数据平台架构分为五层:数据源层、数据采集层、大数据平台层、数据仓库层和应用层。其中,大数据平台层为核心,负责数据的存储和计算,支持离线和实时数据处理。数据仓库层则基于大数据平台构建数据模型,应用层则利用这些模型实现具体的应用场景。文中还提供了Lambda和Kappa架构的视频讲解。
976 3
【赵渝强老师】基于大数据组件的平台架构
|
设计模式 API 持续交付
深入理解微服务架构:设计模式与实践
【10月更文挑战第19天】介绍了微服务架构的核心概念、设计模式及最佳实践。文章详细探讨了微服务的独立性、轻量级通信和业务能力,并介绍了聚合器、链式和发布/订阅等设计模式。同时,文章还分享了实施微服务的最佳实践,如定义清晰的服务边界、使用API网关和服务发现机制,以及面临的挑战和职业心得。

热门文章

最新文章