ElasticSearch学习笔记+华为搜索项目

Elastic Search相关
elasticsearch 索引更新:
1.可以直接PUT, id相同时,版本号加1,直接覆盖;2.update,只传局部内容,进行局部修改。

索引更新的底层原理:
es索引过于复杂(倒排索引分词啥的),没有实际意义的修改,更新时本质是删除旧文档,增加新文档。(版本号加1)

es层面:路由到分片,执行更新,结果同步到副本。
lucene层面:1.新文档先被收集到内存索引缓存,2.增加一个新的段,一个新的提交点,
3.新的段被开启,就可以 被搜索可见。
4.缓存清空,等待接收新文档。
refresh:新段被搜索可见(更新完可以立马执行,默认每秒自动刷新一次)

elasticsearch:
索引更新为啥都要隔一段时间才可被搜索到。(隔一秒才生效。。默认1秒)

原因: !!刷新对性能开销很大!!设置时间间隔,就是要减少刷新操作,提高性能。。,不要刷新太频繁。。(往往隔一段时间),refresh参数还是可以自己设置的,结合实际情况(我们数据更新前,refresh设为-1,关闭刷新,怕影响数据更新性能; 数据更新完,开启refresh立刻刷新同步生效。。),

refresh: lucene段刷新;(既可以在内存,也可以在磁盘本地。。)

flush:刷新到磁盘。。。持久化变更。保证数据安全。


——- es搜索的流程:
搜索需要一个更加复杂的执行模型,不确定命中哪些文档。搜索分为两个阶段: query then fetch.
query:
1.请求发送到协调节点,协调节点自己创建一个优先队列(协调节点广播查询请求到所有相关分片,并将它们的响应整合成全局排序的结果集合。)
2.协调节点广播请求到集群中每个节点的分片拷贝,查询请求可以被某个主分片或副本分片处理。(搜索可以设置偏好,搜哪些范围主或副)
3.每个分片在本地执行查询请求,并创建from+size的优先队列,
4.分片返回一个轻量级的查询结果列表(仅包含文档id和排序需要的值)给协调节点;
5.协调节点将分片级的结果汇总到自己的优先队列中,代表全局排序结果集合,至此查询结束。
fetch:
1.协调节点辨别哪些文档需要被取回,并向相关分片提交GET请求;
2.每个分片加载并丰富文档,返回给协调节点。
3.每个文档都被取回了,则返回给客户端。

搜索耗时 = 最慢分片的处理时间 + 协调节点合并时间(一个节点有问题,就会导致所有响应缓慢)

游标查询 scroll
深度分页的代价在于结果集全局排序。
避免深度分页,有效执行大批量的文档查询,游标查询用字段_doc排序,让es仅仅从还有结果的分片返回下一批结果。

启动游标查询时,设置参数scroll值为我们期望的游标查询的过期时间(相当于一个时间窗口,在这时间段内系统会缓存消耗资源,时间过后系统会考虑释放掉占用的资源),每个批次通过scroll_id保持同步,前后连续起来。


Elastic Serach分片底层原理:

分片内部原理

1.倒排索引:
倒排索引保存每一个词项出现过的文档总数,对应文档中一个词项出现过的总次数,词项在文档中的顺序,每个文档的长度,所有文档的平均长度,这些信息可以决定相关性顺序,要提前知道集合中的所有文档。
倒排索引写入磁盘后,是不可改变的。(所以,不需要锁; 索引会缓存,读请求直接读内存,比起读磁盘,性能大大提升;filter缓存也很方便,不变的;要修改的话,就要重建索引)

动态更新索引:
保持索引不变性,增加新的补充索引来反映新近的修改,每一个倒排索引都会被轮流查询到,查询完后再对结果合并。

ES基于Lucene,引入按段搜索(Segment)的概念. 每一段本身都是一个倒排索引。索引本身就是所有段(segment)的集合,
提交点(commit):一个个列出所有已知段的文件。

