BookKeeper的網絡服務基於Netty實現,其主要的內容就是構造NettyServer。
構造NettyServer
構造NettyServer之前,初始化eventLoopGroup和監聽端口,然後就是Netty的標準ServerBootstrap的配置流程。
private void listenOn(InetSocketAddress address, BookieSocketAddress bookieAddress) throws InterruptedException {
if (!conf.isDisableServerSocketBind()) {
ServerBootstrap bootstrap = new ServerBootstrap();
// 設置Accept事件循環和work事件循環中的內存分配器
bootstrap.option(ChannelOption.ALLOCATOR, allocator);
bootstrap.childOption(ChannelOption.ALLOCATOR, allocator);
// 設置Accept事件循環組和work事件循環組
bootstrap.group(eventLoopGroup, eventLoopGroup);
// 是否立即發送
bootstrap.childOption(ChannelOption.TCP_NODELAY, conf.getServerTcpNoDelay());
// Timeout to drain the socket on close. 即socket 關閉時可以等待的時間
bootstrap.childOption(ChannelOption.SO_LINGER, conf.getServerSockLinger());
// 設置自適應的ReceiveBuffer, 真正分配buffer時也是用的上面的allocator
bootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR,
new AdaptiveRecvByteBufAllocator(conf.getRecvByteBufAllocatorSizeMin(),
conf.getRecvByteBufAllocatorSizeInitial(), conf.getRecvByteBufAllocatorSizeMax()));
// 高低水位,用於限制客戶端限流
bootstrap.option(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(
conf.getServerWriteBufferLowWaterMark(), conf.getServerWriteBufferHighWaterMark()));
if (eventLoopGroup instanceof EpollEventLoopGroup) {
bootstrap.channel(EpollServerSocketChannel.class);
} else {
bootstrap.channel(NioServerSocketChannel.class);
}
// 添加channel 添加 hanlder pipeline
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
synchronized (suspensionLock) {
while (suspended) {
suspensionLock.wait();
}
}
BookieSideConnectionPeerContextHandler contextHandler =
new BookieSideConnectionPeerContextHandler();
ChannelPipeline pipeline = ch.pipeline();
// For ByteBufList, skip the usual LengthFieldPrepender and have the encoder itself to add it
// Outbound
pipeline.addLast("bytebufList", ByteBufList.ENCODER_WITH_SIZE);
// Inbound
pipeline.addLast("lengthbaseddecoder", new LengthFieldBasedFrameDecoder(maxFrameSize, 0, 4, 0, 4));
// Outbound
pipeline.addLast("lengthprepender", new LengthFieldPrepender(4));
// Inbound
pipeline.addLast("bookieProtoDecoder", new BookieProtoEncoding.RequestDecoder(registry));
// Outbound
pipeline.addLast("bookieProtoEncoder", new BookieProtoEncoding.ResponseEncoder(registry));
// Inbound
pipeline.addLast("bookieAuthHandler", new AuthHandler.ServerSideHandler(
contextHandler.getConnectionPeer(), authProviderFactory));
// 正常情況下,channel初始化時,isRunning會被設置成true,並且requestProcessor
// 會被設置爲BookieRequestProcessor
ChannelInboundHandler requestHandler = isRunning.get()
? new BookieRequestHandler(conf, requestProcessor, allChannels)
: new RejectRequestHandler();
// Inbound
pipeline.addLast("bookieRequestHandler", requestHandler);
// Inbound
pipeline.addLast("contextHandler", contextHandler);
}
});
// Bind and start to accept incoming connections
Channel listen = bootstrap.bind(address.getAddress(), address.getPort()).sync().channel();
if (listen.localAddress() instanceof InetSocketAddress) {
if (conf.getBookiePort() == 0) {
conf.setBookiePort(((InetSocketAddress) listen.localAddress()).getPort());
}
}
}
...
}
主要配置是在ChannelInitializer中設置Handler,注意這裏的順序關係,Outbound的handler不能放在最後一個,因爲在調用ctx.writer()時,在最後一個InbounderHandler之後的OutBoundHandler不起作用。
依次添加的InboundHandler
-
LengthFieldBasedFrameDecode:用來解析bytebuf頭部是消息長度類型的消息,配置參數較多,這使用的是(maxFrameSize, 0, 4, 0, 4),其含義如下:
* BEFORE DECODE (14 bytes) AFTER DECODE (12 bytes) * +--------+----------------+ +----------------+ * | Length | Actual Content |----->| Actual Content | * | 0x000C | "HELLO, WORLD" | | "HELLO, WORLD" | * +--------+----------------+ +----------------+ * @param maxFrameLength: frame的最大長度,超過這個會拋出TooLongFrameException * @param lengthFieldOffset:長度字段的起始位置 * @param lengthFieldLength:長度字段的長度 * @param lengthAdjustment : 長度字段的補償值,表示長度字段內容+lengthAdjustment=實際body的長度 * @param initialBytesToStrip :需要跳過的bytes數量,即長度字段的長度 以(maxFrameSize, 0, 4, 0, 4)爲例,這裏是說明長度字段其實位置是0,長度是4個bytes,lengthAdjustment是0 ,說明長度字段表示的就是body字段的長度,initialBytesToStrip表示跳過4bytes字後的數據時body數據
-
BookieProtoEncoding.RequestDecoder :BK 協議響應的 decoder,根據協議版本區分處理方式,如果是 v3 版本,則使用 RequestEnDecoderV3 進行 decode 操作;否則使用 RequestEnDeCoderPreV3 執行 decode 操作
-
AuthHandler.ServerSideHandler:認證handler
-
BookieRequestHandler:處理BookkeeperProtocol.Request和 BookieProtocol.Request類型的數據,實際處理類是BookieRequestProcessor,BookieRequestProcessor處理根據請求類型,做不同的處理。
-
BookieSideConnectionPeerContextHandler:保存當前客戶端的相關信息
InboundHanlder的調用順序是正序的,即一條消息的處理會經過:LengthFieldBasedFrameDecode
-> BookieProtoEncoding.RequestDecoder -> AuthHandler.ServerSideHandler -> BookieRequestHandler -> BookieSideConnectionPeerContextHandler。
依次添加的OutboundHandler
- ByteBufList.ENCODER_WITH_SIZE:一個在message頭部加上四個字節表示message長度的encoder
- LengthFieldPrepender:一個在message頭部加上長度信息的encoder,這裏指定的長度是4bytes
- BookieProtoEncoding.ResponseEncoder:BK協議響應的encoder,根據message的類型,使用不同的encoder編碼,BookkeeperProtocol.Response -> ResponseEnDecoderV3, BookieProtocol.Response -> ResponseEnDeCoderPreV3,默認直接發向下一個handler
OUtboundHandler的調用順序是逆序的,即一條消息的處理經過:BookieProtoEncoding.ResponseEncoder ->
LengthFieldPrepender -> ByteBufList.ENCODER_WITH_SIZE