摘要

提到设计一个数据架构,几乎所有人条件反射都会想到:搭建一套Hadoop,Spark安装起,Hive装好,MPP引擎配置好,感觉那么多的开源引擎,随便选择,岂能设计不出来一个数据架构。
开源引擎与数据架构的关系,就像是你手握一堆的零件,需要把它组装成一辆性能还不错的汽车,这个过程中会遇到非常多的问题,比如零件尺寸是否合适,零件质量是否达标,应该如何组装,组装后的零件之间的协作性能是否能达标等等。
对应到数据架构上来说,于微小之处,于架构设计,我们都会碰到很多这样的问题,比如我在把开源的Hive安装好后,就会出现在使用异步调度系统和Hive集成的时候,HiveServer2就内存泄漏了,存算分离下使用Hive的时候,location为root path的时候,会失败,且表无法删除。
这都是开源Hive的bug,那么遇到这个问题怎么办?一种办法是你自己去修了它,这也是很多人不太理解,明明Hive都在Apache下了,属于顶级项目了,为什么还有那么多企业说对Hive修改了很多内容,这些企业修改了啥,其实就是修改了这些问题。
另一种办法就是自己不修,反馈给开源社区,看社区什么时候去修复,这种情况必然就会影响自己的架构,从而影响业务,比如业务就是碰到这个问题,怎么办?临时换个引擎顶一顶?

开源的约束

开源的引擎,是一堆可以选择的零件,既然是零件,那么必然会有不适合的地方,能够准确的识别出开源引擎的约束,对于一个数据架构师来说,是入门的技能,仔细观察会发现在众多的开源引擎中,几乎每一个引擎都有其先天的约束和独特的长处。
想想Hive引擎都过去这么多年了,任何批处理引擎在诞生的时候,都会拿Hive做对比,宣称自己的速度可以提升多少,那么为什么直到今日,市面上不少的数仓,依旧是Hive?这背后本质的原因在于,在分布式作业环境下,Hive是唯一一个提供了标准的数据访问接口,且拥有最完备的容错机制的引擎。
Hive完美的做到了接口,元数据和计算引擎的解耦,从架构耦合开始,Hive最大的问题就是重度依赖Yarn了, 当然Hive的约束也非常明显,首先自然是速度上不去,这也成了其他引擎的发力点,其次是Hive的元数据设计,对更改非常不友好。
但是得益于其较为不错的架构设计,所以iceberg,hudi才能更好的无缝融合,弥补这一块的缺陷。

既然Hive慢,那么那些计算的快的引擎,又是什么情况呢?在同维度下,Hive之外的选择是Spark,但是Spark却又没有一个合适的作业服务器,Spark thrift server的约束过于明显,好在目前有类似Kyuubi补齐了这部分,其次Spark由于设计理念的问题,它要优于Hive,必然就有不一样的架构,而不一样的架构,自然会引入新的问题,举一个最常见的问题。

create table IF NOT EXISTS spark01 ( name string ) stored as orc;
insert into spark01 values('fcbai');
insert into spark01 values('fcbai1');
insert into spark01 values('fcbai2');
insert into spark01 values('fcbai3');
create table g16 using orc AS select * from spark01;
insert overwrite table g16 select * from g16;

在SparkSQL里面,执行如上SQL会出现:

spark-sql> insert overwrite table g16 select *  from  g16;
Error in query: Cannot overwrite a path that is also being read from.

本质上的问题是Spark对数仓常用的数据类型做了自己的实现方式,在他自己的实现方式下,目标路径会先被清空,随后才执行写入,而Hive是先写入到临时目录,任务完成后再将结果数据替换目标路径。
overwrite 的目录是在算子执行的开始阶段 delete 的,如果是 dynaic partition overwrite,则是留到 commit 阶段。
对于 dynamic partition overwrite,是可以源目录和目标目录是一样的,但是非dynamic则不行,具体到Spark的代码位置是:
src/main/scala/org/apache/spark/sql/execution/datasources/DataSourceStrategy.scala的153行:

case InsertIntoDir(_, storage, provider, query, overwrite)
if query.resolved && provider.isDefined &&
provider.get.toLowerCase(Locale.ROOT) != DDLUtils.HIVE_PROVIDER =>
val outputPath = new Path(storage.locationUri.get)
if (overwrite) DDLUtils.verifyNotReadPath(query, outputPath)

InsertIntoDataSourceDirCommand(storage, provider.get, query, overwrite)

