三. elasticsearch分析 - http請求的處理流程

        我們知道elasticsearch主要有兩種通信方式: Http和Transport。第二篇主要分析了通過Transport發送請求的處理方式。本篇分析elasticsearch如何處理http請求的。同樣以Netty3HttpServerTranport爲例。

Netty3HttpServerTransport類

public class Netty3HttpTranport{

    protected void doStart() {
        boolean success = false;
        try {
            this.serverOpenChannels = new Netty3OpenChannelsHandler(logger);
            if (blockingServer) {
                serverBootstrap = new ServerBootstrap(new OioServerSocketChannelFactory(
                    Executors.newCachedThreadPool(daemonThreadFactory(settings, HTTP_SERVER_BOSS_THREAD_NAME_PREFIX)),
                    Executors.newCachedThreadPool(daemonThreadFactory(settings, HTTP_SERVER_WORKER_THREAD_NAME_PREFIX))
                ));
            } else {
                serverBootstrap = new ServerBootstrap(new NioServerSocketChannelFactory(
                    Executors.newCachedThreadPool(daemonThreadFactory(settings, HTTP_SERVER_BOSS_THREAD_NAME_PREFIX)),
                    Executors.newCachedThreadPool(daemonThreadFactory(settings, HTTP_SERVER_WORKER_THREAD_NAME_PREFIX)),
                    workerCount));
            }
            serverBootstrap.setPipelineFactory(configureServerChannelPipelineFactory());

            serverBootstrap.setOption("child.tcpNoDelay", tcpNoDelay);
            serverBootstrap.setOption("child.keepAlive", tcpKeepAlive);
            if (tcpSendBufferSize.getBytes() > 0) {

                serverBootstrap.setOption("child.sendBufferSize", tcpSendBufferSize.getBytes());
            }
            if (tcpReceiveBufferSize.getBytes() > 0) {
                serverBootstrap.setOption("child.receiveBufferSize", tcpReceiveBufferSize.getBytes());
            }
            serverBootstrap.setOption("receiveBufferSizePredictorFactory", receiveBufferSizePredictorFactory);
            serverBootstrap.setOption("child.receiveBufferSizePredictorFactory", receiveBufferSizePredictorFactory);
            serverBootstrap.setOption("reuseAddress", reuseAddress);
            serverBootstrap.setOption("child.reuseAddress", reuseAddress);
            this.boundAddress = createBoundHttpAddress();
            success = true;
        } finally {
            if (success == false) {
                doStop();  // otherwise we leak threads since we never moved to started
            }
        }
    }

    protected void dispatchRequest(RestRequest request, RestChannel channel) {
        httpServerAdapter.dispatchRequest(request, channel, threadPool.getThreadContext());
    }

    public ChannelPipelineFactory configureServerChannelPipelineFactory() {
        return new HttpChannelPipelineFactory(this, detailedErrorsEnabled, threadPool.getThreadContext());
    }

}

        doStart爲Netty3HttpServerTransport對象的程序運行入口。我們很明顯的可以看到用了netty的ServerBootstrap,重點的一行代碼是serverBootstrap.setPipelineFactory(configureServerChannelPipelineFactory), 這裏是設置netty的pipline參數,configureServerChannelPipelineFactory方法很簡單,就是在內部初始化了一個HttpChannelPipelineFactory對象,我們向下探究HttpChannelPipelineFactory類。

HttpChannelPiplineFactory類

protected static class HttpChannelPipelineFactory implements ChannelPipelineFactory {

    protected final Netty3HttpServerTransport transport;
    protected final Netty3HttpRequestHandler requestHandler;

    public HttpChannelPipelineFactory(Netty3HttpServerTransport transport, boolean detailedErrorsEnabled, ThreadContext threadContext) {
        this.transport = transport;
        this.requestHandler = new Netty3HttpRequestHandler(transport, detailedErrorsEnabled, threadContext);
    }

