es查询⼤⽂本效率_超详细的Elasticarch⾼性能优化实践ES 性能调优
ES 的默认配置,是综合了数据可靠性、写⼊速度、搜索实时性等因素。实际使⽤时,我们需要根据公司要求,进⾏偏向性的优化。
写优化
假设我们的应⽤场景要求是,每秒 300 万的写⼊速度,每条 500 字节左右。
针对这种对于搜索性能要求不⾼,但是对写⼊要求较⾼的场景,我们需要尽可能的选择恰当写优化策略。
综合来说,可以考虑以下⼏个⽅⾯来提升写索引的性能:加⼤ Translog Flush ,⽬的是降低 Iops、Writeblock。
增加 Index Refresh 间隔,⽬的是减少 Segment Merge 的次数。
调整 Bulk 线程池和队列。
优化节点间的任务分布。
优化 Lucene 层的索引建⽴,⽬的是降低 CPU 及 IO。
①批量提交
ES 提供了 Bulk API ⽀持批量操作,当我们有⼤量的写任务时,可以使⽤ Bulk 来进⾏批量写⼊。
每次提交的数据量为多少时,能达到最优的性能,主要受到⽂件⼤⼩、⽹络情况、数据类型、集群状态等因素影响。
通⽤的策略如下:Bulk 默认设置批量提交的数据量不能超过 100M。数据条数⼀般是根据⽂档的⼤⼩和服务器性能⽽定的,但是单次批处理的数据⼤⼩应从 5MB~15MB 逐渐增加,当性能没有提升时,把这个数据量作为最⼤值。
我们可以跟着,感受⼀下 Bulk 接⼝,如下所⽰:
$ vi request
$ cat request
作文素材网站{ "index" : { "_index" : "chandler","_type": "test", "_id" : "1" } }
{ "name" : "钱丁君","age": "18" }
$ curl -s -H "Content-Type: application/json" -XPOST localhost:9200/_bulk --data-binary @request; echo
{"took":214,"errors":fal,"items":[{"index":{"_index":"chandler","_type":"test","_id":"1","_version":1,"result":"created","_shards": {"total":2,"successful":1,"failed":0},"_q_no":0,"_primary_term":1,"status":201}}]}
$ curl -XGET localhost:9200/chandler/test/1?pretty
{
"_index" : "chandler",
"_type" : "test",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"name" : "钱丁君",
"age" : "18"
}
}
Bulk 不⽀持 Gget 操作,因为没什么⽤处。
②优化存储设备
ES 是⼀种密集使⽤磁盘的应⽤,在段合并的时候会频繁操作磁盘,所以对磁盘要求较⾼,当磁盘速度提升之后,集群的整体性能会⼤幅度提⾼。
磁盘的选择,提供以下⼏点建议:使⽤固态硬盘(Solid State Disk)替代机械硬盘。SSD 与机械磁盘相⽐,具有⾼效的读写速度和稳定性。
使⽤ RAID 0。RAID 0 条带化存储,可以提升磁盘读写效率。
小孩淋巴结肿大在 ES 的服务器上挂载多块硬盘。使⽤多块硬盘同时进⾏读写操作提升效率,在配置⽂件 ES 中设置多个存储路径,如下所⽰:
path.data:/path/to/data1,/path/to/data2。
避免使⽤ NFS(Network File System)等远程存储设备,⽹络的延迟对性能的影响是很⼤的。
③合理使⽤合并
Lucene 以段的形式存储数据。当有新的数据写⼊索引时,Lucene 就会⾃动创建⼀个新的段。
随着数据量的变化,段的数量会越来越多,消耗的多⽂件句柄数及 CPU 就越多,查询效率就会下降。
由于 Lucene 段合并的计算量庞⼤,会消耗⼤量的 I/O,所以 ES 默认采⽤较保守的策略,让后台定期进⾏段合并,如下所述:索引写⼊效率下降:当段合并的速度落后于索引写⼊的速度时,ES 会把索引的线程数量减少到 1。
这样可以避免出现堆积的段数量爆发,同时在⽇志中打印出“now throttling indexing”INFO 级别的“警告”信息。
提升段合并速度:ES 默认对段合并的速度是 20m/s,如果使⽤了 SSD,我们可以通过以下的命令将这个合并的速度增加到 100m/s。
PUT /_cluster/ttings
{黄花菜鸡蛋汤
"persistent" : {
"indices.store.throttle.max_bytes_per_c" : "100mb"
}
}
④减少 Refresh 的次数
Lucene 在新增数据时,采⽤了延迟写⼊的策略,默认情况下索引的 refresh_interval 为 1 秒。
Lucene 将待写⼊的数据先写到内存中,超过 1 秒(默认)时就会触发⼀次 Refresh,然后 Refresh 会把内存中的的数据刷新到操作系统的⽂件缓存系统中。
如果我们对搜索的实效性要求不⾼,可以将 Refresh 周期延长,例如 30 秒。
这样还可以有效地减少段刷新次数,但这同时意味着需要消耗更多的Heap内存。
如下所⽰:
⑤加⼤ Flush 设置
Flush 的主要⽬的是把⽂件缓存系统中的段持久化到硬盘,当 Translog 的数据量达到 512MB 或者 30 分钟时,会触发⼀次 Flush。
增加参数值意味着⽂件缓存系统中可能需要存储更多的数据,所以我们需要为操作系统的⽂件缓存系统留下⾜够的空间。
婚姻法离婚规定
⑥减少副本的数量
ES 为了保证集群的可⽤性,提供了 Replicas(副本)⽀持,然⽽每个副本也会执⾏分析、索引及可能的合并过程,所以 Replicas 的数量会
严重影响写索引的效率。
当写索引时,需要把写⼊的数据都同步到副本节点,副本节点越多,写索引的效率就越慢。
如果我们需要⼤批量进⾏写⼊操作,可以先禁⽌ Replica 复制,设置 index.number_of_replicas: 0 关闭副本。在写⼊完成后,Replica
修改回正常的状态。
读优化
①避免⼤结果集和深翻
在上⼀篇讲到了集群中的查询流程,例如,要查询从 from 开始的 size 条数据,则需要在每个分⽚中查询打分排名在前⾯的 from+size 条
数据。
协同节点将收集到的n×(from+size)条数据聚合,再进⾏⼀次排序,然后从 from+size 开始返回 size 条数据。
当 from、size 或者 n 中有⼀个值很⼤的时候,需要参加排序的数量也会增长,这样的查询会消耗很多 CPU 资源,从⽽导致效率的降低。
为了提升查询效率,ES 提供了 Scroll 和 Scroll-Scan 这两种查询模式。
Scroll:是为检索⼤量的结果⽽设计的。例如,我们需要查询 1~100 页的数据,每页 100 条数据。
如果使⽤ Search 查询:每次都需要在每个分⽚上查询得分最⾼的 from+100 条数据,然后协同节点把收集到的 n×(from+100)条数据
聚合起来再进⾏⼀次排序。
每次返回 from+1 开始的 100 条数据,并且要重复执⾏ 100 次。
如果使⽤ Scroll 查询:在各个分⽚上查询 10000 条数据,协同节点聚合 n×10000 条数据进⾏合并、排序,并将排名前 10000 的结果
快照起来。这样做的好处是减少了查询和排序的次数。
Scroll 初始查询的命令是:
$ vim scroll
$ cat scroll
{
"query": {
"match": {
"name": "钱丁君"
}
},
"size":20
}
$ curl -s -H "Content-Type: application/json; chart=UTF-8" -XGET localhost:9200/chandler/test/_arch?scroll=2m --
data-binary @scroll; echo
{"_scroll_id":"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAAAGFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAABxZQemN0LTZ {"total":5,"successful":5,"skipped":0,"failed":0},"hits":{"total":1,"max_score":0.8630463,"hits":
[{"_index":"chandler","_type":"test","_id":"1","_score":0.8630463,"_source":{ "name" : "钱丁君","age": "18" }}]}}小学生游戏
以上查询语句的含义是,在 chandler 索引的 test type ⾥查询字段 name 包含“钱丁君”的数据。
scroll=2m 表⽰下次请求的时间不能超过 2 分钟,size 表⽰这次和后续的每次请求⼀次返回的数据条数。
在这次查询的结果中除了返回了查询到的结果,还返回了⼀个 scroll_id,可以把它作为下次请求的参数。
Scroll-Scan:Scroll 是先做⼀次初始化搜索把所有符合搜索条件的结果缓存起来⽣成⼀个快照,然后持续地、批量地从快照⾥拉取数据直
到没有数据剩下。
⽽这时对索引数据的插⼊、删除、更新都不会影响遍历结果,因此 Scroll 并不适合⽤来做实时搜索。郭人豪
其思路和使⽤⽅式与 Scroll ⾮常相似,但是 Scroll-Scan 关闭了 Scroll 中最耗时的⽂本相似度计算和排序,使得性能更加⾼效。
为了使⽤ Scroll-Scan,需要执⾏⼀个初始化搜索请求,将 arch_type 设置成 Scan,告诉 ES 集群不需要⽂本相似计算和排序,只是按
照数据在索引中顺序返回结果集:
$ vi scroll
$ cat scroll
{
"query": {
"match": {
"name": "钱丁君"
}
},
"size":20,
"sort": [
"_doc"
]
}
$ curl -H "Content-Type: application/json; chart=UTF-8" -XGET 'localhost:9200/chandler/test/_arch?
scroll=2m&pretty=true' --data-binary @scroll
{
"_scroll_id" :
"DnF1ZXJ5VGhlbkZldGNoBQAAAAAAAABWFlB6Y3QtNk9oUmdpc09Tb21rX2NXQXcAAAAAAAAAVxZQemN0LTZPaFJnaXNPU29
欲开头的四字成语
"took" : 3,
"timed_out" : fal,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1,
"max_score" : null,
"hits" : [
{
"_index" : "chandler",
"_type" : "test",
"_id" : "1",
"_score" : null,
"_source" : {
"name" : "钱丁君",
"age" : "18"
},
"sort" : [
]
}
]
}
春风像什么}
注意:Elasticarch 2.1.0 版本之后移除了 arch_type=scan,使⽤ "sort": [ "_doc"] 进⾏代替。
Scroll 和 Scroll-Scan 有⼀些差别,如下所⽰:Scroll-Scan不进⾏⽂本相似度计算,不排序,按照索引中的数据顺序返回。
Scroll-Scan 不⽀持聚合操作。
Scroll-Scan 的参数 Size 代表着每个分⽚上的请求的结果数量,每次返回 n×size 条数据。⽽ Scroll 每次返回 size 条数据。
②选择合适的路由
ES 中所谓的路由和 IP ⽹络不同,是⼀个类似于 Tag 的东西。在创建⽂档的时候,可以通过字段为⽂档增加⼀个路由属性的 Tag。在多分⽚的 ES 集群中,对搜索的查询⼤致分为如下两种。
ES 内在机制决定了拥有相同路由属性的⽂档,⼀定会被分配到同⼀个分⽚上,⽆论是主分⽚还是副本。
查询时可以根据 Routing 信息,直接定位到⽬标分⽚,避免查询所有的分⽚,再经过协调节点⼆次排序。如果在查询条件中不包含Routing,在查询时就遍历所有分⽚,整个查询主要分为 Scatter、Gather 两个过程:Scatter(分发):请求到达协调节点之后,协调节点将查询请求分发给每个分⽚。
Gather(聚合):协调点在每个分⽚上完成搜索,再将搜索到的结果集进⾏排序,将结果数据返回给⽤户。
通过对⽐上述两种查询流程,我们不难发现,使⽤ Routing 信息查找的效率很⾼,避免了多余的查询。
所以我们在设计 Elasticarch Mapping 时要合理地利⽤ Routing 信息,来提升查询的效率。
例如,在⼤型的本地分类⽹站中,可以将城市 ID 作为 Routing 的条件,让同⼀个城市的数据落在相同的分⽚中。
默认的公式如下: