Elasticsearch:入门

ElasticSearch概念

ElasticSearch是一个实时分布式搜索和分析引擎,能够让你以前所未有的速度处理大数据成为可能。

它用于全文搜索、结构化搜索、分析以及将三者混用。并且可以在单机以及集群上运行。ES将三个功能整合成为一个一体化的、实时的应用,其对新用户的门槛很低。

为什么要用

大部分数据库在提取可用知识方面显得异常无能。尽管它们能够通过时间戳或者精确匹配做过滤。

  • 但是它们能够进行全文搜索,处理同义词和根据相关性给文档打分吗?
  • 它们能根据同一份数据生成分析和聚合的结果吗?
  • 它们在没有大量工作进程(线程)的情况下能做到对数据的实时处理吗?

概念

ElasticSearch是一个基于Apache Lucene的开源搜索引擎,Apache Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。

但是Lucene只是一个库,如果想要使用,则必须使用Java作为开发语言进行集成,而且Lucene非常复杂,需要深入了解检索的相关知识理解它是如何工作的。

ElasticSearch使用Java开发,并使用Lucene作为核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

ElasticSearch还可以这样描述:

  • 分布式的实时文件存储,每个字段都被索引并可被搜索。
  • 分布式的实时分析搜索引擎。
  • 可以扩展到上百台服务器,处理PB级结构化或非结构化数据。

面向文档

应用中的对象很少只是简单的键值列表,例如MySQL数据库那样,更多的时候它拥有复杂的数据结构,例如包含日期、地理位置、另一个对象或数组。而数据库是行列组成的表格,如果要将一个对象存储到MySQL当中,则就像是将一个丰富、信息表现力强的对象拆散了放入一个非常大的表格中。你不得不拆散对象以适应表模式(一列对应一个字段),然后在查询时再进行重建

Elasticsearch是面向文档的,意味着它可以存储整个对象或文档,然而它不仅仅时存储,还会索引每个文档的内容使之可以被搜索。再Elasticsearch中,你可以对文档(并非表结构)进行索引、搜索、排序、过滤。这种理解数据的方式与MySQL完全不同,也是Elasticsearch能够进行复杂的全文搜索的原因之一。

Elasticsearch使用JSON作为文档序列化格式。

集群和节点

节点node是一个运行着地Elasticsearch实例,集群cluster是一组具有相同cluster.name的节点集合,他们协同工作,共享数据并提供故障转移和扩展功能。

修改cluster.name可以通过修改config/目录下的elasticsearch.yml文件,然后重启Elasticsearch来实现。

与Elasticsearch交互

JavaAPI

Elasticsearch为Java用户提供了两种内置客户端

节点客户端

节点客户端以无数据节点身份加入集群,即自身不存储任何数据,但是它自导数据在集群中的具体位置,并且能够直接转发请求到对应的节点上。

传输客户端

这个更轻量的传输客户端能够发送请求到远端集群,它自己不加入集群,只是简单转发请求给集群中的节点。Java客户端都使用9300端口与集群交互,使用Elasticsearch传输协议(Elasticsearch transport protocol)。

HTTP

基于HTTP,以JSON为数据交互格式的Restful API。向Elastic发出的请求的组成部分与其他普通的HTTP请求是一样的。

1
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
  • VERB HTTP方法:get、post、put、head、delete
  • PROTOCOL:http或者https(当Elasticsearch前有https代理时可用)
  • HOST: Elasticsearch集群中的任何一个节点的主机名,如果是在本地的节点,那么就叫localhost
  • PORT: Elasticsearch HTTP服务所在的端口,默认为9200
  • PATH: API路径,PATH中可以包含多个组件,例如_cluster/stats
  • QUERY_STRING:一些可选的查询请求参数,?pretty可以使得请求返回更加美观易读的JSON数据
  • BODY:一个JSON格式的请求主体

一个完整的请求:

1
curl -XGET “localhost:9200/_count?pretty” -d '

异常

1
{"error":"Content-Type header [application/x-www-form-urlencoded] is not supported

则修改为:

1
curl -H "Content-Type: application/json" -XGET "localhost:9200/_count?pretty" -d '

异常2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"error": {
"root_cause": [{
"type": "mapper_parsing_exception",
"reason": "failed to parse"
}],
"type": "mapper_parsing_exception",
"reason": "failed to parse",
"caused_by": {
"type": "not_x_content_exception",
"reason": "Compressor detection can only be called on some xcontent bytes or compressed xcontent bytes"
}
},
"status": 400
}

垃圾windows命令行导致的,这边建议使用Ubuntu的WSL

一个put的示例

1
2
3
4
curl 
-H "Content-Type: application/json"
-XPUT "http://localhost:9200/megacorp/employee/1"
-d'{"first_name": "John","last_name": "Smith","age": 25,"about": "rock","interests": ["sports", "music"]}'

安装

elasticsearch安装后解压到相应目录即可

启动

进入bin目录后:elasticsearch-service start

关闭服务:elasticsearch-service stop

如果是第一次启动则需要:elasticsearch-service install

插件

安装插件Marvel,Marvel是Elasticsearch的管理和监控工具,包含一个sense的交互控制台,使用户方便地通过浏览器直接于Elasticsearch进行交互。

./bin/elasticsearch-plugin install elasticsearch/marvel/latest

禁用监控,通过命令关闭Marvel:echo 'marvel.agent.enabled: false' >> ./config/elasticsearch.yml

查看Marvel

Start

索引