逐段搜索(segment):
1.新文档被收集到内存索引缓存;
2.不时地,缓存被提交:
一个新的段,一个追加的倒排索引,被写入磁盘;
一个新的包含新段名字的提交点,被写入磁盘;
磁盘进行同步-所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们被写入到物理文件。
3.新的段被开启,让它包含的文档可见已被搜索;
4.内存缓存被清空,等待接收新的文档。
当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。用相对较低的成本将新文档添加到索引。
删除:提交点提交一个.del文件,文件列出被删除的段信息。
文档更新:文档新版本被索引到一个新的段中。

磁盘成了性能的瓶颈,还不够快。
提交一个新的段到磁盘,需要一个从缓存同步操作,确保断电不会丢失数据。但同步代价很大。
Lucene允许新段被写入和打开,使其包含的文档在未进行完整提交时便对搜索可见。比进行提交代价小得多,还可以频繁执行。
写入打开轻量的过程叫refresh,分片默认一秒自动刷新一次,近实时,一秒可见。

为了数据可靠,必须做持久化变更,translog(事务日志)。缓存被清空,translog不会,会继续记录,
每隔一段时间,flush,索引全量刷新,translog被删除,生成新的translog。

自动刷新,创建新的段,会很耗性能,还会轮流检查段,更慢。。
后台进行段合并,索引和搜索时,段合并操作自动进行。


es倒排索引更底层的数据结构: 搜索原理
term index -> term dictionary -> doc list. (倒排索引针对field,每个field有一个自己的倒排索引)
word太多了,找到word是一个问题。。(term dictionary…) 所以引入了term index来提高word的查找效率。。
Lucene的倒排索引,增加了最左边的字典树(term index,是在内存中的, 减少IO次数),存储单词前缀。通过字典树找到term的大概位置,然后再去block’ 块中查找该词,然后找到倒排链表。

term index,采用FST(Finite State Transducers)算法,对内存存储做了进一步的压缩。(FST,按字母表示,表示所有单词的状态,重复利用单词前缀和后缀, 查询速度快,节省内存空间)

term dictionary, 在磁盘中以block形式存储,一个block内部前缀基本相同,利用公共前缀压缩,节省磁盘空间。

倒排链posting list: 求交集时,按bitset位求交集, skiplist跳跃表优化求交性能。 (增量编码,压缩存储空间)
增量编码:只存增加部分,分块,按需分配(可以存更多文档)
求交集: 都是有序的数组(倒排链表),用跳跃表
当文档数少于4096时,用整型数组(跳跃表,多个跳跃表 互相跳),文档数较多时,用bitmap(转化为位运算 按位与)

源码说,合并过程是树合并。。交集,并集,差集。。


Elastic Search稳定性保证
elasticsearch 选主流程。。(大概2个步骤)
https://blog.csdn.net/bao2901203013/article/details/110752035
1.节点发现。发现集群中的节点,
2. 选择主节点。
以及选择主节点。ES支持多种不同的Discovery选择,内置的实现称为Zen Discovery,其封装了节点发现(ping)、选主等实现过程。

Elasticsearch中的节点在配置时会设置节点的功能项目,如下:
node.master:true
node.data:true
node.master为true表示这个节点能作为候选节点参加选举、fasle表示不能参加选举。
node.data:true表示这个节点可以作为数据节点存储数据,false表示不存储数据。

选举流程:Elasticsearch中的候选节点通过Bully算法来进行选举,该算法假定所有节点都有一个唯一的ID,使用该ID对节点进行排序,选择最小的节点作为Master。

对某节点的投票数达到一定值(n/2+1),并且该节点自己也选举自己,这个节点就是master。(否则重新选举 直到满足条件)


