版权说明: 本文章版权归本人及博客园共同全部,转载请标明原文出处( http://www.javashuo.com/article/p-eyuptbrf-ds.html ),如下内容为我的理解,仅供参考。html
1、前言apache
数据平台已迭代三个版本,从头开始遇到不少常见的难题,终于有片断时间整理一些已完善的文档,在此分享以供所需朋友的缓存
实现参考,少走些弯路,在此篇幅中偏重于ES的优化,关于HBase,Hadoop的设计优化估计有不少文章能够参考,再也不赘述。安全
2、需求说明网络
项目背景:数据结构
在一业务系统中,部分表天天的数据量过亿,已按天分表,但业务上受限于按天查询,而且DB中只能保留3个月的数据(硬件高配),分库代价较高。多线程
改进版本目标:app
1. 数据能跨月查询,而且支持1年以上的历史数据查询与导出。elasticsearch
2. 按条件的数据查询秒级返回。ide
3、elasticsearch检索原理
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官方: http://lucene.apache.org/core/7_2_1/core/org/apache/lucene/codecs/lucene70/package-summary.html#package.description
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进行数据分区,则可减小分片的检索工做,以提升性能。
4、优化案例
在咱们的案例中,查询字段都是固定的,不提供全文检索功能,这也是几十亿数据能秒级返回的一个大前提:
一、ES仅提供字段的检索,仅存储HBase的Rowkey不存储实际数据。
二、实际数据存储在HBase中,经过Rowkey查询,以下图。
三、提升索引与检索的性能建议,可参考官方文档(如 https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html)。
一些细节优化项官方与其余的一些文章都有描述,在此文章中仅提出一些本案例的重点优化项。
4.1 优化索引性能
一、批量写入,看每条数据量的大小,通常都是几百到几千。
二、多线程写入,写入线程数通常和机器数至关,能够配多种状况,在测试环境经过Kibana观察性能曲线。
三、增长segments的刷新时间,经过上面的原理知道,segment做为一个最小的检索单元,好比segment有50个,目的须要查10条数据,但须要从50个segment
分别查询10条,共500条记录,再进行排序或者分数比较后,截取最前面的10条,丢弃490条。在咱们的案例中将此 "refresh_interval": "-1" ,程序批量写入完成后
进行手工刷新(调用相应的API便可)。
四、内存分配方面,不少文章已经提到,给系统50%的内存给Lucene作文件缓存,它任务很繁重,因此ES节点的内存须要比较多(好比每一个节点能配置64G以上最好)。
五、磁盘方面配置SSD,机械盘作阵列RAID5 RAID10虽然看上去很快,可是随机IO仍是SSD好。
六、 使用自动生成的ID,在咱们的案例中使用自定义的KEY,也就是与HBase的ROW KEY,是为了能根据rowkey删除和更新数据,性能降低不是很明显。
七、关于段合并,合并在后台按期执行,比较大的segment须要很长时间才能完成,为了减小对其余操做的影响(如检索),elasticsearch进行阈值限制,默认是20MB/s,
可配置的参数:"indices.store.throttle.max_bytes_per_sec" : "200mb" (根据磁盘性能调整)
合并线程数默认是:Math.max(1, Math.min(4, Runtime.getRuntime().availableProcessors() / 2)),若是是机械磁盘,能够考虑设置为1:index.merge.scheduler.max_thread_count: 1,
在咱们的案例中使用SSD,配置了6个合并线程。
4.2 优化检索性能
一、关闭不须要字段的doc values。
二、尽可能使用keyword替代一些long或者int之类,term查询总比range查询好 (参考lucene说明 http://lucene.apache.org/core/7_4_0/core/org/apache/lucene/index/PointValues.html)。
三、关闭不须要查询字段的_source功能,不将此存储仅ES中,以节省磁盘空间。
四、评分消耗资源,若是不须要可以使用filter过滤来达到关闭评分功能,score则为0,若是使用constantScoreQuery则score为1。
五、关于分页:
(1)from + size:
每分片检索结果数最大为 from + size,假设from = 20, size = 20,则每一个分片须要获取20 * 20 = 400条数据,多个分片的结果在协调节点合并(假设请求的分配数为5,则结果数最大为 400*5 = 2000条) 再在内存中排序后而后20条给用户。这种机制致使越日后分页获取的代价越高,达到50000条将面临沉重的代价,默认from + size默认以下:
index.max_result_window : 10000
(2) search_after: 使用前一个分页记录的最后一条来检索下一个分页记录,在咱们的案例中,首先使用from+size,检索出结果后再使用search_after,在页面上咱们限制了用户只能跳5页,不能跳到最后一页。
(3) scroll 用于大结果集查询,缺陷是须要维护scroll_id
六、关于排序:咱们增长一个long字段,它用于存储时间和ID的组合(经过移位便可),正排与倒排性能相差不明显。
七、关于CPU消耗,检索时若是须要作排序则须要字段对比,消耗CPU比较大,若是有可能尽可能分配16cores以上的CPU,具体看业务压力。
八、关于合并被标记删除的记录,咱们设置为0表示在合并的时候必定删除被标记的记录,默认应该是大于10%才删除: "merge.policy.expunge_deletes_allowed": "0"。
{ "mappings": { "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": {......} }
5、性能测试
优化效果评估基于基准测试,若是没有基准测试没法了解是否有性能提高,在这全部的变更前作一次测试会比较好。在咱们的案例中:
一、单节点5千万到一亿的数据量测试,检查单点承受能力。
二、集群测试1亿-30亿的数量,磁盘IO/内存/CPU/网络IO消耗如何。
三、随机不一样组合条件的检索,在各个数据量状况下表现如何。
四、另外SSD与机械盘在测试中性能差距如何。
性能的测试组合有不少,一般也很花时间,不过做为评测标准时间上的投入有必要,不然生产出现性能问题很难定位或很差改善。对于ES的性能研究花了很多时间,最多的关注点就是lucene的优化,能深刻了解lucene原理对优化有很大的帮助。
6、生产效果
目前平台稳定运行,几十亿的数据查询100条都在3秒内返回,先后翻页很快,若是后续有性能瓶颈,可经过扩展节点分担数据压力。