当前位置:首页 » 操作系统 » es推荐算法

es推荐算法

发布时间: 2022-11-26 09:57:18

‘壹’ es深入搜索之全文检索

  我们之前介绍过结构化搜索的简单使用,接下来,我们来看怎样在全文字段中搜索最相关的文档。

  全文搜索包括两个最重要的方面:

  1. 查询与结果的相关性,并根据相关性对结果进行排名。
  2. 分析,将数据转化为有区别的、规范化的的过程。

  所有的查询都或多或少的会进行相关度计算,但不是所有的查询都会有分析阶段,文本查询可以分为两个部分:
  1. 基于词项的查询,如 term 或 fuzzy 这样的查询是没有分析阶段的。他们对单个词项进行操作。
  2. 基于全文的查询,比如match, 它们会先了解字段映射的信息,判断字段是否被分词,是否是日期还是数字等, 再根据映射信息,构建要查询的词项列表,根据列表进行查询。

  匹配查询 match 是个核心查询。无论需要查询什么字段, match 查询都应该会是首选的查询方式。使用方式如下:

es执行上列步骤的过程如下:

  如果一次只能搜索一个词语,那么全文搜索会不太灵活,幸运的是 match 也支持多词查询 。

以上查询其实先后执行了两次 term 查询,使用 bool 进行包含,然后将结果进行合并返回。

  以上查询其实会导致出现不相关的结果,我们只想搜索包含words1 和 words2 的文档,而不是 or 的结果。match 查询还可以接受 operator 操作符作为输入参数,默认情况下该操作符是 or 。

这种操作还是有些不妥,在 and 和 or 中间选择太过绝对,如果用户给出了5个词项,我们想只要满足其中4 个 就表示匹配,match 也提供了 minimum_should_match 参数,他是一个最小匹配参数,我们可以控制满足的词项超过改值则表示匹配,最好是使用百分比,因为你也不知道用户提供了多少个词项。该参数的设置非常灵活,完整的信息参考文档,请看 https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-minimum-should-match.html#query-dsl-minimum-should-match

如果我们使用 bool 查询黑色、大屏、手机,其中should 语句匹配得越多表示文档的相关度越高,但是我们想要手机所占的权重比较大,内容包括手机的文档排名靠前,可以使用 boost 设置相对权重,注意是相对权重,默认是1。

在说相关度被破坏的原因之前,我们先看看es对于相关度是如何计算的

es 的相似度算法被定义为检索词频率/反向文档频率, TF/IDF ,包括以下内容:

有时,我们索引了一些文档,然后检索发现有些相关度较低的返回排名靠前?

  出现上述原因的情况是因为es由于性能原因,不会计算所有索引该文档的节点的IDF,比如我们索引了10个文档, 其中6个文档中包含 foo ,而由于es是分布式的,一个索引会被分为多个分片,有可能分片一包含的5 个文档,有 4 个包含foo, 而另外一个在分片二中,所以会导致结果有差异。

  在实际应用中,不会出现该问题,因为本局和全局的IDF差异会随着文档数量的增加逐渐降低。如果想要自己处理该问题,可以在搜索请求之后增加 ?search_type=dfs_query_then_fetch ,他会使得es先计算各个分片的 IDF, 然后在求出全局的 IDF, 生产环境中不要使用。因为只要有足够的数据就可以使得差异减少。

‘贰’ ElasticSearch-工作流程

启动过程
当ElasticSearch节点启动时,使用广播技术来发现同一集群中的其他节点(配置文件中的集群名称)并于它们连接。集群中会有一个节点被选为管理节点(master node),负责集群的状态管理以及在集群拓扑变化时做出反应,分发索引分片至集群的相应节点。

es写数据
1)客户端选择一个node发送请求,这个node就是coordinating node(协调节点)
2)协调节点对document进行路由,将请求转发给对应的node
3)node上的primary shard处理请求,然后将数据同步到replica shard
①先写入内存,并将操作写入translog(数据不能被搜索,translog会在每隔5秒或者每次写入完成后写入磁盘)
②es每隔1秒(配置)进行一个刷新(refresh),写入内存到新数据被写入文件缓存中,并构成一个segement(数据能被搜索,未写入磁盘,可能丢失)
③每隔30分钟或者translog大小达到阈值,触发commit,执行fsync操作,当前translog被删除
(merge:每次refresh都会生成一个segment,segment过多会消耗资源,搜索变慢。A和B两个segment,小segmentC,A,B被读到内存中和Cmerge,生产大segement D,触发commit)
4)返回响应结果给客户端
es删除数据
磁盘上每个segment都有一个.del文件关联,当发送删除请求时,在.del中标记为删除,文档仍能够被搜索到,但会从结果中过滤掉。merge时.del文件中标记但数据不会被包括在新的segment中
es读数据
1)客户端发送请求到协调节点
2)协调节点将请求转发到对应的shard(通过对doc key进行哈希( Murmur哈希算法 ),判断出doc在哪个shard上,然后对该shard查询)
3)每个shard将搜索结果(doc id)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作
4)协调节点根据doc id去各节点拉取document,返回给客户端
es更新数据
创建新文档时,es会为该文档分配一个版本号。对文档但每次更新都会产生一个新的版本号。当执行更新时,旧版本在.del文件中标记删除,并且新版本在新segment中写入索引
并发控制
基于乐观锁和版本号
master选举
①如果集群中存在master,认可该master,加入集群
②如果集群中不存在master,从具有master资格的节点中选id最小的节点作为master
实时性(FileSystem Cache)
一个Index由若干segment组成,搜索时按segment搜索,索引一条segment后,每个段会通过fsync操作持久化到磁盘,而fsync 操作比较耗时。
es中新增的document会被收集到indexing buffer区后被重写成一个segment,然后直接写入FileSystem Cache中,只要sengment文件被写入cache后,这个sengment就可以打开和查询,从而确保在短时间内就可以搜到。

‘叁’ es源码笔记-7.x 选主流程

Discovery模块负责发现集群中的节点,以及选择主节点。ES支持多种不同Discovery类型选择,内置的实现有两种:Zen Discovery和Coordinator,其他的包括公有云平台亚马逊的EC2、谷歌的GCE等。

它假定所有节点都有一个唯一的ID,使用该ID对节点进行排序。任何时候的当前Leader都是参与集群的最高ID节点。该算法的优点是易于实现。但是,当拥有最大ID的节点处于不稳定状态的场景下会有问题。例如,Master负载过重而假死,集群拥有第二大ID的节点被选为新主,这时原来的Master恢复,再次被选为新主,然后又假死
ES 通过推迟选举,直到当前的 Master 失效来解决上述问题,只要当前主节点不挂掉,就不重新选主。但是容易产生脑裂(双主),为此,再通过“法定得票人数过半”解决脑裂问题

1、多数派原则:必须得到超过半数的选票才能成为master。
选出的leader一定拥有最新已提交数据:在raft中,数据更新的节点不会给数据旧的节点投选票,而当选需要多数派的选票,则当选人一定有最新已提交数据。在es中,version大的节点排序优先级高,同样用于保证这一点。

正确性论证:raft是一个被论证过正确性的算法,而ES的算法是一个没有经过论证的算法,只能在实践中发现问题,做bug fix,这是我认为最大的不同。

是否有选举周期term:raft引入了选举周期的概念,每轮选举term加1,保证了在同一个term下每个参与人只能投1票。ES在选举时没有term的概念,不能保证每轮每个节点只投一票。
选举的倾向性:raft中只要一个节点拥有最新的已提交的数据,则有机会选举成为master。在ES中,version相同时会按照NodeId排序,总是NodeId小的人优先级高。

2、Paxos算法
Paxos非常强大,尤其在什么时机,以及如何进行选举方面的灵活性比简单的Bully算法有很大的优势,因为在现实生活中,存在比网络连接异常更多的故障模式。但 Paxos 实现起来非常复杂

本篇只讨论内置的Zen Discovery

整体流程可以概括为:选举临时Master,如果本节点当选,则等待确立Master,如果其他节点当选,则尝试加入集群,然后启动节点失效探测器。

如果集群刚启动则参与选主,否则加入集群
org.elasticsearch.node.Node.start()

选举过程的实现位于 org.elasticsearch.discovery.zen.ZenDiscovery.findMaster() ,该函数查找当前集群的活跃 Master,或者从候选者中选择新的Master。如果选主成功,则返回选定的Master,否则返回空

上面选择临时主节点非常简单,
首先需要判断当前候选者人数是否达到法定人数,否则选主失败。

取列表中的最小值,比较函数通过compareNodes实现,只是对节点 ID 进行排序

选举出的临时Master有两种情况:该临时Master是本节点或非本节点。

(2)超时(默认为30秒,可配置)后还没有满足数量的join请求,则选举失败,需要进行新一轮选举。

超时后直接return,当非临时节点加入集群不成功时,重新发起选主流程
org.elasticsearch.discovery.zen.ZenDiscovery.innerJoinCluster()

(3)成功后发布新的clusterState。
实现如下:

submitStateUpdateTask最终通过TaskBatcher# submitTasks来提交任务。执行任务并发布集群状态的总体过程在 MasterService#runTasks 方法中实现。

(2)向Master发送加入请求,并等待回复。超时时间默认为1分钟(可配置),如果遇到异常,则默认重试3次(可配置)。这个步骤在joinElectedMaster方法中实现。

最终当选的Master会先发布集群状态,才确认客户的join请求,因此,joinElectedMaster返回代表收到了join请求的确认,并且已经收到了集群状态。所以如果返回不成功,则重新发起选主流程
(3)检查收到的集群状态中的Master节点如果为空,或者当选的Master不是之前选择的节点,则重新选举。

