> 本文最早发布于公众号极客军营,原文地址
学习ElasticSearch(后续简称ES)最好的方法就是多动手实践。在该系列教程中,我会使用“在线书店”这个小项目贯穿每一个章节。这个项目的背景很简单,每本书都有ISBN、标题(title)、描述(description)、价格(price)、作者(author)、出版社(publisher)和库存(stock)七个属性,将书的信息存储在ES中,用户可以根据书的某些属性信息搜索想要买的书。
为了能让大家对ES整体有一个直观的感受,本节我将带领大家快速浏览ES的各项能力。你刚看到某些概念可能会一头雾水,不过没有关系,本节涉及到的所有内容在后续的章节中都有详细介绍,大家对本节中出现的概念只需要先有个认识即可。
与ElasticSearch交互
要使用ES提供的服务,首先要知道如何给ES下达指令。ES服务器提供了一系列的RESTful API,可以通过HTTP协议将数据或指令以JSON序列化的形式发送给ES提供的API接口。
我们都知道,基于RESTful风格设计的网络应用使用GET、POST、PUT、DELETE四种HTTP动词对服务端资源进行操作,其中GET用于获取资源,POST用于新建资源,PUT用于更新资源,DELETE用于删除资源。
例如,通过下面的curl命令发送HTTP POST请求给ES,保存一本书的信息:
curl -X POST -H "Content-Type: application/json" -d \
'{"title": "零基础学Java", "description": "自学JAVA的入门图书", "price": 66.88}' \
http://localhost:9200/book_store/_doc?pretty
该条curl命令的执行结果如下图,任何编程语言或工具都可以通过HTTP协议调用ElasticSearch提供的RESTful API接口。
如果我们每次都通过一长串的curl命令调用ElasticSearch未免有点繁琐,Kibana为我们提供了更方便的操作方式。
在Kibana左侧导航栏中,有一个菜单叫做Dev Tools(开发工具),菜单位置见下图:
使用Dev Tools可以让我们以非常便利的方式操作ES,例如下图在Dev Tools中的操作与上述的curl命令完全相同——在ES中保存一本书的信息。
相比于使用curl命令, 在Kibana Dev Tools中可以省略对Content-Type的声明、ES服务器的IP地址和监听端口。因为这些信息Kibana都知道,会自动帮我们添加上,就不用我们额外明确指定了。此外在Kibana中输入的命令格式比较美观,点击箭头(Click to send request)就可以直接向ES发送命令,并在其右侧显示Response结果,使用起来非常方便。
后面所有的示例代码都是在Kibana中使用的,即都不需要明确指定HTTP Header信息和服务器IP地址。
基本概念
在介绍其它内容之前,我们先来仔细看看ES返回的Response:
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "E2lww30BAtxnt_qoQMJz",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"_seq_no" : 15,
"_primary_term" : 2
}
ES的Response也是JSON结构,从其result字段可以得知我们已经成功在ES中保存了一本书的信息。此外还有三个需要关注的字段:_index、_type和_id,它们分别表示索引(Index)、类型(Type)、文档ID(Document ID)。
- 文档(Document):数据读写的最基本单元,在本例中,一本书就对应一个文档。又比如在电商系统中,一件商品就对应一个文档。在人事系统中,一名员工也对应一个文档。每个文档都包含一个_id字段,作为文档的标识,它的值在同类型的文档中是唯一的。
- 索引(Index):索引是文档的容器,是一类相似文档的集合,是文档存储的逻辑单元。
- 类型(Type):在ES的早期版本,一个索引可以包含多个类型,一个类型下包含多个文档。但到了7.0版本后,一个索引只能有一个类型,且类型的名字必须是_doc。所以目前类型(Type)已经没有什么实质的意义。
我们将ElasticSearch与关系型数据库中的各个概念做一个粗略类比,以便帮助大家理解:
索引(Index)+类型(Type) | 表(Table) |
文档(Document) | 行(Row) |
文档字段(Document Field) | 列(Column) |
文档ID(Document ID) | 主键(Primary Key) |
我们使用索引book_store存储所有图书的信息,下面的Request表示在索引book_store的_doc类型下保存一本书的信息(类型也只能是_doc):
POST /book_store/_doc
{
"ISBN": "9787187651807",
"title": "零基础学Java",
"description": "零基础自学JAVA编程的入门图书",
"price": 66.88,
"author": "Poype",
"publisher": "极客军营",
"stock": 55
}
相比于数据库在执行insert操作前需要提前创建好对应的表,使用ES无需在写入文档前创建好对应的索引。如果写入文档时发现对应的索引不存在,ES会自动帮我们创建好那个索引。
由于在保存文档的时候没有提供唯一ID,ES会为该文档自动生成一个唯一ID作为_id字段的值。
获取文档数据
可以根据上述的文档ID获取一个文档的信息,使用HTTP GET方法向ES服务器发送查询Request,在Request中明确指定索引和文档ID。注意这里的文档ID是由ES随机生成的,所以每次的ID都不一样。
GET /book_store/_doc/FWmJw30BAtxnt_qoj8IE
ES收到该请求后会返回如下Response:
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj8IE",
"_version" : 1,
"_seq_no" : 16,
"_primary_term" : 2,
"found" : true,
"_source" : {
"ISBN" : "9787187651807",
"title" : "零基础学Java",
"description" : "零基础自学JAVA编程的入门图书",
"price" : 66.88,
"author" : "Poype",
"publisher" : "极客军营",
"stock" : 55
}
}
Response中包含了对该文档说明的元数据,如我们已经介绍过的_index、_type和_id三个字段。此外,found字段为true表示成功找到了对应的文档。_source字段下面是真正的文档数据。
如果用一个不存在的文档ID发起查询会收到下面的Response,其中found字段为false表示不存在指定ID的文档。
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj81E",
"found" : false
}
修改文档数据
可以根据文档ID修改已存在的文档。使用HTTP PUT方法发送修改文档的Request:
PUT /book_store/_doc/FWmJw30BAtxnt_qoj8IE
{
"ISBN": "9787187651807",
"title": "零基础学Java",
"description": "零基础自学JAVA编程的入门图书",
"price": 55.66,
"author": "Poype",
"publisher": "极客军营",
"stock": 55
}
我们将书的价格从原来的66.88修改为55.66,其它信息保持不变。ES收到该请求后返回如下结果:
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj8IE",
"_version" : 2,
"result" : "updated",
"_shards" : {
...
},
"_seq_no" : 17,
"_primary_term" : 2
}
result字段为updated表示文档已经更新成功。另外需要注意的是_version和_seq_no两个字段,它们的值都有所增加。这两个字段是用来做并发控制的,具体含义在后续章节会有详细介绍。
删除文档
使用HTTP DELETE方法可以删除一个文档,例如删除之前的文档;
DELETE /book_store/_doc/FWmJw30BAtxnt_qoj8IE
ES收到该请求后返回如下Response:
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj8IE",
"_version" : 8,
"result" : "deleted",
"_shards" : {
...
},
"_seq_no" : 23,
"_primary_term" : 2
}
result字段为deleted表示该文档已经被成功删除。
全文搜索
看到这里你也许会比较失望,我们目前介绍的这些CRUD操作任何数据库也都能支持的很好,那么ES的优势是什么呢?别急,下面我们就来看下,相比于传统数据库,ElasticSeach所擅长的领域——全文搜索(Full Text Search)。
为了更好的对其进行说明,我们再向ES多增加几本书的信息:
POST /book_store/_doc/
{
"ISBN": "9787111213826",
"title": "JAVA编程思想",
"description": "JAVA学习经典,殿堂级著作",
"price": 89.88,
"author": "Bruce Eckel",
"publisher": "机械工业出版社",
"stock": 231
}
POST /book_store/_doc/
{
"ISBN": "9085115891807",
"title": "Python编程,从入门到实践",
"description": "零基础学Python编程教程书籍",
"price": 82.30,
"author": "Eric Matthes",
"publisher": "人民邮电出版社",
"stock": 121
}
POST /book_store/_doc/
{
"ISBN": "9787115449153",
"title": "Elasticsearch实战",
"description": "Elasticsearch入门教程书籍",
"price": 79,
"author": "Radu Gheorghe",
"publisher": "人民邮电出版社",
"stock": 87
}
POST /book_store/_doc/
{
"ISBN": "9787115472588",
"title": "鸟哥的Linux私房菜",
"description": "适用Linux系统应用、开发及运维人员",
"price": 98.80,
"author": "鸟哥",
"publisher": "人民邮电出版社",
"stock": 77
}
例如一位顾客想搜索一本关于Java的书。如果是使用关系型数据库,你可能会执行类似下面的SQL帮助顾客找到想要的书;
select * from book_store where title like %Java%;
这条SQL的性能很差,需要扫描整个表。而且兼容性也不好,例如书名中包含java和JAVA的都无法被检索到。
换作ElasticSearch,可以使用如下的查询命令搜索Java相关的书籍:
GET /book_store/_doc/_search
{
"query": {
"match": {
"title": "java"
}
}
}
ElasticSearch收到该请求后返回如下结果:
{
"took" : 619,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 0.9278223,
"hits" : [
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "GGmpw30BAtxnt_qoXMJq",
"_score" : 0.9278223,
"_source" : {
"ISBN" : "9787111213826",
"title" : "JAVA编程思想",
"description" : "JAVA学习经典,殿堂级著作",
"price" : 89.88,
"author" : "Bruce Eckel",
"publisher" : "机械工业出版社",
"stock" : 231
}
},
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj8IE",
"_score" : 0.9278223,
"_source" : {
"ISBN" : "9787187651807",
"title" : "零基础学Java",
"description" : "零基础自学JAVA编程的入门图书",
"price" : 55.66,
"author" : "Poype",
"publisher" : "极客军营",
"stock" : 55
}
}
]
}
}
可以看到Response中已经包含了我们想要的结果。它的兼容性很好,在书名中包含Java或JAVA的文档都能被成功搜索到。它的性能也很高,即使是很大量的数据,ElasticSearch也可以很快为用户搜索到想要的数据。
更复杂的搜索
假如有一位顾客,他想买一本学习Java的书,但是他的预算有限,最多只能付70元。所以他想检索Java相关的书籍,且书的售价必须小于70元。类似的搜索场景在我们日常生活中很常见,可以通过如下命令实现这个搜索需求,其中的”lt“表示小于(less than):
GET /book_store/_doc/_search
{
"query": {
"bool": {
"filter": {
"range": {
"price": {
"lt": 70
}
}
},
"must": {
"match": {
"title": "java"
}
}
}
}
}
ES收到该请求后返回如下结果:
{
"took" : 320,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.9278223,
"hits" : [
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "FWmJw30BAtxnt_qoj8IE",
"_score" : 0.9278223,
"_source" : {
"ISBN" : "9787187651807",
"title" : "零基础学Java",
"description" : "零基础自学JAVA编程的入门图书",
"price" : 55.66,
"author" : "Poype",
"publisher" : "极客军营",
"stock" : 55
}
}
]
}
}
高亮匹配片段
假如又有一位顾客,他希望找一本经典著作。我们可以通过搜索书的描述(description)中含有“经典”二字的文档实现这个需求。
但该顾客不仅希望能获取到搜索结果,还希望能够一眼看出为什么返回给他结果符合他的要求。一般可以通过在搜索结果中把关键字加高亮以便让用户一眼就能识别到。
GET /book_store/_doc/_search
{
"query": {
"match": {
"description": "经典"
}
},
"highlight": {
"fields": {
"description": {}
}
}
}
我们在搜索命令中用highlight指定了高亮字段的名称,ElasticSearch收到该请求后返回如下结果:
{
...
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 2.8796844,
"hits" : [
{
"_index" : "book_store",
"_type" : "_doc",
"_id" : "GGmpw30BAtxnt_qoXMJq",
"_score" : 2.8796844,
"_source" : {
"ISBN" : "9787111213826",
"title" : "JAVA编程思想",
"description" : "JAVA学习经典,殿堂级著作",
"price" : 89.88,
"author" : "Bruce Eckel",
"publisher" : "机械工业出版社",
"stock" : 231
},
"highlight" : {
"description" : [
"JAVA学习<em>经</em><em>典</em>,殿堂级著作"
]
}
}
]
}
}
相比于之前的搜索结果,这次在Response中还包含highlight字段。其中使用html标签给匹配的关键字加高亮。但这个高亮加的不是很恰当,相比于“经典”,“经典”这种高亮方式才更加合理。大家这里先有个印象,等后面我们学了分词器,大家就知道该如何优化这个问题了。
小结
本章通过一个小示例,带大家快速浏览了ElasticSearch中的一些基本概念。我们了解了如何与ElasticSearch服务器交互、如何通过Kibana更方便的操作ElasticSearch、如何执行基本的CRUD操作、以及如何实现简单的搜索需求。下面的章节将正式为大家详细介绍ElasticSearch的各项技术细节。
喜欢本文的朋友,欢迎关注公众号极客军营,收看更多精彩内容