可以看到只要是overwrite,就会进入DDLUtils.verifyNotReadPath,而这里的检查要求output path不能在read path里面:

def cannotOverwritePathBeingReadFromError(): Throwable = {
new AnalysisException("Cannot overwrite a path that is also being read from.")
}

可以强制使用Hive解析器,也就是设置spark.sql.hive.convertMetastoreOrc=false来进行解析,但是这样的设置会带来2个问题,一个是只对先创建表的语句生效,但是如果使用AS语法创建的表的话是不生效的,另一个是优于spark与hive的解析器兼容问题,对于orc格式使用hive解析器并不能争取读取:

ORC split generation failed with exception: null

这个问题也无法通过修改strategy处理的,hive.exec.orc.split.strategy是只对hive生效的,spark的话是spark.hadoop.hive.exec.orc.split.strategy,上面的问题是由于sparK走了hive解析器后,存在兼容性的问题。

这些问题都导致看起来,Spark貌似比Hive更快,但是你想用Spark去替换Hive的时候,会发现它不是那么面面俱到,当我们在讨论引擎之间的兼容性的时候,说的就是这个问题。
除了同纬度的Spark,其他交互式分析类的引擎呢?比如Doris,CK以及Presto。这类引擎主打速度,对于分布式作业来说,如果速度能上来,那么存储和容错必然就会成为短板,比如速度快的doris,ck都拥有自己的存储,优势可以在上面做大量的向量化进行加速,劣势就是脱离了HDFS的存储生态,导致用户的存储成本直接翻倍,其次像ck还有单表约束的问题,而presto拥有强悍的跨源分析,但是一旦资源不足或者作业不稳定,容错引发的性能问题会直接拉长作业周期。

正因为开源引擎有如此多的约束,才会有众多的商业公司包装完善开源引擎,形成自己的独特生态,例如dremio, snowflake,国内的sr, selectdb,原流等都是这些方向优秀的企业。

数据架构

回到架构问题上来看,架构到底在干什么事情?有这么多开源引擎,这些数据架构师还在干什么?在我看来,对于一个还不错的数据架构师来说,首先需要具备这样的本领:

  • 深知常用的引擎自身的优势与劣势。
  • 深知多个引擎之间细微的兼容差异。
  • 深知多个引擎在进行配合的时候,所带来的负面效应,例如成本是否会增加?容错是否会降低?运维复杂度是否会增加?
  • 深知引擎的开源节奏,能够大概了解这个引擎的生态,这直接决定了引擎的生命周期,例如ambari,基本宣告死亡,虽然社区又死灰复燃的迹象,但是不明显。
  • 深知引擎的复杂度,能够和当前的团队能力进行匹配,确保团队能够驾驭必要时刻对引擎的开发,这直接决定了一个团队是否能在有效周期内,交付出业务。

在这个基础上,更重要的就是懂得拆分工作项目,我们时常再说一个架构师,或者一个技术Leader,到底他们是具备什么样的能力,基于我自己的一些看法,包括曾经面试接触过很多候选人,我自己认为一个合格的架构师或者Leader最重要的一点是能够把复杂的问题分解后,给别人讲懂。
有很多看起来优秀的人,能说会道,聪明伶俐,一下子就能理解到你的问题,但是似乎总是带不好团队,仔细分析会发现,这里面的问题在于,TA能很好的听懂你的需求,知晓你的问题,理解你的困境,且知道如何解决。
但是无法把TA理解到的东西,再传递给其他人,无法让其他人帮助TA完成这一副完整的图案,这种需求再传递,且清晰,是一个架构师的必备技能。

所以所谓的数据架构,其实就在于如下几个工作内容:

  • 从众多的引擎中选择出最合适的那几个,然后组装出一个系统。
  • 告诉团队你要做什么事,给每一个人划分出工作范围和目标,且,要让每一个人都意识到,自己的价值。
  • 权衡成本和业务诉求,制定出阶段性的交付目标。
  • 提早发现前面提到的引擎约束对业务的影响,在影响发生之前做好方案。
  • 定期回顾架构的业务产出,为自己的事的投入产出比,输出论证。

满足如上几点,才能算是一个数据架构师,在数据这个领域,做了一个还不错的数据架构,带领一个团队,交付了一个还不错的数据项目。

尾声

当一个项目进入到数据架构阶段的时候,其实意味着已经经过了论证阶段,也就是大家都认为:数据对有意义,那么该如何判定,数据真的有价值,或者说,这个企业应该开始做数据这件事,这个问题后续再说。


扫码手机观看或分享: