创建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格式的请求,并确认可以使用该请求正确进行搜索。这样就可以满足标准业务要求的搜索。接下来,我们将尝试生成满足业务要求的返回值。