ES深入学习第二课.md

refresh 操作:refresh_interval默认为1S–近实时

1.translog 默认写入磁盘
2.index buffer 清空,translog不清空

flush 操作:

1.段合并,缓存中合并后的段写入磁盘
2.清空translog

Query

Step1:客户端请求到 Node3
Step2:Node3转发每个主或副本分片每个分片在本地执行查询、打分、添加:from+size到优先队列
Step3:结果返回给协调节点(文档ID+排序值),协调节点合并后产生全局排序后列表

注:第二步中-正常情况下,每次读请求操作只会被“同步组”里面shard执行一次。在异常情况下,有可能会出现“同步组”里面的多个shard执行了多次读操作,例如协调node在shard没有响应的情况下,会重新选择另外一个shard,再次发出读请求操作。

Fetch

Step1:协调节点向相关Node 发送Get请求
Step2:分片所在节点向协调节点返回数据
Step3:协调节点等待所有数据返回,将结果返回给客户端

scatter: 协调节点接收请求,将检索请求分发给各个数据节点;
search:在各个分片执行检索;
gather:数据节点将检索结果汇集到协调节点;
marge: 协调节点将数据结果进行合并,返回给客户端;

优化业务层:

1、索引层面优化配置

默认情况下,6.x及之前的版本中Elasticsearch索引有5个主分片和1个副本,7.X及之后版本1主1副。 这种配置并不适用于所有业务场景。 需要正确设置分片配置,以便维持索引的稳定性和有效性。

1.1、分片大小

分片大小对于搜索查询非常重要。

一方面, 如果分配给索引的分片太多,则Lucene分段会很小,从而导致开销增加。 当同时进行多个查询时,许多小分片也会降低查询吞吐量。

另一方面,太大的分片会导致搜索性能下降和故障恢复时间更长。

Elasticsearch官方建议一个分片的大小应该在20到40 GB左右。
例如,如果您计算出索引将存储300 GB的数据,则可以为该索引分配9到15个主分片。
根据集群大小,假设集群中有10个节点,您可以选择为此索引分配10个主分片,以便在集群节点之间均匀分配分片。

1.2、数据动态持续写入场景

如果存在连续写入到Elasticsearch集群的数据流,如:实时爬虫互联网数据写入ES集群。则应使用基于时间的索引以便更轻松地维护索引。如果写入数据流的吞吐量随时间而变化,则需要适当地改变下一个索引的配置才能实现数据的动态扩展。那么,如何查询分散到不同的基于时间索引的所有文档?答案是别名。可以将多个索引放入别名中,并且对该别名进行搜索会使查询就像在单个索引上一样。当然,需要保持好平衡。注意思考:将多少数据写入别名?别名上写入太多小索引会对性能产生负面影响。例如,是以周还是以月为单位为单位建立索引是需要结合业务场景平衡考虑的问题?如果以月为单位建议索引性能最优,那么相同数据以周为单位建立索引势必会因为索引太多导致负面的性能问题。
然后通过Curator进行定期清理。

1.3、Index Sorting

注意:索引排序机制是6.X版本才有的特性。

在Elasticsearch中创建新索引时,可以配置每个分片中的分段的排序方式。 默认情况下,Lucene不会应用任何排序。 index.sort.* 定义应使用哪些字段对每个Segment内的文档进行排序。index sorting机制通过写入的时候指定了某一个或者多个字段的排序方式,会极大提升检索的性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
curl -X PUT "localhost:9200/twitter" -H 'Content-Type: application/json' -d'
{
"settings" : {
"index" : {
"sort.field" : "date",
"sort.order" : "desc"
}
},
"mappings": {
"properties": {
"date": {
"type": "date"
}
}
}
}

2、分片层面优化配置

分片是底层基本的读写单元,分片的目的是分割巨大索引,让读写并行执行。写入过程先写入主分片,主分片写入成功后再写入副本分片。副本分片的出现,提升了集群的高可用性和读取吞吐率。在优化分片时,分片的大小、节点中有多少分片是主要考虑因素。副本分片对于扩展搜索吞吐量很重要,如果硬件条件允许,则可以小心增加副本分片的数量。容量规划的一个很好的启动点是分配分片,“《深入理解Elasticsearch》强调:最理想的分片数量应该依赖于节点的数量。”其数量是节点数量的1.5到3倍。分配副本分片数的公式:max(max_failures, ceil(num_nodes / num_primaries) - 1)。原理:如果您的集群具有num_nodes节点,总共有num_primaries主分片,如果您希望最多能够同时处理max_failures节点故障,那么适合您的副本数量为如上公式值。总的来说:节点数和分片数、副本数的简单计算公式如下:节点数=分片数*(副本数+1)。