1、es通过主从模式以及发现机制保证节点之间的负载均衡,但是es使用量的急剧增加暴露了很多问题,例如,Zen的minimum_master_nodes设置经常配置错误,这会使群集更容易出现裂脑和丢失数据的风险
2、7.x以上版本Coordinator提供了安全的亚秒级的master选举时间,而Zen可能要花几秒钟来选择一个新的master
3、es的master挂了,数据节点在这区间还能对外提供服务吗?
参考
Elasticsearch分布式一致性原理剖析

‘肆’ elasticSearch理论篇—索引、节点、分片

传统我们检索文章,是逐个遍历找到对应关键词的位置;

而倒排索引,是通过分词策略,形成词与文章的映射关系表,这种词典+映射表即为倒排索引。

倒排索引的底层实现是基于:FST(Finite State Transcer)数据结构。
lucene [lu'sen] 从4+版本后开始大量使用的数据结构是FST。FST有两个优点:

利用es的分片预分配。

不能,因为分片数是 文档路由算法 中重要的元素:

shard = hash(routing) % number_of_primary_shards

动态修改分片将意味着几乎重新索引文档数据,这是比仅仅将分片从一个节点复制到另一个节点更重量级的操作。

一个分片存在于单个节点,一个节点可以包含多个分片。

elasticSearch天然具有分布式的特征,实现水平扩容时通过 分片预分配 。在创建索引时,选择合适的分片数。

随着数据量的增加,可以动态的增加节点数,elasticSearch将会自动将分片分配到新增的节点上,当重新分配完成时,每个分片将会有接近至少两倍于之前的运算速度。

elasticSearch中新添加的索引默认被指定了5个主分片。这意味着我们最多可以将那个索引分散到5个节点上,每一个节点一个分片。

不能,一个分片并不是没有代价的。

es适当的预分配是好的,但是上千个分片就有些糟糕。

ElasticSearch推荐的最大JVM堆空间是30~32G, 所以把你的分片最大容量限制为30GB, 然后再对分片数量做合理估算. 例如, 你认为你的数据能达到200GB, 推荐你最多分配7到8个分片。
在开始阶段, 一个好的方案是根据你的节点数量按照1.5~3倍的原则来创建分片. 例如,如果你有3个节点, 则推荐你创建的分片数最多不超过9(3x3)个。当性能下降时,增加节点,ES会平衡分片的放置。
对于基于日期的索引需求, 并且对索引数据的搜索场景非常少. 也许这些索引量将达到成百上千, 但每个索引的数据量只有1GB甚至更小. 对于这种类似场景, 建议只需要为索引分配1个分片。如日志管理就是一个日期的索引需求,日期索引会很多,但每个索引存放的日志数据量就很少。

副本分片 可以实现高可用。当持有主分片的节点挂掉之后,一个副本分片将会晋升为主分片的角色。

在索引写入时,副本分片做着和主分片相同的工作。新文档首先被索引进主分片然后在同步到其他所有的副本分片。增加副本分片并不会增加索引容量。

副本分片可以服务于读请求,如果索引偏向查询,那么可以通过增加副本的数目来提升查询性能。但也要为此增加额外的硬件资源。

当使用上面配置时,每一个分片的副本分片数量为1个。

一个拥有两个主分片一份副本的索引可以在四个节点中横向扩展

Elasticsearch: 权威指南 » 数据建模 » 扩容设计 » 扩容的单元

面试官:Elasticsearch如何设计索引?满分答案来了

elasticsearch 设置多少分片合适

新年手打,24道进阶必备Elasticsearch 面试真题(建议收藏!)

‘伍’ 二十七、ElasticSearch聚合分析中的算法讲解

1、易并行聚合算法,三角选择原则,近似聚合算法
(1)、易并行聚合算法:比如max

(2)、不易的,如count(distinct)

(2)精准+大数据:hadoop,批处理,非实时,可以处理海量数据,保证精准,可能会跑几个小时
(3)大数据+实时:es,不精准,近似估计,可能会有百分之几的错误率
(4)、近似聚合算法
如果采取近似估计的算法:延时在100ms左右,0.5%错误
如果采取100%精准的算法:延时一般在5s~几十s,甚至几十分钟,几小时, 0%错误
2、cardinality去重及算法优化和HLL算法分析
es,去重,cartinality metric,对每个bucket中的指定的field进行去重,取去重后的count,类似于count(distcint)

precision_threshold优化准确率和内存开销可以提高去重性能
cardinality算法,会占用precision_threshold * 8 byte 内存消耗,100 * 8 = 800个字节
HyperLogLog++ (HLL)算法性能优化
默认情况下,发送一个cardinality请求的时候,会动态地对所有的field value,取hash值; 将取hash值的操作,前移到建立索引的时候

3、percentiles百分比算法以及网站访问时延统计
需求:比如有一个网站,记录下了每次请求的访问的耗时,需要统计tp50,tp90,tp99
tp50:50%的请求的耗时最长在多长时间
tp90:90%的请求的耗时最长在多长时间
tp99:99%的请求的耗时最长在多长时间

数据:

不同概率百分比之间的防问效率:

分组统计防问百分比,并计算平均值

4、percentile ranks网站访问时延SLA统计

SLA:就是你提供的服务的标准
例以地区分组,计算以不同时间的响应百分比

5、doc value原理
(1)index-time生成
PUT/POST的时候,就会生成doc value数据,也就是正排索引
(2)核心原理与倒排索引类似
正排索引,也会写入磁盘文件中,os cache先进行缓存,以提升访问doc value正排索引的性能,如果os cache内存大小不足够放得下整个正排索引,doc value,就会将doc value的数据写入磁盘文件中
(3)性能问题:
es官方是建议,es大量是基于os cache来进行缓存和提升性能的,不建议用jvm内存来进行缓存,那样会导致一定的gc开销和oom问题给jvm更少的内存,给os cache更大的内存。
(4)、column压缩
doc1: 550
doc2: 550
doc3: 500
合并相同值,550,doc1和doc2都保留一个550的标识即可
(1)所有值相同,直接保留单值
(2)少于256个值,使用table encoding模式:一种压缩方式
(3)大于256个值,看有没有最大公约数,有就除以最大公约数,然后保留这个最大公约数
(4)如果没有最大公约数,采取offset结合压缩的方式:
如果的确不需要doc value,比如聚合等操作,那么可以禁用,减少磁盘空间占用
PUT my_index
{
"mappings": {
"my_type": {
"properties": {
"my_field": {
"type": "keyword"
"doc_values": false
}
}
}
}
}
6、对于分词的field执行aggregation,发现报错。。。

如果直接对分词field执行聚合,报错,大概意思是说,你必须要打开fielddata,然后将正排索引数据加载到内存中,才可以对分词的field执行聚合操作,而且会消耗很大的内存

给分词的field,设置fielddata=true,发现可以执行

也可以用内置field不分词,对string field进行聚合

7、分词field+fielddata的工作原理
(1)、不分词的所有field,可以执行聚合操作 --> 如果你的某个field不分词,那么在index-time时,就会自动生成doc value --> 针对这些不分词的field执行聚合操作的时候,自动就会用doc value来执行
(2)、分词field,是没有doc value的。在index-time,如果某个field是分词的,那么是不会给它建立doc value正排索引的,因为分词后,占用的空间过于大,所以默认是不支持分词field进行聚合的
fielddata加载到内存的过程是lazy加载的,对一个analzyed field执行聚合时,才会加载,而且是field-level加载的。一个index的一个field,所有doc都会被加载,而不是少数doc,不是index-time创建,是query-time创建
为什么fielddata必须在内存?因为分词的字符串,需要按照term进行聚合,需要执行更加复杂的算法和操作,如果基于磁盘和os cache,那么性能会很差。

8、fielddata相关优化配置
(1)、内存限制
indices.fielddata.cache.size: 20%,超出限制,清除内存已有fielddata数据,fielddata占用的内存超出了这个比例的限制,那么就清除掉内存中已有的fielddata数据
默认无限制,限制内存使用,但是会导致频繁evict和reload,大量IO性能损耗,以及内存碎片和gc

(2)监控fielddata内存使用
GET /_stats/fielddata?fields=*
GET /_nodes/stats/indices/fielddata?fields=*
GET /_nodes/stats/indices/fielddata?level=indices&fields=*

(3)、circuit breaker断路器
如果一次query load的feilddata超过总内存,就会oom --> 内存溢出
circuit breaker会估算query要加载的fielddata大小,如果超出总内存,就短路,query直接失败
indices.breaker.fielddata.limit:fielddata的内存限制,默认60%
indices.breaker.request.limit:执行聚合的内存限制,默认40%
indices.breaker.total.limit:综合上面两个,限制在70%以内

(4)、fielddata filter的细粒度内存加载控制

min:仅仅加载至少在1%的doc中出现过的term对应的fielddata
比如说某个值,hello,总共有1000个doc,hello必须在10个doc中出现,那么这个hello对应的fielddata才会加载到内存中来
min_segment_size:少于500 doc的segment不加载fielddata
加载fielddata的时候,也是按照segment去进行加载的,某个segment里面的doc数量少于500个,那么这个segment的fielddata就不加载

(5)、fielddata预加载

query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强
(6)、global ordinal序号标记预加载

有很多重复值的情况,会进行global ordinal标记
doc1: status1
doc2: status2
doc3: status2
doc4: status1

status1 --> 0 status2 --> 1

doc1: 0
doc2: 1
doc3: 1
doc4: 0

建立的fielddata也会是这个样子的,这样的好处就是减少重复字符串的出现的次数,减少内存的消耗

‘陆’ es查询数据的工作原理是什么

查询,GET某一条数据,写入了某个document,这个document会自动给你分配一个全局唯一的id,doc id,同时也是根据doc id进行hash路由到对应的primary shard上面去。也可以手动指定doc id,比如用订单id,用户id。

我们可以通过doc id来查询,会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,从那个shard去查询

1)客户端发送请求到任意一个node,成为coordinate node(协调结点)
2)coordinate node进行hash后对document进行路由,将请求转发到对应的node,此时会使用round-robin 随机轮询算法 ,在primary shard以及其所有replica node中 随机选择一个 ,让读请求负载均衡
3)接收请求的node返回document给coordinate node
4)coordinate node返回document给客户端

