目录
Exploring Your Cluster(探索你的集群)
Elasticsearch是一个高度可扩展的开源的全文搜索和分析引擎,它可以接近实时的快速的存储、搜索、分析大量的数据
基本概念
对es有几个核心概念,开始学习之前理解这些概念将极大的帮助你理解后面的学习
Near Realtime(NRT)--接近实时
elasticsearch 是一个接近实时的搜索平台。通俗的讲就是,从你索引一个文档直到这个文档可被搜索会有一段时间的延迟(一般是1s)。个人理解就是你创建了个文档,会有将近1秒的延迟,别人才能搜索到这个文档
Cluster--集群
集群就是一个或者多个节点(服务器)的集合,承载着你的所有数据并在所有节点之间提供联合索引和搜索能力。一个集群被一个唯一的名字所标识,默认是elasticsearch。这个名字很重要,因为一个节点要加入集群,必须指定这个集群的名字。
确保在不同的环境不要使用相同的集群名字,否则节点将加入错误的集群。
注意,一个集群就有一个节点是合法的。除此之外,你也可以有多个相互独立的集群,每个用一个唯一的名字标识。
Node--节点
节点就是集群中的一个单个服务器,它存储数据,参与集群的索引和搜索。就像一个集群,节点也用一个名字来标识,默认是一个随机的传奇人物的名字,在节点启动的时候被指定。
一个节点可以配置加入一个集群通过集群的名字。默认,每个节点设置为加入名字为elasticsearch 的集群,这就意味着,如果你启动了多个节点在你的网络-假定他们能彼此发现对方-他们将自动的组织成一个名字为elasticsearch的集群。
在一个集群中,你可以拥有你想要数量的节点。除此之外,如果没有其他elastisearch节点当前运行在你的网络上,启动一个节点将默认形成一个新的单个节点的名字为elastisearch的集群
index--索引
一个索引就是文档的集合,这些文档有几分相似的特性。比如,你可以有一个索引存储客户数据,另一个索引存储产品分类,另一个索引存储订单信息。一个索引用一个名字来标识(必须是小写),这个名字被用来指定索引当针对其中的文档执行索引、搜索、更新、删除操作时。
在一个集群中,你可以定义任意数量的索引,只要你想。
es 的index 相当于mysql 的database (个人理解)
Type--类型
在索引中,你可以定义一个或者多个类型。type是index的逻辑分类,怎么定义完全取决于你。大体上,具有相同字段的文档的集合被定义为一个type。打个比方,你运行了一个博客平台,且所有数据存储在了一个index里。在这个index里,你可以给用户数据定义一个type,给博客数据定义一个type,给评论数据定义一个type。
type相当于mysql的 table
Document -- 文档
一个文档是可被索引的基本信息单元。例如,你可以有一个针对顾客的文档,还可以有个针对产品的文档,还可以有一个针对订单的文档。文档用json格式表示,此格式是普遍使用的一种数据交换格式。
在一个index/type中,你可以存储你想要数量的文档。注意,尽管一个文档物理上存在一个index中,一个文档事实上必须被指定到一个index中的type上。
document相当于myslq 的 row
shards & replicas -- 分片与副本
一个index可能存储大量的数据,且超过了一个节点硬件的容量限制。比如,一个index存储了10亿个文档,这将占据1TB的硬盘,这不适合存储在一个节点的硬盘上,或者在如此大的数据量的情况下,从单一节点请求未免太慢。
去解决这个问题,elastisearch细分index为多个碎片称之为shards。当创建索引时,你可以定义你想要的shards数量。每个shard都是功能齐全、独立的‘index',且可分布在集群的任何节点上。
分片重要主要有以下两点:
- 它允许你水平的拆分/扩展你的数据量
- 它允许你分发和并行的跨分片操作(可能在多个节点上),从而提高性能和吞吐量。
shard是如何分布的和文档是如何聚合回搜索请求点是完全由elasticsearch管理的,这对于你来说是透明的。
在网络/云环境,故障在任何时候都可能不期而至,这是非常有用和高度推荐的去部署一个故障转移机制,以防shard/node不知何故掉线或者消失因为某种原因。为此,elasticsearch允许你去给shards 部署一个或者多个拷贝被称为副本shards,或者副本简而言之。
副本重要的两点原因:
- 支持高可用当shard/node挂掉。基于这个原因,需要明确注意的是副本永远不要和他的shard分配到一个节点(node)上。
- 它可以扩展搜索量和吞吐量,因为搜索可以并行的在所有副本上执行
总结,每个index可以拆分成多个shards。一个index也可以被复制0次或者多次。一旦被复制,每个index将拥有主分片(primary)和副本分片(replica)。index在创建的时候可以指定shards和replicas的数量。index创建后,副本的数量可以在任何时候动态的调整,但是主分片(primary)的数量是不能调整的。
默认,每个index在es里被分配5个主分片和一个副本,这个的意思是如果你有至少两个节点在你的集群里,你的index将有5个主分片和另五个副本分片(1 个完整的副本),总共10个分片对每个index。
注意
每个es的shard 都是一个lucene index。在每个Luene index里,documents数量都有个上线。根据LUCENE-5843,这个限制是2,147,483,519(=integer.MAX_VALUE-128)。你可以监控shard size 使用_cat/shards api (之后会学习到)
Elasticsearch安装
Elasticsearch的运行需要java的支持,es需要至少java 7。当写作本节的时候,推荐使用Oracle JDK version 1.8.0_73。所以在安装es之前需要先查看java 的版本通过执行以下命令
- java -version
- echo $JAVA_HOME
es官网有很重类型的es安装文件,现在我们以tar文件为例说明(linux 平台)
下载tar 文件:
curl-L-O https://download.elastic.co/elasticsearch/release/org/elasticsearch/distribution/tar/elasticsearch/2.3.2/elasticsearch-2.3.2.tar.gz
解压
tar-xvf elasticsearch-2.3.2.tar.gz
进入解压后的 es目录
cd elasticsearch-2.3.2/bin
启动es
./elasticsearch 此时集群名字和节点名字都是默认的 如果想开启守护进程 可以加-d 参数
如果想启动es的时候自己指定集群名字和节点名字,可以加以下参数
./elasticsearch--cluster.name my_cluster_name --node.name my_node_name
其中my_cluster_name和my_node_name就是要新命名的集群名字和 节点名字
默认 es使用端口号9200来支持REST API的请求。
Exploring Your Cluster(探索你的集群)
我们可以使用curl或者其他任何可以进行http/rest请求的工具与es进行交互请求。
集群健康
curl'localhost:9200/_cat/health?v'
epoch timestamp cluster status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1462841542 08:52:22 elasticsearch yellow 1 1 139 139 0 0 139 0 - 50.0%
这个我自己搭建的一个cluster,可以看到我集群elasticsearch 状态为yellow,因为我只有一个节点,主分片和副本分片不能在一个node上,故副本分片没有被分配,因此状态是yellow,但是整个集群是完全可用的。
集群状态有三种,green、yellow、red。green的意思就是集群一切正常,功能完全可用。yellow的意思是所有数据可用但是一些副本没有被分配(集群完全可用),red的意思是一些数据是不可用的因为某种原因。注意就算一个集群的状态是red,它仍然是部分可用的(仍将从可用的分片上提供请求服务),但是需要尽快修复,因为已经丢失数据了。
查看集群列表
curl'localhost:9200/_cat/nodes?v'
列出所有index
curl'localhost:9200/_cat/indices?v'
创建索引
curl -XPUT 'localhost:9200/customer?pretty'
此命令代表创建一个名为customer的index
如果返回此值就代表创建成功
{
"acknowledged" : true
}
索引文档以及查询
现在我们加一些数据到我们的customer索引。我们之前提到过,为了索引一个文档,我们必须告诉es这个文档需要放在这个索引的那个type下面。
让我们索引一个简单的customer文档进入customer索引、external type 带一个id值1,像下面这样
我们json格式的文档 {"name":"John Doe"}
curl -XPOST 'localhost:9200/customer/external/1?pretty' -d '{"name":"John Doe"}'
返回值
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"created" : true
}
从上面我们可以看到,在索引customer、external type内我们成功创建了一个新的customer 文档。文档还有个内部id 1 ,这个是我们在索引文档的时候指定的。
有一点需要明确一下,在你索引一个文档到一个索引之前,没必须预先建一个索引。
在之前的例子中,es会自动创建customer索引如果不存在的话。
现在让我们取出刚才索引的文档
curl -XGET 'localhost:9200/customer/external/1?pretty'
返回值
{
"_index" : "customer",
"_type" : "external",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"name" : "John Doe"
}
}
这个返回结果没有什么特别的,除了一个字段,found,声明我们用id 1 找到了一个文档,和另外一个字段_source,返回了整个我们之前索引的
json结构的文档。
修改你的数据
elasticsearch 提供了接近实时的数据操作和搜索功能。默认的情况下,有一秒的延迟(刷新间隔)从你索引、更新、删除一个文档到这个操作的结果出现在你的搜索结果中。
这是一个重要的区别和其他平台像sql (当事物结束,数据立即可用)
索引或者替换文档
之前我们已经看到如何索引一个单独的文档,让我们重新看一下这个命令
curl -XPUT 'localhost:9200/customer/external/1?pretty' -d '{"name":"Jone Doe"}'
上面的命令将索引一个文档在索引customer、type external中,并且id值为1。如果我们再一次执行上面的命令用不同的(或者相同的)文档,
Elasticsearch 将替换(也就是重新索引reindex)一个新的文档在原来的存在的文档之上根据id 1.(version 加1)
当索引一个文档的时候 ,id 是可选的,如果没写id ,es 将随机生成一个id,下面的列子就是在未指定id的情况下索引一个文档
curl -XPOST 'localhost:9200/customer/external?pretty' -d '
{
"name": "Jane Doe"
}'
-XPOST 'localhost:9200/customer/external?pretty' -d '
{
"name": "Jane Doe"
}'
注意,上面我们用了post 动作而不是 put
更新文档
除了索引、替换文档,我们也可以更新文档。
不过请注意,es不是在底层就地更新数据。不论何时我们进行更新操作,es一次性的删除旧的文档数据,然后索引一个新的文档。
下面的例子是更新id为的1文档 name为Jane Doe
curl -XPOST 'localhost:9200/customer/external/1/_update?pretty' -d '
{
"doc":{"name":"Jane Doe"}
}'
下面的例子是更新id为的1文档 name值为Jane Doe,同时增加一个age字段
curl-XPOST'localhost:9200/customer/external/1/_update?pretty'-d'{ "doc": { "name": "Jane Doe", "age": 20 }}'
注意到,写和更新一次只能操作一个文档,将来es 可能会提供类似mysql一样的更新功能,就像 update where一样
删除文档
删除一个文档是相当简单的。这个例子展示了如何删除之前我们创建的customer 索引 id 为2的文档。
curl -XDELETE 'localhost:9200/customer/external/1?pretty'
-XDELETE 'localhost:9200/customer/external/1?pretty'
delete-by-query 插件可以删除所有满足条件的文档。
批量执行
除了能够索引、更新、删除一个文档,Elasticsearch 也提供了同时包含以上操作的批量操作功能通过使用_bulk api.
这个功能是非常重要的,它提供了一种有效的机制去尽可能快的执行多个操作以尽可能小的网络开销。
作为一个快速开始的例子,下面的调用索引了两个文档(ID 1 - John Doe 和 ID 2 - Jane Doe) 在一个批量操作中
curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
'
-XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
'
这个例子更新第一个文档(id 1),然后删除第二个文档(id 2) 在一个批量操作中
curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}
'
-XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}
'
注意上面的delete 操作,它没有明确的指出source doc,因为删除只需要知道id就可以了。
bulk api 执行所有操作连续的并且是按顺序执行的。如果一个操作以某种原因失败了,es 将会继续执行它之后的其他操作,
当bulk api 结束了,它会提供每个操作的状态(按照执行的顺序),你可以检查每个操作的执行是否成功。
搜索你的数据
查询api
让我们以一些简单的查询开始。有两个基本路径去执行查询:一个发送请求参数通过rest request uri,另一个是发送请求参数通过rest request body。request body方式让你可以定义可读性更强的 json 格式查询。
下面这个例子返回bank index下的所有文档
curl -X GET "localhost:9200/bank/_search?q=*&sort=account_number:asc&pretty"
让我们首先仔细分析这个查询。我们正在搜索bank 索引,参数q=*命令es 去匹配index里的所有文档。sort=account_number:asc参数指示结果按照字段account_number升序排序。pretty参数告诉es返回美观的json格式结果。
响应结果 (部分):
{
"took" : 63,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1000,
"max_score" : null,
"hits" : [ {
"_index" : "bank",
"_type" : "_doc",
"_id" : "0",
"sort": [0],
"_score" : null,
"_source" : {"account_number":0,"balance":16623,"firstname":"Bradshaw","lastname":"Mckenzie","age":29,"gender":"F","address":"244 Columbus Place","employer":"Euron","email":"bradshawmckenzie@euron.com","city":"Hobucken","state":"CO"}
}, {
"_index" : "bank",
"_type" : "_doc",
"_id" : "1",
"sort": [1],
"_score" : null,
"_source" : {"account_number":1,"balance":39225,"firstname":"Amber","lastname":"Duke","age":32,"gender":"M","address":"880 Holmes Lane","employer":"Pyrami","email":"amberduke@pyrami.com","city":"Brogan","state":"IL"}
}, ...
]
}
}
关于响应结果,我们看到以下部分:
- took - elasticsearch执行查询所耗费的毫秒数
- timed_out - 告诉我们查询是否超时
- _shards - 告诉我们多少分片被查询,以及成功或者失败的被查询的分片
- hits - 搜索的结果
- hits.total - 满足条件的结果总数
- hits.hits - 真实的数据集(默认十条)
下面是用request body形式的查询
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
"query": { "match_all": {} },
"sort": [
{ "account_number": "asc" }
]
}
'
介绍查询语言
Elasticsearch提供一种json形式的、特定领域的语言以供你使用去执行查询。这称为查询dsl 。这个查询语言非常全面,乍一看可能令人生畏,最好的学习方式是以几个例子开始。
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
"query": { "match_all": {} }
}
'
仔细分析这个语句,query部分告诉我们我们的query定义成什么样子,match_all部分仅仅是我们期望执行的查询类型。match_all query 仅仅是查询索引中的所有文档。
除了query参数,我们也可以传递其他参数去影响查询结果,我们传递size参数:
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
"query": { "match_all": {} },
"size": 1
}
'
注意如果size 没有传递,默认10
下面这个查询执行match_all查询 返回 10-19行记录
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
"query": { "match_all": {} },
"from": 10,
"size": 10
}
'
from参数(从0开始)指定开始的位置,size参数指定返回的结果集大小。这个在分页查询很有用,注意如果from未指定,默认0.
下面这个例子执行match_all查询,按照balance倒序排序,返回10条件记录(默认)
curl -X GET "localhost:9200/bank/_search" -H 'Content-Type: application/json' -d'
{
"query": { "match_all": {} },
"sort": { "balance": { "order": "desc" } }
}
'
执行查询
我们已经看了几个基本的查询参数,让我们去深入研究下dsl。让我们先看看返回的字段。默认所有字段都返回。如果我们不想返回所有的字段,我们有能力请求想要返回的字段。
下面这个例子展示了如何返回两个字段,account_number 和balance
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_all": {} },
"_source": ["account_number", "balance"]
}'
之前我们看到match_all的用法,下面我们看看match的匹配用法
下面这个例子,匹配account_number=20的记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "account_number": 20 } }
}'
下面这个例子返回address字段包含mill 的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill" } }
}'
下面这个例子返回address包含mill或者lane的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill lane" } }
}'
现在我们介绍布尔查询,布尔查询允许我们用布尔逻辑把小的查询组合成大的查询。
下面这个查询返回address既包含mill又包含lane 的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
下面这个查询返回address 包含mill或者lane的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
下面这个查询返回address既不包含mill又包含lane的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
我们可以同时在一个布尔查询中结合must should must_not 。
下面这个查询返回年龄等于40但是state不包含ID 的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}'
执行过滤器
在上一节,我们跳过了一个概念score(_score在查询结果中)。score是一个数字,相对的衡量记录和查询匹配度。score分数值越大,说明记录相关度越大,score分数越小,说明记录相关度越小。
下面这个例子用布尔查询返回 20000<= balance <= 30000 的所有记录
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}'
执行聚合
下面这个例子已state字段分组,返回top 10 (默认)states 按照count 倒序(默认)。
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state"
}
}
}
}'
类似sql
SELECT state, COUNT(*) FROM bank GROUP BY state ORDER BY COUNT(*) DESC
响应结果
"hits" : {
"total" : 1000,
"max_score" : 0.0,
"hits" : [ ]
},
"aggregations" : {
"group_by_state" : {
"buckets" : [ {
"key" : "al",
"doc_count" : 21
}, {
"key" : "tx",
"doc_count" : 17
}, {
"key" : "id",
"doc_count" : 15
}, {
"key" : "ma",
"doc_count" : 15
}, {
"key" : "md",
"doc_count" : 15
}, {
"key" : "pa",
"doc_count" : 15
}, {
"key" : "dc",
"doc_count" : 14
}, {
"key" : "me",
"doc_count" : 14
}, {
"key" : "mo",
"doc_count" : 14
}, {
"key" : "nd",
"doc_count" : 14
} ]
}
}
}
注意,我们设置了size=0 为了不展示search结果,因为我们只想看聚合结果。
在上面聚合的基础上,下面这个例子计算balance的平均值通过state
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}'
在上面dsl 聚合的基础上,让我们按照balance的平均值倒序排列
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state",
"order": {
"average_balance": "desc"
}
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}'
下面这个例子论证了 按照age年龄段分组,然后按照性别分组,最后获取balance的平均值
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_age": {
"range": {
"field": "age",
"ranges": [
{
"from": 20,
"to": 30
},
{
"from": 30,
"to": 40
},
{
"from": 40,
"to": 50
}
]
},
"aggs": {
"group_by_gender": {
"terms": {
"field": "gender"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}
}
}'