3、Elasticsearch整体层面配置

配置Elasticsearch集群时,最主要的考虑因素之一是确保至少有一半的可用内存进入文件系统缓存,以便Elasticsearch可以将索引的hot regions保留在物理内存中。在设计集群时还应考虑物理可用堆空间。 Elasticsearch建议基于可用堆空间的分片分配最多应为20个分片/ GB,这是一个很好的经验法则。例如,具有30GB堆的节点最多应有600个分片,以保持集群的良好状态。一个节点上的存储可以表述如下:节点可以支持的磁盘空间= 20 (堆大小单位:GB)(以GB为单位的分片大小)由于在高效集群中通常会看到大小在20到40 GB之间的分片,因此最大存储空间可以支持16 GB可用堆空间的节点,最多可达12 TB的磁盘空间(201640=12.8TB)。边界意识有助于为更好的设计和未来的扩展操作做好准备。可以在运行时以及初始阶段进行许多配置设置。在构建Elasticsearch索引和集群本身以获得更好的搜索性能时,了解在运行时哪些配置可以修改以及哪些配不可以修改是至关重要的。

3.1 动态设置

1、设置历史数据索引为只读状态。

基于时间的动态索引的执行阶段,如果存放历史数据的索引没有写操作,可以将月度索引设置为只读模式,以提高对这些索引的搜索性能。6.X之后的只读索引实战设置方式:

1
2
3
4
PUT /twitter/_settings
{
"index.blocks.read_only_allow_delete": null
}

2、对只读状态索引,进行段合并。

当索引设置为只读时,可以通过强制段合并操作以减少段的数量。优化段合并将导致更好的搜索性能,因为每个分片的开销取决于段的计数和大小。

注意1:不要将段合并用于读写索引,因为它将导致产生非常大的段(每段> 5Gb)。

注意2:此操作应在非高峰时间进行,因为这是一项非常耗资源的操作。

段合并操作实战方式:

1
curl -X POST "localhost:9200/kimchy/_forcemerge?only_expunge_deletes=false&max_num_segments=100&flush=true"

3、使用preference优化缓存利用率

有多个缓存可以帮助提高搜索性能,例如文件系统缓存,请求缓存或查询缓存。然而,所有这些缓存都维护在节点级别,这意味着如果您在拥有1个或更多副本且基于默认路由算法集群上连续两次运行相同的请求,这两个请求将转到不同的分片副本上 ,阻止节点级缓存帮助。由于搜索应用程序的用户一个接一个地运行类似的请求是常见的,例如为了检索分析索引的部分较窄子集,使用preference标识当前用户或会话的偏好值可以帮助优化高速缓存的使用。

preference实战举例:

1
2
3
4
5
6
7
8
GET /_search?preference=xyzabc123
{
"query": {
"match": {
"title": "elasticsearch"
}
}

4、禁止交换

可以在每个节点上禁用交换以确保稳定性,并且应该不惜一切代价避免交换。它可能导致垃圾收集持续数分钟而不是毫秒,并且可能导致节点响应缓慢甚至断开与集群的连接。在Elasticsearch分布式系统中,让操作系统终止节点更有效。可以通过将bootstrap.memory_lock设置为True来禁用它。
Linux系统级配置:

sudo swapoff -a
Elasticsearch配置文件elasticsearch.yml配置:

1
bootstrap.memory_lock: true

5、增加刷新间隔 refresh_interval

默认刷新间隔为1秒。这迫使Elasticsearch每秒创建一个分段。实际业务中,应该根据使用情况增加刷新间隔,举例:增加到30秒。
这样之后,30s产生一个大的段,较每秒刷新大大减少未来的段合并压力。最终会提升写入性能并使搜索查询更加稳定。
更新刷新间隔实战:

1
2
3
4
5
6
PUT /twitter/_settings
{
"index" : {
"refresh_interval" : "1s"
}
}

6、设置index.merge.scheduler.max_thread_count

index.merge.scheduler.max_thread_count默认设置为Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2))。但这适用于SSD配置。对于HDD,应将其设置为1。
实战:

1
2
3
curl -XPUT 'localhost:9200/_settings' -d '{ 
"index.merge.scheduler.max_thread_count" : 1
}

7、禁止动态分配分片

有时,Elasticsearch将重新平衡集群中的分片。此操作可能会降低检索的性能。
在生产模式下,需要时,可以通过cluster.routing.rebalance.enable设置将重新平衡设置为none。

1
2
3
4
5
6
PUT /_cluster/settings 
{
"transient" : {
"cluster.routing.allocation.enable" : "none"
}
}

其中典型的应用场景之包括:

(1). 集群中临时重启、剔除一个节点;
(2). 集群逐个升级节点;当您关闭节点时,分配过程将立即尝试将该节点上的分片复制到集群中的其他节点,从而导致大量浪费的IO. 在关闭节点之前禁用分配可以避免这种情况。

8、充分利用近似日期缓存效果

现在使用的日期字段上的查询通常不可缓存,因为匹配的范围一直在变化。然而,就用户体验而言,切换到近似日期通常是可接受的,并且能更好地使用查询高速缓存带来的益处。
实战如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
GET index/_search
{
"query": {
"constant_score": {
"filter": {
"range": {
"my_date": {
"gte": "now-1h/m",
"lte": "now/m"
}
}
}
}
}
}

3.2 初始设置

1、合并多字段提升检索性能

query_string或multi_match查询所针对的字段越多,检索越慢。提高多个字段的搜索速度的常用技术是在索引时将其值复制到单个字段中。对于经常查询的某些字段,请使用Elasticsearch的copy-to功能。例如,汽车的品牌名称,发动机版本,型号名称和颜色字段可以与复制到指令合并。它将改善在这些字段上进行的搜索查询性能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
PUT movies
{
"mappings": {
"properties": {
"cars_infos": {
"type": "text"
},
"brand_name": {
"type": "text",
"copy_to": "cars_infos"
},
"engine_version": {
"type": "text",
"copy_to": "cars_infos"
},
"model ": {
"type": "text",
"copy_to": "cars_infos"
},
"color": {
"type": "text",
"copy_to": "cars_infos"
}
}
}
}

2、设置分片分配到指定节点

实战业务中经常遇到的业务场景问题:如何将分片设置非均衡分配,有新节点配置极高,能否多分片点过去?某个 shard 分配在哪个节点上,一般来说,是由 ES 自动决定的。以下几种情况会触发分配动作:

● 1)新索引生成

● 2)索引的删除

● 3)新增副本分片

● 4)节点增减引发的数据均衡

ES 提供了一系列参数详细控制这部分逻辑,其中之一是:在异构集群的情为具有更好硬件的节点的分片分配分配权重。为了分配权重,需要设置cluster.routing.allocation.balance.shard值,默认值为0.45f。数值越大越倾向于在节点层面均衡分片。

实战:

1
2
3
4
5
6
PUT _cluster/settings
{
“transient” : {
“cluster.routing.allocation.balance.shard” : 0.60
}
}

3、调整熔断内存比例大小

查询本身也会对响应的延迟产生重大影响。为了在查询时不触发熔断并导致Elasticsearch集群处于不稳定状态,
可以根据查询的复杂性将indices.breaker.total.limit设置为适合您的JVM堆大小。此设置的默认值是JVM堆的70%。

1
2
3
4
5
6
PUT /_cluster/settings
{
"persistent" : {
"indices.breaker.fielddata.limit" : "60%"
}
}

《Elastic源码分析》作者张超指出:“Elasticsearch 7.0 增加了 indices.breaker.total.use_real_memory 配置项,可以更加精准的分析当前的内存情况,及时防止 OOM 出现。虽然该配置会增加一点性能损耗,但是可以提高 JVM 的内存使用率,增强了节点的保护机制。”

4、特定搜索场景,增加搜索线程池配置

默认情况下,Elasticsearch将主要用例是搜索。在需要增加检索并发性的情况下,可以增加用于搜索设置的线程池,与此同时,可以根据节点上的CPU中的核心数量多少斟酌减少用于索引的线程池。

举例:更改配置文件elasticsearch.yml增加如下内容:
thread_pool.search.queue_size: 500 #queue_size允许控制没有线程执行它们的挂起请求队列的初始大小。

5、打开自适应副本选择