es最强大的是做全文检索,就是比如你有三条数据

java真好玩儿啊
java好难学啊
j2ee特别牛

你根据java关键词来搜索,将包含java的document给搜索出来

es就会给你返回:java真好玩儿啊,java好难学啊

1)客户端发送请求到一个coordinate node
2)协调节点 将搜索请求转发到 所有的shard 对应的primary shard或replica shard
3)query phase: 每个shard将自己的搜索结果 (其实就是一些 doc id ), 返回给协调节点 ,由协调节点进行数据的 合并、排序、分页 等操作,产出最终结果
4)fetch phase:接着由 协调节点,根据doc id去各个节点上拉取实际的document数据 ,最终返回给客户端

尤其要注意的这里是先拿的id哟

‘柒’ ES集群原理与搭建

查看集群健康状况:URL+ /GET _cat/health

Cluster

代表一个集群,集群中有多个节点,其中有一个为主节点,这个主节点是可以通过选举产生的,主从节点是对于集群内部来说的。es的一个概念就是去中心化,字面上理解就是无中心节点,这是对于集群外部来说的,因为从外部来看es集群,在逻辑上是个整体,你与任何一个节点的通信和与整个es集群通信是等价的。

Shards

代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。

replicas

代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。

Recovery

代表数据恢复或叫数据重新分布,es在有节点加入或退出时会根据机器的负载对索引分片进行重新分配,挂掉的节点重新启动时也会进行数据恢复。

