在 Java 应用程序中使用弹性搜索

269 阅读12分钟

在 Java 应用程序中使用弹性搜索

高性能 RESTFUL 搜索引擎和文档存储的快速启动指南

作者 Matt Tyson

发布时间: 2016年3月8日


如果你曾经使用过阿帕奇·卢塞尼或阿帕奇·索尔,你知道这种体验会很激烈。特别是如果您需要扩展基于 Lucene 或 Solr 的解决方案,您了解弹性搜索项目背后的动机。弹性搜索(内置于 Lucene 之上)在支持从框中分组扩展的简单管理包中提供高性能的全文搜索功能。您可以通过标准的 REST API 或编程语言特定的客户库与弹性搜索进行交互。

本教程展示了弹性搜索在实践中的工作原理。首先从命令线访问 REST API 的基础知识。然后设置本地弹性搜索服务器,并从简单的 Java 应用程序与它进行交互。

先决条件

要完成所有这些教程的示例,您需要在系统上进行弹性搜索。下载最新的弹性搜索捆绑包,获取您的平台。将包裹减压到方便的位置。

当您看到记录消息started时,节点已准备好接受请求。

对于Java的例子,你还需要EclipseApache Maven。如果您的系统上还没有两者,请下载并安装它们。

您还需要 cURL。

使用 cURL 进行REST命令

您可以针对弹性搜索发出 cURL 请求,这使得该框架易于从命令线外壳中尝试。

弹性搜索是无模式的。它可以吃任何东西,你喂它,并处理它供以后查询。"

弹性搜索是无模式的,这意味着它可以吃任何东西,你喂它,并处理它供以后查询。弹性搜索中的所有内容都存储为文档,因此您的第一个练习是存储包含歌词的文件。首先创建一个索引,它是所有文档类型的容器 - 类似于关系数据库中的数据库,如 MySQL。然后将文档插入索引,以便您可以查询文档的数据。

创建索引

弹性搜索命令的通用格式是: REST VERB__HOST:9200/index/doc-typeREST VERBHOST:9200/index/doc-type— where REST VERBREST VERB is PUT, GET, or DELETE. (Use the cURL -X verb prefix to specify the HTTP method explicitly.)

要创建索引,请在外壳中运行此命令:

curl ‑XPUT "http://localhost:9200/music/"

架构可选

虽然弹性搜索是无模式的,在引擎盖下,它使用Lucene,它使用架构。但弹性搜索隐藏了这种复杂性。在实践中,您可以将弹性搜索单据类型视为简单的子点或表名。但是,如果您需要,您可以指定一个模式,因此您可以将其视为一个可选的架构数据存储。

插入文档

要在索引下创建/music类型,将插入文档。在第一个例子中,你的文件包括数据——包括一行歌词——关于"装饰大厅",这是威尔士诗人约翰·塞罗格·休斯在1885年首次写下的传统圣诞曲调。

要将"大厅"文档插入索引中,运行此命令(将此命令和教程的其他 cURL 命令键入单行):

XPUT "http://localhost:9200/music/songs/1" ‑d '
{ "name": "Deck the Halls", "year": 1885, "lyrics": "Fa la la la la" }'

前一个命令使用PUT动词将文档添加到/songs文档类型,并给文档 1 的 ID。URL 路径表示索引/文型/ID。

查看文档

要查看文档,请使用简单的GET命令:

curl ‑XGET "http://localhost:9200/music/songs/1"

弹性搜索响应之前你PUT索引中的 JSON 内容:

{"_index":"music","_type":"songs","_id":"1","_version":1,"found":true,"_source":
{ "name": "Deck the Halls", "year": 1885, "lyrics": "Fa la la la la" }}

更新文档

如果你意识到日期是错误的,你想把它改到1886年呢?通过运行更新文档:

curl ‑XPUT "http://localhost:9200/music/lyrics/1" ‑d '{ "name":
"Deck the Halls", "year": 1886, "lyrics": "Fa la la la la" }'

由于此命令使用相同的唯一 ID 1,因此文档会进行更新。

删除文档(但尚未删除)

不要删除文档,但要知道删除文档:

curl ‑XDELETE "http://localhost:9200/music/lyrics/1"

从文件中插入文档

