是什么
Elasticsearch 使用 Java 开发并使用 Lucene 作为其核心来实现所有索引和搜索的功能,它的目的是通过简单的 RESTful API 来隐藏 Lucene 的复杂性,从而让全文搜索变得简单。
特点:
- 全文搜索
- 分布式的实时文件存储,每个字段都被索引并可被搜索
- 分布式的实时分析搜索引擎
- 可以扩展到上百台服务器,处理PB级结构化或非结构化数据
- 可以通过简单的 RESTful API 与各种开发语言交互
- 上手简单,对初学者隐藏了复杂的搜索引擎理论
- 配置灵活
安装
Elasticsearch 在安装之前要安装 Java ,然后从官网中下载 Elasticsearch 的压缩包,在本地的自定义的目录下进行解压即可。另外有一个 Elasticsearch 的管理和监控工具 Marvel ,它包含了一个叫做 Sense 的交互式控制台,使用户方便的通过浏览器直接与 Elasticsearch 进行交互。这个不是必须要安装的。
与Elasticsearch交互
-
如果是用 Java 开发语言,那可以直接使用 Java API,Elasticsearch 为 Java 用户提供了两种内置客户端:节点客户端和传输客户端
-
以 JSON 为数据交互格式的 RESTful API ,所有程序语言都可以使用 RESTful API ,通过默认的 9200 端口的与Elasticsearch 进行通信。向 Elasticsearch 发出的请求的组成部分与其它普通的HTTP请求是一样的:
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路径(例如_count将返回集群中文档的数量),PATH可以包含多个组件,例如_cluster/stats或者_nodes/stats/jvm QUERY_STRING 一些可选的查询请求参数,例如?pretty参数将使请求返回更加美观易读的JSON数据 BODY 一个JSON格式的请求主体(如果请求需要的话)举例:
curl -XGET 'http://localhost:9200/_count?pretty' -d ' { "query": { "match_all": {} } } '
面向文档
Elasticsearch 是面向文档 (document oriented) 的,这意味着它可以存储整个对象或文档 (document) 。然而它不仅仅是存储,还会索引 (index) 每个文档的内容使之可以被搜索。在 Elasticsearch 中,你可以对文档(而非成行成列的数据)进行索引、搜索、排序、过滤。这种理解数据的方式与以往完全不同,这也是 Elasticsearch 能够执行复杂的全文搜索的原因之一。
ELasticsearch 使用 JSON ,作为文档序列化格式。JSON 现在已经被大多语言所支持,而且已经成为NoSQL领域的标准格式。它简洁、简单且容易阅读。
JSON 文档来表示一个用户对象:
{
"email": "john@smith.com",
"first_name": "John",
"last_name": "Smith",
"info": {
"bio": "Eco-warrior and defender of the weak",
"age": 25,
"interests": [ "dolphins", "whales" ]
},
"join_date": "2014/05/01"
}
索引
在 Elasticsearch 中,文档归属于一种类型 (type) ,而这些类型存在于索引 (index) 中,我们可以画一些简单的对比图来类比传统关系型数据库:
Relational DB -> Databases -> Tables -> Rows -> Columns
Elasticsearch -> Indices -> Types -> Documents -> Fields
Elasticsearch 集群可以包含多个索引 (indices) (数据库),每一个索引可以包含多个类型 (types)(表),每一个类型包含多个文档 (documents)(行),然后每个文档包含多个字段 (Fields)(列)。
“索引”的含义:
索引 (index) 这个词在 Elasticsearch 中有着不同的含义,所以有必要在此做一下区分:
索引(名词): 如上文所述,一个索引 (index) 就像是传统关系数据库中的数据库,它是相关文档存储的地方,index的复数是 indices 或 indexes。
索引(动词): “索引一个文档”表示把一个文档存储到索引(名词)里,以便它可以被检索或者查询。这很像SQL中的 INSERT 关键字,差别是,如果文档已经存在,新的文档将覆盖旧的文档。
倒排索引:传统数据库为特定列增加一个索引,例如 B-Tree 索引来加速检索。Elasticsearch 和 Lucene 使用一种叫做倒排索引 (inverted index) 的数据结构来达到相同目的。
增加一个员工信息具体操作案例:
PUT /megacorp/employee/1
{
"first_name" : "John",
"last_name" : "Smith",
"age" : 25,
"about" : "I love to go rock climbing",
"interests": [ "sports", "music" ]
}
其中 megacorp 是索引名,employee 是类型名, 1 是员工 ID。
然后可以继续试着添加若干个员工:
PUT /megacorp/employee/2
{
"first_name" : "Jane",
"last_name" : "Smith",
"age" : 32,
"about" : "I like to collect rock albums",
"interests": [ "music" ]
}
PUT /megacorp/employee/3
{
"first_name" : "Douglas",
"last_name" : "Fir",
"age" : 35,
"about": "I like to build cabinets",
"interests": [ "forestry" ]
}
搜索
-
执行 HTTP 的 GET 请求并指出文档的“地址”——索引、类型和 ID 既可:
GET /megacorp/employee/1根据这三部分信息,我们就可以返回原始 JSON 文档,响应的内容中包含一些文档的元信息。John Smith 的原始 JSON 文档包含在 _source 字段中。
{ "_index" : "megacorp", "_type" : "employee", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "first_name" : "John", "last_name" : "Smith", "age" : 25, "about" : "I love to go rock climbing", "interests": [ "sports", "music" ] } }通过 HTTP 方法 GET 来检索文档,同样的,我们可以使用 DELETE 方法删除文档,使用 HEAD 方法检查某文档是否存在。如果想更新已存在的文档,我们只需再 PUT 一次。
-
尝试一个最简单的搜索全部员工的请求,与上一个命令不同指出是在结尾使用关键字 _search 来取代原来的文档 ID 。响应内容的 hits 数组中包含了我们所有的三个文档。默认情况下搜索会返回前 10 个结果:
GET /megacorp/employee/_search { "took": 6, "timed_out": false, "_shards": { ... }, "hits": { "total": 3, "max_score": 1, "hits": [ { "_index": "megacorp", "_type": "employee", "_id": "3", "_score": 1, "_source": { "first_name": "Douglas", "last_name": "Fir", "age": 35, "about": "I like to build cabinets", "interests": [ "forestry" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "1", "_score": 1, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } }, { "_index": "megacorp", "_type": "employee", "_id": "2", "_score": 1, "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } } ] } }如果要搜索姓氏中包含 “Smith” 的员工。可以在命令行中使用轻量级的搜索方法。这种方法常被称作查询字符串 (query string) 搜索,因为我们像传递 URL 参数一样去传递查询语句:
GET /megacorp/employee/_search?q=last_name:Smith请求中依旧使用 _search 关键字,然后将查询语句传递给参数 q= 。这样就可以得到所有姓氏为 Smith 的结果。
-
查询字符串搜索便于通过命令行完成特定的搜索,但是它也有局限性(参阅简单搜索章节)。Elasticsearch 提供丰富且灵活的查询语言叫做 DS L查询 (Query DSL) ,它允许你构建更加复杂、强大的查询。
DSL (Domain Specific Language 特定领域语言) 以 JSON 请求体的形式出现。我们可以这样表示之前关于 Smith 的查询:
GET /megacorp/employee/_search { "query" : { "match" : { "last_name" : "Smith" } } }返回的结果和之前查询的结果一样,但是这里不使用查询字符串 (query string) 做为参数,而是使用请求体代替。这个请求体使用 JSON 表示,其中使用了 match 语句。
-
如果需要结合更复杂条件进行搜索,则需要添加过滤器,如下:
GET /megacorp/employee/_search { "query" : { "filtered" : { "filter" : { "range" : { "age" : { "gt" : 30 } <1> } }, "query" : { "match" : { "last_name" : "smith" <2> } } } } }<1> 这部分查询属于区间过滤器 (range filter) ,它用于查找所有年龄大于 30 岁的数据—— gt 为 "greater than" 的缩写。
<2> 这部分查询与之前的 match 语句(query)一致。
结果中只显示了一个员工:
{ ... "hits": { "total": 1, "max_score": 0.30685282, "hits": [ { ... "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } } ] } } -
到目前为止搜索都很简单:搜索特定的名字,通过年龄筛选。接下来可以使用一种更高级的搜索,全文搜索——一种传统数据库很难实现的功能。搜索所有喜欢 “rock climbing” 的员工:
GET /megacorp/employee/_search { "query" : { "match" : { "about" : "rock climbing" } } }得到两个匹配的结果文档:
{ ... "hits": { "total": 2, "max_score": 0.16273327, "hits": [ { ... "_score": 0.16273327, <1> "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] } }, { ... "_score": 0.016878016, <2> "_source": { "first_name": "Jane", "last_name": "Smith", "age": 32, "about": "I like to collect rock albums", "interests": [ "music" ] } } ] } }默认情况下,Elasticsearch 根据结果相关性评分来对结果集进行排序,所谓的“结果相关性评分”就是文档与查询条件的匹配程度。排名第一的 John Smith 的 about 字段明确的写到 “rock climbing” 。但是为什么 Jane Smith 也会出现在结果里呢?原因是 “rock” 在她的 abuot 字段中被提及了。因为只有 “rock” 被提及而 “climbing” 没有,所以她的 _score 要低于 John 。 这个例子很好的解释了 Elasticsearch 如何在各种文本字段中进行全文搜索,并且返回相关性最大的结果集。相关性 (relevance) 的概念在 Elasticsearch 中非常重要,而这个概念在传统关系型数据库中是不可想象的,因为传统数据库对记录的查询只有匹配或者不匹配。
-
目前我们可以在字段中搜索单独的一个词,但是有时候你想要确切的匹配若干个单词或者短语 (phrases) 。例如我们想要查询同时包含 "rock" 和 "climbing" (并且是相邻的)的员工记录。 要做到这个,我们只要将 match 查询变更为 match_phrase 查询即可:
GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } } }毫无疑问,该查询返回John Smith的文档。
-
很多应用喜欢从每个搜索结果中高亮 (highlight) 匹配到的关键字。在 Elasticsearch 中高亮片段是非常容易的。在语句上增加highlight参数:
GET /megacorp/employee/_search { "query" : { "match_phrase" : { "about" : "rock climbing" } }, "highlight": { "fields" : { "about" : {} } } }最后运行会得到相同的结果,但是在返回结果中会有一个新的部分叫做 highlight ,这里包含了来自 about 字段中的文本。
{ ... "hits": { "total": 1, "max_score": 0.23013961, "hits": [ { ... "_score": 0.23013961, "_source": { "first_name": "John", "last_name": "Smith", "age": 25, "about": "I love to go rock climbing", "interests": [ "sports", "music" ] }, "highlight": { "about": [ "I love to go <em>rock</em> <em>climbing</em>" <1> ] } }
聚合
Elasticsearch 有一个功能叫做聚合 (aggregations) ,它允许你在数据上生成复杂的分析统计。它很像 SQL 中的 GROUP BY 但是功能更强大。举例找所有职员中最大的共同点(兴趣爱好)是什么:
GET /megacorp/employee/_search
{
"aggs": {
"all_interests": {
"terms": { "field": "interests" }
}
}
}
运行结果:
{
...
"hits": { ... },
"aggregations": {
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "forestry",
"doc_count": 1
},
{
"key": "sports",
"doc_count": 1
}
]
}
}
}
我们可以看到两个职员对音乐有兴趣,一个喜欢林学,一个喜欢运动。这些数据并没有被预先计算好,它们是实时的从匹配查询语句的文档中动态计算生成的。如果我们想知道所有姓 "Smith" 的人最大的共同点(兴趣爱好),我们只需要增加合适的语句既可:
GET /megacorp/employee/_search
{
"query": {
"match": {
"last_name": "smith"
}
},
"aggs": {
"all_interests": {
"terms": {
"field": "interests"
}
}
}
}
all_interests 聚合已经变成只包含和查询语句相匹配的文档了:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2
},
{
"key": "sports",
"doc_count": 1
}
]
}
聚合也允许分级汇总。例如让我们统计每种兴趣下职员的平均年龄:
GET /megacorp/employee/_search
{
"aggs" : {
"all_interests" : {
"terms" : { "field" : "interests" },
"aggs" : {
"avg_age" : {
"avg" : { "field" : "age" }
}
}
}
}
}
虽然这次返回的聚合结果有些复杂,但任然很容易理解:
...
"all_interests": {
"buckets": [
{
"key": "music",
"doc_count": 2,
"avg_age": {
"value": 28.5
}
},
{
"key": "forestry",
"doc_count": 1,
"avg_age": {
"value": 35
}
},
{
"key": "sports",
"doc_count": 1,
"avg_age": {
"value": 25
}
}
]
}
可以看出通过这个特性可以完成相当复杂的聚合工作,你可以处理任何类型的数据。
分布式的特性
Elasticsearch 在分布式概念上做了很大程度上的透明化,用户不需要知道任何关于分布式系统、分片、集群发现或者其他大量的分布式概念。既可以运行在你的笔记本上,也可以运行在拥有100个节点的集群上,其工作方式是一样的。
Elasticsearch 致力于隐藏分布式系统的复杂性。以下这些操作都是在底层自动完成的:
将你的文档分区到不同的容器或者分片 (shards) 中,它们可以存在于一个或多个节点中。
将分片均匀的分配到各个节点,对索引和搜索做负载均衡。
冗余每一个分片,防止硬件故障造成的数据丢失。
将集群中任意一个节点上的请求路由到相应数据所在的节点。
无论是增加节点,还是移除节点,分片都可以做到无缝的扩展和迁移。