    @Override
    public ChannelPipeline getPipeline() throws Exception {
        ChannelPipeline pipeline = Channels.pipeline();
        pipeline.addLast("openChannels", transport.serverOpenChannels);
        HttpRequestDecoder requestDecoder = new HttpRequestDecoder(
            (int) transport.maxInitialLineLength.getBytes(),
            (int) transport.maxHeaderSize.getBytes(),
            (int) transport.maxChunkSize.getBytes()
        );
        if (transport.maxCumulationBufferCapacity.getBytes() >= 0) {
            if (transport.maxCumulationBufferCapacity.getBytes() > Integer.MAX_VALUE) {
                requestDecoder.setMaxCumulationBufferCapacity(Integer.MAX_VALUE);
            } else {
                requestDecoder.setMaxCumulationBufferCapacity((int) transport.maxCumulationBufferCapacity.getBytes());
            }
        }
        if (transport.maxCompositeBufferComponents != -1) {
            requestDecoder.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
        }
        pipeline.addLast("decoder", requestDecoder);
        pipeline.addLast("decoder_compress", new HttpContentDecompressor());
        HttpChunkAggregator httpChunkAggregator = new HttpChunkAggregator((int) transport.maxContentLength.getBytes());
        if (transport.maxCompositeBufferComponents != -1) {
            httpChunkAggregator.setMaxCumulationBufferComponents(transport.maxCompositeBufferComponents);
        }
        pipeline.addLast("aggregator", httpChunkAggregator);
        pipeline.addLast("encoder", new ESNetty3HttpResponseEncoder());
        if (transport.compression) {
            pipeline.addLast("encoder_compress", new HttpContentCompressor(transport.compressionLevel));
        }
        if (SETTING_CORS_ENABLED.get(transport.settings())) {
            pipeline.addLast("cors", new Netty3CorsHandler(transport.getCorsConfig()));
        }
        if (transport.pipelining) {
            pipeline.addLast("pipelining", new HttpPipeliningHandler(transport.pipeliningMaxEvents));
        }
        pipeline.addLast("handler", requestHandler);
        return pipeline;
    }
}

        在HttpChannelPipelineFactory類裏,我們觀察getPipeline()方法,該方法裏爲所有的request配置了一系列的處理方法,我們重點關注倒數第二行代碼 - pipline.addLast("handler", requestHandler), 其中requestHandler爲請求的最終處理對象。它屬於Netty3HttpRequestHandler類。

public class Netty3HttpRequestHandler extends SimpleChannelUpstreamHandler{
    private final Netty3HttpServerTransport serverTransport;
    private final boolean httpPipeliningEnabled;
    private final boolean detailedErrorsEnabled;
    private final ThreadContext threadContext;
    @Override
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
        HttpRequest request;
        OrderedUpstreamMessageEvent oue = null;
        if (this.httpPipeliningEnabled && e instanceof OrderedUpstreamMessageEvent) {
            oue = (OrderedUpstreamMessageEvent) e;
            request = (HttpRequest) oue.getMessage();
        } else {
            request = (HttpRequest) e.getMessage();
        }

        // the netty HTTP handling always copy over the buffer to its own buffer, either in NioWorker internally
        // when reading, or using a cumulation buffer
        Netty3HttpRequest httpRequest = new Netty3HttpRequest(serverTransport.xContentRegistry, request, e.getChannel());
        Netty3HttpChannel channel = new Netty3HttpChannel(serverTransport, httpRequest, oue, detailedErrorsEnabled, threadContext);
        serverTransport.dispatchRequest(httpRequest, channel);
        super.messageReceived(ctx, e);
    }
...
}

        我們可以看到該類繼承了SimpleChannelUpstreamHandler, 請求最終是由messageReceived方法來進行處理。同樣在倒數第二行代碼,通過Netty3HttpServerTranport“派發”請求。回到Netty3HttpServerTransport類,可以看到dispatchRequest方法,該方法內部調用HttpServerAdapter類進行派發,它是一個接口,只有一個實現類即爲HttpServer.

HttpServer類