这是另一个技巧。您可以使用文件的内容从命令行插入文档。尝试此方法为另一首传统歌曲"凯西·琼斯的巴拉德"添加文档。将列表 1 复制到名为 Caseyjones.json 的文件中;或者,使用样本代码包中的 caseyjones.json 文件(参见下载)。将文件放在任何方便运行 cURL 命令的任何地方。(在代码下载中,文件在根目录中。

列表 1.杰森医生的 "凯西琼斯的巴拉德"
{
  "artist": "Wallace Saunders",
  "year": 1909,
  "styles": ["traditional"],
  "album": "Unknown",
  "name": "Ballad of Casey Jones",
  "lyrics": "Come all you rounders if you want to hear
The story of a brave engineer
Casey Jones was the rounder's name....
Come all you rounders if you want to hear
The story of a brave engineer
Casey Jones was the rounder's name
On the six‑eight wheeler, boys, he won his fame
The caller called Casey at half past four
He kissed his wife at the station door
He mounted to the cabin with the orders in his hand
And he took his farewell trip to that promis'd land

Chorus:
Casey Jones‑‑mounted to his cabin
Casey Jones‑‑with his orders in his hand
Casey Jones‑‑mounted to his cabin
And he took his... land"
}
PUT`通过运行在索引中的此文档:`music
$ curl ‑XPUT "http://localhost:9200/music/lyrics/2" ‑d @caseyjones.json

当你在它, 保存列表 2 的内容 (为 "Walking Boss,another folk song) 到walking.json 文件。

列表 2.“Walking Boss” JSON
{
  "artist": "Clarence Ashley",
  "year": 1920
  "name": "Walking Boss",
  "styles": ["folk","protest"],
  "album": "Traditional",
  "lyrics": "Walkin' boss
Walkin' boss
Walkin' boss
I don't belong to you

I belong
I belong
I belong
To that steel driving crew

Well you work one day
Work one day
Work one day
Then go lay around the shanty two"
}

将此文档推入索引:

$ curl ‑XPUT "http://localhost:9200/music/lyrics/3" ‑d @walking.json

搜索REST API

是时候运行一个基本查询,它的作用不仅仅是您运行查找"获取大厅"文档的简单查询GET。文档 URL 具有_search为此目的的内置端点。要在歌词中找到所有带有*"你*"字样的歌曲:

curl ‑XGET "http://localhost:9200/music/lyrics/_search?q=lyrics:'you'"

参数q表示查询。

答复是:

(newline){"took":107,"timed_out":false,"_shards":{"total":5,"successful":5,"failed":0},"hits":{"total":2,"max(newline)_score":0.15625,"hits":[{"_index":"music","_type":"songs","_id":"2","_(newline)score":0.15625,"_source":{"artist": "Wallace Saunders","year": 1909,"styles":(newline)["traditional"],"album": "Unknown","name": "Ballad of Casey Jones","lyrics": "Come all you rounders(newline)if you want to hear The story of a brave engineer Casey Jones was the rounder's name.... Come all(newline)you rounders if you want to hear The story of a brave engineer Casey Jones was the rounder's name(newline)On the six‑eight wheeler, boys, he won his fame The caller called Casey at half past four He kissed(newline)his wife at the station door He mounted to the cabin with the orders in his hand And he took his(newline)farewell trip to that promis'd land Chorus: Casey Jones‑‑mounted to his cabin Casey Jones‑‑with his(newline)orders in his hand Casey Jones‑‑mounted to his cabin And he took his... land"(newline)}},{"_index":"music","_type":"songs","_id":"3","_score":0.06780553,"_source":{"artist": "Clarence(newline)Ashley","year": 1920,"name": "Walking Boss","styles": ["folk","protest"],"album":(newline)"Traditional","lyrics": "Walkin' boss Walkin' boss Walkin' boss I don't belong to you I belong I(newline)belong I belong To that steel driving crew Well you work one day Work one day Work one day Then go(newline)lay around the shanty two"}}]}}

使用其他比较器

还提供各种其他比较器。例如,查找 1900 年之前写的所有歌曲:

curl ‑XGET "http://localhost:9200/music/lyrics/_search?q=year:<1900

此查询返回完整的"Caesy Jones"和"Walking Boss"文件。

限制字段

要限制您在结果中看到的fields字段,请在查询中添加参数:

curl ‑XGET "http://localhost:9200/music/lyrics/_search?q=year:>1900&fields=year"

检查搜索返回对象

列表 3 显示弹性搜索从上一个查询返回的数据。

列表 3.查询结果
{
    "took": 6,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 1.0,
        "hits": {
            "_index": "music",
            "_type": "lyrics",
            "_id": "1",
            "_score": 1.0,
            "fields": {
                "year": 1920            }
        }, {
            "_index": "music",
            "_type": "lyrics",
            "_id": "3",
            "_score": 1.0,
            "fields": {
                "year": 1909            }
        }    }
}

在结果中,弹性搜索会为您提供多个 JSON 对象。第一个对象包含有关请求的元数据:查看请求需要多少毫秒(采取)以及是否超时(timed_out)。_shards字段与弹性搜索是一个集群服务这一事实有关。即使在这种单节点本地部署中,弹性搜索在逻辑上也被分组成碎片。

继续列出 3 中的搜索结果,注意hits对象包含:

  • total字段可告诉您获得了多少结果
  • max_score,这开始发挥作用的全文搜索
  • 实际结果

实际结果包含 fields 属性,因为您向查询添加了 fields 参数。 否则,结果将包含“源”并包含完整的匹配文档。 _index_type_id 是不言自明的; _score 指的是全文搜索命中强度。 这四个字段总是在结果中返回。

使用 JSON 查询 DSL

基于查询字符串的搜索很快变得复杂。 对于更高级的查询,Elasticsearch 提供了一个完整的基于 JSON 的领域特定语言 (DSL)。 例如,要搜索 album 值为 traditional 的每首歌曲,请创建一个包含以下内容的 query.json 文件:

{
    "query" : {
        "match" : {
            "album" : "Traditional"
        }
    }
}

然后运行:

curl ‑XGET "http://localhost:9200/music/lyrics/_search" ‑d @query.json

使用来自 Java 代码的弹性搜索

弹性搜索的全部力量来自于通过语言 API 使用它。"

弹性搜索的全部力量来自于通过语言 API 使用它。现在,我将向您展示 Java API,然后从应用程序中进行一些搜索。应用程序使用火花微帧,所以设置应用程序是快速的。

示例应用

为新项目创建目录,然后运行(在单行上键入命令):

mvn archetype:generate ‑DgroupId=com.dw ‑DartifactId=es‑demo 
‑DarchetypeArtifactId=maven‑archetype‑quickstart ‑DinteractiveMode=false

要生成一个项目供您在 Eclipse 中使用,将 cd 放入 Maven 创建的项目目录并运行 mvn eclipse:eclipse

在 Eclipse 中,选择 File > Import > Existing Project into Workspace。 导航到您使用 Maven 的文件夹,选择项目,然后单击“完成”。

在 Eclipse 中,您可以看到基本的 Java 项目布局,包括根目录中的 pom.xml 文件和 com.dw.App.java 主类文件。 将您需要的依赖项添加到 pom.xml 文件中。 清单 4 显示了完整的 pom.xml 文件。

列表 4.完整的 pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema‑instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven‑v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.dw</groupId>
  <artifactId>es‑demo</artifactId>
  <packaging>jar</packaging>
  <version>1.0‑SNAPSHOT</version>
  <name>es‑demo</name>
  <url>http://maven.apache.org</url>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven‑compiler‑plugin</artifactId>
        <configuration>
          <compilerVersion>1.8</compilerVersion>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark‑core</artifactId>
    <version>2.3</version>
</dependency>
<dependency>
    <groupId>com.sparkjava</groupId>
    <artifactId>spark‑template‑freemarker</artifactId>
    <version>2.3</version>
</dependency>
<dependency>
    <groupId>org.elasticsearch</groupId>
    <artifactId>elasticsearch</artifactId>
    <version>2.1.1</version>
</dependency>
  </dependencies>
</project>
  

清单 4 中的依赖项获得 Spark 框架核心、Spark Freemarker 模板支持和 Elasticsearch。 另请注意,我将 <source> 版本设置为 Spark 需要的 Java 8(因为它大量使用 lambdas)。 我不了解你,但我最近构建了许多 RESTful 应用程序,所以为了改变节奏,你会给应用程序一个更传统的提交和加载 UI。 在 Eclipse 中,在导航器中右键单击项目,然后选择 Configure > Convert to Maven Project,以便 Eclipse 可以解析 Maven 依赖项。 转到项目,右键单击,然后选择 Maven > Update Project

Java客户配置

弹性搜索的 Java 客户端功能强大;它可以旋转嵌入实例,并在必要时运行管理任务。但在这里,我专注于运行应用程序任务对你已经运行的节点。

当您使用弹性搜索运行 Java 应用程序时,有两种操作模式可用。该应用程序可以在弹性搜索组中扮演更主动或更被动的角色。在称为节点客户端的更活跃的情况下,应用程序实例会收到来自群集的请求,并确定哪个节点应像正常节点那样处理请求。(该应用程序甚至可以托管索引并服务请求。另一种模式(称为运输客户端)只需将任何请求转发到另一个弹性搜索节点,该节点将确定最终目的地。

获取运输客户端

对于演示应用,请选择(通过应用中发生的初始化.java)为运输客户端,并将弹性搜索处理保持在最低限度:

Client client = TransportClient.builder().build()
   .addTransportAddress(new InetSocketTransportAddress(InetAddress.getByName("localhost"), 9300));

如果您连接到弹性搜索集群,构建者可以接受多个地址。(在这种情况下,您只有一个本地托座节点。连接到端口 9300, 而不是 9200, 就像你从 curl 为 Rest Api 所做的那样。Java 客户端使用此特殊端口,使用 9200 将不起作用。(其他弹性搜索客户端 - Python 客户端为一个 - 确实使用 9200 访问 REST API。

当服务器启动时创建客户端,并在整个请求处理过程中使用它。Spark 使用"胡子"模板引擎的 Java 实现来渲染页面,Spark 定义了请求端点 - 但我不会对这些简单的使用案例发表评论。

该应用程序的索引页面展示了 Java 客户端的功能:

搜索结果的舍恩肖特

The UI:

  • 渲染现有歌曲列表
  • 提供添加歌曲的按钮
  • 使艺术家和歌词搜索成为可能
  • 返回结果,突出显示匹配项

搜索和处理结果

在清单 5 中,/ 的根 URL 被映射到 index.mustache 页面。

列表 5.基本搜索
Spark.get("/", (request, response) ‑> {
        SearchResponse searchResponse = 
            client.prepareSearch("music").setTypes("lyrics").execute().actionGet();
        SearchHit[] hits = searchResponse.getHits().getHits();

            Map<String, Object> attributes = new HashMap<>();
            attributes.put("songs", hits);

            return new ModelAndView(attributes, "index.mustache");
        }, new MustacheTemplateEngine());

列表 5 中有趣的部分开始于:

SearchResponse searchResponse = client.prepareSearch("music").setTypes("lyrics").execute().actionGet();

这一行显示了搜索 API 的简单使用。 使用 prepareSearch 方法指定一个索引(在本例中为 music),然后执行查询。 查询本质上说,“给我‘音乐’索引中的所有记录。” 此外,将文档类型设置为 lyrics,尽管在这种简单的情况下这不是必需的,因为索引只包含一种文档类型。 在更大的应用程序中,将需要该设置。 这个 API 调用类似于你之前看到的 curl -XGET "http://localhost:9200/music/lyrics/_search" 调用。

SearchResponse 对象包含有趣的功能——例如命中计数和分数——但现在,你只需要一个结果数组,你可以通过 searchResponse.getHits().getHits(); 获得这些结果。

最后,将结果数组添加到视图上下文并让 Mustache 渲染它。 Mustache 模板如下所示:

index.mustache
<html>
<body>
<form name="" action="/search">
  <input type="text" name="artist" placeholder="Artist"></input>
  <input type="text" name="query" placeholder="lyric"></input>
  <button type="submit">Search</button>
</form>
<button onclick="window.location='/add'">Add</button>
<ul>
{{#songs}}
  <li>{{id}} ‑ {{getSource.name}} ‑ {{getSource.year}}
    {{#getHighlightFields}} ‑
      {{#lyrics.getFragments}}
        {{#.}}{{{.}}}{{/.}}
      {{/lyrics.getFragments}}
    {{/getHighlightFields}}
  </li>
{{/songs}}
</ul>

</body>
</html>

Show more

高级查询和匹配突出显示

要支持更高级的查询和匹配突出显示,请使用 /search,如下所示:

清单 7. 搜索和突出显示
Spark.get("/search", (request, response) ‑> {
        SearchRequestBuilder srb = client.prepareSearch("music").setTypes("lyrics");

        String lyricParam = request.queryParams("query");
        QueryBuilder lyricQuery = null;
        if (lyricParam != null && lyricParam.trim().length() > 0){
            lyricQuery = QueryBuilders.matchQuery("lyrics", lyricParam);
        }
        String artistParam = request.queryParams("artist");
        QueryBuilder artistQuery = null;
        if (artistParam != null && artistParam.trim().length() > 0){
          artistQuery = QueryBuilders.matchQuery("artist", artistParam);
        }

        if (lyricQuery != null && artistQuery == null){
          srb.setQuery(lyricQuery).addHighlightedField("lyrics", 0, 0);
        } else if (lyricQuery == null && artistQuery != null){
          srb.setQuery(artistQuery);
        } else if (lyricQuery != null && artistQuery != null){
          srb.setQuery(QueryBuilders.andQuery(artistQuery, 
              lyricQuery)).addHighlightedField("lyrics", 0, 0);
        }

        SearchResponse searchResponse = srb.execute().actionGet();

SearchHit[] hits = searchResponse.getHits().getHits();

    Map<String, Object> attributes = new HashMap<>();
    attributes.put("songs", hits);

    return new ModelAndView(attributes, "index.mustache");
}, new MustacheTemplateEngine());

Show more

清单 7 中需要注意的第一个有趣的 API 是QueryBuilders.matchQuery("lyrics", lyricParam);。 这是您为“歌词”字段设置查询的地方。 另外值得注意的是QueryBuilders.andQuery(artistQuery, lyricQuery),它是如何在AND查询中将查询的artistlyrics部分连接在一起。

.addHighlightedField("lyrics", 0, 0); 调用告诉 Elasticsearch 在 lyrics 字段上生成搜索命中高亮结果。 第二个和第三个参数按顺序指定无限大小的片段和无限数量的片段。

呈现搜索结果时,将突出显示的结果放入 HTML。 Elasticsearch 可以生成有效的 HTML,该 HTML 使用 <em> 标签来突出显示匹配字符串出现的位置。

插入文档

让我们看看以编程方式插入索引。列出 8 显示添加处理。

列表 8.插入索引
Spark.post("/save", (request, response) ‑> {
      StringBuilder json = new StringBuilder("{");
      json.append("\"name\":\""+request.raw().getParameter("name")+"\",");
      json.append("\"artist\":\""+request.raw().getParameter("artist")+"\",");
      json.append("\"year\":"+request.raw().getParameter("year")+",");
      json.append("\"album\":\""+request.raw().getParameter("album")+"\",");
      json.append("\"lyrics\":\""+request.raw().getParameter("lyrics")+"\"}");

      IndexRequest indexRequest = new IndexRequest("music", "lyrics",
          UUID.randomUUID().toString());
      indexRequest.source(json.toString());
      IndexResponse esResponse = client.index(indexRequest).actionGet();

      Map<String, Object> attributes = new HashMap<>();
      return new ModelAndView(attributes, "index.mustache");
    }, new MustacheTemplateEngine());

在这里,你创建一个JSON字符串直接生成它与一个StringBuilder 。在制作应用程序中,使用像 Boon 或 Jackson 这样的库。

弹性搜索工作的部分是:

IndexRequest indexRequest = new IndexRequest("music", "lyrics", UUID.randomUUID().toString());

在这种情况下,使用 UUID 生成 ID。

结论

您正在快速通过命令行和 Java 应用程序使用 Elasticsearch。 您现在熟悉索引、查询、突出显示和多字段搜索。 Elasticsearch 在一个相对简单易用的软件包中为您提供了令人印象深刻的功能。 Elasticsearch 作为一个项目有一些有趣的结果,您可能也会感兴趣。 特别是,所谓的 ELK 堆栈——Elasticsearch、Logstash(用于日志管理)和 Kibana(用于报告/可视化)——正在获得关注。