Elasticsearch源碼解析之HTTP請求響應處理

因爲ES已經存在多個版本,主要是每一個版本的啓動流程都不一樣,我這裏不想單獨去分析某一個版本如何進行啓動的,解析ES如何去響應HTTP請求的,以及背後如何去實現。下面簡單給大家分析下,HTTP服務器實現。

HTTP Server

Elasticsearch Netty註冊服務器 Netty4HttpServerTransport

 protected void doStart() {
        boolean success = false;
        try {
            serverBootstrap = new ServerBootstrap();

            serverBootstrap.group(new NioEventLoopGroup(workerCount, daemonThreadFactory(settings,
                HTTP_SERVER_WORKER_THREAD_NAME_PREFIX)));

            // NettyAllocator will return the channel type designed to work with the configuredAllocator
            serverBootstrap.channel(NettyAllocator.getServerChannelType());

            // Set the allocators for both the server channel and the child channels created
            serverBootstrap.option(ChannelOption.ALLOCATOR, NettyAllocator.getAllocator());
            serverBootstrap.childOption(ChannelOption.ALLOCATOR, NettyAllocator.getAllocator());

            serverBootstrap.childHandler(configureServerChannelHandler());
            serverBootstrap.handler(new ServerChannelExceptionHandler(this));

            serverBootstrap.childOption(ChannelOption.TCP_NODELAY, SETTING_HTTP_TCP_NO_DELAY.get(settings));
            serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, SETTING_HTTP_TCP_KEEP_ALIVE.get(settings));
          // 省略部分代碼

            final ByteSizeValue tcpSendBufferSize = SETTING_HTTP_TCP_SEND_BUFFER_SIZE.get(settings);
            if (tcpSendBufferSize.getBytes() > 0) {
                serverBootstrap.childOption(ChannelOption.SO_SNDBUF, Math.toIntExact(tcpSendBufferSize.getBytes()));
            }

            final ByteSizeValue tcpReceiveBufferSize = SETTING_HTTP_TCP_RECEIVE_BUFFER_SIZE.get(settings);
            if (tcpReceiveBufferSize.getBytes() > 0) {
                serverBootstrap.childOption(ChannelOption.SO_RCVBUF, Math.toIntExact(tcpReceiveBufferSize.getBytes()));
            }

            serverBootstrap.option(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator);
            serverBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, recvByteBufAllocator);

            final boolean reuseAddress = SETTING_HTTP_TCP_REUSE_ADDRESS.get(settings);
            serverBootstrap.option(ChannelOption.SO_REUSEADDR, reuseAddress);
            serverBootstrap.childOption(ChannelOption.SO_REUSEADDR, reuseAddress);
            // 綁定端口和地址
            bindServer();
            success = true;
        } finally {
            if (success == false) {
                doStop(); // otherwise we leak threads since we never moved to started
            }
        }
    }

用過Netty知道上面代碼什麼意思,設置worker線程,TCP設置,設置管道handler。Netty的連接出來一般都是在childHandler()設置ChannelInitializer 實現類中添加,看下configureServerChannelHandler()主要初始化了HttpChannelHandler,在initChannel()能看到添加了那個處理器。

        protected void initChannel(Channel ch) throws Exception {
            Netty4HttpChannel nettyHttpChannel = new Netty4HttpChannel(ch);
            ch.attr(HTTP_CHANNEL_KEY).set(nettyHttpChannel);
            ch.pipeline().addLast("read_timeout", new ReadTimeoutHandler(transport.readTimeoutMillis, TimeUnit.MILLISECONDS));
            final HttpRequestDecoder decoder = new HttpRequestDecoder(
                handlingSettings.getMaxInitialLineLength(),
                handlingSettings.getMaxHeaderSize(),
                handlingSettings.getMaxChunkSize());
            decoder.setCumulator(ByteToMessageDecoder.COMPOSITE_CUMULATOR);
            ch.pipeline().addLast("decoder", decoder);
            ch.pipeline().addLast("decoder_compress", new HttpContentDecompressor());
            ch.pipeline().addLast("encoder", new HttpResponseEncoder());
            final HttpObjectAggregator aggregator = new HttpObjectAggregator(handlingSettings.getMaxContentLength());
            aggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
            ch.pipeline().addLast("aggregator", aggregator);
            if (handlingSettings.isCompression()) {
                ch.pipeline().addLast("encoder_compress", new HttpContentCompressor(handlingSettings.getCompressionLevel()));
            }
            if (handlingSettings.isCorsEnabled()) {
                ch.pipeline().addLast("cors", new Netty4CorsHandler(transport.corsConfig));
            }
            ch.pipeline().addLast("pipelining", new Netty4HttpPipeliningHandler(logger, transport.pipeliningMaxEvents));
            ch.pipeline().addLast("handler", requestHandler);
            transport.serverAcceptedChannel(nettyHttpChannel);
        }

