Lucene On Spark -- 云原生OLAP引擎加速数据分析(一)
一.概述
大数据OLAP引擎根据灵活性、及时性、成本收益比,通常分为如下三种:
A.预聚合查询结果:提前计算好各个维度的汇总数据,常见方案有:kylin,druid等 。
B.即席查询类:实时查询明细数据,常见方案有:clickhouse,doris,TiDB等。
C.离线写入+现场计算:常见方案有:ES,Solr,bitmap on hbase/redis/pgsql等,可附加使用HBase/MongoDB等作为明细级数据存储。
三者转换关系如下:
通常在数据运营及数据探索的过程中,需要追求一定的灵活性,较低的硬件成本以及可接受的响应速度,此时通常会选择上述C方案。
考虑到企业数据分析通常不追求高并发和毫秒级响应,而更需要在海量数据的分析灵活性和成本上寻求出路,因此常驻在线服务如ES,Solr等,仍然不是最好的选择。
基于上述场景,本文提出了一种新的解决方案。
将Lucene索引文件分成多个partition存储到HDFS,并通过Spark引擎分布式执行方式加速索引的写入和计算,以及处理缓存。
为完成上述方案,要解决几项关键的技术问题:
1)Lucene HDFSIndexWriter和HDFSIndexReader开发。
2)Spark分布式执行各个partition结果,支持筛选,分组,聚合等运算,并对结果集进行Merge。
3)完善的缓存加速机制,解决HDFS随机读性能问题。
二.Lucene On HDFS实现
Solr作为基于Lucene的分布式搜索引擎之一(另一个知名的基于Lucene的搜索引擎产品是ElasticSearch),其曾在SolrCloud源码中实现了基于HDFS的索引,但8.6版本后被标记为Deprecated,9.0版本之后从solr-core中移出,作为单独的模块被放在/solr/modules/hdfs下。
参考如下问题单:
Deprecate HDFS support from 8x
https://issues.apache.org/jira/browse/SOLR-14021
Migrating HDFS into a module
https://issues.apache.org/jira/browse/SOLR-14660
除上述问题单中讨论的原因外,此处分析一下Solr不适合以HDFS文件系统作为其后端存储系统的其他原因:
1.性能问题:Solr定位为搜索引擎,这种场景通常追求高并发低延时,使用本地文件系统以及固态硬盘通常更合适。
2.配置难度大:无论是Solr,HDFS还是Lucene,都需要一定的技术积累才能完全掌握,Solr在开放其HDFS相关配置时,对配置的说明写得比较模糊,因此导致性能调优难度大。参考:https://solr.apache.org/guide/8_2/running-solr-on-hdfs.html#example-solrconfig-xml-for-hdfs
而OLAP场景下,由于并不追求毫秒级响应,且并发量不高,不会存在上述问题。
对Lucene On HDFS的实现。可参考
solr\modules\hdfs\src\java\org\apache\solr\hdfs\store\HdfsDirectory.java
三.Spark分布式执行
Lucene Index的分布式查询和处理,实际上是一个MapReduce的过程,在map阶段需要查询各个分区的数据得到分区查询结果,在reduce阶段对各个分区的查询结果进行合并,每个分区对应的是ES和Solr中的Shard概念,实际上是Lucene的单个索引目录。
若需要支持100个spark的partition同时计算,则分区数至少应该是100。
Lucene文件大致来讲有三种数据类型:
IndexableField:倒排索引,用于筛选
StoredField:行式存储,用于获得筛选结果集的详情数据
DocValuesField:列式存储,用于对某个字段进行排序,分组或者聚合统计
但实际情况会更为复杂:
是否支持单字段多值(Multivalued),以及不同类型的索引使用不同的类:
如对倒排索引来说:
LongPoint,IntPoint,DoublePoint等是数字类型的倒排索引使用的类。
StringField是字符串类型的倒排索引使用的类。
对行式存储来说:
对于字符串类型,在StringField类中还可以加入Field.Store.YES或Field.Store.NO来指定是否需要行式存储。
但对于数字类型,则需要另外增加一个StoredField来指定是否进行行式存储,另外,字符串类型若不需要倒排索引,仅需要存储,也可以使用StoredField。
对列式存储来说:
需要关注数据类型,是否需要排序,是否需要分组,是否需要聚合,是否支持多值等情况:
如对分组字符串字段,需要使用SortedSetDocValuesFacetField来进行存储,该字段会把值默认存入”$facet”,
值得一提的是facet方法各个引擎有各自不同的实现,lucene自身也有两种实现,一种是创建一个附加索引,另一种则是使用DocValues,一般认为使用DocValues是一种更加通用的方式,而创建附加索引则会有更高的性能,Solr和ES也都是利用DocValues的方案来进行分组的。
如果,您认为阅读这篇博客让您有些收获,不妨点击一下右下角的【推荐】。
如果,您希望更容易地发现我的新博客,不妨点击一下左下角的【关注我】。
如果,您对我的博客所讲述的内容有兴趣,请继续关注我的后续博客,我是【Arli】。
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。