分布式系统一致性保证
elasticsearch 并发下 读写如何保证一致?
(分布式环境下,一条数据更新完 所有用户都能读到最新值
(本身有一致性的保证支持)
1.乐观并发控制, + 版本号,每次更新时版本号加1, 其它请求来更新时,读取最新版本号 不符合则更新不成功,继续重试。
2. es 主节点更新完时 会同步给副本节点。
3.写操作时,有个一致性参数可以传递(one shard,all shard, quorum shard),保证大部分分片都是活跃可用 才执行。
4.读操作时,可以设置读的偏好。读主和副,保证读到的都是最新版本。


Elastic Search优化点:

es数据更新必问。。。
es要么增量更新。。(每次只更新少部分。。)
读写分离。。(可以保证读和写不冲突,效率有保证。。。)
一些小技巧:
(1)写之前 关闭刷新,refresh_interval=-1,(禁用刷新,写的过程不会消耗机器性能;
(2) 写之前副本数设为0,(副本数可以提升查询效率,写入过程会有数据同步给副本的操作,会影响机器性能,卡顿等现象。。)
(3)bulk写(批量), 减少io,传输一次,一次就有大量数据。。也是减少机器性能消耗。。
(4) 写完之后,副本数 和 刷新间隔 才恢复正常,使得新数据搜索可见; 同步到副本。。
(5日期来管理数据版本; 别名使得新索引生效。。

es优化:
1.内存要足够大,(内存至少为数据总量的一半。。或者相等。。)
2.不要做复杂的检索逻辑。
3.只存相关的数据,存的数据要精简。。(倒排+正排id查询,相结合。。 50ms+30ms)
4.读写分离。(副本分片读,主分片写。。 冷热分离不太懂,暂时不管。。)
5.文档map设计要简单。。
6.不要大分页。。 减少分数计算逻辑。。(只要少量数据来做计算。。)

es数据量越来越多时,怎么处理??(我们业务当前没遇到,但是方法还是要了解的)
1.动态索引层面, 时间来递增。。(只保留最新的,,删除旧的。。) 划分不同的集群。(优质集群,长尾集群,对数据进行归类划分,全量集群)
2.存储层面, 冷热数据分离,,最近3天数据为热数据,时间过长为冷数据。
3.部署层面, 扩容,增加新机器。


ElasticSearch的分片数量设置???
单个分片的容量分配、 分片数设置、副本分片的设置。 确定这些值的方案设计方法:
(0) 前提:我们的索引建立要足够优化。。(比如,字段类型设计是否合理,查询是否高效。。)
(1) 用真实环境的硬件机器一台, 数据全部压缩到单个分片,直到"挂掉"(响应时间达到我们可接受的上限比如50ms。)是一个测试的过程。(得到单个分片的容量。)
(2) 总数据量除以单个分片的容量,,得到分片数。。

集群增加额外的节点的优势: 可以增加额外容量、增加副本数(增加搜索查询的性能), 对于索引写入的性能没有帮助。

1.过多分片:(1)一个分片底层即为一个Lucene索引(都相当于一个进程),会消耗一定的文件句柄、内存和CPU运转。(2)一个节点上分片数过多,搜索查询时就会资源竞争。(3)相关度词频统计是基于分片的,分片数过多且每一个分片上文档数过少,相关度也会很低。
查询大量小分片时,每个小分片的处理速度会更快,更多的任务按顺序排队处理,更多的并发查询,更多的小分片会降低查询吞吐量。
2.过少分片:每个文件要容纳更多的文档数,相当于一个Luene底层索引要担负更多的责任,一个索引是有一定能力上限的,比如文档过多时延会降低,等等。。适当增加分片数,通过多个Lucene实例并行来提高机器的资源利用效率。。。


布隆过滤器(bloom filter)
使用场景:仅需了解key是否存在,不关心value(内存有限)
是一种概率型数据结构,插入、查询很高效,用来判断某种东西存在还是不存在。
布隆过滤器优点:高效、占用空间少
缺点:返回结果是概率性的,不确切的。
传统hashmap key判断是否存在,数据量大时要占用大量内存。
原理:对一个字符串采用k个哈希函数映射,映射到一些01的位置上。查询时 位置上都为1,则说明字符串可能存在。某个位置为0,则一定不存在。
(关键参数:哈希函数个数和布隆过滤器长度,布隆过滤器长度越长,哈希函数越多,判断存在的误报率越小;哈希函数个数越多,bit为1速度越快)
应用场景:布隆过滤器减少磁盘io请求,不存在的值就不要去查询了。

——-
es: long keyword
keyword不会被分词处理,
long:数据范围最大(不建议用,数据范围大 索引和搜索效率会很低)

——-
文本相似度计算:
1.提取特征
tf-idf, bag of word,提取文本特征

2.相似度度量。
编辑距离。 欧式距离(l2), 余弦距离。 jaccard相似度。

——-
分数计算
传统 query-fetch模式,会导致排名不准确。每个分片都基于自己分片上数据进行打分计算。 es进行整体排名是基于每个分片计算后的分值来排序(打分依据不一样)
对分数要求高,保证排名准确,用dfs-query-fetch,但是这种方法性能很差(预查询会在额外的分片中轮询)


数据库 , 行式存储 和 列式存储。。

1.行式存储:关系型数据库,基于行式存储的思想,结构固定(提前固定好,不能改变),会造成资源浪费。。(有些没值的 但是也要占用字段空间。。) 一行只需要一个主键,, 存的都是业务数据。。 (行存储,目前不太适合海量的数据存储和计算,,)
2.列式存储:非关系型数据库,NoSql,结构弱化,按需存储。。 一行数据需要多个主键。。不仅要存业务数据,还要存储列名。。结构灵活,随时增加删除key-value。更像map。。

比较:
1.写入:行存储一次写入,列存储多次写入(写入次数频繁),写入性能上,行存储占优势。
2. 读取: 行存储有冗余数据,列存储不会有冗余数据; 列存储读取性能更高。 行存储类型解析也消耗很大,列存储类型简单。
3. 列存储:每一列由一个线程来处理,并发性能会很高。列存储可以采用很多压缩算法来优化存储和网络传输。

OLTP类型的业务,适合行存储,再加上少量优化即可。。
OLAP类型的业务,适合列存储(分析过程需要访问所有数据行,但是有时候只关注个别列的字段。。)

OLTP(On-Line Transaction Processing,在线联机事务处理), 会有在线并发且数据量不大的查询,主要用于管理事务的系统。增删改比较多。。(MySQL, Oracle等产品)(各个事业单位的内部办公系统。。)
OLAP(On-Line Analysis Processing, 在线联机分析处理),计算比较多。。(HBase, Hive…)


倒排索引检索很快,(搜索时可以通过搜索关键词快速得到结果集)

但是字段值排序的性能却不是很理想。很慢(排序时,需要某个字段值的集合,要转置倒排索引)列存储。
Doc Values就是一种列存储。索引时创建字段的Doc Values。 Doc Values用于字段排序、字段聚合、地理位置过滤,等等。(索引时创建的平行数据结构,排序的关键结构)用内存和页缓存,使得读写更高效。


空间位置识别:
类似于一个分类任务(针对query的一个分类任务),每一个分类对应生成固定的dsl(套上去即可),简化召回逻辑。。。 简化DSL查询。

距离:附近的餐馆:
召回餐馆class,
es里面的geo模块 会有优化机制,并不会都计算。。。四叉树, geo-hash,(在一个圈内的才计算。。)

一般先用term,range过滤掉尽可能多的无关的文档!!!, 然后再用地理位置过滤器。

1.地理位置的表示。
elasticsearch中的地理位置的表示方式有两种:
(1) 字段类型为:“location”:{“type”:“geo_point”}, 地理坐标点字段。。(可以用来过滤,排序,相关性打分,),传入经纬度数据就可以建立地理位置索引了。
(2) 用geoJson格式定义复杂的地理形状(点,线,面,多边形等等),geoJson格式是通用的地理形状表示格式标准, es中使用geo_shape数据类型。

2.地理位置的过滤。
2.1 基于地理坐标点的过滤。
geo_bounding_box(找出落在指定矩形框内的点), 比较简单的方式。
geo_distance(与指定位置在给定距离内的点), !!!! 项目中使用的。。
还有距离范围, 和 多边形过滤。
这些过滤器,判断指定坐标点是否落在了指定形状的过滤器中。
!!原理:指定形状的区域被转换成一系列以quad/geohash为前缀的tokens,并被用来在倒排索引中搜索拥有相同tokens的文档。

矩形框过滤器很简单,(指定左上角、右下角即可),
经纬度 距离过滤器计算代价稍微昂贵(elasticsearch内部有优化), 距离过滤器是个圆,先用矩形框圈起来,过滤矩形外的,这一步会很快;最后再处理矩形和圆差集的边缘的那一部分。。(矩形框有时候,就可以满足用户需求。。。)

-------地理位置距离计算的优化点:
(1)plane(distance_type):距离计算会很快,但是会损失精度,把地球当做一个平面。
(2) function_score 查询,在 rescore 语句 中可以限制只对前 n 个结果进行计算。
(3)优化策略: 先用过滤器term match filter等找到满意要求的doc;
需要排序的步骤,比如match phrase的位置距离计算, geo_distance对两点之间的距离计算, 只针对每个shard分片的top k的文档来计算分数排序。。(用rescore, window_size = 50(用户只会翻5页。。))

——1111—-
geo hash(一种空间匹配方法):底层原理,将经纬度用二进制表示,经纬度合并,经度占偶数位,纬度占奇数位,使用base32进行编码。(geo hash)表示的是一个矩形区域,不是一个点。编码越长,表示的范围越小,位置也越精确。用前缀可以表示更大范围的区域。
(编码过程:1.对全球地理范围 采用二分法,不断缩小区间,在范围内为1 不在为0;2.经纬度分别编码得到一串二进制串(这里可以设置精度范围,来控制串的长度;奇数位取纬度,偶数位取精度;3.每5位表示一个10进制数(转为字符串),最终得到一个字符串。

geo hash:把经纬度进行编码,可以进行前缀匹配,前缀匹配上的,距离也会比较近。。(匹配不上的,也过滤掉一大部分。。)
把地理位置经纬度,,编码成一个字符串(类似于邮政编码。。前缀相同的越多,位置距离越接近,通过前缀匹配,可以过滤掉大部分数据。。字符串越长,精度越高) 地理坐标点数据在建立索引时,会索引所有相关近似的geohashes字符串。(geohash_prefix=True, 精度设置150米)
前缀匹配,,trie树???
geo_hash 查询时,filter, 对给定的召回结果,进行geohash过滤,neighbors=True, 精度保持一致。过滤完再进行详细的距离计算(获得距离分数。。这时候需要计算的量就很少了。。)


query 和 filter区别:是两种查询方式。
query:包含文档信息和相关性得分。(每个term倒排链表取交集)
filter:不包含得分,要么是文档信息,要么是null。(只有过滤)

term:完全匹配,不进行分析器分析处理,文档必须包含整个搜索词汇。
match: 模糊匹配,es会对field进行分词和分析处理。包含部分就行。会很快,类似或的运算,只有term在文档,就返回。比match_phrase速度要高10倍。
match_phrase: 查询非常慢,每个term都有一个倒排链表,然后要计算term在文档中的position位置距离是否符合要求。。(满足短语有序。。)

must: 有个 geo_distance{},限制5km(离用户当前经纬度)

function 函数计算 用来排序,里面有自己设计的score-function,(script score) 和 linear(geo-function)
linear:geo-functions(origin:location,offset:1m,scale:5km,decay:0.5)
function-score 相关性 和 其它分数计算 functions(热度 距离 丰富度)
好好看看 geo模块原理。。

我们用的elasticsearch版本:
7.1.x 版本, 2019年末出的版本。。。

查询相关

  1. query和filter的区别
    filter是直接过滤掉,没有分数;(都是结构化数据)(查询上下文)
    query会涉及匹配相似度打分,_score说明文档匹配度,(过滤上下文)

term直接去查倒排索引,效率最高。

2.bulk/mget批量请求
一次请求多个操作,减少了频繁反复的网络请求,必然提高了索引速度。
一次把请求加载到内存,用指定的格式请求,减少了内存占用;
请求中的每个操作都是独立的,依次解析通过路由转发到对应的主节点上,并行执行操作;然后再同步到对应的副本分片上,索引效率也必然提高了。

3.搜索-es搜索的强大之处
结构化搜索:有些字段可以使用结构化搜索(年龄、性别等),日期可以用来排序;
全文搜索:可以找出所有匹配关键字的文档,并排序返回。

mapping(映射):数据在字段中如何存储;
analysis(分析):数据如何处理,已被更好的检索到。(分词、标准化过程)(字符过滤器 -> 分词器 -> token过滤器(小写化,停用词,同义词,词干提取)) index属性控制怎么索引字符串(analyzed/not analyzed(就是映射为一个精确值))
query dsl(特定领域的查询语言): es的查询语言。

4.elasticsearch中的数据,分为精确值和全文,两大类型
精确值:就是非常精确的,如:日期、用户名、用户id和邮箱。都是结构化数据。精确值很容易查询,结果二值化,要么匹配要么不匹配。

全文:指文本数据,人类容易识别的语言书写,如邮件内容、推文内容。通常是非结构化数据,都是自然语言,规则复杂,计算机难以理解。
全文类型不会做精确匹配,只会在全文域中做搜索,并且能理解搜索意图。(因此,我们需要对全文类型的数据做文档分析,然后建立倒排索引)

对搜索字符串、全文域中的内容,做同样的标准化规则处理。(才能保证搜索到想要的内容)
检索语句:分 查询 和 过滤 两种检索形式。。
过滤filter:不评分,二值的结果。
查询:会评分,匹配的多好,这种相关性概念很适合全文搜索,因为全文搜索没有正确答案,
查询操作拥有变成不评分的能力,单独的query指scoring query
filters模块被移除了,只保留filter(表示不评分、只过滤的查询)
过滤查询:计算很快,结果会被缓存,(filter query)
评分查询:不仅要找出匹配的文档,还要计算相关性,结果不会缓存,相关性计算很费力。
倒排索引(inverted index): 使得评分查询性能表现良好,但是整体上来看,过滤查询的性能更高,更稳定。
过滤的目标:减少评分查询检查的文档个数。。
应用场景:全文搜索或者需要相关性分数的评分查询。 其余都尽量用过滤。。

should: 满足这些语句,将增加修正性得分,不满足也没事。
bool用来汇总每个子句的得分。

同一个字符串字段,可以用两种方式进行索引,比如analyzed用于搜索,not_analyzed用于排序。fields字段用于mapping。

es中的相关性:全文本字段的值相对于全文本检索词相似程度的算法。TF/IDF,
检索词频率:检索词在字段出现的频率,频率越高,相关性越高,
反向文档频率:检索词在索引中出现的频率,频率越高,相关性越低,在大多数文档中都出现的词,则权重更低。
字段长度准则:字段越长,相关性越低。

搜索和CRUD不同,CRUD只是对单个文档进行操作,路由唯一确定,对单个分片操作即可。

聚合

大数据规模的聚合,提供了cardinality 和 percentiles 两种算法来实现近似聚合,估算的方式。高速的执行效率,极小的内存消耗。
cardinality (注:基数)度量。 它提供一个字段的基数,即该字段的 distinct 或者 unique 值的数目

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值