说说Elasticsearch那点事ElasticSearch&Luceneelasticsearch

Elasticsearch 5.x 源码分析(6)Request

2017-06-27  本文已影响505人  华安火车迷

本篇只关注一个细节,就是在一个端到端的场景,一个Request是如何构造并传输,从Client 到Node 的Action 接收端又如何被parse,接着传输到各个的 shards 去处理;反之一个Response 生成后如何一直传输回到 Node 端最后回到 Transport Client 或者 REST Client。本篇主要回答了这几天处理的一个问题:如果采用REST Client 来做 Query 查询的话,有没有一种办法可以把 JSON 的Response 解析成 SearchResponse 对象如 Transport Client 般地处理回复。
所以我们分两部分来讲:1. 看一下各种Request 和Response 的源码;2. 看一下如何去parse 一个 JSON 成一个SearchResponse 对象。


Transport Client 调用链

我们先从最简单的例子开始吧,由于我在项目开发中用 Template Search 比较多那么就看一个最简单的 Template Search 的例子

SearchResponse sr = new SearchTemplateRequestBuilder(client)
        .setScript("template_gender")                       
        .setScriptType(ScriptType.STORED)     
        .setScriptParams(template_params)                   
        .setRequest(new SearchRequest())                    
        .get()                                              
        .getResponse(); 