從上面代碼知道處理請求的是: requestHandler,它的實現類: Netty4HttpRequestHandler

    protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest<FullHttpRequest> msg) {
        Netty4HttpChannel channel = ctx.channel().attr(Netty4HttpServerTransport.HTTP_CHANNEL_KEY).get();
        FullHttpRequest request = msg.getRequest();
        boolean success = false;
        Netty4HttpRequest httpRequest = new Netty4HttpRequest(request, msg.getSequence());
        try {
            if (request.decoderResult().isFailure()) {
                Throwable cause = request.decoderResult().cause();
                if (cause instanceof Error) {
                    ExceptionsHelper.maybeDieOnAnotherThread(cause);
                    serverTransport.incomingRequestError(httpRequest, channel, new Exception(cause));
                } else {
                    serverTransport.incomingRequestError(httpRequest, channel, (Exception) cause);
                }
            } else {
                serverTransport.incomingRequest(httpRequest, channel);
            }
            success = true;
        } finally {
            if (success == false) {
                httpRequest.release();
            }

可以看出處理http請求的方法,委派了Netty4HttpServerTransport,也就是上面進行Netty server的類。這裏的邏輯將httpRequest,channel 轉換成Elasticsearch 模板對象,屏蔽掉底層api,再從線程池中獲取ThreadContext進行任務執行。類似一個http分發器。詳細代碼就不展示出來,一連串的方法調用,看下圖


TransportAction.doExecute是一個抽象方法,由NodeClient.transportAction返回的實現類去調用執行。每一個URL都會有對應的transportAction實現類,這個和我們平常MVC架構不一樣。NodeClient內置了Map<ActionType, TransportAction> actions,裏面包含所有HTTP請求處理方法,有300多個值對應不同場景的處理。
看下最簡單的響應,當我請求ES:9200端口時,返回基礎信息,由TransportMainAction如何響應的

public class TransportMainAction extends HandledTransportAction<MainRequest, MainResponse> {

    private final String nodeName;
    private final ClusterService clusterService;

    @Inject
    public TransportMainAction(Settings settings, TransportService transportService,
                               ActionFilters actionFilters, ClusterService clusterService) {
        super(MainAction.NAME, transportService, actionFilters, MainRequest::new);
        this.nodeName = Node.NODE_NAME_SETTING.get(settings);
        this.clusterService = clusterService;
    }

    @Override
    protected void doExecute(Task task, MainRequest request, ActionListener<MainResponse> listener) {
        ClusterState clusterState = clusterService.state();
        listener.onResponse(
            new MainResponse(nodeName, Version.CURRENT, clusterState.getClusterName(),
                    clusterState.metaData().clusterUUID(), Build.CURRENT));
    }
}

總結

分析這麼多代碼, ES處理HTTP請求鏈雖然是執行過程比較繞,但是實際代碼還是比較簡單,整體還是去分析TransportAction.doExecute如何響應請求的。

·

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章