public class HttpServer extends AbstractLifecycleComponent implements HttpServerAdapter {
    public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) {
        if (request.rawPath().equals("/favicon.ico")) {
            handleFavicon(request, channel);
            return;
        }
        RestChannel responseChannel = channel;
        try {
            int contentLength = request.content().length();
            if (restController.canTripCircuitBreaker(request)) {
                inFlightRequestsBreaker(circuitBreakerService).addEstimateBytesAndMaybeBreak(contentLength, "<http_request>");
            } else {
                inFlightRequestsBreaker(circuitBreakerService).addWithoutBreaking(contentLength);
            }
            // iff we could reserve bytes for the request we need to send the response also over this channel
            responseChannel = new ResourceHandlingHttpChannel(channel, circuitBreakerService, contentLength);
            restController.dispatchRequest(request, responseChannel, client, threadContext);
        } catch (Exception e) {
            try {
                responseChannel.sendResponse(new BytesRestResponse(channel, e));
            } catch (Exception inner) {
                inner.addSuppressed(e);
                logger.error((Supplier<?>) () ->
                    new ParameterizedMessage("failed to send failure response for uri [{}]", request.uri()), inner);
            }
        }
    }
...

}

        觀察該方法,在try代碼塊裏可以看到最終是由RestController來派發請求。我們繼續往下探究。

RestController類

public class RestController extends AbstractComponent {
    public void dispatchRequest(final RestRequest request, final RestChannel channel, final NodeClient client, ThreadContext threadContext) throws Exception {
        if (!checkRequestParameters(request, channel)) {
            return;
        }
        try (ThreadContext.StoredContext ignored = threadContext.stashContext()) {
            for (String key : headersToCopy) {
                String httpHeader = request.header(key);
                if (httpHeader != null) {
                    threadContext.putHeader(key, httpHeader);
                }
            }

            final RestHandler handler = getHandler(request);

            if (handler == null) {
                if (request.method() == RestRequest.Method.OPTIONS) {
                    // when we have OPTIONS request, simply send OK by default (with the Access Control Origin header which gets automatically added)
                    channel.sendResponse(new BytesRestResponse(OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
                } else {
                    final String msg = "No handler found for uri [" + request.uri() + "] and method [" + request.method() + "]";
                    channel.sendResponse(new BytesRestResponse(BAD_REQUEST, msg));
                }
            } else {
                final RestHandler wrappedHandler = Objects.requireNonNull(handlerWrapper.apply(handler));
                wrappedHandler.handleRequest(request, channel, client);
            }
        }
    }
...

}

        進入RestController類的dispatchRequest,可以發現對每個request獲取相關的RestHandler來進行處理,如果RestHandler不爲空的話,那麼調用handlerRequest方法,該方法即爲最終request的請求處理方法。我們進入RestHandler, 可以發現其爲一個藉口,有兩個具體的實現子類。DeprecationRestHandler爲RestHandler提供代理,方便打印log。真正的子類實現是在BaseHandler裏。

BaseRestHandler類

/**
 * Base handler for REST requests.
 * <p>
 * This handler makes sure that the headers &amp; context of the handled {@link RestRequest requests} are copied over to
 * the transport requests executed by the associated client. While the context is fully copied over, not all the headers
 * are copied, but a selected few. It is possible to control what headers are copied over by returning them in
 * {@link ActionPlugin#getRestHeaders()}.
 */
public abstract class BaseRestHandler extends AbstractComponent implements RestHandler {

    @Override
    public final void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
        // prepare the request for execution; has the side effect of touching the request parameters
        final RestChannelConsumer action = prepareRequest(request, client);

        // validate unconsumed params, but we must exclude params used to format the response
        // use a sorted set so the unconsumed parameters appear in a reliable sorted order
        final SortedSet<String> unconsumedParams =
            request.unconsumedParams().stream().filter(p -> !responseParams().contains(p)).collect(Collectors.toCollection(TreeSet::new));

        // validate the non-response params
        if (!unconsumedParams.isEmpty()) {
            final Set<String> candidateParams = new HashSet<>();
            candidateParams.addAll(request.consumedParams());
            candidateParams.addAll(responseParams());
            throw new IllegalArgumentException(unrecognized(request, unconsumedParams, candidateParams, "parameter"));
        }

        // execute the action
        action.accept(channel);
    }
...

}

