创建Elasticsearch插件系列(2)搜索

在本系列中,我们将解释有关Elasticsearch插件的基本编写方法。上一次,我们通过创建一个简单的插件来显示Hello World来解释了插件的大致创建方式。第二次,我们将创建一个能够接受简单搜索请求的插件。
已经创建的源代码可以在github上找到(已对上一次的源代码进行了修正)。

目标

Elasticsearch的大部分搜索请求可以通过JSON格式的请求进行处理。然而,使用默认配置,管理功能(如索引删除等)也会被暴露,并且直接从Web访问Elasticsearch并不理想。因此通常情况下,Elasticsearch会像数据库一样被用于Web→AP服务器→Elasticsearch的流程中。此外,通过使用集群,可以轻松确保可用性,但AP服务器必须通过某种独特的方式来确保可用性。这将增加运维负担并增加监控对象的数量。如果能够在Elasticsearch中实现AP服务器的功能,那么只需管理Elasticsearch就能构建满足一定业务要求的应用程序。这次我们的目标是在Elasticsearch中实现AP服务器,将验证插件可以实现的程度。首先,我们将尝试创建一个通过查询参数(例如text)来对文档的“text”字段进行全文搜索的REST API。

实施摘要

关于插件框架,与上次一样。
不同之处在于从RestHandler开始,向索引节点发送“搜索请求”。
我们要根据REST请求生成向索引节点发送的“搜索请求”,但是在Elasticsearch的JSON格式的REST请求中,可以很方便地生成“搜索请求”。这次我们要使用它。

活动背景

由于近期Gradle发布了6.1版本,所以我选择了使用这个版本,其他环境和上一次几乎一样。
– Elasticsearch 7.4.2
– Gradle 6.1
– OracleJDK 13(也可以使用OpenJDK)
– Windows(下面是命令执行示例,插件的创建和运行与Elasticsearch的执行环境相符)

在中国语中,“記事中の记法”可以被翻译为“文章中的记法”。

    [~]は環境に応じた変数です。ご自分の環境に合わせて読みかえて下さい。

事前的准备工作

    • Elasticsearchのインストール OSS版 [ELASTICSEARCH_DIR]に展開

 

    • Gradleのインストールリリース版 [GRADLE_DIR]に展開

 

    JDKのインストール OracleJDK [JAVA_DIR]に配置

项目准备

我将使用之前创建的项目。

准备插件源代码。

创建一个用于生成JSON格式请求的实用工具。

从文件中加载模板更容易理解,但Elasticsearch对于从插件访问Java标准API有很大限制。虽然可以通过配置使其可访问,但在本次操作中我们将将其直接嵌入源代码中。关于放宽对API访问限制的问题,将在以后的操作中进行描述。

package taka8.elasticsearch.rest;

public class QueryTemplate {

//  {
//    "query" : {
//      "match" : {
//        "text" : {
//          "query" : "[[text]]",
//          "operator" : "and"
//        }
//      }
//    }
//  }
    private static final String TEXT_ONLY = "{\"query\":{\"match\":{\"text\":{\"query\":\"[[text]]\",\"operator\":\"and\"}}}}";

    public static String getTextOnly(String text) {
        return QueryTemplate.TEXT_ONLY.replace("[[text]]", text);
    }
}

我要创建一个RestHandler。

package taka8.elasticsearch.rest;

import static org.elasticsearch.rest.RestRequest.Method.GET;

import java.io.IOException;

import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.xcontent.DeprecationHandler;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.rest.BaseRestHandler;
import org.elasticsearch.rest.RestController;
import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.rest.action.RestStatusToXContentListener;
import org.elasticsearch.search.builder.SearchSourceBuilder;

// REST APIを受け付けるRestHandlerはBaseRestHandlerを継承すると作りやすい
public class TextOnlySearchAction extends BaseRestHandler {

    public TextOnlySearchAction(final RestController controller) {
        assert controller != null;
        // 要求を受け付けるパスを定義する。
        controller.registerHandler(GET, "/{index}/search_text", this);
    }

    // RestHandlerの名前を定義する。
    // 人が見て分かりやすい名前にする。
    // 使用方法を返すAPIで使用される
    @Override
    public String getName() {
        return "search_text_action";
    }

    @Override
    protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException {
        SearchRequest searchRequest = new SearchRequest();
        request.withContentOrSourceParamParserOrNull(parser -> _parseSearchRequest(searchRequest, request, parser));
        return channel -> {
            // 検索結果からREST APIの戻り値を作成するListenerを生成する。
            // ここでは、検索結果をそのまま返す標準のListenerにする。
            RestStatusToXContentListener<SearchResponse> listener = new RestStatusToXContentListener<>(channel);
            // インデックスノードに対して要求を投げる。Elasticsearchはインデックスを分割して複数のノードに配置する事で、検索性能と可用性を担保している。
            // このため、REST要求を受け付けるノードから実際の検索を行うノードに対して要求を投げる必要がある。
            client.search(searchRequest, listener);
        };
    }

    // RESTの入力からインデックスノードに対しての検索要求を初期化する。
    private void _parseSearchRequest(SearchRequest searchRequest, RestRequest request,
            XContentParser requestContentParser) throws IOException {
        if (searchRequest.source() == null) {
            searchRequest.source(new SearchSourceBuilder());
        }
        // クエリのtextパラメータから、ElasticsearchのJSON形式の検索要求を生成する。
        String source = QueryTemplate.getTextOnly(request.param("text"));
        // JSON形式の検索要求用のパーサ。
        XContentParser parser = XContentType.JSON.xContent().createParser(request.getXContentRegistry(),
                DeprecationHandler.THROW_UNSUPPORTED_OPERATION, source);
        searchRequest.source().parseXContent(parser);
        searchRequest.indices(Strings.splitStringByCommaToArray(request.param("index")));
    }

}

确认操作

部署

在上一次的操作确认步骤中,将安装Elasticsearch。

将要搜索的文件注册

使用能够发出POST请求的工具,将以下的json格式文档通过POST方式发送到以下URL:
http://localhost:9200/test/_bulk

{"create": {"_id": "1"}}
{"text": "自動車に乗る"}
{"create": {"_id": "2"}}
{"text": "自転車を降りる"}

确认图像

使用浏览器访问”http://localhost:9200/test/search_text?text=自動車”,并确认检索到包含”自動車”的文件(以下是经过JSON格式化的示例)。

{
  "took": 43,
  "timed_out": false,
  "_shards": {
    "total": 1,
    "successful": 1,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": {
      "value": 1,
      "relation": "eq"
    },
    "max_score": 1.0921589,
    "hits": [
      {
        "_index": "test",
        "_type": "_doc",
        "_id": "1",
        "_score": 1.0921589,
        "_source": {
          "text": "自動車に乗る"
        }
      }
    ]
  }
}

附言

使用插件从查询生成JSON格式的请求,并确认可以使用该请求正确进行搜索。这样就可以满足标准业务要求的搜索。接下来,我们将尝试生成满足业务要求的返回值。

广告
将在 10 秒后关闭
bannerAds