目录
ElasticSearch基础
ElasticSearch工作流程
启动流程
当ES启动时,它使用发现模块(discovery)模块来发现同一个集群中的其他节点(关键是配置集群名字)并与它们连接。默认情况下ES节点会向网络发送广播,以找到同集群的其他节点。
集群中有一个节点被选为主,该节点负责集群的状态管理和集群在拓扑变化时做出反应。分发索引分片到集群的相应节点上去。
管理节点读取集群的状态信息,如果有必要,它会进行恢复处理。在该阶段管理节点会检查有哪些索引分片,并决定哪些分片将用做主分片。此后,整个集群进入黄色状态。
这意味着,集群可以执行查询,但是吞吐量是未知的,如果某个主分片的副本较少,管理节点将决定根据主分片创建分片和副本,如果一切顺利,集群将进入到绿色状态。
故障检测
正常工作时,管理节点会监控所有可用节点,如果任何节点在预定时间内不响应,则认为该节点已经断开。然后错误处理过程开始启动。这意味着可能在集群-分片间重新做平衡,选择新的主节点。对每个丢失的主分片,一个新的主分片将从原来的副本中选择出来。新分片和副本的放置策略是可配的。主节点会发送ping请求到相应的其他节点,相反的,所有节点也会向主节点发送ping 请求来检查主节点是否在正常工作。
查询DSL进阶
检测模型
布尔检测模型
原理:
布尔检索模型主要是把AND,OR,NOT三种逻辑运算符把检索词连接起来,构成一个逻辑检索式。
优点:
- 简单,容易理解
- 可以处理结构化查询
- 速度快
缺点
- 检索策略只是基于0或1的完全比配
- 不存在语义特性
- 没有加权的概念
tf-idf检测模型
原理:
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
TFIDF的主要思想是:如果某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。TF-IDF实际上是:TF * IDF,TF词频(Term Frequency),IDF逆向文件频率(Inverse Document Frequency)。TF表示词条在文档d中出现的频率。IDF的主要思想是:如果包含词条t的文档越少,也就是n越小,IDF越大,则说明词条t具有很好的类别区分能力。如果某一类文档C中包含词条t的文档数为m,而其它类包含t的文档总数为k,显然所有包含t的文档数n=m+k,当m大的时候,n也大,按照IDF公式得到的IDF的值会小,就说明该词条t类别区分能力不强。但是实际上,如果一个词条在一个类的文档中频繁出现,则说明该词条能够很好代表这个类的文本的特征,这样的词条应该给它们赋予较高的权重,并选来作为该类文本的特征词以区别与其它类文档。
向量空间模型
原理:
该方法主要是把文本内容转换为向量空间的向量运算,以空间上的相似度表达语义的相似度。该模型的数学基础是余弦相似性理论。
概率检索模型
是目前效果最好的模型之一,okapi BM25这一经典概率模型计算公式已经在搜索引擎的网页排序中广泛使用。概率检索模型是从概率排序原理推导出来的。
原理:
是通过概率的方法将查询和文档联系起来,给定一个用户查询,如果搜索系统能够在搜索结果排序时按照文档和用户需求的相关性由高到底排序,那么这个搜索系统的准确性是最优的。在文档集合的基础上尽可能准确地对这种相关性进行估计就是其核心。
缺点:
- 计算复杂度大,不适合大型网络
- 参数估计难度较大
- 条件概率值难估计
- 系统的检索性能提高不明显,需与其他检索模型结合
查询改写
在进行"前缀查询"或是"通配符查询"时,实际上会转化为对多个关键词的查询,处于性能优化的考虑,Lucene会把这种查询转化为一组开销较小的查询方式,采取的主要手段便是“查询改写(rewrite)”
查询改写使用的主要策略为通过使用常量得分查询(constant score query)来代替常规查询来减少查询时性能的开销,对于可能匹配term较多的查询时,性能提升尤为明显。
在查询改写的过程中可以根据需要自定义改写的方式,可支持的配置如下:
- scoring_boolean:该选项将每个生成的词项转化为布尔查询中的一个或从句(Boolean should clause)。这种改写方法需要针对每个文档都计算得分。因此,这种方法比较耗费CPU,而且有些查询生成了太多的词项,以至于超出了布尔查询默认的1024个从句的限制。默认的布尔查询限制可以通过设置Elasticsearch.yml文件的index.query.bool.max_clause_count属性来修改。改写后的布尔查询的从句数越多,查询性能越低
- constant_score_boolean:该选项与前面提到过的scoring_boolean类似,但是CPU耗费更少,这是因为并不计算每个从句的得分,而是每个从句得到一个与查询权重相同的一个常数得分,默认情况下等于1,我们也可以通过设置查询权重来改变这个默认值。与scoring_boolean类似,该选项也有布尔从句数的限制。
- constant_score(==默认选项==):在匹配的词项较少时,与constant_score_boolean类似,匹配的词项较多时,会遍历并查询所有的词项,然后使用相同的常数得分。
- top_terms_N:与scoring_boolean相似之处在于都会对从句计算得分,不同之处在于,该方法只保留最佳的N个词项,以避免触及布尔从句数的限制,并提升查询整体性能。
- top_terms_boost_N:该选项与top_terms_N类似,不同之处在于它的文档得分不是通过计算得出的,而是被设置为跟查询权重(boost)一致,默认值为1。
对于如何选择rewrite配置的问题,可以遵循如下原则:
- 如果您能接受较低的精度和相关性(但是追求更高的性能),那么可以采用top-N查询改写方法。
- 如果您需要更高的查询精度和更好的相关性(同时可以接受较低的性能),那么应该采用布尔方法。
过滤器
过滤器的作用
Elsaticsearch在提供了丰富查询功能的同时,还提供了过滤器的功能,过滤器的作用顾名思义,主要用来对查询结果过滤,但过滤的过程中不会涉及到store的计算性能损耗(是不会对已有查询的store造成影响),同时频繁使用的过滤器还会被Elasticsearch自动的缓存,总而提高查询的性能。
工作原理
过滤器持有的关于文档的唯一重要信息是该文档是否匹配这个过滤器(仅仅一个标记而已)
过滤器通过返回一个被称为DocIdSet(org.apache.lucene.search.DocIdSet)的数据结构来提供匹配信息。这个数据结构的用途是为索引段提供经过滤器过滤后的数据。它可以使用Bits接口(org.apache.lucene.util.Bits)的有关实现。Bits接口可以随机访问过滤器中的文档信息(主要是检查索引段中的某个文档是否和该过滤器匹配)。Bits的数据结构非常高效,因为CPU可以使用位运算来完成过滤。
不是所有过滤器都是用Bits数据结构,比如数值区间过滤器、脚本过滤器、以及基于地理位置的一组过滤器。这些特殊的过滤器选择把数据记录在字段缓存里,然后再遍历所需处理的文档集合,逐个进行过滤操作。
与或非过滤器和布尔过滤器
与或非过滤器不使用Bits,而布尔过滤器使用Bits。因此,请尽可能使用布尔过滤器,与或非过滤器一般在需要脚本过滤、地理位置过滤、和数值区间过滤时使用。
一般来讲,在组合使用多个过滤器时,如果其中包含不使用Bits的处理器,则需要使用与或非过滤器,如果所有的都是用Bits结构,则可以使用布尔过滤器。
性能考量
最重要的一点,过滤器所处理的查询部分不需要计算文档得分。
在1.4版本以后,执行嵌套查询时所使用的bitsets默认提前加载就好了。这样做可以使查询更快但更耗费内存。
可以通过index.load_fixed_bitset_filters_eagerly配置项为false来禁用提前加载。
后置过滤和过滤查询
某些时候,使用后置过滤(post_filter)时,Elasticsearch查询的执行速度没有期望的快。
下图展示了查询的执行过程:
这样,Doc3是没有必要计算得分的。
原则:如果过滤器执行很快,开销很小,并且易于缓存,直接使用过滤查询即可。相反,如果过滤器执行很慢,CPU开销较大,并且难于缓存(比如有大量唯一值的情况),请使用后置过滤,或者尝试优化过滤器。比如可以降低时区间过滤器的时间粒度。
查询的分类
查询方法
在不使用API的情况下,两种查询方式:
- URL查询
curl -XGET 'localhost:9200/twitter/tweet/_search?q=user:kimchy&pretty'
- Request Body 查询
curl -XGET 'localhost:9200/twitter/tweet/_search?pretty' -H 'Content-Type: application/json' -d'
{
"query" : {
"term" : { "user" : "kimchy" }
}
}'
基本查询
基本查询内部不可包含其他查询,这类查询通常作为其他复杂查询的一部分或者单独传给ES.
- match:该查询会对==查询词==进行分词,分词后查询语句中的任何一个词项被匹配,文档就会被搜索到。这种查询不需要查询语法解析,可以使用这种查询方式。
- match_all:该查询匹配所有类型的文档,常用于对所有索引内容进行归类处理的场景。
- term 查询:该查询的"查询词"不会被分词。
- query_string查询:该查询允许在一个查询中使用多个特殊的条件关键字进行查询(例如 AND|OR|NOT)对多个字段进行查询
- 其他:match,multi_match,fuzzy_like_this,ids,query_string,simple_query_string,range,prefix,term,terms等等
组合查询
组合查询的唯一用途是把其他查询组合在一起使用。
- bool查询:可以把多个查询用布尔逻辑组织在一起
- dis_max查询:这种查询的结果文档得分和最高权重的子查询打分高度相关,而不是如bool查询那样对所有子查询得分进行求和。如果希望最高得分子查询在打分过程中起到决定性作用,该查询不二选择。
无分析查询
该类查询不需要进行分析,而是直接传递给lucene索引。这意味着我们不需要操心分析过程是否如我们期望的方式执行并生成合适的词项,也不需要针对特定的不分词字段执行查询。这类查询精确匹配传入的词语,不会使用语言分析器等工具对词语进行分词或其他处理。
- term查询:该查询的"查询词"不会被分词。
- prefix查询:前缀查询。常用于自动完成功能,用户输入一段文本,搜索系统返回所有这个文本开头的文档。
- 这类查询包括:common,ids,prefix,span_term,term,terms,wildcard查询。
全文检索查询
当需要构建类似于Google这样的查询时,可以使用该类查询,该类查询会根据输入与配置进行查询结果的打分操作。
相关查询主要包括:match,multi_match,query_string,simple_query_string等
模式匹配查询
通配符查询(wildcard query)、前缀查询(prefix query)、正则表达式查询(regexp query)
支持相似度查询的查询
这类查询可以根据给定词项查找近似词项或文档的查询方式的集合。例如,需要找出包含“crimea”近似的词项,可以执行fuzzy查询。或者,希望找出文档标题和输入文本标题相似的文档,可以使用more_like_this查询。
这类查询包括:fuzzy_like_this,fuzzy_like_this_field,more_like_this,more_like_this_field.
打分相关的查询
该类查询可以通过自定义权重的方式来改善查询精度或是查询得分。
词项位置敏感查询
可以使用索引中存储的词项位置作为查询条件的查询,例如 “需要查询masting以及elasticsearch两个词项,且这连个单词相互临近,顺序必须是masting在前面,前后距离还不超过3个位置”
该类查询的开销也很大,需要消耗大量的CPU资源
相关查询主要包括:match_phrase,span_XXX
结构敏感查询
该种类型的查询可以提供类似于SQL中Join的关系查询 ,能够在子文档或者嵌套文档中进行查询,或查找属于给定父文档的子文档,则需要使用刚刚提及的查询方式之一。
- nested查询:文档中可以包含嵌套类型的字段,这些字段可以用来索引一些数组对象,每个对象都可以单独的查询
- 这类查询包括:nested,has_child,has_parent,top_children
地理位置查询
该类查询主要针对geo_point的查询以及geo_shape
相关查询主要包括:geo_distance,geo_bounding_box,geo_polygon,geo_shape
搜索高亮查询
该类查询主要通过对查询结果使用默认标签()或是自定义标签的方式来实现查询结果高亮的应用场景
相关查询主要包括:highlighter,fast_vector_highlighter
分布式索引架构
选择合适的分片数和副本数
依照经验,最理想的分片应该依赖于节点的数量。分片的数量需要在创建索引的时候确定,但副本的数量可以使用相关API随时更改。增加副本数理论上可以增加处理请求的并发量。
- 如果数据集的大小有限制而且严格定义好的,可以只使用一个分片,一个副本
- 除了上述情况,可以采用公式 “所需最大节点数=分片数*(副本数+1)”的计算方式,例如你计划用10个分片和2个分片副本,那么最大的节点数是30
分片分配过度的影响:
增加分发搜索命令到每个分片以及分片结果合并的开销
副本分配过度的影响:
额外的存储空间开销 ;从主分片复制数据到分片副本时的开销
主节点的主要功能之一是决定将哪些分片分配给哪些节点,以及何时移动节点之间的分片以集群的数据平衡。
- 群集级别的分片分配策略:分片分配是将分片分配给节点的过程。这可能发生在初始恢复,副本分配,rebalancing或添加/删除节点时。
- 基于磁盘的的分片分配策略:Elasticsearch可以基于磁盘的使用情况配置分配策略来决定是将新分片分配给该节点还是主动将分片从该节点迁出.
- 基于机架/区域感知的分片分配策略:和大多数分布式集群的分配策略一样,ES也支持基于机架/区域感知的分片分配策略,该策略针对大面积基础设施出现问题时的保证集群高可用的策略。
- 基于过滤的分片分配策略:可以在索引或是集群级别来配置分片的分配,例如那些节点可以分配集群,那些节点不可以分配某个索引等。
路由
路由和分片
在分布式环境下一个索引可以分多个shard,在索引数据的时候,索引数据采用以下算法写到对应的shard上:
shard_num = hash(_routing) % num_primary_shards
1
默认情况下,Elasticsearch使用文档ID值作为依据将其哈希到相应的主分片上,这种算法基本上会保持所有数据在所有分片上的一个平均分布,而不会产生数据热点。
假设你有一个100个分片的索引。当一个请求在集群上执行时会发生什么呢?
这个搜索的请求会被发送到一个节点
接收到这个请求的节点,将这个查询广播到这个索引的每个分片上(可能是主分片,也可能是复制分片)
每个分片执行这个搜索查询并返回结果
结果在通道节点上合并、排序并返回给用户
因为默认情况下,Elasticsearch使用文档的ID(类似于关系数据库中的自增ID,当然,如果不指定ID的话,Elasticsearch使用的是随机值)将文档平均的分布于所有的分片上,这导致了Elasticsearch不能确定文档的位置,所以它必须将这个请求广播到所有的100个分片上去执行。这同时也解释了为什么主分片的数量在索引创建的时候是固定下来的,并且永远不能改变。因为如果分片的数量改变了,所有先前的路由值就会变成非法了,文档相当于丢失了。
通过路由控制索引数据
所有的文档API(get,index,delete,update和mget)都能接收一个routing参数,可以用来形成个性化文档分片映射。一个个性化的routing值可以确保相关的文档存储到同样的分片上——比如,所有属于同一个用户的文档。
指定方法1:
curl -XPOST 'http://localhost:9200/store/order?routing=user123' -d '
{
"productName": "sample",
"customerID": "user123"
}'
指定方法2:
curl -XPUT 'http://localhost:9200/store/order/_mapping' -d '
{
"order": {
"_routing": {
"required": true,
"path": "customerID"
}
}
}'
通过路由控制检索数据
利用路由机制的查询也是非常简单明了的,只需要在查询中指定对应的路由值即可:
curl -XGET 'http://localhost:9200/store/order/_search?routing=user123' -d '
{
"query": {
"filtered": {
"query": {
"match_all": {}
},
"filter": {
"term": {
"userID": "user123"
}
}
}
}
}'
通过指定的路由值,我们就可以直接定位到user123的文档所在的分片,而不用一股脑的向索引的所有节点都发送请求。这样的话,会大大减少系统资源的浪费。
别名
理解为一个虚拟的索引,这个虚拟索引对真在存在的索引进行一些特殊包装,对于使用者而言可以像使用正常索引那样来使用别名。大致可以完成如下功能
- 用来将某个路由值与某个索引绑定
- 一个别名可以对应多个真实索引
- 隐藏索引配置的细节
curl -XPOST 'http://localhost:9200/_aliases' -d '
{
"actions": {
"add": {
"index": "documents",
"alias": "documentsA",
"routing": "A"
}
}
}'
调整分片的部署策略
部署意识
部署意识允许我们使用参数来配置分片和它们的副本的部署。
例:
在Elasticsearch.yml文件里加入下面的属性:
cluster.routing.allocation.awareness.attributes: group
这会告诉Elasticsearch使用node.group属性作为意识参数。
在使用分片部署意识的时候,ES不会将主分片和副本放到拥有相同属性值的节点上(本例是node.group)。
同时,我们可以强制部署意识由特定值激活。如下图:
过滤
ES允许我们在整个集群或者索引的级别来配置分片的分配。
属性配置如下:
##集群级别
cluster.routing.allocation.include
cluster.routing.allocation.require
cluster.routing.allocation.exclude
##索引级别
index.routing.allocation.include
index.routing.allocation.require
index.routing.allocation.exclude
- include:包含所有定义了这个参数的节点
- require:要求所有的节点必须拥有和属性值匹配的值
- exclude: 排除特定的节点
也可以在运行时修改,相关API略。
确定每个节点可以给一个索引分配的分片总数:
index.routing.allocation.total_shards_per_node: 4
查询执行偏好
在Elasticsearch中,会将相关操作随机分发到分片或者分片副本上。如果往集群中发送大量的查询命令,最终每个分片和分片副本上执行的查询命令数量会大致相同,但是可以通过preference参数来配置查询的执行地点。
为了便于说明,借助下表对参数进行说明:
节点名称 | 节点编号 | 包含索引 | 分片与副本分配 |
node1 | 6GVd-ktcS2um4uM4AAJQhQ | masting | primary shard 0 |
node2 | iw76-abdcdefdsdsdsdfsd | masting | primary shard 1 |
node3 | wJq0kPSHTHCovjuCsVK0-A | masting | replica shard 1,replica shard 0 |
- _primary: 使用该属性值,发送到集群的相关操作请求只会在主分片上执行。
- _primary_first:该属性值与_primary属性值导致相似的集群行为,但是具有容错机制。如果发送查询命令到mastering索引时附带了值为_primary_first的preference参数,该命令将在名称为node1和node2的节点上执行,但是如果有一个(或者更多)的主分片失效,查询命令将转到其它的分片上执行,在本例中会转到node3上执行。
- _local: ElasticSearch会优先在本地的节点上执行相关操作。比如,如果我们向node3发送附带一条preference参数值为_local的查询命令,最终该查询命令会在node3上执行。但是,如果我们把相同的命令发送到node2节点,那么最终该命令不仅会在编号为1的分片(节点为node2)上执行,同时也会分发到node1或者node3上执行,这两个节点上有编号为0的分片。该属性值在减小网络传输时间上特别有用。只要用到了_local preference参数值,我们就能确保查询命令会尽可能地在本地的节点上执行。
- _only_node:wJq0kPSHTHCovjuCsVK0-A:这类的操作只会在指定标识(本例中是wJq0kPSHTHCovjuCsVK0-A)的节点上执行。所以在本例中,查询命令只会在node3节点上的两个分片副本上执行。需要注意的是,如果指定节点中的分片不足以覆盖到整个索引的数据,那么命令就只会在指定节点的相关分片上执行。比如,如果我们将查询命令的preference参数值设置为 _only_node:6GVd-ktcS2um4uM4AAJQhQ,我们就只会获取到一个分片的数据。这个属性值在如下的应用场景中非常有用:用户已经知道某个节点所在的服务器处理能力强大,希望一些特定的查询命令只在该节点上执行。
- _prefer_node:wJq0kPSHTHCovjuCsVK0-A:这个选项用来把preference参数值设置成_prefer_node:,后面附带的值是一个节点Id(本例中就是wJq0kPSHTHCovjuCsVK0-A) ,这会导致ElasticSearch优先选择指定的节点来执行查询命令,但是如果该节点上缺少索引数据的一些分片,那么查询命令会发到含有欠缺分片的节点上。这与_only_node选项是类似的,_prefer_node也可以用来选择特定的节点,但是具备容错机制。
- _shards:0,1:这个preference值用来指定相关操作执行的某类分片(在本例中就是所有的分片,因为整个mastering索引只有id为0和1的分片)。这是唯一的一个可以结合其它属性使用的preference值。比如,如果希望命令只执行在本地节点的id为0或者1的分片上,我们可以把0,1两个值和_local值用“;”连接起来,最终得到的preference参数值就是这样了:0,1:_local。允许用户发出的命令只在某个分片上执行这一特性用于诊断集群问题是非常有帮助的。
- 自定义字符串值:这个自定义值会确保附带相同custom值的查询命令会在同样的分片上执行。比如,如果我们的查询命令附带preference参数值为mastering_Elasticsearch,那么命令会在node1和node2的主分片上执行。如果我们又发送了另一个附带同样preference参数值的查询命令,该命令也只会在node1和node2的分片上执行。该功能用于应对以下应用场景:假如集群中的各个节点刷新速率不一样,我们不希望用户在重复同一个命令时看到不同的结果,就应该使用该功能。