        BaseHandler.dispatchRequest()方法的最後一行代碼,可以發現最終執行相關操作,action是一個函數式接口,我們看看它有哪些具體實現。

        至此,我們可以大致瞭解,elasticsearch爲每類通過rest發起的操作都會配對一個對應的處理方式。這裏我們同樣跟蹤Index操作,選擇RestIndexAction類。我們點擊上面圖片的裏RestIndexAction, 最終定位到prepareRequest的return 語句,也就是說最終返回的就是RestChannelConsumer。觀察(channel->client.index),我們可以發現最終執行client.index方法,這裏的client是傳入的參數值NodeClient對象。

public class RestIndexAction extends BaseRestHandler {

    @Inject
    public RestIndexAction(Settings settings, RestController controller) {
        super(settings);
        controller.registerHandler(POST, "/{index}/{type}", this); // auto id creation
        controller.registerHandler(PUT, "/{index}/{type}/{id}", this);
        controller.registerHandler(POST, "/{index}/{type}/{id}", this);
        CreateHandler createHandler = new CreateHandler(settings);
        controller.registerHandler(PUT, "/{index}/{type}/{id}/_create", createHandler);
        controller.registerHandler(POST, "/{index}/{type}/{id}/_create", createHandler);
    }

    final class CreateHandler extends BaseRestHandler {
        protected CreateHandler(Settings settings) {
            super(settings);
        }

        @Override
        public RestChannelConsumer prepareRequest(RestRequest request, final NodeClient client) throws IOException {
            request.params().put("op_type", "create");
            return RestIndexAction.this.prepareRequest(request, client);
        }
    }

    @Override
    public RestChannelConsumer prepareRequest(final RestRequest request, final NodeClient client) throws IOException {
        IndexRequest indexRequest = new IndexRequest(request.param("index"), request.param("type"), request.param("id"));
        indexRequest.routing(request.param("routing"));
        indexRequest.parent(request.param("parent")); // order is important, set it after routing, so it will set the routing
        if (request.hasParam("timestamp")) {
            deprecationLogger.deprecated("The [timestamp] parameter of index requests is deprecated");
        }
        indexRequest.timestamp(request.param("timestamp"));
        if (request.hasParam("ttl")) {
            deprecationLogger.deprecated("The [ttl] parameter of index requests is deprecated");
            indexRequest.ttl(request.param("ttl"));
        }
        indexRequest.setPipeline(request.param("pipeline"));
        indexRequest.source(request.content());
        indexRequest.timeout(request.paramAsTime("timeout", IndexRequest.DEFAULT_TIMEOUT));
        indexRequest.setRefreshPolicy(request.param("refresh"));
        indexRequest.version(RestActions.parseVersion(request));
        indexRequest.versionType(VersionType.fromString(request.param("version_type"), indexRequest.versionType()));
        String sOpType = request.param("op_type");
        String waitForActiveShards = request.param("wait_for_active_shards");
        if (waitForActiveShards != null) {
            indexRequest.waitForActiveShards(ActiveShardCount.parseString(waitForActiveShards));
        }
        if (sOpType != null) {
            indexRequest.opType(IndexRequest.OpType.fromString(sOpType));
        }

        return channel ->
            client.index(indexRequest, new RestStatusToXContentListener<>(channel, r -> {
                try {
                    return r.getLocation(indexRequest.routing());
                } catch (URISyntaxException ex) {
                    logger.warn("Location string is not a valid URI.", ex);
                    return null;
                }
            }));
    }

}

        繼續往下走,我們可以發現一個方法調用鏈 index->execute -> doExecute, doExecute方法是一個抽象方法,由子類實現,我們在上面已經知道client類型爲NodeClient。一次我們進入NodeClient類裏。

AbstractClient類

public abstract class AbstractClient extends AbstractComponent implements Client {
...
    @Override
    public void index(final IndexRequest request, final ActionListener<IndexResponse> listener) {
        execute(IndexAction.INSTANCE, request, listener);
    }