(2)、ES为什么要实现集群

在单台ES服务器节点上,随着业务量的发展索引文件慢慢增多,会影响到效率和内存存储问题等。

我们可以采用ES集群,将单个索引的分片到多个不同分布式物理机器上存储,从而可以实现高可用、容错性等。

ES集群中索引可能由多个分片构成,并且每个分片可以拥有多个副本。通过将一个单独的索引分为多个分片,我们可以处理不能在一个单一的服务器上面运行的大型索引,简单的说就是索引的大小过大,导致效率问题。不能运行的原因可能是内存也可能是存储。由于每个分片可以有多个副本,通过将副本分配到多个服务器,可以提高查询的负载能力。

(3)、ES是如何解决高并发

ES是一个分布式全文检索框架,隐藏了复杂的处理机制,内部使用 分片机制、集群发现、分片负载均衡请求路由。

Shards 分片:代表索引分片,es可以把一个完整的索引分成多个分片,这样的好处是可以把一个大的索引拆分成多个,分布到不同的节点上。构成分布式搜索。分片的数量只能在索引创建前指定,并且索引创建后不能更改。

Replicas分片:代表索引副本,es可以设置多个索引的副本,副本的作用一是提高系统的容错性,当某个节点某个分片损坏或丢失时可以从副本中恢复。二是提高es的查询效率,es会自动对搜索请求进行负载均衡。

1、每个索引会被分成多个分片shards进行存储,默认创建索引是分配5个分片进行存储。每个分片都会分布式部署在多个不同的节点上进行部署,该分片成为primary shards。

注意:索引的主分片primary shards定义好后,后面不能做修改。

2、为了实现高可用数据的高可用,主分片可以有对应的备分片replics shards,replic shards分片承载了负责容错、以及请求的负载均衡。

注意: 每一个主分片为了实现高可用,都会有自己对应的备分片,主分片对应的备分片不能存放同一台服务器上。主分片primary shards可以和其他replics shards存放在同一个node节点上。

3、documnet routing(数据路由)

当客户端发起创建document的时候,es需要确定这个document放在该index哪个shard上。这个过程就是数据路由。

路由算法:shard = hash(routing) % number_of_primary_shards

如果number_of_primary_shards在查询的时候取余发生的变化,无法获取到该数据

注意:索引的主分片数量定义好后,不能被修改

高可用视图分析(下图所示:上面的图,如果节点1与节点2宕机了,es集群数据就不完整了。下面图,如果节点1与节点2宕机了,es集群数据还是完整的)

(1)、服务器环境

准备三台服务器集群

| 服务器名称 | IP地址 |
| node-1 | 192.168.212.182 |
| node-2 | 192.168.212.183 |
| node-3 | 192.168.212.184 |

(2)、关闭防火墙

(3)、**** http://192.168.212.185:9200/_cat/nodes?pretty

*号表示为master节点

注意:

注意克隆data文件会导致数据不同步

报该错误解决办法 :

failed to send join request to master

因为克隆导致data文件也克隆呢,直接清除每台服务器data文件。

‘捌’ ES 索引解析(倒排索引 | 正排索引)

