一、ElasticSearch简介
1、ES是什么?
ElasticSearch是个基于Lucene实现的、(准)实时的、分布式的全文搜索和分析引擎。
准实时,意味着有轻微的延迟,通常为1秒,就可以从入库建索引文件到能够进行关键字搜索。
2、ES能干什么?
ES主要提供全文搜索、结构化搜索,以及分析的功能,并能将这三者混用。
3、ES的特性
- 支持Restful风格的http接口
- 输入输出支持json风格
- 分布式索引、搜索
- 索引自动分片、负载均衡
- 自动发现机器、组建集群
- 高性能
- 高可扩展性
- 高可用、提供复制机制
- 使用简单
二、ES的基本概念
1、索引词(term)
一个能够被索引的精确值,区分大小写:即可以通过term查询进行准确的搜索。
2、文本(text)
一段普通的非结构化文字,文本会被分析成一个个索引词,存储在ES的索引库中,这样才能进行搜索。
3、分析(analysis)
将文本转换成索引词的过程,分析的结果依赖于分词器。
分词器的分析机制用于对全文文本进行分词,分词后用于建立可供搜索的反向索引。
4、索引(index)
index类似于关系数据库中的数据库,每个索引有不同的字段,可以对应不同的类型;
每个索引可以有一个或多个主索引片,同时每个索引还可以有零个或多个副本索引片。
5、类型(type)
type类似于关系数据库中的表,每种type都可以指定不同的列,映射定义文档中的每个字段的类型,并可以指定如何分析
6、文档(document)
document类似于关系数据库表中的行记录,每个存储在index中的一个document都有一个原始的json文档,被存储在一个叫_source的字段中。当搜索document时默认返回的就是_source字段。
一个document不只有数据,还包含了元数据(metadata)-- 关于文档的信息。三个必须的元数据节点是:
- _index:文档存储的地方,名字必须全部小写,不能以下划线开头,不能包含逗号;
- _type:名字可以大写或小写,不能包含下划线和逗号;
- _id:是一个字符串,是一个文件的唯一标识。_id和_type, _index组合时,在ES就可以唯一标识一个document。在创建新文档时,_id可以自定义,也可以让ES自动生成,自动生成的_id有22个字符长。
7、映射(mapping)
mapping类似于关系数据库中的表结构,每一个index都有一个mapping,他定义了index中每一个字段的类型,以及一个index范围内的设置。一个mapping可以被事先定义,或者在第一次存储文档的时候自动识别。
mapping机制用于进行字段类型的确认,将每个字段匹配为一直确定的数据类型(string, number, boolean, data等)
8、字段(field)
field类似于关系数据库中表的列,每个字段都对应一个字段类型,如整型、字符串、对象等。
在ES中,每个document里面的字段都默认会被索引并被查询,也就是说,每个字段都专门有一个反向索引用于快速检索。与其他数据库不同的是,他可以在一次查询中,利用所有的这些反向索引,以惊人的速度返回结果。
ES与关系数据库对比:
ES | 关系数据库 |
---|---|
index | 数据库 |
type | 数据库表 |
document | 表中的行 |
mapping | 表结构定义 |
field | 表中的列 |
9、来源字段(source field)
默认情况下,原始文档将被存储在_source这个字段中,查询的时候也是返回这个字段。这样可以从搜索结果中获取原始的对象,这个对象显示为一个精确的json字符串。
10、集群(cluster)
集群是指,有一个或多个ES节点组成的组,他们具有相同的cluster.name,这些节点协同工作,共享数据和负载。
ES是一个分布式的document存储引擎,他可以实时存储并检索复杂的数据结构,序列化的json文档。
换言之,一旦document被存储到ES中,他就可以在任意节点中被搜索到。
11、节点(node)
节点是指集群中的一个ES服务器,一个节点是一个逻辑上独立的服务,他是集群的一部分,可以存储数据,并参与集群的索引和搜索功能。在节点启动时,节点将使用广播来发现具有相同集群名称的集群,并尝试加入该集群。
主节点(master node)
每个集群都有一个主节点,这是由程序自动选择的,如果当前主节点失败,程序会自动选举其他节点作为主节点。
主节点将临时管理集群级别的一些变更,例如新建或删除索引,增加或移除节点等。主节点不参与文档级别的变更或搜索,这意味着在流量增长的时候,主节点不会成为集群的瓶颈。
12、路由(routing)
路由指:在操作文档时,选择文档所在分片的过程。一个文档会存储在一个唯一的主分片中,具体存储在哪个分片,是通过散列值来进行选择的。默认情况下,这个值由文档的id生成。如果文档有一个指定的父文档,从父文档ID中生成,该值可以在存储文档的时候进行修改。
13、分片(shard)
分片指:一个单一的Lucene实例,用来真正存储索引,并提供搜索功能。这个是由ES管理的比较底层的功能,ES的索引是指向主分片和副本分片的逻辑空间。
对于使用,只需要指定分片的数量,其他不需要做过多的事情。在开发使用过程中,我们对应的对象都是索引,ES会自动管理集群中所有的分片,当发生故障的时候,ES会把分片移动到不同的节点或添加新的节点。
注意:可以在一个单一的Lucene索引中存储的最大值为2147483519 (=integer.max_value - 128)个文档。
主分片(primary shard)
每个文档都存储在一个分片中,当你存储一个文档的时候,系统会首先存储在主分片中,然后会复制到不同的副本中。默认情况下,一个索引有5个主分片。可以在事先指定分片的数量,当分片一旦建立,分片的数量则不能修改。
副本分片(replica shard)
副本是主分片的复制,每一个主分片有零个或多个副本,这样做有两个目的:
1)增加高可用性:当主分片失败的时候,可以从副本分片中选择一个作为主分片
2)提高性能:当查询的时候,可以到主分片或副本分片中进行查询。
默认情况下,一个主分片有一个副本,但副本的数量可以在后面动态的配置增加。副本必须部署在不同的节点上,不能部署在和主分片相同的节点上。
三、ES的基本使用
3.1、请求格式
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>?<QUERY_STRING>' -d'<BODY>'
1)VERB:HTTP方法:get, post, put, head, delete
2)PROTOCOL: http或者https协议
3)HOST:ES集群中的任何一个节点的主机名
4)PORT:ES HTTP服务所在的端口,默认为9200
5)PATH:指定要访问的路径和要做的操作,比如:/索引名/类型名/具体要操作的文档ID
6)QUERY_STRING:一些可选的查询请求参数,例如 ?pretty 参数将使请求返回格式化过的json数据
7)BODY:一个JSON格式的请求主体
返回
ES返回一个类似200的HTTP状态码和JSON格式的响应主体(除了HEAD请求),如果要显示HTTP头,在curl命令后跟-i参数
3.2、创建索引
curl -XPUT http://localhost:9200/mytest?pretty
也可以按照json格式来写,例如:
curl -XPUT 'http://localhost:9200/mytest2/' -d '{
"settings" : {
"index" : {
"number_of_shards" : 5,
"number_of_replicas" : 2
}
}
}'
查看索引,列出所有的索引
curl -XGET http://localhost:9200/_cat/indices
跟 ?v 能看到结果的表头
3.3、插入数据
curl -XPUT http://localhost:9200/mytest/product/p1 -d '{
"name":"Mac Book 笔记本",
"price":12345,
"description":"这是一款笔记本",
"cats":["3c", "computer"]
}'
1、请记住 _index, _type, _id三者唯一确定了一个document,所以想要保证document是新加入的,最简单的方式是使用post方法让ES自动生成唯一 _id
2、如果想使用自定义的_id,必须告诉ES应该在 _index, _type, _id三者都不同时,才接受请求。为了做到这点,有两种方法:
1)第一种方法,使用op_type查询参数:
PUT /website/blog/123?op_type=create {...}
2)第二种方法是在URL后加 /_create 作为端点:
PUT /website/blog/123/_create {...}
3、如果请求成功的创建了一个新document, ES将返回正常的元数据,且响应状态码是201 Created。另一方面,如果包含相同的 _index, _type和 _id的文档已经存在,ES将返回409 Conflict 响应状态码。
4、当创建文档的时候,如果索引不存在,则会自动创建该索引。自动创建的索引会自动映射每个字段的类型。
禁用“自动创建索引”功能,可以通过配置文件设置 action.auto_create_index为false。禁用自动映射的字段类型,可以通过配置文件设置 index.mapper.dynamic 为false。
自动创建索引可以通过模板设置索引名称,例如:可以设置
action.auto_create_index为 +aaa*, -bbb*, +ccc*, -* (+表示准许,-表示禁止)
3.4、查询数据
curl -XGET http://localhost:9200/mytest/product/p1?pretty
1、通常,GET请求将返回文档的全部,存储在 _source 参数中。但是你感兴趣的字段只是title, 请求个别字段可以使用 _source参数。多个字段可以用逗号分隔:
curl -XGET http://localhost:9200/mytest/product/p1?_source=name,price
2、如果你想做的只是检测文档是否存在,对内容完全不敢兴起,使用HEAD方法来代替GET,HEAD请求不会返回响应体,只有HTTP头
3、你也可以禁掉source,只有设置_source=false即可
4、如果不写type,可以用_all来代替,表示在所有的type中获取
5、如果你只想获取source中的一部分内容,还可以用_source_include或者_source_exclude来包含或者过滤其中的某些字段,如:
curl -GET http://localhost:9200/mytest/product/p1?_source_exclude=name,price
6、也可以用fields来选择source中的字段,如:
curl -XGET http://localhost:9200/mytest/product/p1?fields=name,price
注意:从返回值可以看出,返回的字段是数组类型,因此只有基本类型的字段可以从fields中进行查询,对象数据是不行的。
7、如果只想获取文档的内容,可以直接指定_source,例如:
curl -XPOST http://localhost:9200/mytest/product/p1/_source?pretty
3.5、修改数据
curl -XPOST http://localhost:9200/mytest/product/p1/_update?pretty -d ’{
"doc":{"name":"新款Mac Book 笔记本","price":54321}
}'
1、document在ES中是不可变的,如果需要更新已存在的document, 可以替换掉它
2、在内部,ES已经标记旧document为删除,并添加了一个完整的新document。旧版本的document不会立即消失,但你也不能去访问他。ES会再你继续索引更多数据时清理被删除的文档。
3、ES可以在操作中指定version参数,如:
curl -XPOST http://localhost:9200/mytest/product/p1/_update?version=1 -d '{
"doc":{"name":"新款Mac Book笔记本","price":54321},
}'
4、检查是否需要更新索引, 如果设置detect_noop为false, 那么不管这个字段是否发生了改变,都会更新索引, 设置为:
"doc":{"name":"new_name"},
"detect_noop":false
5、upsert:如果要修改的文档不存在,就执行upsert,如:
curl -XPOST http://localhost:9200/mytest/product/p11/_update?pretty -d '{
"doc":{"name":"新款mac book笔记本", "price":54321},
"upsert":{"name":"upsert haha"}
}'
还可以设置”doc_as_upsert":true, 直接把doc部门当做新的文档插入
3.6、删除数据
curl -XDELETE http://localhost:9200/mytest/product/p1?pretty
删除一个文档,也不会立即从磁盘上删除,它只是被标记成已删除。ES将会再你之后添加更多索引的时候才会在后头进行删除内容的清理。
删除索引
curl -XDELETE http://localhost:9200/mytest?pretty
3.7、简单查询
ES搜索有两种方式:
1)通过URL参数进行搜索
2)通过POST请求参数进行搜索,如:
curl _XGET http://localhost:9200/mytest/product/_search?q=price:12345
默认搜索出前10条,可以跟的参数有:
1)q:查询字符串
2)df:当查询中没有定义前缀的时候,默认使用的字段
3)analyzer:当分析查询字符串的时候使用的分词器
4)lowercase_expanded_terms:搜索的时候忽略大小写标志,默认为true
5)analyze_wildcard:通配符或者前缀查询是否被分析,默认为false
6)default_operator:默认多个条件的关系,AND或者OR,默认OR
7)Ienient:如果设置为true,字段类型转换失败的时候将被忽略,默认为false
8)explain:在每个返回结果中,将包含评分机制的解释
9)_source:是否包含元数据,同时支持_source_include和_source_exclude
10)fields:只返回索引中指定的列,多个列中间用逗号分开
11)sort:排序,例如fieldName:acs或者fieldName:desc
12)track_scores:评分轨迹,当排序的时候,设置为true的时候返回评分的信息
13)timeout:超时的时间设置
14)terminate_after:在每个分片中查询的最大条数,如果设置返回结果中会有一个terminated_early字段
15)from:开始的记录数
16)size:搜索结果的条数
17)search_type:搜索的类型,可以是dfs_query_then_fetch, query_then_fetch, 默认query_then_fetch
3.7.1、关于timeout
一般情况下,搜索请求不会超时。通常,协调节点会等待接收所有分片的回答。如果有一个节点遇到问题,他会拖慢整个搜索请求。
Timeout参数告诉协调节点,最多等待多久,就可以放弃等待,而将已有结果返回。返回部分结果总比什么都没有好。搜索请求的返回将会指出这个搜索是否超时,以及有多少分片成功答复了。
你可以定义timeout参数为10或者10ms,或者1s,例如:
GET /_search?timeout=10ms
ES将返回再请求前收集到的结果。
注意:超时不是一个断路器,也就是说timeout不会停止执行查询,它仅仅告诉你目前顺利返回结果的节点,然后关闭连接。在后台,其他分片可能依旧执行查询,尽管结果已经被发送。
3.7.2、查询多index多type
通过限制搜索的不同index或type,可以在集群中跨所有document搜索。ES转发搜索请求到集群中的主分片或每个分片的复制分片上,收集结果后,选择顶部的10个返回。
可以通过定义URL中的索引或类型类限定搜索的范围,例如:
1)/_search:在所有index的所有type中搜索
2)/mytest/_search:在索引mytest的所有type中搜索。
3)/mytest, test2/_search:在索引mytest和test2的所有type中搜索
4)/my*, t*/_search:在以my或t开头的索引的所有类型中搜索
5)/mytest/product/_search:在索引mytest的类型product中搜索
6)/mytest, test2/product, user/_search:在索引mytest和test2的类型为product和user中搜索
7)/_all/product, user/_search:AI所有索引的product和user中搜索
当你搜索包含单一index时,ES转发搜索请求到这个index的主分片或每个分片的复制分片上,然后汇总每个分片的结果。搜索包含多个索引也是同样的方式,只不过或有更多的分配被关联。
注意:搜索一个索引有5个主分片和5个索引各有一个分片,事实上是一样的。
3.7.3、分页
ES接受from和size参数:
- size:结果数,默认为10
- from:跳过开始的结果数,默认为0
如果你想每页显示5个结果,页面从1到2,请求如下:
GET /_search?size=5
GET /_search?size=5&from=5
在集群系统中深度分页
为什么深度分页有问题?假设在一个有5个主分片的索引中搜索,当请求结果的第一页(结果1到10)时,每个分片产生自己最顶端10个结果,然后返回他们给请求节点,他们再拍下这所有的50个结果,以选出顶端的10个结果。
现在假设请求第1000页,结果10001到10010, 工作方式都相同,不同的是每个分片都必须产生顶端的10010个结果,然后请求节点排序这50050个结果,并丢弃50040个!
可以看到在分布式系统中,排序结果的花费随着分页的深入而成倍增长,这也是为什么网络搜索引擎中,任何预计不能返回多余1000个结果的原因。
3.7.4、关于routing
可以在操作文档的时候,指定用来计算路由的routing值,从而限定操作会落在哪些分片上,如果在新增文档的时候指定了routing,那么后续对这个文档的所有操作,都应该使用同样的routing值,这个技术在设计非常大的搜索系统时非常有用,例如:
1、新增Document,指定routing:
curl -XPUT http://localhost:9200/mytest/product/p12?routing=myrouting -d '{
"name":"Mac Book 笔记本1212“,
”price":12,
"description":"这是一款笔记本“,
”cats":["3c","computer"]
}'
2、查询的时候,也带上相同的routing值:
curl -XGET http://localhost:9200/mytest/product/p12?pretty=true\&routing=myrouting
3.7.5、关于加减符号
搜索中,还可以使用加减号,"+"前缀表示语句匹配条件必须被满足。类似的”-“前缀表示条件必须不被满足。所有条件如果没有+或-表示是可选的:匹配越多,相关的document就越多,例如:
curl -XGET http://localhost:9200/mytest/product/_search?q=+price:12345\&pretty
四、索引
4.1、索引基本流程
4.1.1、准备知识
1、在ES中构建索引,相当于创建库和标
2、指定mapping, 相当于指定表结构
4.1.2、构建索引数据
1、要把要搜索的原始数据加入到ES中,可通过客户端开发、运行导入。
2、ES会对这些数据进行分析,依赖于分词器、分析器等,分析的结果就是索引的数据。
3、把这些数据添加到索引中,其实就是放到底层的Lucene中。
4.1.3、查询阶段
1、ES对查询词进行处理
2、ES查找和匹配document, 各种查询会有完全不一样的过程
3、ES对查询结果进行处理,如:评分、排序、高亮等。
4.2、API约定
ES对外提供的API是以http协议的方式,通过json格式以REST方式。
4.2.1、多索引查询支持以下参数:
1)ignore_unavailable:当索引不存在或者关闭的时候,是否忽略这些索引,值为true或false
2)allow_no_indices:当使用通配符查询时,当有索引不存在的时候,是否返回查询失效。
3)expand_wildcards:控制什么类型的索引被支持,值为open, close, none, all。open表示只支持open类型的索引,close表示只支持关闭状态的索引,none表示不可用,all表示同时支持open和close索引。
注意:文档操作API和索引别名API不支持多索引参数。
4.2.2、通用参数
1、pretty参数:返回经过格式化的json。系统还提供了 “?format=yaml"的yaml格式。
2、human参数:”?human=true" (默认为false) 提供适合人类阅读的数据,如:“size_in_bytes”:1024 变成 “size”:“1kb”
3、响应过滤(filter_path):所有返回值可以通过filter_path来减少返回值的内容,多个值可以通过逗号分开。即指定过滤掉的field, 例如:
curl -XGET http://localhost:9200/website/blog/_search?pretty\&filter_path=took,hits.hits._id -d '{
"query":{
"match":{
"operator":"or",
"query":"value 笔记本",
"type":"boolean"
|
}
}'
它也支持通配符*匹配任何部分的字段的名称,如:
curl -XGET http://localhost:9200/website/blog/_search?pretty\&filter_path=took,hits.h*._id -d '{
"query":{
"match":{
"operator":"or",
"query":"value 笔记本",
"type":"boolean"
|
}
}'
我们可以用两个通配符 ** 来匹配不确定名称的字段,例如我们可以返回lucene版本的段信息:
curl -XGET http://localhost:9200/_segments?pretty\&filter_path=indices.**.version
注意:有事直接返回ES的某个字段的原始值,如_source字段。如果你想过滤_source字段,可以结合_source字段和filter_path参数,如:
curl -XGET http://localhost:9200/_search?pretty\&filter_path=hits.hits._source\&_source=source_node
4.3、索引操作
4.3.1、创建索引
手动创建索引,可以在请求中加入所有设置和类型映射:
curl -XPOST http://localhost:9200/mytest3 -d '{
"settings":{
"number_of_shards":3,
"number_of_replicas":2
},
"mapptings":{
"type1":{
"properties":{
"field1":{"type":"string","index":"not_analyzed"}
}
}
}
}'
可以在config/elasticsearch.yml中添加下面的配置来防止自动创建索引:
action.auto_create_index:false
4.3.2、获取索引
curl -XGET http://localhost:9200/mytest3?pretty
还可以加入过滤,支持:_settings, _mapptings, _warmers, _aliases, 如:
curl -XGET http://localhost:9200/mytest3/_settings,mappings?pretty
检查索引是否存在
curl -XHEAD -i http://localhost:9200/mytest3
根据返回值来判断
打开/关闭索引
curl -XPOST http://localhost:9200/mytest/_close
curl -XPOST http://localhost:9200/mytest/_open
4.3.3、Put mapping API
该API允许你创建索引,并设定mapping, 或者给存在的索引增加新的类型,或者在已有的类型中增加新的字段。一般不能修改已有的字段。
例1、创建索引并设定Mapping:
PUT twitter{
"mapping":{
"tweet":{
"properties":{
"message":{"type":"string"}
}
}
}
}
例2、给存在的索引增加新的user类型
PUT twitter/_mapping/user{
"properties":{
"name":{"type":"string"}
}
}
例3、在已有类型tweet中增加新的字段user_name
PUT twitter/_mapping/tweet{
"properties":{
"user_name":{"type":"string"}
}
}
这个API的index部分,可以接受多个Index的名字,或者使用通配符。
4.3.4、获取mapping API
可以指定index和type, 也可以都不指定,例如:
curl -XGET http://localhost:9200/_mapping/twitter,kimchy
curl -XGET http://localhost:9200/_all/_mapping/tweet,book
curl -XGET http://localhost:9200/_all/_mapping
curl -XGET http://localhost:9200/_mapping
获取字段的mapping API
curl -XGET http://localhost:9200/twitter/_mapping/tweet/field/text
curl -XGET http://localhost:9200/twitter,kimchy/_mapping/tweet/field/message
curl -XGET http://localhost:9200/_all/_mapping/tweet,book/field/message,user.id
curl -XGET http://localhost:9200/_all/_mapping/tw*/field/*.id
curl -XGET http://localhost:9200/_all/_mapping/*/field/*?pretty
检查类型是否存在
curl -XHEAD -i http://localhost:9200/mytest/product?pretty
4.3.5、更新索引的设置
ES提供了优化好的默认配置,除非你明白这些配置的行为和为什么要这么做,否则请不要修改这些配置。下面是两个最重要的设置:
1、number_of_shards:定义一个索引的主分片个数,默认值是5。这个配置在索引创建后不能修改。
2、number_of_replicas:每个主分片的复制分片个数,默认是1。
curl -XPUT http://localhost:9200/mytest/_settings -d '{
"index":{"number_of_replicas":4}
}'
更新索引分析器
需要先关闭索引,然后再更新分析器,然后再打开索引:
curl -XPOST http://lcoalhost:9200/mytest/_close
curl -XPUT http://localhost:9200/mytest/_settings -d '{
"analysis":{
"analyzer":{
"content":{
"type":"custom","tokenizer":"whitespace"
}
}
}
}'
获取settings信息
curl -XGET http://localhost:9200/twitter/_settings
curl -XGET http://localhost:9200/twitter,kimchy/_settings
curl -XGET http://localhost:9200/_all/_settings
curl -XGET http://localhost:9200/2016.*/_settings
4.3.6、Analyze API
这个API执行对文本的分析,然后返回token结果,例如:
curl -XGET http://localhost:9200/_analyze?pretty -d '{
"analyzer":"standard",
"text":"this is a notebook"
}'
1、可以把analyzer设置成ik看看效果
2、text部分,支持使用数组,里面放多个字符串
3、还可以设置更细致的 tokenizers, token filters, char filters,例如:
"analyzer":"standard",
"tokenizer":"keyword",
"token_filter":["lowercase"],
"char_filter":["html_strip"],
4.4、索引管理
4.4.1、索引统计信息
curl http://localhost:9200/_stats?pretty
curl http://localhost:9200/mytest/_stats?pretty
多个索引之间用逗号分隔
4.4.2、Indices Segments API
提供低级别的lucene中索引段的信息,例如:
curl -XGET http://localhost:9200/mytest/_segments?pretty
curl -XGET http://localhost:9200/mytest,mytest2/_segments
curl -XGET http://localhost:9200/_segments
如果想要查看更详细的信息,可以在url上添加?verbose=true
Indices Recovery API
提供查看索引分片的恢复信息,会报告recovery的状态,例如:
curl -XGET http://localhost:9200/mytest,mytest2/_recovery?pretty
curl -XGET http://localhost:9200/_recovery?pretty&human
可以添加detailed=true的参数,来查看更详细的信息
Indices Shard Store API
提供查看索引分片的存储信息,例如:
curl -XGET http://localhost:9200/mytest/_shard_stores?pretty
curl -XGET http://localhost:9200/mytest,test2/_shard_stores
curl -XGET http://localhost:9200/_shard_stores
Clear Cache API
用来清除1到多个索引相关的缓存,例如:
curl -XPOST http://localhost:9200/mytest/_cache/clear?pretty
curl -XPOST http://localhost:9200/mytest,mytest2/_cache/clear
curl -XPOST http://localhost:9200/_cache/clear
Flush API
Flush API的功能是把索引在内存里面的数据,存储到具体的存储器上,并删除相应的内部事务日志。
例如:
curl -XPOST http://lcoalhost:9200/mytest/_flush?pretty
curl -XPOST http://localhost:9200/mytest,mytest2/_flush
Refresh API
刷新索引,使得上次refresh的操作引起的变化,都能反映到查询上。
curl -XPOST http://localhost:9200/mytest/_refresh?pretty
curl -XPOST http://localhost:9200/mytest,mytest2/_refresh
curl -XPOST http://localhost:9200/_refresh
Force merge API
提供枪支让索引里面的lucene段进行合并的功能
curl -XPOST http://localhost:9200/mytest/_forcemerge?pretty
curl -XPOST http://localhost:9200/mytest,mytest2/_forcemerge
4.5、索引配置
4.5.1、内存控制器
在ES中有很多控制器可以峰值内存溢出,每个控制器可以指定内存使用的最大值。除此之外,还有一个总的控制器在确定整个系统使用的最大内存值。这些配置都是可以动态更新的。
总的内存控制:indices.breaker.total.limit:总的内存使用大写,默认为JVM对内存的70%
4.5.2、缓存
1、节点查询缓存:是负责缓存查询的结果。每个节点都有一个查询缓存,这个缓存为这个节点下的所有分片服务。这个缓存采用LRU算法;当缓存满时,把最少使用的数据优先删掉。查询缓存只有使用过滤的时候才会起作用。
2、索引缓冲区:用于存储新的索引文档,当缓冲区满后,缓冲区中的文件被写入磁盘上的一个段,他会在节点的所有分片上分离。它的设置是静态的,并且必须在集群中的每个数据节点上配置。
3、分片请求缓存:当一个搜索请求是对一个索引或者多个索引的时候,每一个分片都是进行它自己内容的搜索,然后把结果返回到协调节点上,然后把这些结果合并到一起,统一对外提供。分片缓存模块缓存了这个分片的搜索结果。这使得搜索频率高的请求会立即返回。
注意:请求缓存只荤菜查询条件size=0的搜索,缓存的内容有hits.total, aggregation,suggestions, 不缓存原始的hits。通过now查询的结果将不缓存。
4、缓存失效:只有在分片的数据,实际上发生了变化的时候,刷新分片缓存才会失效。刷新的时间间隔越长,缓存的数据越多,当缓存不够的时候,最少使用的数据将被删除。缓存过期可以手工设置。
五、Mapping
5.1、Mapping概述
Mapping是用来定义一个ducument和他所包含的fields,如何被存储和索引的过程。例如,使用mapping来定义:
1、字符串字段是否作为全文本搜索字段。
2、哪些字段包含数据,日期或地理信息
3、document中所有field的值是否应该被索引到_all字段
4、日期值的格式
5、自定义规则来控制动态添加的字段的映射。
5.2、Mapping类型概述
每个index有一个或多个mapping type, type是对ducment划分的逻辑组,index中每个document都有一个type,每个type拥有自己的mapping或模式定义(schema definition)。每个Mapping type包括:
1、关联到类型上的元数据,如:_index, _type, _id, _source
2、字段或属性的定义,如:字段类型,每个字段的数据类型,以及字段被ES处理的方式。
数据类型概述
数据类型有如下几类:
1、基本类型:string, date, long, double, boolean, ip等
2、嵌套类型
3、特殊类型:geo_point, geo_shape, completion等
5.3、动态Mapping概述
在添加数据的时候,ES会使用动态mapping类猜测字段类型,从而自动进行映射。在运行期需要的时候,动态的添加映射,加入新的type或者字段,效果跟预先做好映射是一样的。
看个官方文档的sample:
PUT my_index {
"mappings":{
"user":{
"_all":{"enabled":false}
"properties":{
"title":{"type":"string"},
"body":{"type":"string"},
"user_id":{
"type":"string","index":"not_analyzed"
}
},
"created":{
"type":"string","format":"strict_date_optional_time||epoch_millis"
}
}
}
}
查看type的mapping
curl -XGET http://localhost:9200/mytest/_mapping/product?pretty
根对象
mapping最高一层被称为根对象,他包含下面几项:
1)一个properties节点,列出了文档中可能包含的每个字段的mapping
2)多个元数据字段,每个都以下划线开头,例如_type, _id, _source
3)设置项,控制如何动态处理新的字段,如analyzer, dynamic_date_formats,dynamic_templates
4)其他属性,可以同时应用在根对象和其他obejct类型的字段上,如enabled, dynamic等属性。
属性
属性就是properties节点下的属性的设置,可以设置很多东西,比如:
1、type
{
"字段1":{"type":"integer"}
}
2、index:index参数控制字符串以何种方式被索引,它包含下面三个之一:
1)analyzed:首先分析这个字符串,然后索引。换言之,以全文形式索引此字段。
2)not_analyzed:索引这个字段,使之可以被搜索。但索引内容和指定值一样。不分析此字段。
3)no:不索引这个字段,这个字段不能被搜索到。
string类型字段默认是analyzed。如果我们想映射字段为确切值,需要设置他为not_analyzed:
"字段1":{
"type":"string","index":"not_analyzed"
}
注意:除了string外的其他简单类型(long, double, date等)也接受index参数,但相应的值只能是no或not_analyzed,他们的值不能被分析。
3、analyzer:确定在索引和搜索时全文字段使用的分析器。
对于analyzed类型的字符串字段,使用analyzer参数来指定哪一种分析器,将在搜索和索引的时候使用。默认的,ES使用standard分析器,但是你可以通过指定一个内奸的分析器来更改他,如:whitespace, simple, english
{
"字段1":{
"type":"string","analyzed":"english"
}
}
5.4、动态模板
使用dynamic_templates, 你可以完全控制新字段的mapping,你设置可以通过字段名或数据类型应用一个完全不同的mapping。
每个模板都有一个名字,用于描述这个模板的用途,一个mapping字段用于指明这个mapping怎么使用,和至少一个参数(如match)来定义这个模板适用于哪个字段。
模板安装顺序来检查,第一个匹配的模板会被启用。例如给string类型字段定义两个模板:
es:字段名以 _es 结尾需要使用spanish分析器
en:所有其他字段使用 english 分析器
我们讲es模板放在第一位,因为它比匹配所有字符串的 en 模板更特殊一点:
PUT /my_index {
"mapping":{
"my_type":{
"dynamic_templates":[
"es":{
"match":"*_es",
"match_mapping_type":"string",
"mapping":{
"type":"string","analyzer":"spanish"
}
},
"en":{
"match":"*",
"match_mapping_type":"string",
"mapping":{
"type":"string","analyzer":"english"
}
}
]
}
}
}
六、倒排索引
6.1、Lucene如何处理文档
在Lucene中,一个文档由一组简单的key/value对组成,一个字段至少需要有一个value,但是任何字段都可以有多个value。类似的,一个单独的字符串可能在分析过程中被转换成多个值。Lucene不关心这些值是字符串、数字还是日期,所有的值都被当成不透明字节。
当我们在Lucene中索引一个文档时,每个字段的值都被加到相关字段的倒排索引中。你也可以选择将原始数据存起来以备今后取回。
ES中类型是怎么实现的
ES中一个index可能包含多个type,每个type有各自的mapping和document。因为Lucene没有document type的概念,每个document的type名被存储在一个叫 _type的元数据字段中。当我们搜索一种特殊类型的document时,ES简单的通过 _type字段来过滤出这些document。
Lucene同样没有mapping的概念,mapping是ES将复杂的json文档映射成Lucene需要的扁平化数据的方式。
预防type陷阱
事实上,不同type的document可以被加到同一个index里,这带来了一些预想不到的困难。为了保证你不会遇到这些冲突,建议在同一个index的每一个type中,确保用统一的方式映射同名的字段。
6.2、倒排索引概述
ES使用一种叫做倒排索引(inverted index) 的结构来做快速的全文搜索。倒排索引由”在文档中出现的唯一的单词列表,以及对每个单词在文档中的位置“组成。
现代搜索引擎的关键步骤就是建立倒排索引,倒排索引一般表示为一个关键词,然后是他的频度(出现的次数),位置(出现在哪一篇文章或网页中,及有关的日期,作者等信息)等构成。例如,我们有两个文档,每个文档content字段包含:
Doc1:This is a dog
Doc2:That is a dog too
为了创建倒排索引,我们首先切分每个文档的content字段为单独的单次(叫做terms或tokens),然后把所有的唯一词放入列表并排序,结果是这个样子:
现在想要搜索” is a dog too", 就只需要找到每个词在文档中存在即可。
很明显,Doc1包含了3个,而Doc2包含了4个词,如果此时我们来个简单的相似度算法,只计算匹配单次的数目,这样我们就可以说第二个文档比第一个匹配度更高,也即是第二个文档对于我们的查询具有更多的相关性。
这样简单的方式存在一些问题,比如:
1)This和this会任务是不同的词
2)dog和dogs很相似,毕竟他们是同根词
3)dog和puppy意思相近,他们是同义词
如果按照简单的匹配的话,上面这些情况就得不到我们预期的结果了。
解决方式:我们可以对存储的term进行一些变化操作,比如:
1)统一大小写,比如都小写
2)同根词都记录成 词根
3)同义词都转换成一个词来记录
6.3、Lucene概述
Lucene是一个开源的高性能的java全文搜索引擎工具包,不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。
Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便在目标系统中实现全文检索的功能,或以此为基础建立起完成的全文检索引擎。
6.4、Lucene倒排索引
Lucene采用的也是倒排索引,不过仅知道关键词在哪些文章中出现还不够,我们还需要知道关键词在文章中出现的次数和出现的位置,通常有两种位置:
1、字符位置,即记录该次是文章中第几个字符(优点是关键词高亮显示时定位快)
2、关键词位置,即记录该次是文章中第几个关键词(优点是节约空间、词组(phase)查询快),Lucene中记录的就是这种位置。
因此,Lucene记录的格式大致是:词项、词频、指针,示例如下:
6.5、Lucene的基本实现
实现时,Lucene将上面的三列分别作为词典文件(Term Dictionary)、词频文件(frequencies)、位置文件(positions)保存。其中词典文件不仅保存有每个关键词,还保留了指向频率文件和位置文件的指针,通过指针可以找到该关键词的频率信息和位置信息。
Lucene使用了field的概念,用于表达信息所在位置(如标题中,文章中,url中),在建索引中,该field信息也记录在词典信息中,每个关键词都有一个field信息(因为每个关键字一定属于一个或多个field)
压缩算法
为了减小索引文件的大小,Lucene对索引使用了压缩技术。首先,对词典文件中的关键词进行了压缩,关键词压缩为<前缀长度,后缀>,例如:当前词为“阿拉伯语”,上一个词为“阿拉伯”,那么“阿拉伯语”压缩为<3,语>。
其次大量用到了对数字的压缩,数字只保存与上一个值的差值(这样可以减少数字的长度,进而减少保存该数字需要的字节数)。
6.6、ES的倒排索引
1、对document构建倒排索引
2、写入磁盘,而且写入磁盘的倒排索引是不可变的。他有如下好处:
1)不需要锁:因为从来不需要更新一个索引,就不必担心多个程序同时尝试修改
2)一旦索引被读入文件系统的缓存,他就一直在那,不会改变。只要文件系统缓存有足够的空间,大部分的读会直接访问内存而不是磁盘,有助于提升性能。
3)在索引的声明周期内,所有的其他缓存都可用。他们不需要在每次数据变化了都重建,因为数据不会变。
4)写入单个大的倒排索引,可以压缩数据,减少磁盘IO和需要缓存索引的内存大小。
3、不可变的索引有如下缺点:如果想要搜索一个新document, 必须重建整个索引。这不仅严重限制了一个索引能装下的数据,还有一个索引可以被更新的频次。
动态索引
ES是如何在保持不可变的同时更新倒排索引的?
答:使用多个索引。不是重写整个倒排索引,而是增加额外的索引反映最近的变换。每个倒排索引都可以按顺序查询,从最老的开始,最后把结果聚合。
ES底层依赖的Lucene,引入了per-segment search概念,一个段(segment)是有完整功能的倒排索引,但现在Lucene中的索引指的是segment的集合,再加上提交点(commit point,包含所有segment的文件)。
动态索引的说明:
1、Lucene索引是ES中的分片,ES中的索引是分片的集合。当ES搜索索引时,他发送查询请求给该索引下的所有分片,然后过滤这些结果,聚合成全局的结果。
2、一个per-segment search这样工作:
1)新的文档首先写入内存区的索引缓存
2)然后,这些buffer被提交:
- 一个新的segment – 额外的倒排索引 – 写入磁盘
- 新的提交点写入磁盘,包括新segment的名称
- 磁盘是文件同步的 – 所有写操作等待文件系统缓存同步到磁盘,确保他们可以被物理写入
3)新segment被打开,他包含的文档可以被检索
4)内存的缓存被清除,等待接受新的文档。
3、当一个请求被接受,所有segment一次查询。所有segment上的Term统计信息被聚合,确保每个Term和document的相关性被正确计算。通过这种方式,新的document以较小的代价加入索引。
删除和更新
segment是不可变的,所以document既不能从旧的segment中移除,旧的segment也不能更新以反映document最新的版本。相反,每一个提交点包括一个.del文件,包含了segment上已经被删除的document。
当一个document被删除,他实际上只是在.del文件中被标记为删除,新版本的document在新的segment中索引。也许该document的不同版本都会匹配一个查询,但是更老版本会从结果中删除。
理解近实时的搜索
因为per-segment search机制,索引和搜索一个document之间是有延迟的。新的document会再几分钟内可以搜索,但这依然不够快。磁盘是瓶颈,提交一个新的segment到磁盘,需要fsync操作,确保segment被物理地写入磁盘,即使电源失效也不会丢数据。但fsync是昂贵的,他不能再每个document被索引时就触发。
所以需要一种更轻量级的方式,使新的documewnt可以被搜索,这意味着移除fsync。位于ES和磁盘间的是文件系统缓存。在内存索引缓存中的document被写入新的segment,但是新的segment首先写入文件系统缓存,这代价很低;之后会被同步到磁盘,这个代价很大。但一旦一个文件被缓存,他也可以被打开和读取,就像其他文件一样。
Lucene允许新segment写入打开,好让他们包括的document可搜索,而不用执行一次全量提交。这是比提交更轻量的过程,可以经常操作,而不会影响性能。
Refresh API
在ES中,这种写入打开一个新segment的轻量级过程,叫做refresh。默认情况下,每个分片每秒自动刷新一次。这就是为什么说ES是近实时的搜索了:document的改动不会立即被搜索,但会在1秒内可见。
可以手动执行refresh来刷新所有的索引,如:
POST /_refresh
也可以只刷新索引blogs:
POST /blogs/_refresh
虽然刷新比提交更轻量,但他依然有消耗。人工刷新在测试写的时候有用,但不要在生产环境中每写一次就执行刷新,会影响性能。
通常我们想要的是优化索引的速度,而不是要做实时搜索,这时可以通过修改配置项refresh_interval减少刷新的频率:
PUT /my_logs {
"settings":{"refresh_interval":"30s"
}
refresh_interval可以在存在的索引上动态更新。你在创建刷新,大索引的时候,可以关闭自动;在要使用索引的时候再打开他。如:
PUT /my_logs/_settings {
"refresh_interval":-1
}
PUT /my_logs/_settings {
"refresh_interval":"1s"
}
持久化变更
为了ES的可靠性,需要确保变更持久化到磁盘。ES增加了事务日志(translog),来记录每次操作。有了事务日志,过程如下:
1、当一个document被索引,他被加入到内存缓存,同时加到事务日志
2、refresh使得缓存被清除,但事务日志没有,会执行:
1)内存缓冲区的写入到segment中,但没有fsync
2)segment被打开,使得新的document可以搜索
3)缓存被清除。
3、随着更多的文档加入到缓存区,写入日志,这个过程会继续
4、当日志很大了,新的日志会被创建,会进行一次全提交:
1)内存缓存区的所有document都会写入到新segment中
2)清除缓存
3)一个提交点写入硬盘
4)文件系统缓存通过fsync操作flush到磁盘
5)事务日志被清除。
5、事务日志记录了没有flush到硬盘的所有操作。当故障重启后,ES会用最近一次提交点从硬盘恢复所有已知的segment,并且从日志里恢复所有的操作。
6、事务日志还用来提供实时的CRUD操作。当你尝试用ID进行CRUD时,它在检索相关segment内的document前会首先检查日志的最新改动,这意味着ES可以实时地获取document的最新版本。
合并Segment
通过每秒自动刷新创建新的segment,用不了多久segment的数量就爆炸了。有太多的segment是一个问题,每个segment消费文件句柄,内存,CPU资源。更重要的是,每次搜索请求都需要一次检查每个segment,segment越多,查询越慢。
ES通过后台合并segment解决这个问题,小segment被合并成大segment,再合并成更大的segment。然后删除旧的document。这个过程你不必做什么。当你在索引和搜索时,ES会自动处理。索引过程中,refresh会创建新的segment , 然后打开它。
合并过程会再后台选择一些小的segment合并成大的segment,这个过程不会中断索引和搜索。
合并后的操作大致如下:
1、新的segment flush到磁盘
2、新的提交点写入新的segment
3、新的segment打开供搜索
4、旧的segment被删除
合并大的segment会很消耗IO和CPU,如果不检查会影响到搜索性能。默认情况下,ES会限制合并过程,这样搜索就可以有足够的资源进行。
Flush API
在ES中,进行一次提交并删除旧事物日志的操作叫做flush。分片每30分钟,或事物日志过大会进行一次flush操作。flush API可用来进行一次手动flush。
flush索引blogs:
POST /blogs/_flush
flush所有索引,等操作结束再返回:
POST /_flush?wait_for_ongoing
当然,很少需要手动flush,通常自动的就够了。当你要重启或关闭一个索引,flush该索引就是很有用的。当ES尝试恢复或重新打开一个索引时,他必须重放所有的事务日志中的操作,所以日志越小,恢复速度越快。
七、分析器
7.1、分析器概述
索引的分析模块,主要实现分析器的注册、设置文档在索引阶段和搜索阶段使用的分析器,其功能映射到Lucene的Analyzer。
分析器的基本构成:
1、字符过滤器(0到多个)
字符串经过字符过滤器(character filter),在标记前处理字符串。字符过滤器能够去除HTML标记,或者转换 & 为 and
2、分词器(只有一个)
分词器(tokenizer)把数据标记化成独立的词。一个简单的分词器(tokenizer)可以根据空格或逗号将单词分开(这个在中文中不适用)
3、标记过滤(0到多个)
每个词都通过所有标记过滤(token filters), 他可以修改词(如将"Quick“转为小写),去掉词(如停用词像"a", “and”, “the"等),或者增加词(如同义词像"jump” 和 "leap”)
Analyzer由一个单一的Tokenizer和零到多个TokenFilter组成,tokenizer可以被0到对哦个CharFilter处理。Analysis模块允许你注册多个Analyzer的逻辑名字,这样在Mapping或其他API中就可以使用这些名字了。