
2.1 往Elasticsearch中导入数据
搜索引擎不可能凭空产生结果!它需要数据作为输入,这样在被查询时才能输出结果。我们需要将数据转储到Elasticsearch,这是首先要做的准备工作。但在开始将数据存储到Elasticsearch之前,我们先了解一下在本章中将使用的示例应用。
对于本章中的示例,我们需要对需求和数据模型有一个基本的理解。假设我们正在建立一个在线书店;显然,我们不是构建整个应用——我们只对我们讨论的数据模型部分感兴趣。下面我们就讨论一下这个虚构书店的细节,这是使用Elasticsearch的前提条件。
2.1.1 在线书店
为了展示Elasticsearch的特性,本书将以一个虚构的在线技术书店为例。我们要做的就是创建一个图书库存清单,并编写一些查询来搜索这些图书。
注意 按照代码库中的说明对数据进行索引。
这个书店应用的数据模型非常简单。正如表2-1所示,我们有一个book作为实体,其中包含title、author等几个字段。我们无须创建复杂的实体来使事情复杂化,而是把重点放在获取Elasticsearch的实践经验上。
表2-1 book实体的数据模型

Elasticsearch以文档为单元存储数据,它期望文档以JSON格式呈现。由于需要将图书数据存储在Elasticsearch中,因此我们必须将实体建模为基于JSON的文档。如图2-1所示,我们可以用JSON文档来表示一本书。

图2-1 book实体的JSON文档表示
JSON格式使用简单的“名称-值”对来表示数据。在这个例子中,书名(名称)是Effective Java,其作者(值)是Joshua Bloch。我们可以向文档中添加额外的字段(包括嵌套对象),例如,添加prices作为一个嵌套对象。
现在我们已经对在线书店及其数据模型有了一个概念,是时候开始向Elasticsearch中添加一组图书来创建库存了。下面我们就来完成这项工作。
2.1.2 索引文档
为了使用服务器,需要将客户端的数据索引到Elasticsearch中。在现实世界中,有几种方法可以将数据导入Elasticsearch中:创建适配器从关系数据库中导入数据、从文件系统中提取数据,以及从实时数据源流式传输事件等。无论选择哪种方式作为数据源,都需要从客户端应用调用Elasticsearch的RESTful API来将数据加载到Elasticsearch中。
任何基于REST的客户端(cURL、Postman、高级REST客户端、JavaScript/NodeJS的HTTP模块、编程语言SDK等)都可以帮助我们通过API与Elasticsearch进行通信。幸运的是,Elastic有一款产品可以做到这一点(且不止于此),即Kibana。Kibana是一款具有丰富用户界面的Web应用,允许用户索引、查询、可视化和处理数据。这是本书的首选项,我们将在本书中广泛使用Kibana。
RESTful访问
与Elasticsearch的通信是通过基于JSON的RESTful API进行的。在当前的数字世界中,我们几乎不可能找到一种不支持访问RESTful服务的编程语言。事实上,将Elasticsearch设计为基于JSON的RESTful端点是一个明智的选择,因为它实现了与编程语言的解耦,并让它能被更多的人接受和使用。
1. 文档API
Elasticsearch的文档API可以用于创建、删除、更新和检索文档。这些API可以通过使用RESTful操作的HTTP请求进行访问。也就是说,要索引一个文档,需要在一个端点上使用HTTP的PUT或POST请求(后面更多会用到POST)。图2-2展示了HTTP PUT方法完整的URL格式语法。

