Netty源码分析(1)-核心组件与架构

1. Netty 组件架构

Netty 的事件驱动框架基于 主从 Reactor 多线程 实现,其工作处理的流程如下所示。根据功能的不同,Netty 的组件主要可分为两个部分,分别是 事件分发组件 和 业务处理组件

在这里插入图片描述

2. 事件分发组件

事件分发组件主要包含了 EventLoopGroupAcceptor 两类,其中 EventLoopGroup 为事件循环组,负责事件的分发处理,Acceptor 则是服务端处理 accept 事件的处理器,负责将主事件循环组新建的连接注册到从事件循环组,起到连接的作用

2.1 事件循环组 EventLoopGroup

EventLoopGroup 主要管理 EventLoop 的生命周期,内部维护了一组 EventLoop,每个 EventLoop 负责处理多个 Channel 上的事件,而一个 Channel 只对应于一个 EventLoop

2.1.1 事件循环组实例 NioEventLoopGroup

NioEventLoopGroup 继承结构很深,各个关键属性分布在不同的父类中,比较重要的如下:

  1. MultithreadEventLoopGroup:
    DEFAULT_EVENT_LOOP_THREADS : 表示默认的事件循环线程数,默认值为机器可用核心数 * 2
  2. MultithreadEventExecutorGroup:
    children: EventExecutor 数组,维护属于当前 group 的事件循环线程,也就是 NioEventLoop 实例对象

MultithreadEventExecutorGroupEventLoopGroup 最重要的实现,其构造方法主要完成了以下几件事:

  1. new ThreadPerTaskExecutor(new DefaultThreadFactory()) 生成 Executor 实例,并指定其线程工厂
  2. 调用 newChild() 方法为当前 group 新建 NioEventLoop 实例,并指定其 Executor 入参为 ThreadPerTaskExecutor 对象,该对象后续将用于创建和启动 EventLoop 线程
  3. 如果有一个 NioEventLoop 实例新建失败,调用已创建的每个 NioEventLoop 实例的 shutdownGracefully() 方法启动事件循环线程
protected MultithreadEventExecutorGroup(int nThreads, Executor executor,
                                            EventExecutorChooserFactory chooserFactory, Object... args) {
    
    
        if (nThreads <= 0) {
    
    
            throw new IllegalArgumentException(String.format("nThreads: %d (expected: > 0)", nThreads));
        }

        if (executor == null) {
    
    
            // #1
            executor = new ThreadPerTaskExecutor(newDefaultThreadFactory());
        }

        children = new EventExecutor[nThreads];

        for (int i = 0; i < nThreads; i ++) {
    
    
            boolean success = false;
            try {
    
    
                // #2
                children[i] = newChild(executor, args);
                success = true;
            } catch (Exception e) {
    
    
                // TODO: Think about if this is a good exception type
                throw new IllegalStateException("failed to create a child event loop", e);
            } finally {
    
    
                if (!success) {
    
    
                    for (int j = 0; j < i; j ++) {
    
    
                        // #3
                        children[j].shutdownGracefully();
                    }

                    for (int j = 0; j < i; j ++) {
    
    
                        EventExecutor e = children[j];
                        try {
    
    
                            while (!e.isTerminated()) {
    
    
                                e.awaitTermination(Integer.MAX_VALUE, TimeUnit.SECONDS);
                            }
                        } catch (InterruptedException interrupted) {
    
    
                            // Let the caller handle the interruption.
                            Thread.currentThread().interrupt();
                            break;
                        }
                    }
                }
            }
        }

        chooser = chooserFactory.newChooser(children);

        final FutureListener<Object> terminationListener = new FutureListener<Object>() {
    
    
            @Override
            public void operationComplete(Future<Object> future) throws Exception {
    
    
                if (terminatedChildren.incrementAndGet() == children.length) {
    
    
                    terminationFuture.setSuccess(null);
                }
            }
        };

        for (EventExecutor e: children) {
    
    
            e.terminationFuture().addListener(terminationListener);
        }

        Set<EventExecutor> childrenSet = new LinkedHashSet<EventExecutor>(children.length);
        Collections.addAll(childrenSet, children);
        readonlyChildren = Collections.unmodifiableSet(childrenSet);
    }

2.1.2 事件循环实例 NioEventLoop

NioEventLoop 维护了一个线程和任务队列,类似于单线程线程池,支持异步提交执行任务,主要执行 I/O 任务非 I/O 任务。两种任务的执行时间比由变量 ioRatio 控制,默认为 50,表示非 IO 任务执行的时间与 IO 任务的执行时间相等

  1. I/O 任务
    即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 NioEventLoop#processSelectedKeys() 方法触发
  2. 非 IO 任务
    添加到任务队列中的任务,如 register0、bind0 等任务,由 NioEventLoop#runAllTasks() 方法触发

NioEventLoop 的继承结构同样很深,其比较关键的属性如下:

  1. selector 选择器,用于监听注册其上的 channel 连接上的各个事件
  2. SingleThreadEventExecutor:
    taskQueue: 任务存放的队列
    executor: 用于新建线程的线程工厂和执行器

NioEventLoop#run()方法是事件循环处理的核心逻辑,SingleThreadEventExecutor#execute() 方法则为事件循环开始的入口,此处暂不做深入分析

在这里插入图片描述

2.2 连接接收器 Acceptor

Netty 中的 Acceptor 在实现上属于业务处理组件,因为其实现类ServerBootstrapAcceptor继承于 ChannelHandler,主要负责在 Server 端完成对 Channel 的处理

之所以将这个组件归入事件分发组件,是因为从功能划分的角度看其属于 MainReactor 与 SubReactor 之间的连接结构,负责将 MainReactor 接收客户端请求建立的新连接注册到 SubReactor 上