何为倒排索引?首先要了解索引表:由关键词为key,关键词位置属性为value组成的一张表。由于该表不是由key来确定value值,而是由value的属性值来确定key的位置,所以称为倒排索引,带有倒排索引的文件称为倒排文件。通俗的讲倒排索引就好比书的目录,通过目录咱们可以准确的找到相应的数据。下面对lucene倒排索引的结构与算法进行介绍。

对于获取关键词有两种思路,1.根据空格分隔获取所有的字符2.过滤文档中没有意义的词,获取其中的关键词。除此以上还会对词的时态,大小写,同义词,标点符号等做相应的处理,不同的分词器对文档索引的时候做的操作有所差异。
实例1:Tom lives in Zhangye,I live in Zhangye too.
关键词1:[tom][live][in][zhangye][i][live][zhangye]
实例2:He once lived in Shanghai
关键词2:[he][live][shanghai]

根据关键词我们就可以确定关键词所在的文章号,关键词在文章中出现的频次以及该关键词在文章中出现的位置(根据上面获取关键词我们可以知道,索引的时候要么索引所有字符,要么索引关键词,lucene采取的就是索引关键词的方式,这样会节省大量的空间),具体索引如下表:

1)词典文件:每个关键词以及指向频率文件和位置文件的指针和filed(用于表达信息位置,每个关键词都有一个或多个field)信息
2)频率文件:关键词在每个文件中出现频率的文件
3)位置文件:关键词所在文章中的位置文件

关键词压缩为<前缀长度,后缀>,例如:“我爱你中国”=》<3,中国>,另外对数字的压缩,只记录与上一个数字的差值,比如当前文章号是11890,上一个文章号是11870,压缩后只需要报错20,这样就极大的缩小了存储空间。

倒排索引服务于es查询操作,对数据的聚合,排序则需要使用正排索引,下面我们介绍正排索引。

正排索引说白了就是document每个field的值的排序,其实就是doc values,举例说明:
实例:
doc1: { "name": "张三", "age": 27,"sex":"男" }
doc2: { "name": "李四", "age": 30,"sex":“女” }
正排索引:
document name age sex
doc1 jack 27 男
doc2 tom 30 女
正排索引使用场景是排序,聚合,过滤等
注意:
对于分词的field进行聚合(aggregation)操作,需要将fielddata设置为true,否则会报错提示你打开fielddata、将正排索引加载到内存中

doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上。

到此对倒排索引与正排索引就介绍完毕了,如有帮助,请关注!谢谢!

‘玖’ es使用与原理2 -- scoll技术,bouncing results,零停机重建索引等等

默认情况下,是按照_score降序排序的,我们也可以定制排序规则

Elasticsearch使用的是 term frequency/inverse document frequency算法,简称为TF/IDF算法
Term frequency(TF): 搜索文本中的各个词条在field文本中出现了多少次,出现次数越多,就越相关
如:搜索请求:hello world
doc1:hello you, and world is very good
doc2:hello, how are you
doc1 肯定比doc2的评分高,因为hello world都在doc1中出现了。
Inverse document frequency(IDF): 搜索文本中的各个词条在整个索引的所有文档中出现了多少次,出现的次数越多,就越不相关
搜索请求:hello world
doc1:hello, today is very good
doc2:hi world, how are you
比如说,在index中有1万条document,hello这个单词在所有的document中,一共出现了1000次;world这个单词在所有的document中,一共出现了100次
那最终的结果肯定是 word的得分所占比更高

关于_score,ES还有一条规则。
Field-length norm:field长度,field越长,相关度越弱
搜索请求:hello world
doc1:{ "title": "hello article", "content": "babaaba 1万个单词" }
doc2:{ "title": "my article", "content": "blablabala 1万个单词,hi world" }
hello world在整个index中出现的次数是一样多的。最终 doc1得分更高

搜索的时候,要依靠倒排索引;排序的时候,需要依靠正排索引,看到每个document的每个field,然后进行排序,所谓的正排索引,其实就是doc values,doc values 也可以供排序,聚合,过滤等操作使用。doc values是被保存在磁盘上的,此时如果内存足够,os会自动将其缓存在内存中,性能还是会很高;如果内存不足够,os会将其写入磁盘上
正排索引如下:

倒排索引不可变的好处

想象一下有两个文档有同样值的时间戳字段,搜索结果用 timestamp 字段来排序。 由于搜索请求是在所有有效的分片副本间轮询的,那就有可能发生主分片处理请求时,这两个文档是一种顺序, 而副本分片处理请求时又是另一种顺序。
这就是所谓的 bouncing results 问题: 每次用户刷新页面,搜索结果表现是不同的顺序。 让同一个用户始终使用同一个分片,这样可以避免这种问题, 可以设置 preference 参数为一个特定的任意值比如用户会话ID来解决。

如果一次性要查出来比如10万条数据,那么性能会很差,此时一般会采取用scoll滚动查询,一批一批的查,直到所有数据都查询完处理完。

