Jetty架构设计之Connector、Handler组件(上)

简介: Jetty架构设计之Connector、Handler组件

Jetty是Eclipse基金会的一个开源项目,和Tomcat一样,Jetty也是一个“HTTP服务器 + Servlet容器”,并且Jetty和Tomcat在架构设计上有不少相似的地方。但同时Jetty也有自己的特点,主要是更加小巧,更易于定制化。Jetty作为一名后起之秀,应用范围也越来越广,比如Google App Engine就采用了Jetty来作为Web容器。


Jetty整体架构

Jetty Server:多个Connector(连接器)、多个Handler(处理器),以及一个线程池

image.png


Jetty中的Connector组件和Handler组件分别来实现HTTP服务器和Servlet容器的功能,这两个组件工作时所需要的线程资源都直接从一个全局线程池ThreadPool中获取。


Jetty Server可以有多个Connector在不同的端口上监听客户请求,而对于请求处理的Handler组件,也可以根据具体场景使用不同的Handler。这样的设计提高了Jetty的灵活性,需要支持Servlet,则可以使用ServletHandler;需要支持Session,则再增加一个SessionHandler。也就是说我们可以不使用Servlet或者Session,只要不配置这个Handler就行了。


一个Socket上可以接收多个HTTP请求,每次请求跟一个Hanlder线程是一对一的关系,因为keepalive,一次请求处理完成后Socket不会立即关闭,下一次再来请求,会分配一个新的Hanlder线程。


如果有很多TCP建立连接后迟迟没有写入数据导致连接请求堵塞,或

如果有很多handle在处理耗时I/O操作,同样可能拖慢整个线程池,进而影响到accepters和selectors,可能拖慢整个线程池,jetty如何应对呢?

这就是为什么Servlet3.0中引入了异步Servlet的概念,就是说遇到耗时的I/O操作,Tomcat的线程会立即返回,当业务线程处理完后,再调用Tomcat的线程将响应发回给浏览器。


为了启动和协调上面的核心组件工作,Jetty提供了一个Server类来做这个事情,它负责创建并初始化Connector、Handler、ThreadPool组件,然后调用start方法启动它们。




对比Tomcat架构

Tomcat在整体上跟Jetty相似,但是:



  • Jetty中没有Service概念
  • Tomcat中的Service包装了多个连接器和一个容器组件,一个Tomcat实例可以配置多个Service,不同Service通过不同的连接器监听不同的端口;而Jetty中Connector是被所有Handler共享的。


image.png



Tomcat中每个连接器都有自己的线程池,而在Jetty中所有的Connector共享一个全局的线程池。



Connector组件

跟Tomcat一样,Connector的主要功能是对I/O模型和应用层协议的封装。I/O模型方面,最新的Jetty 9版本只支持NIO,因此Jetty的Connector设计有明显的Java NIO通信模型的痕迹。至于应用层协议方面,跟Tomcat的Processor一样,Jetty抽象出了Connection组件来封装应用层协议的差异。



Java NIO


Java NIO的核心组件是Channel、Buffer和Selector。

Channel表示一个连接,可以理解为一个Socket,通过它可以读取和写入数据,但是并不能直接操作数据,需要通过Buffer来中转。


Selector可以用来检测Channel上的I/O事件,比如读就绪、写就绪、连接就绪,一个Selector可以同时处理多个Channel,因此单个线程可以监听多个Channel,这样会大量减少线程上下文切换的开销。


同一个浏览器发过来的请求会重用TCP连接,也就是用同一个Channel。Channel是非阻塞的,连接器里维护了这些Channel实例,过了一段时间超时到了channel还没数据到来,表面用户长时间没有操作浏览器,这时Tomcat才关闭这个channel。



服务端NIO程序

  1. 创建服务端Channel,绑定监听端口并把Channel设为非阻塞方式。


ServerSocketChannel server = ServerSocketChannel.open();
server.socket().bind(new InetSocketAddress(port));
server.configureBlocking(false);
  1. 创建Selector,并在Selector中注册Channel感兴趣的事件OP_ACCEPT,告诉Selector如果客户端有新的连接请求到这个端口就通知我。、
Selector selector = Selector.open();
server.register(selector, SelectionKey.OP_ACCEPT);
  1. Selector会在一个死循环里不断调用select去查询I/O状态,查询某个Channel上是否有数据可读。select会返回一个SelectionKey列表,Selector会遍历这个列表,看看是否有“客户”感兴趣的事件,如果有,就采取相应的动作。


比如下面这个例子,如果有新的连接请求,就会建立一个新的连接。连接建立后,再注册Channel的可读事件到Selector中,告诉Selector我对这个Channel上是否有新的数据到达感兴趣。

 while (true) {
        selector.select();//查询I/O事件
        for (Iterator<SelectionKey> i = selector.selectedKeys().iterator(); i.hasNext();) { 
            SelectionKey key = i.next(); 
            i.remove(); 
            if (key.isAcceptable()) { 
                // 建立一个新连接 
                SocketChannel client = server.accept(); 
                client.configureBlocking(false); 
                //连接建立后,告诉Selector,我现在对I/O可读事件感兴趣
                client.register(selector, SelectionKey.OP_READ);
            } 
        }
    } 

所以服务端在I/O通信上主要完成了三件事情:


  • 监听连接
  • I/O事件查询
  • 数据读写


Jetty设计了Acceptor、SelectorManager和Connection来分别做这三事。



Acceptor

独立的Acceptor线程组处理连接请求。多个Acceptor共享同一个ServerSocketChannel。多个Acceptor线程调用同一个ServerSocketChannel#accept,由操作系统保证线程安全。