ServerBootstrapAcceptor#channelRead()是这个 Acceptor 最重要的方法,其实现了将新建连接注册到 SubReactor 的逻辑

       public void channelRead(ChannelHandlerContext ctx, Object msg) {
    
    
            final Channel child = (Channel) msg;

            child.pipeline().addLast(childHandler);

            setChannelOptions(child, childOptions, logger);

            for (Entry<AttributeKey<?>, Object> e: childAttrs) {
    
    
                child.attr((AttributeKey<Object>) e.getKey()).set(e.getValue());
            }

            try {
    
    
                childGroup.register(child).addListener(new ChannelFutureListener() {
    
    
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
    
    
                        if (!future.isSuccess()) {
    
    
                            forceClose(child, future.cause());
                        }
                    }
                });
            } catch (Throwable t) {
    
    
                forceClose(child, t);
            }
        }

在这里插入图片描述

3. 业务处理组件

业务处理组件主要包含了ChannelChannelHandler 以及 ChannelHandlerContext。一个 Channel 包含了一个 ChannelPipeline,而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表,每个 ChannelHandlerContext 中又关联着一个 ChannelHandler

根据ChannelHandler实现类继承接口的不同,可以将其分为 ChannelInboundHandler入站处理器ChannelOutboundHandler出站处理器,二者分别对应读写操作。 入站事件(read)出站事件(write)ChannelPipeline中链式处理,如果没有拦截器终止事件的传递,入站事件会从链表 head 往后传递到最后一个入站处理器 tail,出站事件会从链表 tail 往前传递到最前一个出站处理器 head,两种类型的 ChannelHandler 互不干扰

在这里插入图片描述

3.1 管道 Channel

Channel是一个管道,是用于连接字节缓冲区和另一端的实体,可以分成服务端 NioServerSocketChannel客户端 NioSocketChannel 两个大类。Netty 中 Channel 经过 ChannelPipeline 中的多个 ChannelHandler 处理器处理,最终完成 IO 数据的处理

  • 服务端 NioServerSocketChannel
    该类继承于 AbstractNioMessageChannel,持有一个 NioMessageUnsafe 对象来完成 Chanel 上 IO 操作
  • 客户端 NioSocketChannel
    该类继承于 AbstractNioByteChannel,持有一个 NioByteUnsafe 对象完成 Channel 上事件的处理

所有继承于AbstractChannel 的 Channel 实例中都会持有一个 DefaultChannelPipeline 对象,该对象用于完成 IO 数据的流处理,另外还会持有一个 EventLoop 对象标识 Channel 所属的事件循环实例, 可用于处理任务提交

在这里插入图片描述

3.2 管道处理器 ChannelHandler

ChannelHandler 是一个接口,处理 I/O 事件或拦截 I/O 操作,可以将事件转发到 ChannelPipeline业务处理链中的下一个 ChannelHandler处理或者终止事件传递

ChannelHandler 主要分为了 进站处理器ChannelInboundHandler出站处理器ChannelOutboundHandler,分别对应 read 和 write 操作处理。处理器可由用户编写处理逻辑,Netty 内部会将其包装为 AbstractChannelHandlerContext对象,再将其注册到业务处理链中,即可完成对 IO 数据的逻辑处理

在这里插入图片描述

3.3 管道处理器上下文 ChannelHandlerContext

ChannelHandlerContext 保存 Channel 相关的上下文信息,其主要有 3 种实现DefaultChannelHandlerContextHeadContext以及 TailContext,以下为其关键属性:

  1. AbstractChannelHandlerContext:
    next: 当前管道处理器上下文节点的下一个节点
    prev: 当前管道处理器上下文节点的上一个节点
    inbound: 当前管道处理器上下文节点的出入站标识属性,为 true 表示该节点可处理入站事件
    outbound: 当前管道处理器上下文节点的出入站标识属性,为 true 表示该节点可处理出站事件
    pipeline: ChannelPipeline 的实例,其实现为 DefaultChannelPipeline
  2. DefaultChannelHandlerContext:
    handler: 封装的 ChannelHandler 处理器

ChannelPipeline 的子类 DefaultChannelPipeline 对象实例化时会生成一个HeadContextTailContext 对象,并将其前后指针互相指向对方,形成了一个双向链表。 DefaultChannelPipeline#addLast() 方法会向该双向链表添加节点,主要处理步骤如下:

  1. newContext()ChannelHandler 封装到 DefaultChannelHandlerContext 对象
  2. addLast0() 将新建的 ChannelHandlerContext 对象加入到双向链表中
  3. callHandlerAdded0() 会回调ChannelHandler#handlerAdded() 方法,以便编写的相关逻辑在处理器添加到处理链中时被执行
public final ChannelPipeline addLast(EventExecutorGroup group, String name, ChannelHandler handler) {
    
    
        final AbstractChannelHandlerContext newCtx;
        synchronized (this) {
    
    
            checkMultiplicity(handler);

            newCtx = newContext(group, filterName(name, handler), handler);

            addLast0(newCtx);

            // If the registered is false it means that the channel was not registered on an eventloop yet.
            // In this case we add the context to the pipeline and add a task that will call
            // ChannelHandler.handlerAdded(...) once the channel is registered.
            if (!registered) {
    
    
                newCtx.setAddPending();
                callHandlerCallbackLater(newCtx, true);
                return this;
            }

            EventExecutor executor = newCtx.executor();
            if (!executor.inEventLoop()) {
    
    
                newCtx.setAddPending();
                executor.execute(new Runnable() {
    
    
                    @Override
                    public void run() {
    
    
                        callHandlerAdded0(newCtx);
                    }
                });
                return this;
            }
        }
        callHandlerAdded0(newCtx);
        return this;
    }

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45505313/article/details/106789671