ES优化:ElasticSearch亿级数据检索案例实战!
⼀、前⾔
数据平台已迭代三个版本,从头开始遇到很多常见的难题,终于有⽚段时间整理⼀些已完善的⽂档,在此分享以供所需朋友的
实现参考,少⾛些弯路,在此篇幅中偏重于ES的优化,关于HBase,Hadoop的设计优化估计有很多⽂章可以参考,不再赘述。
⼆、需求说明
项⽬背景:
在⼀业务系统中,部分表每天的数据量过亿,已按天分表,但业务上受限于按天查询,并且DB中只能保留3个⽉的数据(硬件⾼配),分库代价较⾼。改进版本⽬标:
数据能跨⽉查询,并且⽀持1年以上的历史数据查询与导出。
按条件的数据查询秒级返回。
3.1 关于ES和Lucene基础结构
谈到优化必须能了解组件的基本原理,才容易到瓶颈所在,以免⾛多种弯路,先从ES的基础结构说起(如下图):
⼀些基本概念:
Cluster 包含多个Node的集
Node 集服务单元
Index ⼀个ES索引包含⼀个或多个物理分⽚,它只是这些分⽚的逻辑命名空间
Type ⼀个index的不同分类,6.x后只能配置⼀个type,以后将移除
Document 最基础的可被索引的数据单元,如⼀个JSON串
Shards ⼀个分⽚是⼀个底层的⼯作单元,它仅保存全部数据中的⼀部分,它是⼀个Lucence实例 (⼀个lucene索引最⼤包含2,147,483,519 (= Integer.MAX_VALUE - 128)个⽂档数量)
Replicas 分⽚备份,⽤于保障数据安全与分担检索压⼒
ES依赖⼀个重要的组件Lucene,关于数据结构的优化通常来说是对Lucene的优化,它是集的⼀个存储于检索⼯作单元,结构如下图:
在Lucene中,分为索引(录⼊)与检索(查询)两部分,索引部分包含分词器、过滤器、字符映射器等,检索部分包含查询解析器等。
⼀个Lucene索引包含多个segments,⼀个segment包含多个⽂档,每个⽂档包含多个字段,每个字段经过分词后形成⼀个或多个term。
通过Luke⼯具查看ES的lucene⽂件如下,主要增加了_id和_source字段:
3.2 Lucene索引实现
Lucene 索引⽂件结构主要的分为:词典、倒排表、正向⽂件、DocValues等,如下图:
注:整理来源于lucene官⽅:
Lucene 随机三次磁盘读取⽐较耗时。其中.fdt⽂件保存数据值损耗空间⼤,.tim和.doc则需要SSD存储提⾼随机读写性能。
另外⼀个⽐较消耗性能的是打分流程,不需要则可屏蔽。
关于DocValues:
倒排索引解决从词快速检索到相应⽂档ID, 但如果需要对结果进⾏排序、分组、聚合等操作的时候则需要根据⽂档ID快速到对应的值。
通过倒排索引代价缺很⾼:需迭代索引⾥的每个词项并收集⽂档的列⾥⾯ token。这很慢⽽且难以扩展:随着词项和⽂档的数量增加,执⾏时间也会增加。Solr docs对此的解释如下:
For other features that we now commonly associate with search, such as sorting, faceting, and highlighting, this approach is not very efficient. The faceting engine,
for example, must look up each term that appears in each document that will make up the result set and pull the document IDs in order to build the facet list. In Solr, this is maintained in memory, and can be slow to load (depending on the number of documents, terms, etc.)
在lucene 4.0版本前通过FieldCache,原理是通过按列逆转倒排表将(field value ->doc)映射变成(doc -> field value)映射,问题为逐步构建时间长并且消耗⼤量内存,容易造成OOM。
DocValues是⼀种列存储结构,能快速通过⽂档ID到相关需要排序的字段。在ES中,默认开启所有(除了标记需analyzed的字符串字段)字段的doc values,如果不需要对此字段做任何排序等⼯作,则可关闭
以减少资源消耗。
3.3 关于ES索引与检索分⽚
ES中⼀个索引由⼀个或多个lucene索引构成,⼀个lucene索引由⼀个或多个segment构成,其中segment是最⼩的检索域。
数据具体被存储到哪个分⽚上:
shard = hash(routing) % number_of_primary_shards
默认情况下 routing参数是⽂档ID (murmurhash3),可通过 URL中的 _routing 参数指定数据分布在同⼀个分⽚中,index和search的时候都需要⼀致才能到数据
如果能明确根据_routing进⾏数据分区,则可减少分⽚的检索⼯作,以提⾼性能。
四、优化案例
在我们的案例中,查询字段都是固定的,不提供全⽂检索功能,这也是⼏⼗亿数据能秒级返回的⼀个⼤前提:
ES仅提供字段的检索,仅存储HBase的Rowkey不存储实际数据。
实际数据存储在HBase中,通过Rowkey查询,如下图。
提⾼索引与检索的性能建议,可参考官⽅⽂档
⼀些细节优化项官⽅与其他的⼀些⽂章都有描述,在此⽂章中仅提出⼀些本案例的重点优化项。
4.1 优化索引性能
1、批量写⼊,看每条数据量的⼤⼩,⼀般都是⼏百到⼏千。
2、多线程写⼊,写⼊线程数⼀般和机器数相当,可以配多种情况,在测试环境通过Kibana观察性能曲线。
3、增加segments的刷新时间,通过上⾯的原理知道,segment作为⼀个最⼩的检索单元,⽐如segment有50个,⽬的需要查10条数据,但需要从50个segment分别查询10条,共500条记录,再进⾏排序或者分数⽐较后,截取最前⾯的10条,丢弃490条。
在我们的案例中将此 "refresh_interval": "-1" ,程序批量写⼊完成后进⾏⼿⼯刷新(调⽤相应的API即可)。
4、内存分配⽅⾯,很多⽂章已经提到,给系统50%的内存给Lucene做⽂件缓存,它任务很繁重,所以ES节点的内存需要⽐较多(⽐如每个节点能配置64G以上最好)。
5、磁盘⽅⾯配置SSD,机械盘做阵列RAID5 RAID10虽然看上去很快,但是随机IO还是SSD好。
6、使⽤⾃动⽣成的ID,在我们的案例中使⽤⾃定义的KEY,也就是与HBase的ROW KEY,是为了能根据rowkey删除和更新数据,性能下降不是很明显。
7、关于段合并,合并在后台定期执⾏,⽐较⼤的segment需要很长时间才能完成,为了减少对其他操作的影响(如检索),elasticsearch进⾏阈值限制,默认是20MB/s,
可配置的参数:"indices.store.throttle.max_bytes_per_sec" : "200mb" (根据磁盘性能调整)
合并线程数默认是:Math.max(1, Math.min(4, Runtime().availableProcessors() / 2)),
如果是机械磁盘,可以考虑设置为1:scheduler.max_thread_count: 1,在我们的案例中使⽤SSD,配置了6个合并线程。
4.2 优化检索性能
1、关闭不需要字段的doc values。
2、尽量使⽤keyword替代⼀些long或者int之类,term查询总⽐range查询好 (参考lucene说明 )。
3、关闭不需要查询字段的_source功能,不将此存储仅ES中,以节省磁盘空间。
4、评分消耗资源,如果不需要可使⽤filter过滤来达到关闭评分功能,score则为0,如果使⽤constantScoreQuery则score为1。
5、关于分页:
from + size: 每分⽚检索结果数最⼤为 from + size,假设from = 20, size = 20,则每个分⽚需要获取20 * 20 = 400条数据,多个分⽚的结果在协调节点合并(假设请求的分配数为5,则结果数最⼤为 400*5 = 2000条) 再在内存中排序后然后20条给⽤户。
这种机制导致越往后分页获取的代价越⾼,达到50000条将⾯临沉重的代价,默认from + size默认如下:
index.max_result_window :10000
search_after: 使⽤前⼀个分页记录的最后⼀条来检索下⼀个分页记录
在我们的案例中,⾸先使⽤from+size,检索出结果后再使⽤search_after,在页⾯上我们限制了⽤户只能跳5页,不能跳到最后⼀页。
scroll: ⽤于⼤结果集查询,缺陷是需要维护scroll_id
6、关于排序:我们增加⼀个long字段,它⽤于存储时间和ID的组合(通过移位即可),正排与倒排性能相差不明显。
7、关于CPU消耗,检索时如果需要做排序则需要字段对⽐,消耗CPU⽐较⼤,如果有可能尽量分配16cores以上的CPU,具体看业务压⼒。
8、关于合并被标记删除的记录,我们设置为0表⽰在合并的时候⼀定删除被标记的记录,默认应该是⼤于10%才删
除:"punge_deletes_allowed": "0"。
{
"mappings": {
hbase官方文档"data": {
"dynamic": "false",
"_source": {
"includes": ["XXX"]  -- 仅将查询结果所需的数据存储仅_source中
},
"properties": {
"state": {
"type": "keyword",  -- 虽然state为int值,但如果不需要做范围查询,尽量使⽤keyword,因为int需要⽐keyword增加额外的消耗。                    "doc_values": false  -- 关闭不需要字段的doc values功能,仅对需要排序,汇聚功能的字段开启。
},
"b": {
"type": "long"    -- 使⽤了范围查询字段,则需要⽤long或者int之类(构建类似KD-trees结构)
}
}
}
},
"settings": {......}
}
View Code
五、性能测试
优化效果评估基于基准测试,如果没有基准测试⽆法了解是否有性能提升,在这所有的变动前做⼀次测试会⽐较好。在我们的案例中:
单节点5千万到⼀亿的数据量测试,检查单点承受能⼒。
集测试1亿-30亿的数量,磁盘IO/内存/CPU/⽹络IO消耗如何。
随机不同组合条件的检索,在各个数据量情况下表现如何。
另外SSD与机械盘在测试中性能差距如何。
性能的测试组合有很多,通常也很花时间,不过作为评测标准时间上的投⼊有必要,否则⽣产出现性能问题很难定位或不好改善。
对于ES的性能研究花了不少时间,最多的关注点就是lucene的优化,能深⼊了解lucene原理对优化有很⼤的帮助。
六、⽣产效果
⽬前平台稳定运⾏,⼏⼗亿的数据查询100条都在3秒内返回,前后翻页很快,如果后续有性能瓶颈,可通过扩展节点分担数据压⼒。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。