应打开自适应副本选择。该请求将被重定向到响应最快的节点。当存在多个数据副本时,elasticsearch可以使用一组称为自适应副本选择的标准,根据包含每个分片副本的节点的响应时间,服务时间和队列大小来选择数据的最佳副本。这样可以提高查询吞吐量并减少搜索量大的应用程序的延迟。
这个配置默认是关闭的,实战打开方法:

1
2
3
4
5
6
PUT /_cluster/settings
{
"transient": {
"cluster.routing.use_adaptive_replica_selection": true
}
}

优化基础层:

1、集群规划优化实践

1.1 基于目标数据量规划集群

在业务初期,经常被问到的问题,要几个节点的集群,内存、CPU要多大,要不要SSD?最主要的考虑点是:你的目标存储数据量是多大?可以针对目标数据量反推节点多少。

1.2 要留出容量Buffer

注意:Elasticsearch有三个警戒水位线,磁盘使用率达到85%、90%、95%。不同警戒水位线会有不同的应急处理策略。这点,磁盘容量选型中要规划在内。控制在85%之下是合理的。当然,也可以通过配置做调整。

1.3 ES集群各节点尽量不要和其他业务功能复用一台机器。

除非内存非常大。举例:普通服务器,安装了ES+Mysql+redis,业务数据量大了之后,势必会出现内存不足等问题。

1.4 磁盘尽量选择SSD

Elasticsearch官方文档肯定推荐SSD,考虑到成本的原因。需要结合业务场景,如果业务对写入、检索速率有较高的速率要求,建议使用SSD磁盘。阿里的业务场景,SSD磁盘比机械硬盘的速率提升了5倍。但要因业务场景而异。

1.5 内存配置要合理

官方建议:堆内存的大小是官方建议是:Min(32GB,机器内存大小/2)。Medcl和wood大叔都有明确说过,不必要设置32/31GB那么大,建议:热数据设置:26GB,冷数据:31GB。总体内存大小没有具体要求,但肯定是内容越大,检索性能越好。经验值供参考:每天200GB+增量数据的业务场景,服务器至少要64GB内存。除了JVM之外的预留内存要充足,否则也会经常OOM。

1.6 CPU核数不要太小

CPU核数是和ESThread pool关联的。和写入、检索性能都有关联。建议:16核+。

1.7 超大量级的业务场景,可以考虑跨集群检索

除非业务量级非常大,例如:滴滴、携程的PB+的业务场景,否则基本不太需要跨集群检索。

1.8 集群节点个数无需奇数

ES内部维护集群通信,不是基于zookeeper的分发部署机制,所以,无需奇数。但是discovery.zen.minimum_master_nodes的值要设置为:候选主节点的个数/2+1,才能有效避免脑裂。

1.9 节点类型优化分配

集群节点数:<=3,建议:所有节点的master:true, data:true。既是主节点也是路由节点。集群节点数:>3, 根据业务场景需要,建议:逐步独立出Master节点和协调/路由节点。

1.10 建议冷热数据分离

热数据存储SSD和普通历史数据存储机械磁盘,物理上提高检索效率。

2、索引优化实践

Mysql等关系型数据库要分库、分表。Elasticserach的话也要做好充分的考虑。

2.1 设置多少个索引?

建议根据业务场景进行存储。不同通道类型的数据要分索引存储。举例:知乎采集信息存储到知乎索引;APP采集信息存储到APP索引。

2.2 设置多少分片?

建议根据数据量衡量。经验值:建议每个分片大小不要超过30GB。

2.3 分片数设置?

建议根据集群节点的个数规模,分片个数建议>=集群节点的个数。5节点的集群,5个分片就比较合理。注意:除非reindex操作,分片数是不可以修改的。

2.4副本数设置?

除非你对系统的健壮性有异常高的要求,比如:银行系统。可以考虑2个副本以上。否则,1个副本足够。注意:副本数是可以通过配置随时修改的。

2.5不要再在一个索引下创建多个type

即便你是5.X版本,考虑到未来版本升级等后续的可扩展性。建议:一个索引对应一个type。6.x默认对应_doc,5.x你就直接对应type统一为doc。

2.6 按照日期规划索引

随着业务量的增加,单一索引和数据量激增给的矛盾凸显。按照日期规划索引是必然选择。

好处1:可以实现历史数据秒删。很对历史索引delete即可。注意:一个索引的话需要借助delete_by_query+force_merge操作,慢且删除不彻底。

好处2:便于冷热数据分开管理,检索最近几天的数据,直接物理上指定对应日期的索引,速度快的一逼!