为啥Acceptor组件是直接使用 ServerSocketChannel.accept() 接受连接的,为什么不使用向Selector注册OP_ACCEPT事件的方式来接受连接?直接调用.accept()方法有什么考虑?

直接调用accept方法,编程上简单一些,否则每个Acceptor又要自己维护一个Selector。


在Connector的实现类ServerConnector中,有一个_acceptors的数组,在Connector启动的时候, 会根据_acceptors数组的长度创建对应数量的Acceptor,而Acceptor的个数可以配置。


for (int i = 0; i < _acceptors.length; i++)
{
  Acceptor a = new Acceptor(i);
  getExecutor().execute(a);
}

Acceptor是ServerConnector中的一个内部类,同时也是一个Runnable,Acceptor线程是通过getExecutor得到的线程池来执行的,前面提到这是一个全局的线程池。

Acceptor通过阻塞方式接受连接。

public void accept(int acceptorID) throws IOException
{
  ServerSocketChannel serverChannel = _acceptChannel;
  if (serverChannel != null && serverChannel.isOpen())
  {
    // 这里是阻塞的
    SocketChannel channel = serverChannel.accept();
    // 执行到这里时说明有请求进来了
    accepted(channel);
  }
}



接受连接成功后会调用accepted,将SocketChannel设置为非阻塞模式,然后交给Selector去处理。

private void accepted(SocketChannel channel) throws IOException
{
    channel.configureBlocking(false);
    Socket socket = channel.socket();
    configure(socket);
    // _manager是SelectorManager实例,里面管理了所有的Selector实例
    _manager.accept(channel);
}
目录
相关文章
|
30天前
|
消息中间件 存储 Java
RocketMQ(一):消息中间件缘起,一览整体架构及核心组件
【10月更文挑战第15天】本文介绍了消息中间件的基本概念和特点,重点解析了RocketMQ的整体架构和核心组件。消息中间件如RocketMQ、RabbitMQ、Kafka等,具备异步通信、持久化、削峰填谷、系统解耦等特点,适用于分布式系统。RocketMQ的架构包括NameServer、Broker、Producer、Consumer等组件,通过这些组件实现消息的生产、存储和消费。文章还提供了Spring Boot快速上手RocketMQ的示例代码,帮助读者快速入门。
|
3月前
|
监控 网络协议 Java
Tomcat源码解析】整体架构组成及核心组件
Tomcat,原名Catalina,是一款优雅轻盈的Web服务器,自4.x版本起扩展了JSP、EL等功能,超越了单纯的Servlet容器范畴。Servlet是Sun公司为Java编程Web应用制定的规范,Tomcat作为Servlet容器,负责构建Request与Response对象,并执行业务逻辑。
Tomcat源码解析】整体架构组成及核心组件
|
1月前
|
存储 分布式计算 API
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
大数据-107 Flink 基本概述 适用场景 框架特点 核心组成 生态发展 处理模型 组件架构
84 0
|
2月前
|
负载均衡 5G 网络性能优化
深入解析LTE(长期演进技术)的基本架构及其关键组件
深入解析LTE(长期演进技术)的基本架构及其关键组件
379 2
|
13天前
|
SQL 数据采集 分布式计算
【赵渝强老师】基于大数据组件的平台架构
本文介绍了大数据平台的总体架构及各层的功能。大数据平台架构分为五层:数据源层、数据采集层、大数据平台层、数据仓库层和应用层。其中,大数据平台层为核心,负责数据的存储和计算,支持离线和实时数据处理。数据仓库层则基于大数据平台构建数据模型,应用层则利用这些模型实现具体的应用场景。文中还提供了Lambda和Kappa架构的视频讲解。
【赵渝强老师】基于大数据组件的平台架构
|
3月前
|
Kubernetes API 调度
Kubernetes 架构解析:理解其核心组件
【8月更文第29天】Kubernetes(简称 K8s)是一个开源的容器编排系统,用于自动化部署、扩展和管理容器化应用。它提供了一个可移植、可扩展的环境来运行分布式系统。本文将深入探讨 Kubernetes 的架构设计,包括其核心组件如何协同工作以实现这些功能。
333 0
|
1月前
|
SQL 存储 分布式计算
大数据-157 Apache Kylin 背景 历程 特点 场景 架构 组件 详解
大数据-157 Apache Kylin 背景 历程 特点 场景 架构 组件 详解
29 9
|
1月前
|
消息中间件 监控 Java
大数据-109 Flink 体系结构 运行架构 ResourceManager JobManager 组件关系与原理剖析
大数据-109 Flink 体系结构 运行架构 ResourceManager JobManager 组件关系与原理剖析
66 1
|
1月前
|
存储 安全 开发工具
百度公共IM系统的Andriod端IM SDK组件架构设计与技术实现
本文主要介绍了百度公共IM系统的Andriod端IM SDK的建设背景、IM SDK主要结构和工作流程以及建设过程遇到的问题和解决方案。
55 3
|
1月前
|
测试技术 数据库 Android开发
深入解析Android架构组件——Jetpack的使用与实践
本文旨在探讨谷歌推出的Android架构组件——Jetpack,在现代Android开发中的应用。Jetpack作为一系列库和工具的集合,旨在帮助开发者更轻松地编写出健壮、可维护且性能优异的应用。通过详细解析各个组件如Lifecycle、ViewModel、LiveData等,我们将了解其原理和使用场景,并结合实例展示如何在实际项目中应用这些组件,提升开发效率和应用质量。
44 6