Elasticsearch 是一个接近实时的搜索平台,从索引一个文档到这个文档被搜索到有一个很小的延迟(通常是1s)
一个集群就是由一个节点组织在一起,他们共同持有你全部的数据,并且一起提供索引与搜索功能。一个集群由一个唯一的名字标识,一个节点通常指定某个集群的名字来加入这个集群。
一个集群中只包含一个节点是合法的。另外你可以拥有多个集群,以名字划分
一个节点直观点说就是一个集群中的一台服务器,作为集群的一部分,它存储你的数据,参与集群的索引和搜索功能。它也是用一个名字来标识的。默认情况下,这个名字是随机的Marvel角色的名字,这个名字会在节点启动的时候被分配。
一个节点可以通过配置集群名称的方式加入一个指定的集群,默认情况下,每个节点都会被加入名叫”elasticsearch”的集群当中。
一个索引就是一个拥有相似特征的文档集合。比如说学生数据索引,订单数据索引。一个索引由一个名字来标识(必须全是小写字母),并且当我们对这个索引中的文档进行搜索,更新,删除的时候都会用到这个名字。在一个集群中,你能够创建任意多的索引。
在一个索引中,你可以指定多个类型。一个类型是索引的一个逻辑上的分类,通常会以一组相同字段的文档定义一个类型。比如我们要设计一个博客系统,那么这个博客系统就是一个索引,博客系统中的用户数据定义一个类型,博客数据可以定义一个类型。
一个文档是一个可以被索引的基础信息单元,比如我们可以拥有一个客户文档,产品文档,订单文档,文档以json的形式存储。在一个index/type中可以有多个文档,但是注意,一个文档物理上存在于一个索引中,但是文档必须被索引/赋予一个索引的type
一个索引可以存储超出单个节点硬件限制的大量数据。比如一个具有10亿文档的索引占据1TB的磁盘空间,但是任何一个节点都不能提供这么大的存储空间,为了解决这个问题,Elasticsearch提供了将索引划分成多个分片的能力。当创建索引的时候,可以指定创建多少分片数量。每个分片本身也是一个具有功能完善并且独立的“索引”。
分片之所以重要,主要原因有:
在网络环境里,某个分片完全有可能处于离线活着消失的状态,故障转移机制是非常重要的,因此Elasticsearch 允许我们创建分片的一份活着多份拷贝,这叫复制。
复制重要的原因:
默认情况下,Es(Elasticsearch的简称)为我们每个索引创建5个主分片和一个复制,这就意味着集群中至少有两个节点,索引会将有5个主分片和另外5个复制分片,也就是一个索引一共有10个分片。
ps 这里索引,类型,文档,分片复制的概念可能有点模糊的同学,我可以举一个相似的��
ES —- > Mysql
Index —- > database
Type —- > table
Document —- > table.rows
Shards —- > sharding
Replicas —- > master and slave仅仅是做概念上的理解帮助,两者并不是一种东西
Elasticsearch 需要 java7以及以上版本
用下面的命令下载 Elasticsearch 1.4.2 tar 包
curl -L -O https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-1.4.2.tar.gz
将其解压并且启动单个节点的集群
tar -xvf elasticsearch-1.4.2.tar.gz
cd elasticsearch-1.4.2/bin
./elasticsearch
我们可以覆盖集群或者节点的名字启动
./elasticsearch --cluster.name my_cluster_name --node.name my_node_name
默认情况下,Elasticsearch使用9200来提供REST API 的访问,如果有必要,这个端口是可以被配置的
其二进制文件也可以从 官网
现在我们已经有一个正常运行的节点(和集群),下一步就是要去理解如果与其通信。幸运的是,ES提供了非常全面强大的REST API, 利用这个REST API 可以与集群交互,下面我们搞点事情。
我们使用Curl或者任何一个可以创建HTTP/REST调用的工具来使用该功能
命令
GET localhost:9200/_cat/health?v
响应
epoch timestamp cluster status node.total node.data shards pri relo init unassign
1489252285 02:11:25 elasticsearch green 1 1 0 0 0 0 0
下面默认此格式(上一个是请求,下一个是响应) ⚠️
我们查到集群名字是”elasticsearch”,状态green
上面的响应中,可以看出 一个集群,一个节点,没有分片,由于我们默认配置,所以es使用多播
的发现其他节点。
GET localhost:9200/_cat/nodes?v
host ip heap.percent ram.percent load node.role master name
localhost 127.0.0.1 10 90 0.15 d * Vulture
GET localhost:9200/_cat/indices?v
health index pri rep docs.count docs.deleted store.size pri.store.size
没有索引
PUT localhost:9200/test?pretty
{
"acknowledged" : true
}
pretty 美丽的用json打印返回
GET localhost:9200/_cat/indices?v
health status index pri rep docs.count docs.deleted store.size pri.store.size
yellow open test 5 1 0 0 575b 575b
yellow 代表没有复制,因为默认情况下,ES会分配一个复制,但是现在只有一个节点,所以复制用不了
因为如果索引一个文档,必须有个类型(Type),我们把一个学生信息索引入test
PUT localhost:9200/test/student/1
curl -i -X PUT \
-H "Content-Type:application/json" \
-d \
'{
"name":"igouc"
}' \
'http://localhost:9200/test/student/1'
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 1,
"created": true
}
1 代表id为 1
索引这个文档
GET localhost:9200/test/student/1?pretty
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 1,
"found": true,
"_source":{
"name": "igouc"
}
}
除了found字段-(指明我们找到了一个ID为1的文档)和_source字段(返回我们前一步中索引的完整JSON文档)之外,没有什么特别之处。
curl -XDELETE 'localhost:9200/test?pretty'
{
"acknowledged" : true
}
curl -XDELETE 'localhost:9200/test/student?pretty'
{
"acknowledged" : true
}
curl -XDELETE 'localhost:9200/test/student/1?pretty'
{
"name": "igouc"
}
仔细研究以上的命令,我们可以发现访问Elasticsearch中数据的一个模式。这个模式可以被总结为:
<REST Verb> <Node>:///
Elasticsearch提供了近乎实时的数据操作和搜索功能。默认情况下,从你索引/更新/删除你的数据动作开始到它出现在你的搜索结果中,大概会有1秒钟的延迟。这和其它的SQL平台不同,它们的数据在一个事务完成之后就会立即可用。
curl -i -X PUT \
-H "Content-Type:application/json" \
-d \
'{
"name":"igouc4"
}' \
'http://localhost:9200/test/student/1?pretty'
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 2,
"created": false
}
注意created == false
GET localhost:9200/test/student/1
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 2,
"found": true,
"_source":{
"name": "igouc4"
}
}
如果不指定最后的id,则会自动分配id,注意要用POST⚠️
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"name":"igouc5"
}' \
'http://localhost:9200/test/student/?pretty'
{
"_index": "test",
"_type": "student",
"_id": "AVq-d-gpHptku1SmEgQL",
"_version": 1,
"created": true
}
此时的id是随机的
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"doc":{"name":"igouc6"}
}' \
'http://localhost:9200/test/student/1/_update?pretty'
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 4
}
GET localhost:9200/test/student/1?pretty
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 4,
"found": true,
"_source":{
"name": "igouc6"
}
}
更新文档,Elasticsearch先删除旧文档,然后再索引更新的新文档。且用POST
更新也可以通过使用简单的脚本来进行
curl -i -X POST \
-H "Content-Type:application/json" \
-d \
'{
"script":"ctx._source.name += 2"
}' \
'http://localhost:9200/test/student/1/_update?pretty'
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 5
}
GET localhost:9200/test/student/1?pretty
{
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 5,
"found": true,
"_source":{
"name": "igouc62"
}
}
删除文档是非常直观的
DELETE localhost:9200/test/student/1?pretty
{
"found": true,
"_index": "test",
"_type": "student",
"_id": "1",
"_version": 6
}
指定删除条件
curl -i -X DELETE \
-H "Content-Type:application/json" \
-d \
'{
"query": { "match": { "name": "igouc2" } }
}' \
'http://localhost:9200/test/student/_query?pretty'
{
"_indices":{
"test":{
"_shards":{
"total": 5,
"successful": 5,
"failed": 0
}
}
}
}
下面两个仅仅是指令,没有响应
curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"index":{"_id":"1"}}
{"name": "John Doe" }
{"index":{"_id":"2"}}
{"name": "Jane Doe" }
'
curl -XPOST 'localhost:9200/customer/external/_bulk?pretty' -d '
{"update":{"_id":"1"}}
{"doc": { "name": "John Doe becomes Jane Doe" } }
{"delete":{"_id":"2"}}
'
bulk API按顺序执行这些动作。如果其中一个动作因为某些原因失败了,它将会继续处理后面的动作。当bulk API返回时,它将提供每个动作的状态(按照同样的顺序),所以你能够看到某个动作成功与否。⚠️
我们有两种基本的搜索方式:
下面 我们仅仅使用一次 将请求参数放入URI中,其余都将参数放入请求体,以便美观
GET localhost:9200/bank/_search?q=*$pretty
{
"took" : 9,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
},
"hits" : {
"total" : 1000,
"max_score" : 1.0,
"hits" : [ {
"_index" : "bank",
"_type" : "account",
"_id" : "4",
"_score" : 1.0,
"_source":{"account_number":4,"balance":27658,"firstname":"Rodriquez","lastname":"Flores","age":31,"gender":"F","address":"986 Wyckoff Avenue","employer":"Tourmania","email":"[email protected]","city":"Eastvale","state":"HI"}
}, {
"_index" : "bank",
"_type" : "account",
"_id" : "9",
"_score" : 1.0,
"_source":{"account_number":9,"balance":24776,"firstname":"Opal","lastname":"Meadows","age":39,"gender":"M","address":"963 Neptune Avenue","employer":"Cedward","email":"[email protected]","city":"Olney","state":"OH"}
}, {
"_index" : "bank",
"_type" : "account",
"_id" : "11",
"_score" : 1.0,
"_source":{"account_number":11,"balance":20203,"firstname":"Jenkins","lastname":"Haney","age":20,"gender":"M","address":"740 Ferry Place","employer":"Qimonk","email":"[email protected]","city":"Steinhatchee","state":"GA"}
}, {
"_index" : "bank",
"_type" : "account",
"_id" : "16",
"_score" : 1.0,
"_source":{"account_number":16,"balance":35883,"firstname":"Adrian","lastname":"Pitts","age":34,"gender":"F","address":"963 Fay Court","employer":"Combogene","email":"[email protected]","city":"Remington","state":"SD"}
},
.
.
.
.
}
我们看看这个请求的响应体
如果我们是使用请求体的话,应该这样:
POST localhost:9200/bank/_search?pretty
body:
{
"query":{"match_all":{}}
}
我们来分析下这个请求体
当然除了上面的参数,还可以指定返回的条数(默认10条)
POST localhost:9200/bank/_search?pretty
body:
{
"query":{"match_all":{}},
"from":10,
"size":1
}
上面的这条表示从第10条查询1条
POST localhost:9200/bank/_search?pretty
body:
{
"query":{"match_all":{}},
"sort":{"account_number":{"order":"desc"}}
}
现在我们已经接触了一些简单的搜索语句,下面我们执行一些特殊的搜索
默认情况下,都是返回所有的字段,我们下面可以 用_source
指定返回的字段
POST localhost:9200/bank/_search?pretty
body
{
"query":{"match_all":{}},
"_source":["account_number", "balance", "firstname"]
}
现在让我们进入到查询部分。之前,我们学习了match_all查询是怎样匹配到所有的文档的。现在我们介绍一种新的查询,叫做match查询,这可以看成是一个简单的字段搜索查询(比如对某个或某些特定字段的搜索)
下面这个例子返回账户编号为 20 的文档:
POST localhost:9200/bank/_search?pretty
body:
{
"query":{"match":{"account_number":999}}
}
下面这个例子返回地址中包含词语(term)“mill”的所有账户:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill" } }
}'
下面这个例子返回地址中包含词语“mill” 或者“lane” 的账户:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match": { "address": "mill lane" } }
}'
下面这个例子是match的变体(match_phrase),它会去匹配短语“mill lane”:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": { "match_phrase": { "address": "mill lane" } }
}'
现在,让我们介绍一下布尔查询。布尔查询允许我们利用布尔逻辑将较小的查询组合成较大的查询。
现在这个例子组合了两个match查询,这个组合查询返回包含“mill” 和“lane” 的所有的账户
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中,bool must语句指明了,对于一个文档,所有的查询都必须为真,这个文档才能够匹配成功。
相反的, 下面的例子组合了两个match查询,它返回的是地址中包含“mill” 或者“lane”的所有的账户:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"should": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中bool should语句指明,对于一个文档,查询列表中,只要有一个查询匹配,那么这个文档就被看成是匹配的。
现在这个例子组合了两个查询,它返回地址中既不包含“mill”,同时也不包含“lane”的所有的账户信息:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must_not": [
{ "match": { "address": "mill" } },
{ "match": { "address": "lane" } }
]
}
}
}'
在上面的例子中,bool must_not语句指明,对于一个文档,查询列表中的的所有查询都必须都不为真,这个文档才被认为是匹配的。
我们可以在一个bool查询里一起使用must、should、must_not。 此外,我们可以将bool查询放到这样的bool语句中来模拟复杂的、多层级的布尔逻辑。
下面这个例子返回40岁以上并且不生活在ID(aho)的人的账户:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"bool": {
"must": [
{ "match": { "age": "40" } }
],
"must_not": [
{ "match": { "state": "ID" } }
]
}
}
}'
Elasticsearch中的所有的查询都会触发相关度得分的计算。对于那些我们不需要相关度得分的场景下,Elasticsearch以过滤器的形式提供了另一种查询功能。过滤器在概念上类似于查询,但是它们有非常快的执行速度,这种快的执行速度主要有以下两个原因:
为了理解过滤器,我们先来介绍“被过滤” 的查询,这使得你可以将一个查询(如match_all,match,bool等)和一个过滤器结合起来。作为一个例子,我们介绍一下范围过滤器,它允许我们通过一个区间的值来过滤文档。这通常被用在数字和日期的过滤上。
这个例子使用一个被过滤的查询,其返回值是存款在20000到30000之间(闭区间)的所有账户。换句话说,我们想要找到存款大于等于20000并且小于等于30000的账户。
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"query": {
"filtered": {
"query": { "match_all": {} },
"filter": {
"range": {
"balance": {
"gte": 20000,
"lte": 30000
}
}
}
}
}
}'
分析上面的例子,被过滤的查询包含一个match_all查询(查询部分)和一个过滤器(filter部分)。我们可以在查询部分中放入其他查询,在filter部分放入其它过滤器。 在上面的应用场景中,由于所有的在这个范围之内的文档都是平等的(或者说相关度都是一样的), 没有一个文档比另一个文档更相关,所以这个时候使用范围过滤器就非常合适了
通常情况下,要决定是使用过滤器还是使用查询,你就需要问自己是否需要相关度得分。如果相关度是不重要的,使用过滤器,否则使用查询。如果你有SQL背景,查询和过滤器 在概念上类似于SELECT WHERE语句,一般情况下过滤器比查询用得更多。
除了match_all, match, bool,filtered和range查询,还有很多其它类型的查询/过滤器,我们这里不会涉及。由于我们已经对它们的工作原理有了基本的理解,将其应用到其它类型的查询、过滤器上也不是件难事。
我们可以在es上做聚合操作:
聚合提供了分组并统计数据的能力。理解聚合的最简单的方式是将其粗略地等同为SQL的GROUP BY和SQL聚合函数。在Elasticsearch中,你可以在一个响应中同时返回命中的数据和聚合结果。你可以使用简单的API同时运行查询和多个聚合并一次返回,这避免了来回的网络通信,是非常强大和高效的。
作为开始的一个例子,我们按照state分组,并按照州名的计数倒序排序:
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state"
}
}
}
}'
注意我们将size设置成 0,这样我们就可以只看到聚合结果了,而不会显示命中的结果。
在先前聚合的基础上,现在这个例子计算了每个州的账户的平均存款(还是按照账户数量倒序排序的前10个州):
curl -XPOST 'localhost:9200/bank/_search?pretty' -d '
{
"size": 0,
"aggs": {
"group_by_state": {
"terms": {
"field": "state"
},
"aggs": {
"average_balance": {
"avg": {
"field": "balance"
}
}
}
}
}
}'
注意, 我们把average_balance聚合嵌套在了group_by_state聚合之中。这是所有聚合的一个常用模式。你可以在任意的聚合之中嵌套聚合,这样就可以从你的数据中抽取出想要的结果。
在前面的聚合的基础上,现在让我们按照平均余额进行排序:
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"
}
}
}
}
}
}'
搜索API允许开发者执行搜索查询,返回匹配的搜索结果。这样既可以通过查询字符串,也可以通过查询实体实现。
所有的搜索API都可以跨多个类型使用,也可以通过多索引语法跨索引使用,例如,我们可以搜索twitter索引的跨类型的所有的文档。
GET http://localhost:9200/twittper/_search?q=user:ki
我们也可以带上特定搜索的type:
GET htto://localhost:9200/twittper/type1,type2/_search?q=user:ki
我们也可以夸index搜索
GET http://localhost:9200/type1,type2/twitter/_search?q=user:ki
我们可以用_all
来充当所有的占位符
GET http://localhost:9200/_all/twitter/_search?q=user:ki
或者省略_all
GET http://localhost:9200/twitter/_search?q=user:ki
一个搜索可以用uri来执行,用这种方法进行搜索,并不是所有的选项都是暴露出来的,下面我们记录这些参数
Name | Description |
---|---|
q | 表示查询 |
df | 在查询中,当没有定义字段的前缀的情况下的默认字段前缀 |
analyzer | 当分析查询字符串时,分析器的名字 |
explain | 对于命中,会得到一个命中解释 |
_source | 将其设置为false,查询就会放弃检索_source字段。你也可以通过设置_source_include和_source_exclude检索部分文档 |
fields | 命中的文档返回的字段 |
sort | 排序执行。可以以fieldName、fieldName:asc或者fieldName:desc的格式设置。fieldName既可以是存在的字段,也可以是_score字段。可以有多个sort参数 |
track_scores | 当排序的时候,将其设置为true,可以返回相关度得分 |
timeout | 默认没有timeout |
from | 默认0 |
size | 默认10 |
search_type | 搜索操作执行的类型,有dfs_query_then_fetch, dfs_query_and_fetch, query_then_fetch, query_and_fetch, count, scan几种,默认是query_then_fetch |
lowercase_expanded_terms | terms是否自动小写,默认是true |
analyze_wildcard | 是否分配通配符和前缀查询,默认是false |
有搜索DSL的搜索请求可以被执行
精准匹配
{
"query":{
"term":{
"shop_name":"测试"
}
}
}
模糊匹配(表达式匹配)
{
"query":{
"match":{
"shop_name":"测试"
}
}
}
{
"query":{
"term":{
"shop_name":"test"
}
},
"from":0,
"size":100
}
{
"query":{
"term":{
"shop_name":"test"
}
},
"sort":[{"age":"asc"},{"_score":"desc"}]
}
es支持通过数组数组或者多值字段排序
mode选项控制 用多值字段的什么值来排序
{
"query":{
"term":{
"shop_name":"test"
}
},
"sort":[
{"price":{"order":"desc", "mode":"avg"}}
]
}
missing
参数指缺失字段的处理方式:”missing”:”_last”/”missing”:”true”
{
"sort" : [
{ "price" : {"missing" : "_last"} },
],
"query" : {
"term" : { "user" : "kimchy" }
}
}
通过_geo_distance排序。
{
"sort" : [
{
"_geo_distance" : {
"pin.location" : [-70, 40],
"order" : "asc",
"unit" : "km",
"mode" : "min",
"distance_type" : "sloppy_arc"
}
}
],
"query" : {
"term" : { "user" : "kimchy" }
}
}
地理距离排序支持的排序mode有max,min和avg。
用于控制_source字段的返回。默认情况下,操作返回_source字段的内容,除非你用到了fields参数,或者_source被禁用了。你能够通过_source参数关掉_source检索。
{
"_source": false,
"query" : {
"term" : { "user" : "kimchy" }
}
}
_source也接受一个或者多个通配符模式控制返回值。
{
"_source":"obj.*",
"query" : {
"term" : { "user" : "kimchy" }
}
}
or
{
"_source": [ "obj1.*", "obj2.*" ],
"query" : {
"term" : { "user" : "kimchy" }
}
}
_source里面也可以有include/exclude
{
"query":{
"term":{"shop_name":"test"}
},
"_source":{
"include":["object.*", "a.*"],
"exclude":["object1.*"]
}
}
es允许选择性地加载文档特定的存储字段。
{
"fields" : ["user", "postDate"],
"query" : {
"term" : { "user" : "kimchy" }
}
}
如果fields数组为空,那么就只会返回_id和_type字段。
{
"min_score": 0.5,
"query" : {
"term" : { "user" : "kimchy" }
}
}
返回的文档的得分小于min_score。
ES可以在多个地方用到 java client
获得一个Client是非常容易的,最通用的步骤如下所示:
另外一种方式是创建一个TransportClient来连接集群。
重要提示: 客户端和集群端推荐使用相同的版本,如果版本不同,可能会出现一些不兼容的问题。