图2-2 使用HTTP方法调用Elasticsearch URL端点
正如所见,URL由几个元素组成:
❏ 一个HTTP方法,如PUT、GET或POST;
❏ 服务器的主机名和端口;
❏ 索引名;
❏ 文档API的端点(_doc);
❏ 文档ID;
❏ 请求体。
Elasticsearch API接受一个JSON文档作为请求体,因此需要索引的书应该包含在这个请求里。例如,代码清单2-1将一个ID为1的图书文档索引到books索引中。
代码清单2-1 将图书文档索引到books索引中
PUT books/_doc/1 ←--- 索引是books,文档ID是1 { "title":"Effective Java", ←--- 请求体包含JSON数据 "author":"Joshua Bloch", "release_date":"2001-06-01", "amazon_rating":4.7, "best_seller":true, "prices": { "usd":9.95, "gbp":7.95, "eur":8.95 } } ←---
如果你是第一次看到这样的请求,可能感到有些不知所措,但请相信我——只要将它拆解开来,就会发现它其实并不难理解。第一行命令告诉Elasticsearch如何处理这个请求。我们要求将图书文档(附加在请求体中)存入一个名为books的索引中(可以将索引想象成数据库中的一张表,它是用来存储所有图书文档的集合)。这本书的主键由ID 1表示。
我们也可以使用cURL(cURL是一个命令行数据传输工具,通常用来与互联网上公开的各种服务进行通信)来执行相同的请求,将图书文档持久化到Elasticsearch中。
2. 使用cURL
我们也可以使用cURL与Elasticsearch进行交互,并将图书文档索引到books索引中。注意,在本书中我更倾向于使用Kibana而不是cURL,因此所有的代码都以可在Kibana代码编辑器中执行的脚本形式呈现。完整的cURL命令如图2-3所示(Kibana隐藏了完整的URL)。

图2-3 使用cURL调用Elasticsearch URL端点
正如所见,cURL要求提供请求参数,如内容类型、文档等。因为cURL命令是终端命令(命令行调用),所以准备请求的过程较为烦琐,有时还容易出错。
幸运的是,Kibana允许省略服务器的详细信息、内容类型和其他参数,因此调用看起来就像图2-4中所示的那样。正如我所提到的,在本书中我们会始终使用Kibana与Elasticsearch进行交互。

图2-4 从cURL命令过渡到Kibana的请求命令
是时候开始索引我们的第一个文档了。
2.1.3 索引第一个文档
要使用Kibana索引文档,需要进入Kibana的Dev Tools应用来执行查询。我们会在Dev Tools页面上花很多时间,所以在本书结束时,你会对它非常熟悉!
假设Elasticsearch和Kibana都已经在你的本地机器上运行。在浏览器中访问http://localhost:5601,打开Kibana仪表板,左上角是一个主菜单,包含链接和子链接。为了完成后面的实验,选择“Management”→“Dev Tools”,如图2-5所示。

图2-5 访问Kibana的Dev Tools的导航页面
由于这可能是你第一次访问Dev Tools页面(见图2-6),因此我先解释一下各个组件。当导航到此页面时,会打开Dev Tools代码编辑器,显示两个窗格。可以在左侧窗格中使用Elasticsearch提供的特殊语法来编写代码。编写完代码片段后,可以单击页面中间的运行按钮(一个向右的三角形图标)来调用代码片段中的URL。

图2-6 Kibana的Dev Tools代码编辑器
在Kibana准备就绪之前,必须将其连接到一个Elasticsearch实例(实例的详细信息在Kibana的配置文件中定义)。Kibana会根据服务器的详细信息将代码片段包装在合适的Elasticsearch URL中,并将其发送给服务器运行。
为了将文档索引到Elasticsearch中,我们来创建一个代码片段(如代码清单2-1所示)。图2-7展示了索引请求和响应。

图2-7 在Kibana中索引文档(左),以及Elasticsearch的响应(右)
当代码准备好后,单击运行按钮。Kibana会将这个请求发送给Elasticsearch服务器。在收到请求后,Elasticsearch会对其进行处理,然后存储消息,并将响应发送回客户端(Kibana)。你可以在代码编辑器的右侧窗格中查看响应。
响应是一个JSON文档。在图2-7中,result属性表明文档已成功创建。同时,你还应该看到一个表明请求执行成功的200 HTTP状态码。响应中还包含一些附加的元数据(如索引、ID和文档版本),这些信息直接明了。接下来的几章中我将详细讨论请求和响应的组成部分,这里我先大致地解释一下Elasticsearch的请求和响应流程。整个过程如图2-8所示。

