13 netty的Reactor線程模型及ServerBootStrap啟動流程

1 Netty的Reactor線程模型

上面提到的幾種線程模型,在我們編寫的基于netty的應用中都有可能出現,甚至可能會不用reactor線程。具體屬于哪一種情況,要看我們的代碼是如何編寫的。
我們先以一個使用了reactor線程模型的netty服務端的典型代碼進行說明:

EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(3);
ServerBootstrap b = new ServerBootstrap(); 
b.group(bossGroup, workerGroup)
        .channel(NioServerSocketChannel.class)
        .handler(new LoggingHandler(LogLevel.INFO))
        .option(ChannelOption.SO_BACKLOG, 128)
        .attr(AttributeKey.valueOf("ssc.key"),"scc.value")
        .childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) throws Exception {
                ch.pipeline().addLast(new DiscardServerHandler());
            }
        }) 
        .childOption(ChannelOption.SO_KEEPALIVE, true); 
        .childAttr(AttributeKey.valueOf("sc.key"),"sc.value")
        .bind(port);

在上述代碼片段中代碼很少,卻包含了一個復雜reactor線程模型,如下所示:


image.png

2 ServerBootStrap啟動流程

圖中大致包含了5個步驟,而我們編寫的服務端代碼中可能并不能完全體現這樣的步驟,因為Netty將其中一些步驟的細節隱藏了,筆者將會通過圖形分析與源碼分析相結合的方式幫助讀者理解這五個步驟。這個五個步驟可以按照以下方式簡要概括:

  1. 設置服務端ServerBootStrap啟動參數

  2. 通過ServerBootStrap的bind方法啟動服務端,bind方法會在parentGroup中注冊NioServerScoketChannel,監聽客戶端的連接請求

  3. Client發起連接CONNECT請求,parentGroup中的NioEventLoop不斷輪循是否有新的客戶端請求,如果有,ACCEPT事件觸發

  4. ACCEPT事件觸發后,parentGroup中NioEventLoop會通過NioServerSocketChannel獲取到對應的代表客戶端的NioSocketChannel,并將其注冊到childGroup中

  5. childGroup中的NioEventLoop不斷檢測自己管理的NioSocketChannel是否有讀寫事件準備好,如果有的話,調用對應的ChannelHandler進行處理

需要提醒的是,這五個步驟是筆者自己的總結,主要是為了方便理解,并不是官方的劃分。
下面我們開始詳細介紹每一個步驟:

1. 設置服務端ServerBootStrap啟動參數

ServerBootStrap繼承自AbstractBootstrap,其代表服務端的啟動類,當調用其bind方法時,表示啟動服務端。在啟動之前,我們會調用group,channel、handler、option、attr、childHandler、childOption、childAttr等方法來設置一些啟動參數。

group方法:
group可以認為是設置執行任務的線程池,在Netty中,EventLoopGroup 的作用類似于線程池,每個EventLoopGroup中包含多個EventLoop對象,代表不同的線程。特別的,我們創建的是2個EventLoopGroup的子類NioEventLoopGroup的實例:parentGroup、childGroup, 所以實際上包含的是多個NioEventLoop對象。從名字上就可以看出,二者是專門用于處理java nio事件的,某種程度上這也驗證了我們前面的提到的Netty中的reactor線程模型是結合java nio編程特點來設計的說法。

在創建parentGroup 、childGroup 時,分別傳入了構造參數1和3,這對應了上圖中紅色部分的parentGroup 中只有1個NioEventLoop,綠色部分的childGroup 中有3個NioEventLoop。

特別的,如果我們創建NioEventLoopGroup 的時候,沒有指定參數,或者傳入的是0,那么這個NioEventLoopGroup包含的NioEventLoop個數將會是:cpu核數*2。

具體可參見NioEventLoopGroup的父類MultithreadEventLoopGroup構造時的相關代碼

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
 
private static final int DEFAULT_EVENT_LOOP_THREADS;//默認線程數量,cpu核數*2
.......
static {
    DEFAULT_EVENT_LOOP_THREADS = Math.max(1, SystemPropertyUtil.getInt(
            "io.netty.eventLoopThreads", Runtime.getRuntime().availableProcessors() * 2));
 
    if (logger.isDebugEnabled()) {
        logger.debug("-Dio.netty.eventLoopThreads: {}", DEFAULT_EVENT_LOOP_THREADS);
    }
}
 
protected MultithreadEventLoopGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
    super(nThreads == 0? DEFAULT_EVENT_LOOP_THREADS : nThreads, threadFactory, args);
}
.......
}

