首先自我介绍一下。我是中国联通软件研究院大数据工程师张策。我也是Alluxio社区的PMC成员和Presto贡献者。我对开源大数据很感兴趣。我希望能经常和你交流。全文将重点关注以下内容:缓存加速方面的应用场景存储计算分离的应用混合负载领域的应用轻量级分析相关探索01应用场景首先介绍使用Alluxio作为分布式缓存来加速big数据计算场景。我们事业部有多个数据处理和数据分析业务,分布在不同的平台上。使用GreenPlum数据库的数据处理业务是基于批量存储过程完成数据处理,对计算时间要求严格,有明确的期限。这些业务遇到的主要问题是,当GreenPlum集群规模达到几十台时,遇到扩容瓶颈,进一步扩容给业务运维工程师带来了极大的挑战;一些业务基于Hive运行T+1批处理作业。主要问题是内存、CPU等资源消耗比较大,并行度不高,运行速度比较慢;一些传统的统计服务是跑在Oracle上的,跑在Oracle单机上效果不是特别好,而且Oracle比较贵。一台小型机可能要2000万以上,业务方期待其他数据分析平台替代Oracle。当时我们的课题是基于Spark和Hadoop的开源体系构建一个统一的计算平台,并接管以上三个引擎所承担的所有业务。我们在直接使用Spark+HDFS的时候遇到了一些问题:一是性能不能满足GreenPlum承载的生产业务,因为GreenPlum是MPP数据库,在同等体积下会比Spark快很多;其次,GreenPlum所承载的业务也存在一些问题。没有办法满足伴随的交互式查询场景的性能。其次,对于一些批处理业务,Spark+HDFS缺乏稳定性。批处理业务的每个任务周期都会有几千条或几万条SQL进行迭代计算。如果任务经常失败,没有办法快速完成。那么一定要在那之前完成。因此,为了加速Spark计算,我们引入了Alluxio来加速数据读写性能,提高数据写入的稳定性,提高接管SparkCache后Spark作业的稳定性。这是我们一开始采用的架构,数据编排在我们整个架构中的定位是缓存加速层。02缓存加速的应用下面我们详细看一下用例。如果我们以加速迭代计算为例,我们将sink直接写入Alluxio,然后在下一个SQLsource中读取sink,并将source和sink串联起来。交给Alluxio的缓存,可以显着提高整个流水线的稳定性。因为不需要和磁盘交互,所以可以大大减轻磁盘的压力。通过内存,相对提高了流水线的稳定性,直接读取内存会提升整个流水线的运行速度。第二个用例是我们使用Alluxio来共享整个Job之间的数据,避免使用SparkCache。使用SparkCache会对整个ExecutorJVM造成很大的压力。性能测试结果表明,当数据量达到一定规模时,SparkCache的性能比Alluxio差。而且,随着Spark中间数据的增加,Alluxio对性能的影响是可以预见的,是线性增长的,而不是指数增长的。因此,我们选择使用Alluxio作为中间数据的共享层。为了提高数据读取的性能,可以为每条路径显式配置副本浮动范围。此外,Alluxio还支持数据预加载的功能——distributedLoad。2021年,社区也花了很多精力在上面,目前是一个非常完善的工具。我们可以在执行一些固定任务之前,将数据提前加载到内存中。这可以提高整个流水线的速度。另外,Alluxio还支持一些内存操作工具,比如pin、free等,方便我们更好的管理内存。也就是按照我们建立的pipeline,整个内存布局可以得到最好的优化。03在存储计算分离中的应用下面是一个Alluxio数据整理策略相关的例子。比如我们有一个Spark任务去读取一段数据,它可能会在每个worker上拉取一些副本,比如一些被复用的数据。这种情况会很快填满缓存。针对这种情况,Alluxio可以提供一种称为确定性哈希的策略。这种策略使我们能够阅读某些固定工人。这样就可以把整个集群的副本数累加起来,可以更有效地利用空间。此外,针对大量的场景,它还支持一些与负载均衡相关的策略。比如最高可控策略会选择整个集群剩余空间最大的worker进行读取。这可以使所有工人的使用更加均衡。然而,它也存在一些问题。如果在SparkExecutor端的一个AlluxioWorker上运行大量的AlluxioClient,可能会导致worker的使用率瞬间被占满,从而导致性能下降。针对这种情况,我们可以选择roundrobin策略,在剩下的几个作业中使用roundrobin,这样可以更好的实现集群负载均衡。从整体使用效果来看,Alluxio也带来了比较大的性能和稳定性提升。这是一个典型的SparkTask统计数据。Alluxio对整个GC时间的优化和整个任务总耗时的优化比较显着。它最大的优势是可以显着降低每批Jobs的重算概率,从而使整个过程更加稳定和可预测。最终的效果是,我们从之前的GreenPlum迁移出来的核心业务规模增长了近70倍,承接的用户数量比之前增长了100%,整体业务速度提升了26小时。目前交互式查询业务日数据量已达60TB。例如Spark单表使用2.5TB的数据,查询时间也需要几分钟。本来Oracle业务迁移到Spark后,可以进行用户级的计算,对他们来说有很大的价值。接下来,我们根据集群持续发展的特点,在存储计算分离方面做了一些建设。存储和计算分离的背景是什么?这主要关系到整个集群的发展。首先,我们这边的平台建设是跟着业务走的。随着业务的快速增长,会出现资源碎片化。如图所示,第一年我们新建机房时,我们占用一部分机架,其他业务或者部门也会占用一部分机架。到了第二年,第一年的机房就满了,第二年可以再申请一些新的机房。这就导致了整个机器的不连续性,可能跨越机房,甚至可能跨越建筑物,有时甚至跨越数据中心。与之相伴的是业务的快速增长,基本上是逐年成倍增长,而且资源申请周期很长,至少需要一年时间才能交付,最后的结果就是集群碎片化。更糟糕的是,在业务增长的过程中,其资源需求实际上是不平衡的,主要是存储和计算的不平衡。首先,历史数据的存储需求逐年增加是客观事实。以前,业务只需要将数据保留1到2个月。但是现在因为一些历史趋势分析或者各种计算,一些主要的业务数据需要保存12到24个月。每个周期之间业务数据环比增长10%左右,增长较大时甚至达到5-10倍,主要与业务逻辑变化有关。其次,存储规模的增长大约是计算规模的5到6倍,这也是存储计算发展不平衡的情况。我们使用存储和计算分离的技术来解决这些问题。首先解决计算资源的问题。我们租用了其他商家已有的集群,利用集群在晚上闲置的时候完成数据处理操作。我们在上面部署了一套Spark,还有Alluxio和我们集群的HDFS,组成一个存储和计算分离的数据分析系统。为什么不在这个集群上部署Hadoop?原因是租用的业务集群已经搭建了一套Hadoop,不允许我们再搭建Hadoop,也不可以使用他们的Hadoop。因此,我们使用业务集群的Alluxio来挂载我们平台的HDFS。挂载HDFS后,我们维护和自己平台一样的命名空间,让它们看起来都一样,基本让用户感觉不到。更详细一点,如图所示,对于两个Alluxios,看到的路径是一样的,都是映射到HDFS的路径,所以用户认为这两个路径是一样的。当我们对不同的Path进行读写时,会让不同的Alluxio分别进行读写操作。例如,远程Alluxio对Path1是只读的,它会写入Path2。本地Alluxio会写Path1,而只读Path2,这样就避免了两个Alluxios之间的冲突。在具体的实现方案中,我们也使用了一些自己的设计来避免存储和计算分离遇到的一些问题。首先,在异地业务集群方面,我们采用RocksDB+RaftHA的方式来解决没有本地HDFS时AlluxioHA元数据运行性能问题。因为我们的HDFS在我们的集群里面,中间有一定的网络消耗。如果我们还是直接使用原来的zookeeperHA,我们需要先在他们的集群中搭建一套zookeeperHA,把元数据放到我们自己集群的HDFS上。跨网络的元数据交互可能会带来很多不确定性。比如当带宽满了,或者因为其他网络波动因素,Alluxio本身的性能就会出现波动,甚至整个Alluxio可能因为数据写入或者读取超时而挂掉。因此,我们选择了Alluxio2.x之后不断构建和完善的RocksDB+RaftHA的方式来解决这个问题。其次,在业务集群这边,我们使用HDD作为存储介质,因为我们需要存储所有的中间数据,提高整体的计算性能。Spark作业的中间过程数据直接存储在缓存盘中,不与UFS进行任何交互,因此对于用户而言,该模式的性能不受影响。第三,最终的结果也可以持久化到集群中。因为最终结果的数量不是特别多,所以它的时间消耗还是可以接受的。最后,对于用户来说,他可能需要跨集群部署任务。我们在租用的业务集群中构建了一个DolphinSchedulerWorker,通过Dolphin的调度策略,帮助用户在这个Worker上启动自己的特定任务。对不同集群的提交由Worker的选择控制。对于用户来说,只做了配置的改动,作业提交和管理入口是一样的,解决了跨集群作业管理的问题。实现计算混合部署后,我们收到了大量的数据存储需求。但是我们的集群短时间内无法扩容,所以我们申请了一批大容量存储,然后将大容量存储挂载到Alluxio,历史数据自动降级到大容量存储,并且透明查询时通过Alluxio访问。我们会将前2个月至前12个月的历史数据降级为大容量存储,本地集群只会保留最近几个月会频繁参与计算的数据。对于用户来说,访问路径还是和以前一样,我们通过挂载方式屏蔽了历史数据分级管理的差异。对我们来说好处是单位服务器的存储容量有了明显的提升,大容量存储可以独立扩展,为我们减轻了很多存储压力。下面是我们把存储和计算分开后的实际效果。首先,某核心用户的租用算力占平台分配算力的82%,这是一个比较大的提升。用于承接新业务的租用算力比例达到50%,Alluxio管理的ETL流程数据达到148TB,这是一个非常庞大的数字。因为Alluxio其实并不会把特别多的数据作为缓存来管理。为了管理100到200TB的数据量,我们和社区做了很多工作,包括worker启动超时等优化,以满足中间数据存储的需求。单独扩展的大容量存储的好处是单台服务器的存储容量增加了5倍。计算集群中使用的计算服务器存储容量相对较小,大容量存储单机在相同数量的服务器下可以存储90TB的数据。在这种情况下,容量提升更为明显,因此历史数据存储量大幅减少,扩容成本降低83%。04在混合负载领域的应用随着集群的不断发展,我们引入更多的计算引擎来适应更多的业务场景,从而获得更好的效果。一个典型的场景是我们在一个平台上同时提供Spark和Presto,Spark主要用于ETL,Presto提供即席查询服务。他们在共享Alluxio的时候,会遇到一些问题:首先,AlluxioSystemCache不支持Quota,Spark和Presto的使用没有办法隔离。由于SparkETL作业写入的数据量较大,所以直接将数据写入Alluxio作为pipeline的下一个pipeline。节点读取加速。这种情况下,内存刷新比较频繁,一次会占用较多的内存。这样,Presto就完成了数据的加载。当我想查看数据时,发现缓存没有了。Presto查询的缓存利用率不是特别高。第二种情况是部分用户使用TensorFlow做AI计算,用于自然语言处理或时间序列分析。在AI计算之前,用户首先基于Spark做分布式ETL,然后想直接把ETL结果用到TensorFlow上。在这种情况下,用户很难将分布式数据与本地数据联系起来。TensorFlowonSpark等第三方框架一般封装了一些标准模型。用户使用的模型不是主流模型,但实际效果更好。在这种情况下,没有办法应用目前的第三方框架直接将SparkDataFrame转为TensorFlow数据模型直接使用。这样一来,TensorFlow还是一种读取本地文件的模式,但是把Spark和TensorFlow联动起来就不是很好了。我们做了一些改进来解决刚刚在用例中提到的一些问题。首先,我们放弃了在Presto上使用AlluxioSystemCache,而是使用AlluxioLocalCache来缓存数据,与Spark使用的AlluxioSystemCache隔离开来。我们将为每个PrestoWorker创建一个独立的缓存。这个缓存是放在Ramdisk上的,官方建议是放在SSD里。缓存不占用PrestoWorker的JVM空间,不会对GC造成额外的负担。另外,因为我们有一些磁盘场景,我们和社区一起完善了基于RockDB的缓存实现。以上是我们在缓存隔离方面所做的一些设计。在AI端,我们使用AlluxioFuse将ETL与AI训练推理对接。AlluxioFuse首先挂载本地文件和分布式系统,使分布式文件映射到本地文件。那么用户就可以直接通过读写本地文件的方式来操作分布式系统中的文件,这和在笔记本上开发读写本地代码的逻辑是完全一样的。这样SparkETL和TensorFlow的计算就已经成功开启了。Alluxio目前提供两种保险丝安装方式。首先为每个worker创建一个fuse挂载目录。这种方式可以更好的提高数据访问性能,目前我们正在使用这种模式。还有一个单独的Fuse进程,这样就不需要在本地部署Alluxio集群,有利于灵活部署。您可以根据自己的需要选择不同的部署方式。从目前使用的效果来看,由于引入了Cache,Presto的查询性能提升了50%左右,比OSCache更稳定。如果我们都使用OSCache,性能是最快的,但是因为集群支持数据密集型负载,所以OSCache不是特别稳定,容易被flush。与无缓存加速相比,其查询性能有很好的提升。在大数据AI集成方面,用户无需做任何改动即可使用TensorFlow训练推理代码,并可与SparkETL集成。目前,首批业务已完成60万用户接入。全程基于DolphinScheduler实现大规模数据+AI全程自动调度,用户运维复杂度极低。这是Alluxio的秋露广告。她目前是AlluxioFuse的负责人。AlluxioFuse已经部署在微软、老板直聘、哔哩哔哩、陌陌等公司的生产培训中。05轻量级分析相关探索现状1、中国联通目前正在大力推进数字化转型。很多以前不做数据分析的业务,比如一些传统业务,也想做数据分析。利用之前沉淀的数据,想做预测或者定位原因。目标用户更多是业务工程师,他们更喜欢关系型数据库和SQL。第二种情况是不同的业务需要访问平台运营的公共数据,也需要访问和管理业务的私有数据。这些私有数据的业务口径由业务方制定,并根据自身的公开政策提供给其他业务方。因此,共享私有数据需要数据提供者授权,而公开数据只需要平台授权。第三种情况是目前服务器资源的增长低于业务需求,因为数据分析的需求增长很快,而且不可预测,很多需求没有纳入整体规划。同时业务自有服务器在空闲时间负载比较低,白天一般比较忙。这样的话,他们现有的资源其实可以支持一些新的数据分析服务。因此,考虑到平台在扩容时跟不上业务发展的步伐。为了尽快满足业务数字化建设的需要,我们计划对这些业务进行私有化部署。我们在基于Spark做私有化部署的时候,遇到了一些问题。首先,如果我们按照自己的标准模式为每个用户独立部署一个SparkonYarn,管理复杂度就太高了。我们可能用很多集群来管理SparkonYarn,这样的管理成本是支撑不住的。另外,部分用户服务器已经部署了HBaseonHadoop。这些集群不允许我们在上面部署Hadoop或者Hbase,也不能使用现有的Hadoop。这给Spark系统增加了很多难度。而且,业务用户希望使用这种纯SQL来分析数据。如果是这样,我们将需要添加类似于Kyuubi的工具。我们需要添加许多组件来满足需求。这样的话,多个集群的管理复杂度会非常高。另一种方式是我们不使用onYarn方式,而是使用SparkStandalone方式。其实部署还是比较不错的,只用Spark+Alluxio也不错。但是这种方式的问题是,如果我们使用Sparkperjob模式,它的并发量是有限的。如果我们给一个大概的值,它的并发度就是一个jobfromonequery,并发度比较低,同时并发的可能只有几个。而且Spark的配置复杂度比较高,对SQL用户不是很友好。还需要配置内存大小、执行程序数量或动态分配限制参数。Sparkperjob如果不采用单session模式,就没有办法隔离多个task之间的优先级,也就无法安排一个业务的多条sql。我们的想法是用Presto+Alluxio做一个轻量级的数据分析。基于PrestoIceberg原生连接器,我们可以直接操作存储在Alluxio上的Hadoop目录表。我们只需要添加Presto和Alluxio两个组件就可以完成。整个ETL过程。我们使用了两套挂载系统:第一套是比较传统的Alluxio,直接在平台上挂载HDFS;第二套是Alluxio结构数据服务(structureddataservice)挂载hive表,对于用户集群,他看到的hive表和平台端的hive表是一样的。Mount的好处是可以将数据透明映射到Alluxio命名空间,通过HiveTableload可以将数据提前加载到Alluxio缓存中,提高数据读取性能。所有中间进程只需要在本地以mustcashe的形式将Presto写入Alluxio即可。这些数据经过处理后,最终的结果数据可以回写到平台的hive中。整个架构比较简单,只需要两个组件,每台机器只需要两个进程,没有额外的负担。此外,用户可以通过JDBC或命令行访问Presto,用户可以通过选择性持久化IcebergHadoop表授权其他集群用户访问中间数据。通过Alluxio统一的命名空间设计,用户看到的hive表和Iceberg表是完全一致的,实现了多集群之间的透明管理。在这个轻量级的数据分析栈中,我们只需要使用用户集群搭建两个组件就可以满足需求。因为用户基于Presto以纯SQL方式完成数据分析,所以体验更好。目前我们的用户喜欢使用JDBC连接,所以暂时不需要提供更多的可视化工具。如果需要,只需在我们现有的可视化工具中配置链接。Alluxio挂载平台HDFS用于私有数据共享,SDS挂载平台Hive用于共享数据访问和性能提升。基于PrestoIcebergconnector的hadoopcatalog模式,ETL可以在只写缓存中在本地完成。Alluxio采用磁盘挂载方式,保证缓存空间足够容纳数据分析的中间数据,最终将数据持久化到平台HDFS。Alluxio在写入时会采用三副本策略来保证缓存数据的可用性。今天的分享就到这里,谢谢大家。