scoll,看起来挺像分页的,但是其实使用场景不一样。分页主要是用来一页一页搜索,给用户看的;scoll主要是用来一批一批检索数据,让系统进行处理的

使用scoll滚动搜索,可以先搜索一批数据,然后下次再搜索一批数据,以此类推,直到搜索出全部的数据来
scoll搜索会在第一次搜索的时候,保存一个当时的视图快照,之后只会基于该旧的视图快照提供数据搜索,如果这个期间数据变更,是不会让用户看到的
采用基于_doc进行排序的方式,性能较高
每次发送scroll请求,我们还需要指定一个scoll参数,指定一个时间窗口,每次搜索请求只要在这个时间窗口内能完成就可以了

获得的结果会有一个scoll_id,下一次再发送scoll请求的时候,必须带上这个scoll_id

1 创建索引

2 修改索引

3 删除索引

lucene是没有type的概念的,在document中,实际上将type作为一个document的field来存储,即_type,es通过_type来进行type的过滤和筛选
一个index中的多个type,实际上是放在一起存储的,因此一个index下,不能有多个type重名,而类型或者其他设置不同的,因为那样是无法处理的
比如

底层存储是这样的

将类似结构的type放在一个index下,这些type应该有多个field是相同的
假如说,你将两个type的field完全不同,放在一个index下,那么就每条数据都d会有大量field在底层的lucene中是空值,会有严重的性能问题

1、定制dynamic策略
true:遇到陌生字段,就进行dynamic mapping
false:遇到陌生字段,就忽略
strict:遇到陌生字段,就报错

2、定制自己的dynamic mapping template(type level)

上面的设置是/my_index/my_type 的字段,如果是以_en结尾的,那么就自动映射为string类型

一个field的设置是不能被修改的,如果要修改一个Field,那么应该重新按照新的mapping,建立一个index,然后将数据批量查询出来,重新用bulk api写入index中。
批量查询的时候,建议采用scroll api,并且采用多线程并发的方式来reindex数据,每次scoll就查询指定日期的一段数据,交给一个线程即可。

(1)一开始,依靠dynamic mapping,插入数据,但是不小心有些数据是2017-01-01这种日期格式的,所以title这种field被自动映射为了date类型,实际上业务认为它应该是string类型的

(2)当后期向索引中加入string类型的title值的时候,就会报错

(3)如果此时想修改title的类型,是不可能的

(4)此时,唯一的办法,就是进行reindex,也就是说,重新建立一个索引,将旧索引的数据查询出来,再导入新索引
(5)如果说旧索引的名字,是old_index,新索引的名字是new_index,终端java应用,已经在使用old_index在操作了,难道还要去停止java应用,修改使用的index为new_index,才重新启动java应用吗?这个过程中,就会导致java应用停机,可用性降低
(6)所以说,给java应用一个别名,这个别名是指向旧索引的,java应用先用着,java应用先用goods_index alias来操作,此时实际指向的是旧的my_index

(7)新建一个index,调整其title的类型为string

(8)使用scroll api将数据批量查询出来

(9)采用bulk api将scoll查出来的一批数据,批量写入新索引

(10)反复循环8~9,查询一批又一批的数据出来,采取bulk api将每一批数据批量写入新索引

(11)将goods_index alias切换到my_index_new上去,java应用会直接通过index别名使用新的索引中的数据,java应用程序不需要停机,零提交,高可用

(12)直接通过goods_index别名来查询,是否ok

现有流程的问题,每次都必须等待fsync将segment刷入磁盘,才能将segment打开供search使用,这样的话,从一个document写入,到它可以被搜索,可能会超过1分钟!!!这就不是近实时的搜索了!!!主要瓶颈在于fsync实际发生磁盘IO写数据进磁盘,是很耗时的。

‘拾’ es使用与原理6 -- 聚合分析剖析

有些聚合分析的算法,是很容易就可以并行的,比如说max

有些聚合分析的算法,是不好并行的,比如说,count(distinct),并不是说,在每个node上,直接就出一些distinct value,就可以的,因为数据可能会很多,假设图中的协调节点3百万个数据去重后还剩下100万distinct的数据,那么内存需要来存储这100万条数据,这是不可能的

es会采取近似聚合的方式,就是采用在每个node上进行近估计的方式,得到最终的结论,cuont(distcint),100万,1050万/95万 --> 5%左右的错误率
近似估计后的结果,不完全准确,但是速度会很快,一般会达到完全精准的算法的性能的数十倍

precision_threshold优化准确率和内存开销

brand去重,如果brand的unique value,在100个以内,小米,长虹,三星,TCL,HTL。。。
在多少个unique value以内,cardinality,几乎保证100%准确
cardinality算法,会占用precision_threshold * 8 byte 内存消耗,100 * 8 = 800个字节
占用内存很小。。。而且unique value如果的确在值以内,那么可以确保100%准确
100,数百万的unique value,错误率在5%以内
precision_threshold,值设置的越大,占用内存越大,1000 * 8 = 8000 / 1000 = 8KB,可以确保更多unique value的场景下,100%的准确
field,去重,count,这时候,unique value,10000,precision_threshold=10000,10000 * 8 = 80000个byte,80KB