在創建完parentGroup 和childGroup 之后,我們把其當做參數傳遞給了ServerBootStrap,通過調用帶有2個參數的group方法。在這個方法中,會把parentGroup 當做參數傳遞給ServerBootStrap的父類AbstractBootstrap來進行維護,childGroup 則由ServerBootStrap自己維護。

之后,我們可以調用ServerBootStrap 的group()方法來獲取parentGroup 的引用,這個方法父類AbstractBootstrap繼承的。另外可以通過調用ServerBootStrap自己定義的childGroup()方法來獲取workerGroup的引用。

相關代碼如下:
io.netty.bootstrap.ServerBootstrap

public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
..........
private volatile EventLoopGroup childGroup;//ServerBootStrap自己維護childGroup
..........
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);//將parentGroup傳遞給父類AbstractBootstrap處理
    if (childGroup == null) {
        throw new NullPointerException("childGroup");
    }
    if (this.childGroup != null) {
        throw new IllegalStateException("childGroup set already");
    }
    this.childGroup = childGroup;//給childGroup賦值
    return this;
}
.......
  public EventLoopGroup childGroup() {//獲取childGroup
    return childGroup;
  }
}

io.netty.bootstrap.AbstractBootstrap

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
......
private volatile EventLoopGroup group;//這個字段將會被設置為parentGroup
......
public B group(EventLoopGroup group) {
    if (group == null) {
        throw new NullPointerException("group");
    }
    if (this.group != null) {
        throw new IllegalStateException("group set already");
    }
    this.group = group;
    return (B) this;
}
......
public final EventLoopGroup group() {//獲取parentGroup
    return group;
}
}

channel方法:
channel繼承自AbstractBootstrap,用于構造通道的工廠類ChannelFactory實例,在之后需要創建通道實例,例如NioServerSocketChannel的時候,通過調用ChannelFactory.newChannel()方法來創建。

channel方法內部隱含的調用了channelFactory方法,我們也可以直接調用這個方法。

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
........
//這個工廠類最終創建的通道實例,就是channel方法指定的NioServerSocketChannel
private volatile ChannelFactory<? extends C> channelFactory;
..........
public B channel(Class<? extends C> channelClass) {
    ......
    return channelFactory(new BootstrapChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    ..........
    this.channelFactory = channelFactory;
    return (B) this;
}
.......
final ChannelFactory<? extends C> channelFactory() {
    return channelFactory;
}
}

細心的讀者會發現,除了channel、group方法之外,其他三個方法都是一 一對應的:

  • handler-->childHandler :分別用于設置NioServerSocketChannel和NioSocketChannel的處理器鏈,也就是當有一個NIO事件的時候,應該按照怎樣的步驟進行處理。

  • option-->childOption:分別用于設置NioServerSocketChannel和 NioSocketChannel的TCP連接參數,在ChannelOption類中可以看到Netty支持的所有TCP連接參數。

  • attr-->childAttr:用于給channel設置一個key/value,之后可以根據key獲取

其中:
handler、option、attr方法,都是從AbstractBootstrap中繼承的。這些方法設置的參數,將會被應用到NioServerSocketChannel實例上,由于NioServerSocketChannel一般只會創建一個,因此這些參數通常只會應用一次。源碼如下所示:

public abstract class AbstractBootstrap<B extends AbstractBootstrap<B, C>, C extends Channel> implements Cloneable {
  ... 
   private final Map<ChannelOption<?>, Object> options = new LinkedHashMap<ChannelOption<?>, Object>();
   private final Map<AttributeKey<?>, Object> attrs = new LinkedHashMap<AttributeKey<?>, Object>();
   private volatile ChannelHandler handler;
...
public B channel(Class<? extends C> channelClass) {
    ....
    return channelFactory(new BootstrapChannelFactory<C>(channelClass));
}
public B channelFactory(ChannelFactory<? extends C> channelFactory) {
    .......
    this.channelFactory = channelFactory;
    return (B) this;
}
final ChannelHandler handler() {return handler;}
public B handler(ChannelHandler handler) {.....}
public <T> B option(ChannelOption<T> option, T value){...}//設置ChannelOption參數
final Map<ChannelOption<?>, Object> options(){return options;}//獲取ChannelOption參數
public <T> B attr(AttributeKey<T> key, T value){.....} //設置屬性
final Map<AttributeKey<?>, Object> attrs() {return attrs;}//獲取屬性
}

childHandler、childOption、childAttr方法是ServerBootStrap自己定義的,這些方法設置的參數,將會被應用到NioSocketChannel實例上,由于服務端每次接受到一個客戶端連接,就會創建一個NioSocketChannel實例,因此每個NioSocketChannel實例都會應用這些方法設置的參數。