操作参考:模板使用+rollover API使用。

2.7 务必使用别名

ES不像mysql方面的更改索引名称。使用别名就是一个相对灵活的选择。

3、数据模型优化实践

3.1 不要使用默认的Mapping

默认Mapping的字段类型是系统自动识别的。其中:string类型默认分成:text和keyword两种类型。如果你的业务中不需要分词、检索,仅需要精确匹配,仅设置为keyword即可。根据业务需要选择合适的类型,有利于节省空间和提升精度,如:浮点型的选择。

3.2 选择合理的分词器

常见的开源中文分词器包括:ik分词器、ansj分词器、hanlp分词器、结巴分词器、海量分词器、“ElasticSearch最全分词器比较及使用方法” 搜索可查看对比效果。如果选择ik,建议使用ik_max_word。因为:细粒度的分词结果基本包含粗粒度ik_smart的结果。

3.3 date、long、还是keyword

根据业务需要,如果需要基于时间轴做分析,必须date类型;如果仅需要秒级返回,建议使用keyword。

4、数据写入优化实践

4.1 要不要秒级响应?

Elasticsearch近实时的本质是:最快1s写入的数据可以被查询到。如果refresh_interval设置为1s,势必会产生大量的segment,检索性能会受到影响。所以,非实时的场景可以调大,设置为30s,甚至-1。

4.2 减少副本,提升写入性能。

写入前,副本数设置为0,写入后,副本数设置为原来值。

4.3 能批量就不单条写入

批量接口为bulk,批量的大小要结合队列的大小,而队列大小和线程池大小、机器的cpu核数。

5、检索聚合优化实战

5.1 禁用 wildcard模糊匹配

数据量级达到TB+甚至更高之后,wildcard在多字段组合的情况下很容易出现卡死,甚至导致集群节点崩溃宕机的情况。后果不堪设想。

替代方案:

方案一:针对精确度要求高的方案:两套分词器结合,standard和ik结合,使用match_phrase检索。

方案二:针对精确度要求不高的替代方案:建议ik分词,通过match_phrase和slop结合查询。

5.2极小的概率使用match匹配

中文match匹配显然结果是不准确的。很大的业务场景会使用短语匹配“match_phrase”。match_phrase结合合理的分词词典、词库,会使得搜索结果精确度更高,避免噪音数据。

5.3 结合业务场景,大量使用filter过滤器

对于不需要使用计算相关度评分的场景,无疑filter缓存机制会使得检索更快。举例:过滤某邮编号码。

5.3控制返回字段和结果

和mysql查询一样,业务开发中,select * 操作几乎是不必须的。同理,ES中,_source 返回全部字段也是非必须的。要通过_source 控制字段的返回,只返回业务相关的字段。网页正文content,网页快照html_content类似字段的批量返回,可能就是业务上的设计缺陷。显然,摘要字段应该提前写入,而不是查询content后再截取处理。

5.4 分页深度查询和遍历

分页查询使用:from+size;遍历使用:scroll;并行遍历使用:scroll+slice。斟酌集合业务选型使用。

5.5 聚合Size的合理设置

聚合结果是不精确的。除非你设置size为2的32次幂-1,否则聚合的结果是取每个分片的Top size元素后综合排序后的值。实际业务场景要求精确反馈结果的要注意。尽量不要获取全量聚合结果——从业务层面取TopN聚合结果值是非常合理的。因为的确排序靠后的结果值意义不大。

5.6 聚合分页合理实现

聚合结果展示的时,势必面临聚合后分页的问题,而ES官方基于性能原因不支持聚合后分页。

如果需要聚合后分页,需要自开发实现。包含但不限于:

方案一:每次取聚合结果,拿到内存中分页返回。

方案二:scroll结合scroll after集合redis实现。

6、业务优化

让Elasticsearch做它擅长的事情,很显然,它更擅长基于倒排索引进行搜索。业务层面,用户想最快速度看到自己想要的结果,中间的“字段处理、格式化、标准化”等一堆操作,用户是不关注的。

为了让Elasticsearch更高效的检索,建议:

1)要做足“前戏”字段抽取、倾向性分析、分类/聚类、相关性判定放在写入ES之前的ETL阶段进行;

2)作为技术人员,要明白搜索引擎的原理、Elasticsearch的原理,哪些能做,哪些真的“臣妾做不到”。