doc value正排索引
搜索+聚合 是怎么实现的?
假设是倒排索引实现的

倒排索引来实现是非常不现实的,因为我们搜索的那个字段search_field 有可能是分词的,这就需要去扫描整个索引才能实现聚合操作,效率是及其低下的。
正排索引结构:
doc2: agg1
doc3: agg2
1万个doc --> 搜 -> 可能跟搜索到10000次,就搜索完了,就找到了1万个doc的聚合field的所有值了,然后就可以执行分组聚合操作了
doc value原理

1、doc value原理

(1)index-time生成

PUT/POST的时候,就会生成doc value数据,也就是正排索引

(2)核心原理与倒排索引类似

正排索引,也会写入磁盘文件中,然后呢,os cache先进行缓存,以提升访问doc value正排索引的性能
如果os cache内存大小不足够放得下整个正排索引,doc value,就会将doc value的数据写入磁盘文件中

(3)性能问题:给jvm更少内存,64g服务器,给jvm最多16g

es官方是建议,es大量是基于os cache来进行缓存和提升性能的,不建议用jvm内存来进行缓存,那样会导致一定的gc开销和oom问题
给jvm更少的内存,给os cache更大的内存
64g服务器,给jvm最多16g,几十个g的内存给os cache
os cache可以提升doc value和倒排索引的缓存和查询效率

2、column压缩

doc1: 550
doc2: 550
doc3: 500

合并相同值,550,doc1和doc2都保留一个550的标识即可
(1)所有值相同,直接保留单值
(2)少于256个值,使用table encoding模式:一种压缩方式
(3)大于256个值,看有没有最大公约数,有就除以最大公约数,然后保留这个最大公约数

重点:
对分词的field,直接执行聚合操作,会报错,大概意思是说,你必须要打开fielddata,然后将正排索引数据加载到内存中,才可以对分词的field执行聚合操作,而且会消耗很大的内存
先修改 字段的fielddata属性为true,再查 就能查找到数据

当然,我们也可以使用内置field(keyword)不分词,对string field进行聚合,如果对不分词的field执行聚合操作,直接就可以执行,不需要设置fieldata=true

分词field+fielddata的工作原理

doc value --> 不分词的所有field,可以执行聚合操作 --> 如果你的某个field不分词,那么在index-time,就会自动生成doc value --> 针对这些不分词的field执行聚合操作的时候,自动就会用doc value来执行
分词field,是没有doc value的。。。在index-time,如果某个field是分词的,那么是不会给它建立doc value正排索引的,因为分词后,占用的空间过于大,所以默认是不支持分词field进行聚合的
分词field默认没有doc value,所以直接对分词field执行聚合操作,是会报错的

对于分词field,必须打开和使用fielddata,完全存在于纯内存中。。。结构和doc value类似。。。如果是ngram或者是大量term,那么必将占用大量的内存。。。

如果一定要对分词的field执行聚合,那么必须将fielddata=true,然后es就会在执行聚合操作的时候,现场将field对应的数据,建立一份fielddata正排索引,fielddata正排索引的结构跟doc value是类似的,
但是只会讲fielddata正排索引加载到内存中来,然后基于内存中的fielddata正排索引执行分词field的聚合操作

如果直接对分词field执行聚合,报错,才会让我们开启fielddata=true,告诉我们,会将fielddata uninverted index,正排索引,加载到内存,会耗费内存空间

为什么fielddata必须在内存?因为大家自己思考一下,分词的字符串,需要按照term进行聚合,需要执行更加复杂的算法和操作,如果基于磁盘和os cache,那么性能会很差

我们是不是可以预先生成加载fielddata到内存中来???
query-time的fielddata生成和加载到内存,变为index-time,建立倒排索引的时候,会同步生成fielddata并且加载到内存中来,这样的话,对分词field的聚合性能当然会大幅度增强

热点内容
苹果好用的解压软件 发布:2025-05-17 22:42:23 浏览:381
我的世界服务器莫名崩溃 发布:2025-05-17 22:40:57 浏览:477
我的世界utc服务器ip 发布:2025-05-17 22:36:19 浏览:740
新闻压缩要素 发布:2025-05-17 22:22:11 浏览:118
耳机没有声音怎么办安卓 发布:2025-05-17 22:16:29 浏览:583
bc8android导航 发布:2025-05-17 22:15:50 浏览:639
什么配置的车标好 发布:2025-05-17 21:41:20 浏览:203
linux支持线程 发布:2025-05-17 21:26:14 浏览:184
元神队伍配置都由什么组成 发布:2025-05-17 21:20:18 浏览:477
闲鱼和安卓哪个赚钱 发布:2025-05-17 21:15:56 浏览:584