public class SearchTemplateRequestBuilder
        extends ActionRequestBuilder<SearchTemplateRequest, SearchTemplateResponse, SearchTemplateRequestBuilder> {

    SearchTemplateRequestBuilder(ElasticsearchClient client, SearchTemplateAction action) {
        super(client, action, new SearchTemplateRequest());
    }

    public SearchTemplateRequestBuilder(ElasticsearchClient client) {
        this(client, SearchTemplateAction.INSTANCE);
    }

    public SearchTemplateRequestBuilder setRequest(SearchRequest searchRequest) {
        request.setRequest(searchRequest);
        return this;
    }

顾名思义,SearchTemplateRequestBuilder就是负责生成一个SearchTemplateRequest,我们看看它的继承关系

SearchTemplateRequest 结构
ActionRequest 可以理解成一个通用的可以被Action 层处理的Request 对象,SearchTemplateRequest 就是在外面包了一些自身的属性,如profile,script 等,这里我们不关心。打开ActionRequest 里面就更简单了,并没有任何逻辑,弄了一个方法public abstract ActionRequestValidationException validate();给Action 层做一些字段校验而已。而ActionRequest 又继承自TransportRequest 乃至TransportMessage 那么不用看基本就能猜到ActionRequest 直接就用于节点间传输了,而协议就是走netty 的Transport 了。

还有一个地方就是,在SearchTemplateRequest的构造函数里指定了一个SearchTemplateAction.INSTANCE,这个变量指定了这个Request 请求期望的目的是这个 INSTANCE key的接受者。而这个接受者则在 MustachePlugin 定义了,这里有种感觉就是SearchTemplateAction 这个Action 层好像完全被架空了,仅用来路由请求而已,感觉怪怪的。

MustachePlugin

TransportSearchTemplateAction 只做了一件事,就是从Request里面抽取出script 变量,然后调用scriptService去load 和compile 这个script 并渲染出最终的这个SearchRequest对象。

static SearchRequest convert(SearchTemplateRequest searchTemplateRequest, SearchTemplateResponse response, ScriptService scriptService,
                                 NamedXContentRegistry xContentRegistry) throws IOException {
        Script script = new Script(searchTemplateRequest.getScriptType(), TEMPLATE_LANG, searchTemplateRequest.getScript(),
                searchTemplateRequest.getScriptParams() == null ? Collections.emptyMap() : searchTemplateRequest.getScriptParams());
        CompiledTemplate compiledScript = scriptService.compileTemplate(script, SEARCH);
        BytesReference source = compiledScript.run(script.getParams());
        response.setSource(source);

最后交给了TransportSearchAction 来把Request当做一个普通的Request来处理。

TransportSearchTemplateAction 处理
由于netty处理请求采用异步的方式,所以这里需要一个onResponse() 的回调;
SearchAction把请求发送给SearchService 来执行,SearchService更多的是去控制Lucene 的逻辑,所以从SearchActionSearchService 层之间需要一个转换,来把SearchRequest 转换成Elasticsearch 内部的一种特殊的Request 对象ShardsSearchRequest,这个过程在TransportSearchActiondoExecute方法里做,在这里创建了一个SearchQueryThenFetchAsyncAction来去控制整个发送和接收的控制,包括收到每个shards的Response 后如何做 reducesort等等,在SearchQueryThenFetchAsyncAction你会看到一句getSearchTransport().sendExecuteQuery(getConnection(shardIt.getClusterAlias(), shard.currentNodeId()), buildShardSearchRequest(shardIt), getTask(), listener);
至此验证了最开始的想法,就是从TransportClient 创建的Request,将会一走到处理这类请求的Action层,最终会转换成ShardSearchRequest发送给 Service 层执行 Lucene调用并返回。SearchService相应的Lucene 操作,这里就不再进去看了,有兴趣的可以直接去看 SearchServicepublic SearchPhaseResult executeQueryPhase(ShardSearchRequest request, SearchTask task)方法;和QueryPhasepublic void execute(SearchContext searchContext) throws QueryPhaseExecutionException 方法。

那么SearchResponse 的话就直接反过来看就好了;从SearchAction 开始。。。。


REST Client 调用链

Transport Client 直接创建的是 SearchRequest 对象,并且直接发送到 TransportXXXAction 中去处理,而REST 则创建的是 JSON 格式的 Request,还是拿上面的 SearchTemplate 查询的例子来看,如果用REST Client构造一个SearchTemplate 的request 发送的话,那么接收的endpoint 就是RestSearchTemplateAction,在这个类中我们看到这样一段。

@Override
    public RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
        // Creates the search request with all required params
        SearchRequest searchRequest = new SearchRequest();
        RestSearchAction.parseSearchRequest(searchRequest, request, null);

        // Creates the search template request
        SearchTemplateRequest searchTemplateRequest;
        try (XContentParser parser = request.contentOrSourceParamParser()) {
            searchTemplateRequest = PARSER.parse(parser, new SearchTemplateRequest(), null);
        }
        searchTemplateRequest.setRequest(searchRequest);

        return channel -> client.execute(SearchTemplateAction.INSTANCE, searchTemplateRequest, new RestStatusToXContentListener<>(channel));
    }
private static final ObjectParser<SearchTemplateRequest, Void> PARSER;
    static {
        PARSER = new ObjectParser<>("search_template");
        PARSER.declareField((parser, request, s) ->
                        request.setScriptParams(parser.map())
                , new ParseField("params"), ObjectParser.ValueType.OBJECT);
        PARSER.declareString((request, s) -> {
            request.setScriptType(ScriptType.FILE);
            request.setScript(s);
        }, new ParseField("file"));
        PARSER.declareString((request, s) -> {
            request.setScriptType(ScriptType.STORED);
            request.setScript(s);
        }, new ParseField("id"));
        PARSER.declareBoolean(SearchTemplateRequest::setExplain, new ParseField("explain"));
        PARSER.declareBoolean(SearchTemplateRequest::setProfile, new ParseField("profile"));
        PARSER.declareField((parser, request, value) -> {
            request.setScriptType(ScriptType.INLINE);
            if (parser.currentToken() == XContentParser.Token.START_OBJECT) {
                //convert the template to json which is the only supported XContentType (see CustomMustacheFactory#createEncoder)
                try (XContentBuilder builder = XContentFactory.jsonBuilder()) {
                    request.setScript(builder.copyCurrentStructure(parser).string());
                } catch (IOException e) {
                    throw new ParsingException(parser.getTokenLocation(), "Could not parse inline template", e);
                }
            } else {
                request.setScript(parser.text());
            }
        }, new ParseField("inline", "template"), ObjectParser.ValueType.OBJECT_OR_STRING);
    }

很容易发现,在RestAction接收到一个请求时,会通过RestSearchAction.parseSearchRequest(searchRequest, request, null); 来把一个RestRequest转换成一个SearchRequest ,然后重新扔回去channel 中去,就好像一个TransportClient 一样,在最后的调用里会同传一个toXContentListener 里面有一个方法是SearchResponse -> XContent 的转换,也就是toJson 同样的意义。

最后从Response 到RestResponse

从 JSON Response 反解析成SearchResponse

很多时候,我们又想用REST Client的便利,但是我们又不想拿到一个 String 的Response 然后弄成一个JSON Object慢慢的去解析,我们希望还是直接处理一个SearchResponse 来的爽快。既然有了toXContent()方法,那么有没有 fromXContent() 呢,答案是Elasticsearch 5.5 之前是没有的(或者仅限于SearchHits有)。。。
如果你去看ES 的5.5 branch或者master branch的 log,你会发现其中的一个重点commit有:

每种Aggregation 的parser 类

那么我要做的,目前就是把5.5的这部分代码暂时迁移过来5.3先用着了。。。


后话

有朋友支招说其实github上也有类似的项目了,如jest

Jest is a Java HTTP Rest client for ElasticSearch

可是它始终不是采用了原生的SearchResponse 对象,因此使用起来还是不是很带劲。

而Elasticsearch 自己为啥要这么大费周章话那么多人力物力重写那堆的SearchResponse、SearchHit、Aggregation 呢?答案就是ES也想出一个这样的REST Client,叫做 High Level Rest Client,目的就是直接在REST Client上操作SearchResponse 对象

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/master/java-rest-high.html#java-rest-high

scope

https://github.com/elastic/elasticsearch/issues/23331

有兴趣的话可以上去GitHub 查看这个issue,期望的release 是ES5.6 和ES6.0

全篇完,有问题欢迎讨论。

上一篇下一篇

猜你喜欢

热点阅读