图2-8 Elasticsearch的请求和响应流程的概览
Elasticsearch的请求和响应流程包含以下步骤。
(1)Kibana向Elasticsearch服务器发送请求,并提供必要的输入参数。
(2)服务器接收到请求后,将进行以下操作:
- 分析文档数据,并将其存储在倒排索引(一种高性能的数据结构,是搜索引擎的核心和灵魂)中,以便可以更快地进行访问;
- 创建一个新索引(注意,我们没有预先创建索引)并存储文档;
- 创建所需的映射和数据模型;
- 将响应发送回客户端。
(3)Kibana收到响应,并将其显示在图2-6中的右侧窗格中,以供查看。
我们已经为Elasticsearch索引了第一个文档!索引一个文档类似于在关系数据库中插入一条记录。
请求的组成部分
发送的请求(PUT books/_doc/1)可以拆解为5个部分,我们在这里快速过一遍(如果你愿意,可以跳过本节,等阅读完本章的剩余内容后再回来查看)。
❏ PUT方法——PUT是一个HTTP动词(也称为方法),表明我们正在向服务器发送一个请求来创建资源(在这个示例中是一个图书文档)。Elasticsearch使用HTTP协议进行RESTful API调用,因此需要在请求URL语法中使用PUT、POST、GET、DELETE及其他标准的HTTP方法。
❏ books索引——URL中的books部分被称为索引:一个用于收集所有图书文档的桶。它类似于关系数据库中的表。只有图书文档被存储在这个books索引中(虽然我们希望books索引中只包含图书文档,但从理论上讲,没有什么可以阻止我们索引其他类型——我们有责任不将类型混在一起)。
❏ _doc端点——端点是路径里的一个固定部分,与正在进行的操作相关联。在Elasticsearch的早期版本(版本低于7.0)中,_doc的位置曾被文档的映射类型所占据。随着映射类型被废弃,_doc作为URL里的一个通用的固定端点路径取代了它们(更多信息参见下面的“文档类型和_doc端点”)。
❏ 文档ID——URL中的数字1代表文档的ID。它类似于数据库中记录的主键。我们使用这个标识符来检索文档。
❏ 请求体——请求体是图书数据的JSON表示形式。Elasticsearch期望所有数据都以JSON格式的文档发送。它还支持以JSON的嵌套对象格式发送的嵌套对象。
文档类型和_doc端点
在Elasticsearch 7.x版本之前,一个索引可以包含多种类型的实体(例如,books索引不仅可以包含书,还可以包含书评、图书销售、书店等)。在单个索引中包含所有类型的文档会导致复杂化。字段映射会在多个类型之间共享,从而导致错误和数据稀疏。为了避免类型及其管理的问题,Elastic公司决定移除文档类型。
在早期版本中,带有类型的调用URL看起来像这样:<index_name>/<type>/<id>(例如,books/book/1)。在7.x版本中,文档类型被弃用。现在,一个索引应该只有一种类型:_doc是URL中固定的端点。我们将在第5章中专门讨论有关类型移除的内容。
简而言之,我们使用HTTP的PUT方法将一个ID为1的文档索引到Elasticsearch的books索引中。注意,当索引第一个文档时,我们并没有创建数据模式。我们通过调用API来索引文档,但Elasticsearch从未要求在索引数据之前定义数据模式。
与关系数据库不同,Elasticsearch 并不要求事先创建数据模式,这被称为无模式(schema- less)。Elasticsearch可以从索引的第一个文档中推导出数据模式,并创建出索引(确切地说是books索引)。
这是因为Elasticsearch并不希望在开发过程给使用者制造麻烦。尽管如此,我们在生产环境中还是必须遵循最佳实践,预先创建好自己的数据模式。关于这一点,本书后续会有更多的讨论。
在本节中,我们成功地索引了一个文档。让我们遵循相同的流程来索引更多文档。
2.1.4 索引更多文档
为了让接下来的示例能够顺利运行,我们需要索引更多文档。转到Kibana的代码编辑器,并为另外两个文档编写代码清单2-2所示的代码。
代码清单2-2 再索引两个图书文档
PUT books/_doc/2 ←--- 第二个图书文档 { "title":"Core Java Volume I - Fundamentals", "author":"Cay S. Horstmann", "release_date":"2018-08-27", "amazon_rating":4.8, "best_seller":true, "prices": { "usd":19.95, "gbp":17.95, "eur":18.95 } } PUT books/_doc/3 ←--- 第三个图书文档 { "title":"Java: A Beginner’s Guide", "author":"Herbert Schildt", "release_date":"2018-11-20", "amazon_rating":4.2, "best_seller":true, "prices": { "usd":19.99, "gbp":19.99, "eur":19.99 } }
执行代码清单2-2中的请求,将两个图书文档索引到books索引中。现在我们已经索引了一些文档,让我们来了解一下如何检索或搜索它们。