    @Override
    public final <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void execute(
            Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) {
        listener = threadedWrapper.wrap(listener);
        doExecute(action, request, listener);
    }
    protected abstract <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void doExecute(final Action<Request, Response, RequestBuilder> action, final Request request, ActionListener<Response> listener);

...
}

NodeClient類

/**
 * Client that executes actions on the local node.
 */
public class NodeClient extends AbstractClient {

    private Map<GenericAction, TransportAction> actions;

    public NodeClient(Settings settings, ThreadPool threadPool) {
        super(settings, threadPool);
    }

    public void initialize(Map<GenericAction, TransportAction> actions) {
        this.actions = actions;
    }

    @Override
    public void close() {
        // nothing really to do
    }

    @Override
    public <Request extends ActionRequest,
        Response extends ActionResponse,
        RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>
        > void doExecute(Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) {
        // Discard the task because the Client interface doesn't use it.
        executeLocally(action, request, listener);
    }

    /**
     * Execute an {@link Action} locally, returning that {@link Task} used to track it, and linking an {@link ActionListener}. Prefer this
     * method if you don't need access to the task when listening for the response. This is the method used to implement the {@link Client}
     * interface.
     */
    public <Request extends ActionRequest,
        Response extends ActionResponse
        > Task executeLocally(GenericAction<Request, Response> action, Request request, ActionListener<Response> listener) {
        return transportAction(action).execute(request, listener);
    }

    /**
     * Execute an {@link Action} locally, returning that {@link Task} used to track it, and linking an {@link TaskListener}. Prefer this
     * method if you need access to the task when listening for the response.
     */
    public <Request extends ActionRequest,
        Response extends ActionResponse
        > Task executeLocally(GenericAction<Request, Response> action, Request request, TaskListener<Response> listener) {
        return transportAction(action).execute(request, listener);
    }

    /**
     * Get the {@link TransportAction} for an {@link Action}, throwing exceptions if the action isn't available.
     */
    @SuppressWarnings("unchecked")
    private <Request extends ActionRequest,
        Response extends ActionResponse
        > TransportAction<Request, Response> transportAction(GenericAction<Request, Response> action) {
        if (actions == null) {
            throw new IllegalStateException("NodeClient has not been initialized");
        }
        TransportAction<Request, Response> transportAction = actions.get(action);
        if (transportAction == null) {
            throw new IllegalStateException("failed to find action [" + action + "] to execute");
        }
        return transportAction;
    }
}

        在NodeClient類的doExecute的方法調用鏈 doExecute->executeLocally->transportAction。我們仔細觀察transportAction,裏面有行代碼actiotns.get,這和在上篇裏的TransportProxyClient類初始化時,爲每個action生成對應的TransportActionNodeProxy有點相似,只不過這裏生成的TransportAction。我們繼續,進入TransportAction類。

TransportAction類

public abstract class TransportAction<Request extends ActionRequest, Response extends ActionResponse> extends AbstractComponent {
    public final Task execute(Request request, ActionListener<Response> listener) {
        /*
         * While this version of execute could delegate to the TaskListener
         * version of execute that'd add yet another layer of wrapping on the
         * listener and prevent us from using the listener bare if there isn't a
         * task. That just seems like too many objects. Thus the two versions of
         * this method.
         */
        Task task = taskManager.register("transport", actionName, request);
        if (task == null) {
            execute(null, request, listener);
        } else {
            execute(task, request, new ActionListener<Response>() {
                @Override
                public void onResponse(Response response) {
                    taskManager.unregister(task);
                    listener.onResponse(response);
                }

                @Override
                public void onFailure(Exception e) {
                    taskManager.unregister(task);
                    listener.onFailure(e);
                }
            });
        }
        return task;
    }
...

}

        看到該TransportAction的execute方法,我們可以發現這個上篇裏Transport方式殊途同歸了,也就是不論是通過Http方式還是Transport方式,所有的請求只是在真正處理前的預處理方式不同,但是最終真正的請求處理方法都是一樣。

總結

        不論Http方式和Transport方式,請求處理方式是一樣的,但是從請求發起到請求真正開始被處理這一段邏輯是不同,暫且稱之爲“預處理”,兩種請求方法的預處理有自己對應的邏輯處理方式。

 

 

 

 

 

 

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