在Elasticsearch中索引具有不同的涵义

  • 索引(名词):一个索引就像时传统关系数据库中的数据库,它时相关文档存储的地方
  • 索引(动词):索引一个文档,表示把一个文档存储到索引(名词n)中。以便它可以被检索或者查询,很像SQL的insert,但差别是如果文档已经存在,新的文档将覆盖旧的文档
  • 倒排索引:传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。而Elasticsearch和Lucene使用倒排索引的数据结构达到目的。

默认情况下,文档中的所有字段都会被索引(拥有一个倒排索引),只有这样他们才是可被搜索的。

实例

需求

让我们建立一个员工目录。假设我们刚好在Megacorp工作,这时人力资源部门出于某种目的需要让我们创建一个员工目录,这个目录用于促进人文关怀和用于实时协同工作,所以它有以下不同的需求:

  • 数据能够包含多个值的标签、数字和纯文本。
  • 检索任何员工的所有信息。
  • 支持结构化搜索,例如查找30岁以上的员工。
  • 支持简单的全文搜索和更复杂的短语(phrase)搜索
  • 高亮搜索结果中的关键字。
  • 能够利用图表管理分析这些数据。

索引员工文档

我们首先要做的是存储员工数据,每个文档代表一个员工。在Elasticsearch中存储数据的行为就叫做索引(indexing),不过在索引之前,我们需要明确数据应该存储在哪里。

在Elasticsearch中,文档归属于一种类型(type),而这些类型存在于索引(index)中,我们可以画一些简单的对比图来类比传统关系型数据库:

1
2
Relational DB -> Databases -> Tables -> Rows ->	Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields

Elasticsearch集群可以包含多个索引(indices)(数据库),每一个索引可以包含多个类型(types)(表),每一个类型包含多个文档(documents)(行),然后每个文档包含多个字段(Fields)(列)。

所以为了创建员工目录,我们将进行如下操作:

  • 为每个员工的文档(document)建立索引,每个文档包含了相应员工的所有信息。
  • 每个文档的类型为employee
  • employee类型归属于索引megacorp。
  • megacorp索引存储在Elasticsearch集群中。
1
2
3
4
5
6
7
curl -XPUT 'localhost:9200/megacorp/employee/1{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "rock",
"interests" : ["sports","music"]
}

搜索

做简单搜索,即只需要执行HTTP GET请求并指出文档的”地址”——索引、类型和ID即可。

简单搜索

搜索单个

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/1?pretty"

响应的数据为,数据存在于_source字段中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "rock",
"interests" : [
"sports",
"music"
]
}
}

搜索全部员工

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty"

使用_search代替原来的文档ID,默认下会返回前10个结果,响应内容的hits数组会包含我们的文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"took" : 117,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_score" : 1.0,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "rock",
"interests" : [
"sports",
"music"
]
}
}
]
}
}

query string

搜索first_name包含”Smith”的员工,我们将在命令行中使用轻量级的搜索方法,这种方法被称为查询字符串(query string)搜索,因为像我们传递URL参数一样去传递查询语句。

1
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?q=first_name:Jo&pretty"
  • 查询字符串搜索便于通过命令行完成特定(ad hoc)的搜索
  • 但查询字符串搜索存在局限性

DSL

Elasticsearch提供丰富且灵活的查询语言叫做DSL查询,允许你构建更加复杂、强大的查询。

DSL以JSON请求体的形式出现,可以这样表示之前对first_name的查询

1
2
3
4
5
6
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty" -d'
{"query":{
"match":{
"first_name": "John"
}
}}'

更复杂的搜索

让搜索更为复杂一些,依旧想要找到姓氏为“John”的员工,但是我们只想得到年龄大于30岁的员工,即为语句添加过滤器,使得我们高效率地执行员工结构化的搜索

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty" -d'
{
"query":{
"filtered":{
"filter":{
"range":{
"age":{"gt": 0}
}
},
"query":{
"match":{
"first_name":"John"
}
}
}
}
}'

gt:greater than。

全文搜索

检索所有喜欢"rock climbing"的员工

1
2
3
4
5
6
7
8
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty" -d'
{
"query":{
"match":{
"about": "rock climbing"
}
}
}

默认情况下,Elasticsearch根据结果相关性评分来对结果集进行排序,即文档与查询条件的匹配程序。而在上述搜索中,如果about中只是出现了rock也会出现在结果集合当中。

因此,该全文搜索不仅仅是匹配或不匹配,而且还有着相关性

短语搜索

不仅可以在字段中搜索单独的一个词,还可以匹配若干个词或者短语,例如想要查询同时包含rockclimbing(并且是相邻的)的记录。使用match_parse即可

1
2
3
4
5
6
7
8
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty" -d'
{
"query":{
"match_parse":{
"about": "rock climbing"
}
}
}

高亮搜索

在语句中增加hightlight参数,将会高亮匹配到的关键字。

1
2
3
4
5
6
7
8
9
10
11
12
13
curl -H "Content-Type: application/json" -XGET "http://localhost:9200/megacorp/employee/_search?pretty" -d'
{
"query":{
"match_parse":{
"about": "rock climbing"
}
},
"highlight":{
"fields":{
"about":{}
}
}
}

在返回语句中会增加一个新的部分highlight,内部会包含来自about字段中的文本,并且用<em>和</em>来标识匹配到的单词

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{
"took" : 111,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 0.2876821,
"hits" : [
{
"_index" : "megacorp",
"_type" : "employee",
"_id" : "1",
"_score" : 0.2876821,
"_source" : {
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "rock",
"interests" : [
"sports",
"music"
]
},
"highlight" : {
"about" : [
"<em>rock</em>"
]
}
}
]
}
}

参考