一、概念
1、集群和节点
节点(node)是你运行的Elasticsearch实例。一个集群(cluster)是一组具有相同cluster.name的节点集合,他们协同工作,共享数据并提供故障转移和扩展功能,当有新的节点加入或者删除节点,集群就会感知到并平衡数据。集群中一个节点会被选举为主节点(master),它用来管理集群中的一些变更,例如新建或删除索引、增加或移除节点等;当然一个节点也可以组成一个集群。
2、结点通信
我们能够与集群中的任何节点通信,包括主节点。任何一个节点互相知道文档存在于哪个节点上,它们可以转发请求到我们需要数据所在的节点上。我们通信的节点负责收集各节点返回的数据,最后一起返回给客户端。这一切都由Elasticsearch透明的管理。
3、索引分片
分片用于Elasticsearch在你的集群中分配数据。想象把分片当作数据的容器。文档存储在分片中,然后分片分配给你集群中的节点上。当你的集群扩容或缩小,Elasticsearch将会自动在你的节点间迁移分片,以使集群保持平衡。分片数量只能在索引创建前指定,并且索引创建后不能更改。
4、索引副本
提高系统的容错性,当某个结点的某个分片损坏时,可以从副本中恢复,所以副本分片和源分片不会放在同一节点上。
提高系统查询效率,Elasticsearch会自动对搜索请求进行负载均衡。
5、分片与副本交互
新建、索引和删除请求都是写(write)操作,它们必须在主分片上成功完成才能复制到相关的复制分片上,同步方式的一般步骤如下:
1)客户端给Node_1发送新建、索引或删除请求。
2)节点使用文档的_type和_id确定文档属于分片0。它转发请求到拥有分片0的节点Node_3上。
3)Node_3在主分片上执行请求,如果成功,它转发请求到相应的位于Node_1和Node_2的复制节点上。当所有的复制节点报告成功,Node_3报告成功到请求的节点,请求的节点再报告给客户端。
注意:副本的默认值是同步(sync),即允许Elasticsearch强制反馈传输,客户端接收到成功响应的时候,文档的修改已经被应用于主分片和所有的复制分片,你的修改生效了。而异步(async)复制依旧会转发请求给复制节点,但你将不知道复制节点成功与否,并且可能会因为在不等待其它分片就绪的情况下发送过多的请求而使Elasticsearch过载。
二、配置优化
1、routing优化
正常情况下,索引是根据type和id通过hash取模的方式来存储到不同的shard里面的,查询的时候则是在整个shard组里面做的,即每个shard都要参与查询,然后合并各个查询结果,这样会浪费很多查询;而routing就是可以按照一定的规则,建索引的时候,就可以指定数据存放在哪个shard里面,这样查询的时候,同理,通过routing规则就能够保证有的放矢,只在一个shard里面去进行查询,而不是到处撒网,这样不就快多了吗?当然用routing也有缺点,由于索引存放位置由我们自己控制,并且由于routing值不均匀,肯定会造成索引数据不均匀,即某几个shard里面什么数据也没有,某几个shard里面数据扎堆,数据扎堆的shard肯定对性能有影响。
http://localhost:9200/listing/listing/1?routing=1
2、分片优化
分片数过多会导致检索时打开比较多的文件别外也会导致多台服务器之间通讯。而分片数过少为导至单个分片索引过大,所以检索速度慢。单个分片内容最大为20G。
索引分片数 = 数据总量/单分片数(20G)
3、副本优化(副本越多越稳定,索引越慢)
默认同步方式为主分片完成增删改操作后,发送请求到所有副本,等待所有副本完成后,才返回客户端,所以副本越多索引越慢。建议在批量操作索引前,将副本数设置为0,操作完成后,执行Optimize优化操作,最后将副本数调整为合适的个数。
4、索引段优化(索引段越多检索越慢)
ES索引过程中会refresh和tranlog也就是说我们在索引过程中segments number不止一个,segments number越多检索越慢,而将segments numbers 有可能的情况下保证为1这将可以提到将近一半的检索速度。
curl -XPOST 'http://localhost:9200/listing/_optimize?&pretty&max_num_segments=1'
5、删除索引优化
清除删除文档(Lucene中删除文档会存放到.del文件中,所以要手动清除.del文件)
lucene在检索过程会判断是否删除了,如果删除了在过滤掉。这样也会降低检索效率。所以可以执行清除删除文档。
curl -XPOST 'http://localhost:9200/listing/_optimize?pretty&only_expunge_deletes=true'
三、API
elasticsearch支持多种语言的api:如python、ruby、js、java、http等,而我们开发和调试中最常用的api当然是java和http。
elasticsearch api支持的数据格式:json。
四、HTTP API
1、操作工具curl
1)我们可以借助linux下的工具curl来处理http请求,例如:
curl -XPUT 'http://localhost:9200/listing/_settings?pretty' -d '{"index":{"number_of_replicas":0}}'
curl -XPOST 'http://localhost:9200/listing?pretty' -d @listing.conf
curl -XPOST 'http://localhost:9200/listing/listing/_bulk?pretty' --data-binary @listing.data
2)curl参数
-X:请求方法参数,默认为POST
GET:获得请求对象的当前状态
POST:改变对象的状态(默认参数)
PUT:创建对象、改变索引属性
DELETE:销毁对象
-d:数据参数,可以直接为json数据,如例1;也可以为文件,如例2。
--data-binary:数据参数,elasticsearch在做bulk操作的时候必须使用该参数。
?pretty:附加参数,elasticsearch会将输出的json结果格式化,拥有良好缩进的json。
2、常用url
命令动词:_analyze. _settings, _status, _river, _mapping, _close, _open, _refresh, _flush, _count, _search, _mget, _bulk等
1)创建、删除索引
curl -XPUT 'http://localhost:9200/listing?pretty'
curl -XDELETE 'http://localhost:9200/listing?pretty'
2)打开、关闭索引(必须为/index/_close,不可以为/index/type/_close)
curl -XPUT 'http://localhost:9200/listing/_close?pretty'
curl -XPUT 'http://localhost:9200/listing/_open?pretty'
3)获取索引
curl -XGET 'http://localhost:9200/listing/_settings?pretty'
curl -XGET 'http://localhost:9200/listing/_mapping/listing?pretty'
curl -XGET 'http://localhost:9200/listing/_mapping/listing/field/list_name?pretty'
4)配置索引
更新索引副本个数和刷新周期
curl -XPUT 'http://localhost:9200/listing/_settings?pretty' -d '{"index":{"number_of_replicas":0}}'
curl -XPUT 'http://localhost:9200/listing/_settings?pretty' -d '{"index":{"refresh_interval":-1}}'
刷新索引
curl -XPOST 'http://localhost:9200/listing/_refresh?pretty'
优化索引
curl -XPOST 'http://localhost:9200/listing/_optimize?pretty'
5)创建索引mapping
curl -XPOST 'http://localhost:9200/listing?pretty' -d @listing.conf
{
"settings" : {
"index" : {
"number_of_shards" : 3,
"number_of_replicas" : 1
}
},
"mappings" : {
"item" : {
"dynamic" : "true"
},
"listing" : {
"dynamic" : "false",
"_id" : {
"path" : "list_id"
},
"_type" : {
},
"_source" : {
"enabled" : true,
"compress" : true
},
"properties" : {
"list_id" : { "type" : "long" },
"list_name" : { "type" : "string", "index_analyzer" : "mmseg_max_word", "search_analyzer" : "mmseg_complex" },
"min_price" : { "type" : "float", "null_value" : "0.0" },
"online_time" : { "type" : "date" },
"is_delete" : { "type" : "boolean"},
"tag" : { "type" : "string", "index" : "not_analyzed", "index_name" : "category"},
"image" : { "type" : "binary" },
"brand" : {
"type" : "multi_field",
"fields" : {
"brand" : { "type" : "string", "index" : "analyzed" },
"brand_original" : { "type" : "string", "index" : "not_analyzed" }
}
}
}
}
}
}
6)查询记录数
curl -XGET 'http://localhost:9200/listing/listing/_count?pretty'
curl -XGET 'http://localhost:9200/listing/listing/_count?pretty&q=list_name:iphone5s'
7)实时获取数据(类似于select *)
curl -XGET 'http://localhost:9200/listing/listing/1?pretty&fields=list_name,list_id'
curl -XGET 'http://localhost:9200/listing/listing/_mget?pretty' -d '{
"ids" : ["1", "2"],
"fields" : ["list_id", "list_name"]
}'
8)非实时查询数据(受refresh影响)
curl 'http://localhost:9200/listing/listing/_search?pretty' -d '{
"explain": true,
"from" : 0, "size" : 10,
"fields" : ["list_id", "list_name", "tag", "min_price"],
"query" : {
"query_string": {
"default_operator" : "and",
"fields": ["list_name", "min_price"],
"query": "((list_name:IPHONE6PLUS) AND (list_name:移动电源)) OR (min_price:3333.33)"
}
},
"filter" : {
"term" : { "tag" : "apple2014" }
},
"facets" : {
"tag" : {
"terms" : { "field" : "tag" }
}
},
"sort" : [
{"_score" : "desc"},
{"list_id" : "asc"}
]
}'
9)批量操作数据bulk
curl -XPOST 'http://localhost:9200/listing/listing/_bulk?pretty' --data-binary '
{ "index" : { "_index" : "listing", "_type" : "listing", "_id" : 1 } }
{ "list_id" : 1, "list_name" : "IPadAir2移动电源" }
{ "delete" : { "_index" : "listing", "_type" : "listing", "_id" : 1 } }
{ "create" : { "_index" : "listing", "_type" : "listing" } }
{ "list_id" : 1, "list_name" : "IPadAir2移动电源" }
{ "update" : { "_index" : "listing", "_type" : "listing" } }
{ "doc" : { list_id" : 1, "list_name" : "Iphone6Plus移动电源" } }
'
10)单条操作数据
curl -XPOST 'http://localhost:9200/listing/listing?pretty' -d '{
"list_id" : "2008",
"list_name" : "苹果iphone6plus"
}'
curl -XDELETE 'http://localhost:9200/listing/listing/{1,2}?pretty'
curl -XDELETE 'http://localhost:9200/listing/listing/_query?pretty&q=list_name:iphone5s'
五、JAVA API
1、maven依赖
org.elasticsearch
elasticsearch
1.4.1
2、创建客户端
Settings settings = ImmutableSettings.settingsBuilder()
.put("cluster.name", clusterName)
.put("node.name", nodeName)
.build();
Client client = new TransportClient(settings)
.addTransportAddress(new InetSocketTransportAddress(ip, port));
3、增删改操作
client.prepareIndex(index, type, id)
.setSource(jsonBuilder()
.startObject()
.field("list_id".toLowerCase(), 100)
.endObject()
).execute().actionGet();
client.preparedelete(index, type, id).execute().actionGet();
client.prepareUpdate(index, type, id)
.setSource(jsonBuilder()
.startObject()
.field("list_id".toLowerCase(), 100)
.endObject()
).execute().actionGet();
4、批量操作bulk
将增删改操作批量放入bulk请求中,然后执行即可,举例:
bulkRequest.add(client.prepareIndex(index, type)
.setSource(jsonBuilder()
.startObject()
.field("list_id".toLowerCase(), 100)
.field("list_name".toLowerCase(), "橡皮擦")
.field("min_price".toLowerCase(), 1234.5678)
.endObject()
)
);
BulkResponse bulkResponse = bulkRequest.execute().actionGet();
if (bulkResponse.hasFailures()) {
// process failures by iterating through each bulk response item
}
5、查询
SearchResponse response = client.prepareSearch(index)
.setTypes(type)
.setSearchType(SearchType.DFS_QUERY_THEN_FETCH)
.setQuery(QueryBuilders.queryString(”list_name:移动“))
.setFrom(0).setSize(100)
.setExplain(true)
.execute()
.actionGet();
SearchHits hits = response.getHits();
for (SearchHit hit : hits) {
String json = hit.getSourceAsString();
System.out.println(json);
}
五、全文搜索
对于全文搜索而言,最重要的两个方面是:相关度(Relevance) 和 解析(Analysis)。
尽管所有的查询都会执行某种程度的相关度计算,并不是所有的查询都存在解析阶段。除了诸如bool或者function_score这类完全不对文本进行操作的特殊查询外,对于文本的查询可以被划分两个种类:
基于词条的查询(Term-based Queries) 和 全文查询(Full-text Queries)。
1、基于词条的查询
不含有解析阶段的低级查询(Low-level Queries),一个针对词条Foo的term查询会在倒排索引中寻找该词条的精确匹配(Exact term),然后对每一份含有该词条的文档通过TF/IDF进行相关度_score的计算。
如:基于词条查询 Foo,它只会在倒排索引中匹配Foo,而不会匹配foo、FOO、fOO等。
2、全文查询
类似match或者query_string这样的查询是高级查询(High-level Queries),它们能够理解一个字段的映射:
如果你使用它们去查询一个date或者integer字段,它们会将查询字符串分别当做日期或者整型数。
如果你查询一个精确值(not_analyzed)字符串字段,它们会将整个查询字符串当做一个单独的词条。
但是如果你查询了一个全文字段(analyzed),它们会首先将查询字符串传入到合适的解析器,用来得到需要查询的词条列表。
一旦查询得到了一个词条列表,它就会使用列表中的每个词条来执行合适的低级查询,然后将得到的结果进行合并,最终产生每份文档的相关度分值。
3、match查询
在你需要对任何字段进行查询时,match查询应该是你的首选。它是一个高级全文查询,意味着它知道如何处理全文字段(Full-text, analyzed)和精确值字段(Exact-value,not_analyzed)。
1)单词查询
GET /_search { "query": { "match": { "list_name": "iphone" } } }
2)多词查询
GET /_search { "query": { "match": { "list_name": "iphone ipad" } } }
match查询接受一个operator参数,该参数的默认值是"or"。你可以将它改变为"and"或"not"来要求所有的词条都需要被匹配:
GET /_search { "query": { "match": { "list_name": { "query": "iphone ipad", "operator": "and" } } } }
3)控制精度查询
对于大多数全文搜索的使用场景,你会希望将相关度高的文档包含在结果中,将相关度低的排除在外。换言之,我们需要一种介于两者中间的方案。
match查询支持minimum_should_match参数,它能够让你指定有多少词条必须被匹配才会让该文档被当做一个相关的文档,该参数可以是一个绝对数值或者一个百分比,但是通常指定一个百分比会更有意义(百分比会向下舍入,如下面例子75%会舍入到66.6%,即3个词条中的2个),因为你无法控制用户会输入多少个词条:
GET /_search { "query": { "match": { "list_name": { "query": "iphone ipad iwatch", "minimum_should_match": "75%" } } } }
4)合并查询
bool查询通过must,must_not以及should参数来接受多个查询。
所有的must语句都需要匹配,而所有的must_not语句都不能匹配,should语句一个都不要求匹配,只有一个特例:如果查询中没有must语句,那么至少要匹配一个should语句。
GET /_search { "query": { "bool": {
"must": { "match": { "list_name": "iphone" }},
"must_not": { "match": { "list_name": "ipad" }},
"should": [ { "match": { "list_name": "6" }}, { "list_name": { "list_name": "plus" }} ] } } }
list_name字段中含有词条iphone,且不含有词条ipad的任何文档都会被作为结果返回,同时一份文档不被要求需要含有词条6或者plus,但是如果它含有了,那么它的相关度应该更高。
4、query_string查询
_source字段的一个优势是在ES中你已经拥有了整个文档。你不需要通过数据库来重建你的索引,这种方法通常会更慢。
为了从旧索引中高效地对所有文档进行重索引,可以使用scan和scroll来批量地从旧索引中获取文档,然后使用bulk API将它们添加到新索引中。
六、生产环境平滑修改mapping
1、数据重索引
_source字段的一个优势是在ES中你已经拥有了整个文档。你不需要通过数据库来重建你的索引,这种方法通常会更慢。
为了从旧索引中高效地对所有文档进行重索引,可以使用scan和scroll来批量地从旧索引中获取文档,然后使用bulk API将它们添加到新索引中。
2、索引别名
一个索引别名就好比一个快捷方式(Shortcut)或一个符号链接(Symbolic Link),索引别名可以指向一个或者多个索引,可以在任何需要索引名的API中使用。使用别名可以给我们非常多的灵活性。它能够让我们:在一个运行的集群中透明地从一个索引切换到另一个索引;让多个索引形成一个组,比如last_three_months;为一个索引中的一部分文档创建一个视图(View)
3、通过别名alias实现,一般步骤如下:
1)创建索引listing_v1,起别名为listing,我们使用别名 listing 做所有的增删改查操,操作如下:
PUT /listing_v1
PUT /listing_v1/_alias/listing
2)根据需求修改mapping,创建listing_v2,并且将listing_v1中的数据重索引到listing_v2,等待重索引完成,操作如下:
PUT /listing_v2
数据重索引操作
3)删除listing_v1的别名,修改listing_v2的别名为listing,操作如下:
一个别名能够指向多个索引,因此当我们将别名指向新的索引时,我们还需要删除别名原来到旧索引的指向。这个改变需要是原子的,即意味着我们需要使用_aliases端点:
POST /_aliases {
"actions": [
{ "remove": { "index": "listing_v1", "alias": "listing" }},
{ "add": { "index": "listing_v2", "alias": "listing" }}
]
}