public final class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel> {
...........
private final Map<ChannelOption<?>, Object> childOptions = new LinkedHashMap<ChannelOption<?>, Object>();
private final Map<AttributeKey<?>, Object> childAttrs = new LinkedHashMap<AttributeKey<?>, Object>();
private volatile ChannelHandler childHandler;
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
    super.group(parentGroup);
    ....
    this.childGroup = childGroup;
    return this;
}
public <T> ServerBootstrap childOption(ChannelOption<T> childOption, T value) {...}
 
public ServerBootstrap childHandler(ChannelHandler childHandler) {......}
..............
}

2 調用ServerBootStrap的bind方法

調用bind方法,就相當于啟動了服務端。啟動的核心邏輯都是在bind方法中。
bind方法內部,會創建一個NioServerSocketChannel實例,并將其在parentGroup中進行注冊,注意這個過程對用戶屏蔽了。

parentGroup在接受到注冊請求時,會從自己的管理的NioEventLoop中,選擇一個進行注冊。由于我們的案例中,parentGroup只有一個NioEventLoop,因此只能注冊到這個上。

一旦注冊完成,我們就可以通過NioServerSocketChannel檢測有沒有新的客戶端連接的到來。

如果一步一步追蹤ServerBootStrap.bind方法的調用鏈,最終會定位到ServerBootStrap 父類AbstractBootstrap的doBind方法,相關源碼如下:
io.netty.bootstrap.AbstractBootstrap#doBind

private ChannelFuture doBind(final SocketAddress localAddress) {
    final ChannelFuture regFuture = initAndRegister();//初始化NioServerSocketChannel,并注冊到bossGroup中
    ....//省略
    return promise;

doBind方法中,最重要的調用的方法是initAndRegister,這個方法主要完成3個任務

  1. 創建NioServerSocketChannel實例,這是通過之前創建的ChannelFactory實例的newChannel方法完成
  2. 初始化NioServerSocketChannel,即將我們前面通過handler,option,attr等方法設置的參數應用到NioServerSocketChannel上
  3. 將NioServerSocketChannel 注冊到parentGroup中,parentGroup會選擇其中一個NioEventLoop來運行這個NioServerSocketChannel要完成的功能,即監聽客戶端的連接。

以下是io.netty.bootstrap.AbstractBootstrap#initAndRegister的源碼

final ChannelFuture initAndRegister() {
    final Channel channel = channelFactory().newChannel();//1、創建NioServerSocketChannel實例
    try {
        init(channel);//2、初始化NioServerSocketChannel,這是一個抽象方法,ServerBootStrap對此進行了覆蓋
    } catch (Throwable t) {
        channel.unsafe().closeForcibly();
        return channel.newFailedFuture(t);
    }
 
    ChannelFuture regFuture = group().register(channel);//3、NioServerSocketChannel注冊到parentGroup中
    if (regFuture.cause() != null) {
        if (channel.isRegistered()) {
            channel.close();
        } else {
            channel.unsafe().closeForcibly();
        }
    }
 
    return regFuture;
}

ServerBootStrap實現了AbstractBootstrap的抽象方法init,對NioServerSocketChannel進行初始化。熟悉設計模式的同學會意識到,這是典型的模板設計模式,即父類運行過程中會調用多個方法,子類對特定的方法進行覆寫。

在這里,init方法主要是為NioServerSocketChannel設置運行參數,也就是我們前面通過調用ServerBootStrap的option、attr、handler等方法指定的參數。

特別需要注意的是,除了我們通過handler方法為NioServerSocketChannel 指定的ChannelHandler之外(在我們這里是LoggingHandler),ServerBootStrap的init方法總是會幫我們在NioServerSocketChannel 的處理器鏈的最后添加一個默認的處理器ServerBootstrapAcceptor。

從ServerBootstrapAcceptor 名字上可以看出來,其是客戶端連接請求的處理器。當接受到一個客戶端請求之后,Netty會將創建一個代表客戶端的NioSocketChannel對象。而我們通過ServerBoodStrap指定的channelHandler、childOption、childAtrr、childGroup等參數,也需要設置到NioSocketChannel中。但是明顯現在,由于只是服務端剛啟動,沒有接收到任何客戶端請求,還沒有NioSocketChannel實例,因此這些參數要保存到ServerBootstrapAcceptor中,等到接收到客戶端連接的時候,再將這些參數進行設置,我們可以看到這些參數通過構造方法傳遞給了ServerBootstrapAcceptor。

源碼如下所示:
io.netty.bootstrap.ServerBootstrap#init

@Override
void init(Channel channel) throws Exception {//channel參數類型就是NioServerSocketChannel
   //1、為NioServerSocketChannel設置option方法設置的參數
   final Map<ChannelOption<?>, Object> options = options();
    synchronized (options) {
        channel.config().setOptions(options);
    }
   //2、為NioServerSocketChannel設置attr方法設置的參數
    final Map<AttributeKey<?>, Object> attrs = attrs();
    synchronized (attrs) {
        for (Entry<AttributeKey<?>, Object> e: attrs.entrySet()) {
            @SuppressWarnings("unchecked")
            AttributeKey<Object> key = (AttributeKey<Object>) e.getKey();
            channel.attr(key).set(e.getValue());
        }
    }
    //3、為NioServerSocketChannel設置通過handler方法指定的處理器
    ChannelPipeline p = channel.pipeline();
    if (handler() != null) {
        p.addLast(handler());
    }
     //4、為NioSocketChannel設置默認的處理器ServerBootstrapAcceptor,并將相關參數通過構造方法傳給ServerBootstrapAcceptor
    final EventLoopGroup currentChildGroup = childGroup;
    final ChannelHandler currentChildHandler = childHandler;
    final Entry<ChannelOption<?>, Object>[] currentChildOptions;
    final Entry<AttributeKey<?>, Object>[] currentChildAttrs;
    synchronized (childOptions) {
        currentChildOptions = childOptions.entrySet().toArray(newOptionArray(childOptions.size()));
    }
    synchronized (childAttrs) {
        currentChildAttrs = childAttrs.entrySet().toArray(newAttrArray(childAttrs.size()));
    }
 
    p.addLast(new ChannelInitializer<Channel>() {
        @Override
        public void initChannel(Channel ch) throws Exception {
            ch.pipeline().addLast(new ServerBootstrapAcceptor(
                    currentChildGroup, currentChildHandler, currentChildOptions, currentChildAttrs));
        }
    });
}

io.netty.channel.EventLoopGroup#register方法

在初始化完成之后,ServerBootStrap通過調用register方法將NioServerSocketChannel注冊到了parentGroup中。

從較高的層面來說,parentGroup 的類型是NioEventLoopGroup,一個NioEventLoopGroup可能會管理多個NioEventLoop,對于通道的注冊,NioEventLoopGroup會從多個NioEventLoop中選擇一個來執行真正的注冊。之后這個通道的nio事件,也都是由這個NioEventLoop來處理。也就是說,一個通道只能由一個NioEventLoop來處理,一個NioEventLoop可以處理多個通道,通道與NioEventLoop是多對一的關系。

NioEventLoopGroup的register方法繼承自MultithreadEventLoopGroup。

代碼如下所示:

public abstract class MultithreadEventLoopGroup extends MultithreadEventExecutorGroup implements EventLoopGroup {
.......
@Override
public ChannelFuture register(Channel channel) {
    return next().register(channel);
}
.......
}

next方法的返回值,就是NioEventLoop,可以看到,真正的注冊工作,是NioEventLoop完成的。next()方法還提供了通道在NioEventLoop中平均分配的機制。

NioEventLoopGroup創建的時候,其父類MultithreadEventExecutorGroup中會創建一個EventExecutorChooser實例,之后通過其來保證通道平均注冊到不同的NioEventLoop中。

public abstract class MultithreadEventExecutorGroup extends AbstractEventExecutorGroup {
....
private final EventExecutor[] children;//NioEventLoop是EventExecutor的子類,這里的children指的就是NioEventLoop
private final AtomicInteger childIndex = new AtomicInteger();//上一次接受注冊任務的EventEexcutor編號
private final EventExecutorChooser chooser;
...
protected MultithreadEventExecutorGroup(int nThreads, ThreadFactory threadFactory, Object... args) {
......
children = new SingleThreadEventExecutor[nThreads];
if (isPowerOfTwo(children.length)) {//如果指定的線程數是2的冪
    chooser = new PowerOfTwoEventExecutorChooser();
} else {
    chooser = new GenericEventExecutorChooser();//按照round-robin的方式,來保證平均
}
for (int i = 0; i < nThreads; i ++) {
    boolean success = false;
    try {//創建EventExecutor實例,注意newChild是抽象方法,NioEventLoopGroup對此方法進行了覆蓋,返回的實例是NioEventLoop。
        children[i] = newChild(threadFactory, args);
        success = true;
    } catch (Exception e) {
 .............
  }
}
......
}
//調用此方法,即可以保證任務的平均分配
@Override
public EventExecutor next() {
    return chooser.next();
}
private final class PowerOfTwoEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        return children[childIndex.getAndIncrement() & children.length - 1];
    }
}
 
private final class GenericEventExecutorChooser implements EventExecutorChooser {
    @Override
    public EventExecutor next() {
        return children[Math.abs(childIndex.getAndIncrement() % children.length)];
    }
}
............
 
}
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容