厉害了,ES 如何做到几十亿数据检索 3 秒返回!
作者:mikevictor
www.cnblogs.com/mikevictor07/p/10006553.html
一、前言
二、需求说明
三、ES 检索原理
谈到优化,必须能了解组件的基本原理,才容易找到瓶颈所在,以免走多种弯路,先从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 分片备份,用于保障数据安全与分担检索压力
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:
3.3 关于ES索引与检索分片
四、优化案例
1、ES仅提供字段的检索,仅存储HBase的Rowkey不存储实际数据。
2、实际数据存储在HBase中,通过Rowkey查询,如下图。
3、提高索引与检索的性能建议,可参考官方文档(如 https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html)
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,
(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
6、关于排序:我们增加一个long字段,它用于存储时间和ID的组合(通过移位即可),正排与倒排性能相差不明显。
7、关于CPU消耗,检索时如果需要做排序则需要字段对比,消耗CPU比较大,如果有可能尽量分配16cores以上的CPU,具体看业务压力。
8、关于合并被标记删除的记录,我们设置为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": {......}
}
五、性能测试
优化效果评估基于基准测试,如果没有基准测试无法了解是否有性能提升,在这所有的变动前做一次测试会比较好。在我们的案例中:
1、单节点5千万到一亿的数据量测试,检查单点承受能力。
2、集群测试1亿-30亿的数量,磁盘IO/内存/CPU/网络IO消耗如何。
3、随机不同组合条件的检索,在各个数据量情况下表现如何。
4、另外SSD与机械盘在测试中性能差距如何。
性能的测试组合有很多,通常也很花时间,不过作为评测标准时间上的投入有必要,否则生产出现性能问题很难定位或不好改善。
对于ES的性能研究花了不少时间,最多的关注点就是lucene的优化,能深入了解lucene原理对优化有很大的帮助。
六、生产效果
目前平台稳定运行,几十亿的数据查询100条都在3秒内返回,前后翻页很快,如果后续有性能瓶颈,可通过扩展节点分担数据压力。