前言
本系列為本人Java程式設計方法論 響應式解讀系列的Webflux
部分,現分享出來,前置知識Rxjava2 ,Reactor的相關解讀已經錄製分享視訊,併發布在b站,地址如下:
Rxjava原始碼解讀與分享:www.bilibili.com/video/av345…
Reactor原始碼解讀與分享:www.bilibili.com/video/av353…
NIO原始碼解讀相關視訊分享: www.bilibili.com/video/av432…
NIO原始碼解讀視訊相關配套文章:
BIO到NIO原始碼的一些事兒之NIO 下 之 Selector
BIO到NIO原始碼的一些事兒之NIO 下 Buffer解讀 上
BIO到NIO原始碼的一些事兒之NIO 下 Buffer解讀 下
Java程式設計方法論-Spring WebFlux篇 01 為什麼需要Spring WebFlux 上
Java程式設計方法論-Spring WebFlux篇 01 為什麼需要Spring WebFlux 下
其中,Rxjava與Reactor作為本人書中內容將不對外開放,大家感興趣可以花點時間來觀看視訊,本人對著兩個庫進行了全面徹底細緻的解讀,包括其中的設計理念和相關的方法論,也希望大家可以留言糾正我其中的錯誤。
HttpServer 的封裝
本書主要針對Netty
伺服器來講,所以讀者應具備有關Netty
的基本知識和應用技能。接下來,我們將對Reactor-netty
從設計到實現的細節一一探究,讓大家真的從中學習到好的封裝設計理念。本書在寫時所參考的最新版本是Reactor-netty 0.7.8.Release
這個版本,但現在已有0.8
版本,而且0.7
與0.8
版本在原始碼細節有不小的變動,這點給大家提醒下。我會針對0.8
版本進行全新的解讀。
HttpServer 的引入
我們由上一章可知Tomcat使用Connector
來接收和響應連線請求,這裡,對於Netty
來講,如果我們想讓其做為一個web
伺服器,我們先來看一個Netty
常見的一個用法(這裡摘自官方文件一個例子DiscardServer Demo
):
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
/**
* 丟棄任何進入的資料
*/
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1)
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)
// 繫結埠,開始接收進來的連線
ChannelFuture f = b.bind(port).sync(); // (7)
// 等待伺服器 socket 關閉 。
// 在這個例子中,這不會發生,但你可以優雅地關閉你的伺服器。
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new DiscardServer(port).run();
}
}
複製程式碼
NioEventLoopGroup
是用來處理I/O
操作的多執行緒事件迴圈器,Netty
提供了許多不同的EventLoopGroup
的實現用來處理不同的傳輸。在這個例子中我們實現了一個服務端的應用,因此會有2個NioEventLoopGroup
會被使用。第一個經常被叫做BossGroup
,用來接收進來的連線。第二個經常被叫做WorkerGroup
,用來處理已經被接收的連線,一旦BossGroup
接收到連線,就會把連線資訊註冊到WorkerGroup
上。如何知道多少個執行緒已經被使用,如何對映到已經建立的Channel
上都需要依賴於EventLoopGroup
的實現,並且可以通過建構函式來配置他們的關係。ServerBootstrap
是一個啟動NIO
服務的輔助啟動類。你可以在這個服務中直接使用Channel
,但是這會是一個複雜的處理過程,在很多情況下你並不需要這樣做。- 這裡我們通過指定使用
NioServerSocketChannel
來舉例說明一個新的Channel
如何接收傳進來的連線。 - 這裡的事件處理類經常會被用來處理一個最近已經接收的
Channel
。ChannelInitializer
是一個特殊的處理類,目的是幫助使用者配置一個新的Channel
。 使用其對應的ChannelPipeline
來加入你的服務邏輯處理(這裡是DiscardServerHandler
)。當你的程式變的複雜時,可能你會增加更多的處理類到pipline
上,然後提取這些匿名類到最頂層的類上(匿名類即ChannelInitializer
例項我們可以將其看成是一個代理模式的設計,類似於Reactor
中Subscriber
的設計實現,一層又一層的包裝,最後得到一個我們需要的一個可以層層處理的Subscriber
)。 - 你可以設定這裡指定的
Channel
實現的配置引數。如果我們寫一個TCP/IP
的服務端,我們可以設定socket
的引數選項,如tcpNoDelay
和keepAlive
。請參考ChannelOption
和ChannelConfig
實現的介面文件來對ChannelOption
的有一個大概的認識。 - 接著我們來看
option()
和childOption()
:option()
是提供給NioServerSocketChannel
用來接收進來的連線。childOption()
是提供給由父管道ServerChannel
接收到的連線,在這個例子中也是NioServerSocketChannel
。 - 剩下的就是繫結埠然後啟動服務。這裡我們在伺服器上繫結了其
8080
埠。當然現在你可以多次呼叫bind()
方法(基於不同繫結地址)。
針對bootstrap的option的封裝
在看了常見的Netty
的一個伺服器建立用法之後,我們來看Reactor Netty
給我們提供的Http伺服器的一個封裝:reactor.ipc.netty.http.server.HttpServer
。由上面DiscardServer Demo
可知,首先是定義一個伺服器,方便設定一些條件對其進行配置,然後啟動的話是呼叫其run
方法啟動,為做到更好的可配置性,這裡使用了建造器模式,以便我們自定義或直接使用預設配置(有些是必須配置,否則會丟擲異常,這也是我們這裡面所設定的內容之一):
//reactor.ipc.netty.http.server.HttpServer.Builder
public static final class Builder {
private String bindAddress = null;
private int port = 8080;
private Supplier<InetSocketAddress> listenAddress = () -> new InetSocketAddress(NetUtil.LOCALHOST, port);
private Consumer<? super HttpServerOptions.Builder> options;
private Builder() {
}
...
public final Builder port(int port) {
this.port = port;
return this;
}
/**
* The options for the server, including bind address and port.
*
* @param options the options for the server, including bind address and port.
* @return {@code this}
*/
public final Builder options(Consumer<? super HttpServerOptions.Builder> options) {
this.options = Objects.requireNonNull(options, "options");
return this;
}
public HttpServer build() {
return new HttpServer(this);
}
}
複製程式碼
可以看到,此處的HttpServer.Builder#options
是一個函式式動作Consumer
,其傳入的引數是HttpServerOptions.Builder
,在HttpServerOptions.Builder
內可以針對我們在DiscardServer Demo
中的bootstrap.option
進行一系列的預設配置或者自行調控配置,我們的對於option
的自定義設定主要還是針對於ServerBootstrap#childOption
。因為在reactor.ipc.netty.options.ServerOptions.Builder#option
這個方法中,有對它的父類reactor.ipc.netty.options.NettyOptions.Builder#option
進行了相應的重寫:
//reactor.ipc.netty.options.ServerOptions.Builder
public static class Builder<BUILDER extends Builder<BUILDER>>
extends NettyOptions.Builder<ServerBootstrap, ServerOptions, BUILDER>{...}
//reactor.ipc.netty.options.ServerOptions.Builder#option
/**
* Set a {@link ChannelOption} value for low level connection settings like
* SO_TIMEOUT or SO_KEEPALIVE. This will apply to each new channel from remote
* peer.
*
* @param key the option key
* @param <T> the option type
* @return {@code this}
* @see ServerBootstrap#childOption(ChannelOption, Object)
*/
@Override
public final <T> BUILDER option(ChannelOption<T> key, T value) {
this.bootstrapTemplate.childOption(key, value);
return get();
}
//reactor.ipc.netty.options.NettyOptions.Builder#option
/**
* Set a {@link ChannelOption} value for low level connection settings like
* SO_TIMEOUT or SO_KEEPALIVE. This will apply to each new channel from remote
* peer.
*
* @param key the option key
* @param value the option value
* @param <T> the option type
* @return {@code this}
* @see Bootstrap#option(ChannelOption, Object)
*/
public <T> BUILDER option(ChannelOption<T> key, T value) {
this.bootstrapTemplate.option(key, value);
return get();
}
複製程式碼
這是我們需要注意的地方。然後,我們再回到reactor.ipc.netty.http.server.HttpServer.Builder
,從其build
這個方法可知,其返回一個HttpServer
例項,通過對所傳入的HttpServer.Builder
例項的options
進行判斷,接著,就是對bootstrap.group
的判斷,因為要使用構造器配置的話,首先得獲取到ServerBootstrap
,所以要先判斷是否有可用EventLoopGroup
,這個我們是可以自行設定的,這裡設定一次,bossGroup
和workerGroup
可能都會呼叫這一個,這點要注意下(loopResources
原始碼註釋已經講的很明確了):
//reactor.ipc.netty.http.server.HttpServer.Builder#build
public HttpServer build() {
return new HttpServer(this);
}
//reactor.ipc.netty.http.server.HttpServer#HttpServer
private HttpServer(HttpServer.Builder builder) {
HttpServerOptions.Builder serverOptionsBuilder = HttpServerOptions.builder();
if (Objects.isNull(builder.options)) {
if (Objects.isNull(builder.bindAddress)) {
serverOptionsBuilder.listenAddress(builder.listenAddress.get());
}
else {
serverOptionsBuilder.host(builder.bindAddress).port(builder.port);
}
}
else {
builder.options.accept(serverOptionsBuilder);
}
if (!serverOptionsBuilder.isLoopAvailable()) {
serverOptionsBuilder.loopResources(HttpResources.get());
}
this.options = serverOptionsBuilder.build();
this.server = new TcpBridgeServer(this.options);
}
//reactor.ipc.netty.options.NettyOptions.Builder
public static abstract class Builder<BOOTSTRAP extends AbstractBootstrap<BOOTSTRAP, ?>,
SO extends NettyOptions<BOOTSTRAP, SO>, BUILDER extends Builder<BOOTSTRAP, SO, BUILDER>>
implements Supplier<BUILDER> {
...
/**
* Provide a shared {@link EventLoopGroup} each Connector handler.
*
* @param eventLoopGroup an eventLoopGroup to share
* @return {@code this}
*/
public final BUILDER eventLoopGroup(EventLoopGroup eventLoopGroup) {
Objects.requireNonNull(eventLoopGroup, "eventLoopGroup");
return loopResources(preferNative -> eventLoopGroup);
}
/**
* Provide an {@link EventLoopGroup} supplier.
* Note that server might call it twice for both their selection and io loops.
*
* @param channelResources a selector accepting native runtime expectation and
* returning an eventLoopGroup
* @return {@code this}
*/
public final BUILDER loopResources(LoopResources channelResources) {
this.loopResources = Objects.requireNonNull(channelResources, "loopResources");
return get();
}
public final boolean isLoopAvailable() {
return this.loopResources != null;
}
...
}
複製程式碼
可以看到,這個類是Supplier
實現,其是一個物件提取器,即屬於一個函式式動作物件,適合用於懶載入的場景。這裡的LoopResources
也是一個函式式介面(@FunctionalInterface
),其設計的初衷就是為io.netty.channel.Channel
的工廠方法服務的:
//reactor.ipc.netty.resources.LoopResources
@FunctionalInterface
public interface LoopResources extends Disposable {
/**
* Default worker thread count, fallback to available processor
*/
int DEFAULT_IO_WORKER_COUNT = Integer.parseInt(System.getProperty(
"reactor.ipc.netty.workerCount",
"" + Math.max(Runtime.getRuntime()
.availableProcessors(), 4)));
/**
* Default selector thread count, fallback to -1 (no selector thread)
*/
int DEFAULT_IO_SELECT_COUNT = Integer.parseInt(System.getProperty(
"reactor.ipc.netty.selectCount",
"" + -1));
/**
* Create a simple {@link LoopResources} to provide automatically for {@link
* EventLoopGroup} and {@link Channel} factories
*
* @param prefix the event loop thread name prefix
*
* @return a new {@link LoopResources} to provide automatically for {@link
* EventLoopGroup} and {@link Channel} factories
*/
static LoopResources create(String prefix) {
return new DefaultLoopResources(prefix, DEFAULT_IO_SELECT_COUNT,
DEFAULT_IO_WORKER_COUNT,
true);
}
static LoopResources create(String prefix,
int selectCount,
int workerCount,
boolean daemon) {
...
return new DefaultLoopResources(prefix, selectCount, workerCount, daemon);
}
...
/**
* Callback for server {@link EventLoopGroup} creation.
*
* @param useNative should use native group if current {@link #preferNative()} is also
* true
*
* @return a new {@link EventLoopGroup}
*/
EventLoopGroup onServer(boolean useNative);
...
}
複製程式碼
我們在自定義的時候,可以藉助此類的靜態方法create
方法來快速建立一個LoopResources
例項。另外通過LoopResources
的函式式特性,可以做到懶載入(將我們想要實現的業務藏到一個方法內),即,只有在使用的時候才會生成所需要的物件例項,即在使用reactor.ipc.netty.options.NettyOptions.Builder#loopResources(LoopResources channelResources)
方法時,可進行loopResources(true -> new NioEventLoopGroup())
,即在拿到LoopResources
例項後,只有呼叫其onServer
方法,才能拿到EventLoopGroup
。這樣就可以大大節省記憶體資源,提高效能。
小結
至此,我們將由netty
的普通使用到HttpServer
的封裝完成通過本章給大家展示出來,目的也是告訴大家這個東西是怎麼來的,基於什麼樣的目的,接下來,我們會依照這個思路一步步給大家揭開Reactor-netty
的面紗以及其與Spring webflux
是如何對接設計的。