Hadoop 的诞生改变了企业对数据的存储、处理和分析的过程,加速了大数据的发展,受到广泛的应用,给整个行业带来了变革意义的改变;随着云计算时代的到来, 存算分离的架构受到青睐,企业开开始对 Hadoop 的架构进行改造。
今天与大家一起简单回顾 Hadoop 架构以及目前市面上不同的存算分离的架构方案,他们的利弊各有哪些,希望可以给正在存算分离架构改造的企业一些参考和启发。
一、Hadoop 存算耦合架构回顾
2006 年 Hadoop 刚发布,这是一个 all-in-one 的套装,最早有三个核心的组件:MapReduce 负责计算,YARN 负责资源调度,HDFS 分布式文件系统,负责存数据。
在这三个组件中,发展最迅速和多元的是计算组件这一层,最早只有一个 MapReduce,但业界很快在计算层上面各显神通,造出了一大堆的轮子,包括有 MapReduce,Tez,Spark 这样的计算框架,Hive 这类数据仓库,还有 Presto、Impala 查询引擎,各种各样的组件。配合这些组件的,还有像 scoop 这样的数据流转采集的组件也很丰富,一共有几十款。
底层存储经过了大概 10 年左右的时间,一直是 HDFS 一枝独秀,带来的一个结果就是它会成为所有计算组件默认的设计选择。上面提到的这些大数据生态里发展出来的各种组件,都是面向HDFS API 去做设计的。有些组件也会非常深入的利用 HDFS 的一些能力,比如深入看 Hbase,在写 WAL log 的时候就直接利用了HDFS 的一些很内核的能力,才能达到一个低时延的写入;比如说像最早的 MapReduce 和 Spark 也提供了数据亲和性(Data Locality)的能力,这些都是HDFS 提供的一些特殊的 API。
这些大数据组件面向 HDFS API 设计的做法, 为后续数据平台上云带来了潜在的挑战。
下面是一个简化的局部的架构图,通过这张图快速理解 Hadoop 存算耦合架构。在这张图有三个节点,每个节点里面它都承载了 HDFS DataNode 的存数据的角色,但同时 YARN 也会在这里布一个 Node Manager的进程。有了 Node Manager 之后,YARN 就会认为 HDFS DataNode 的节点,在其管理范围之内,当需要计算任务可以分发到这个节点上来完成。存储任务和数据就在同一个机器里了,计算的时候就可以直接读到磁盘上的数据。
二、为什么 Hadoop 在设计之初是一个存储计算耦合的架构?
一个不能忽略的重要的原因是,网络通讯和硬件的局限。2006年,当时云计算几乎还没有发展,亚马逊才发布第一个服务而已。
在机房里面,当时我们面对的最大的问题就是网卡,主流的还是百兆网卡,刚开始用千兆网卡。这个时候,大数据使用的磁盘,吞吐大概是 50MB/s,对网络带宽来说要乘以 8,也就是 400M bps;如果一个节点里放 8 块盘,吞吐都跑起来,就需要几千兆带宽传输了,但是网卡最高也就1Gb。这就意味着每一个节点网络带宽根本不够,无法让这个节点里面的所有的磁盘的能力都发挥出来。所以如果计算任务在网络的一端,数据在数据节点在网络的另一端,计算任务需要说通过网络传输来进行,网络带宽是一个最明显的瓶颈。
存算分离的需求出现
首先从,企业的需求看,从 2006 年发展到 2016 年左右,这十年我们看到了一些新的变化,第一企业数据增长很快,但是算力的需求其实长得没那么快。这些任务靠人开发,不会发生一天一倍的去涨的情况,但是产生的数据的速度是是非常快的,有可能是指数型的;而且有些数据产生出来,也不一定马上知道怎么用,但未来会用,所以企业都会先把数据尽可能全量的去存起来,再去挖掘它的价值。
在这个背景下,存算耦合的硬件的拓扑的架构就给扩容带来了一个影响,当存储不够,就要去加机器。但是不能只加机器,不能只有硬盘,因为在存算耦合的架构上,数据的节点还需要负责计算,所以 CPU 和内存也不能太差。因此配置的机器都是计算与存储配置非常平衡的机器,在提供足够存储容量的同时,也提供了等量的算力。但实际场景中算力的需求没涨。这样扩出来的算力对企业来说造成了更大的浪费,整个集群在存储和 I/O 上的资源利用率可能是非常不平衡的,当集群越大,这种不平衡就越严重。而且另外买机器也挺难的,购买的机器必须是计算与存储平衡的。
而且,数据调度亲和性的策略在实际的业务中未必能发挥作用,因为数据有可能会有很明显的倾斜,可能会有很局部的热点,需要非常多的算力。大数据平台的任务可能调度到有限节点上,I/O 仍然有可能成为瓶颈。
在这个过程中硬件也有变化,给存算分离架构带来了可行性。首先,10Gb万兆网卡普及了,今天机房里或者包括云上也开始有更多的 20Gb、40Gb,甚至 50Gb,有些 AI 的场景甚至有100Gb的网卡,网络的带宽其实加大了比以前提升了100倍之多。
存储方面,在今天大的数据集群里面,许多企业还是使用磁盘来存储,磁盘的吞吐提升了一倍,从 50MB/s 每秒提升到 100MB/s。一个配置了万兆的网卡的实例,可以支持差不多 12 块磁盘的峰值吞吐,对于大部分企业来说已经够用了,以前网络传输的瓶颈就基本不存在了。
不仅网卡,磁盘也在变化,软件也在变化。最早的时候,我们可能用 csv 或者打一个 zip 包,现在有了更高效的压缩算法,比如说有 snappy、lz4、zstandard 这些。而且有了 Avro、Parquet、Orc 这些列存格式。
这些变化加在一起,都进一步减小了需要传输的数据量。同时, 网卡在提升,再加上硬硬盘本身的吞吐没增加多少,企业以前曾经要面对的 I/O 的瓶颈就逐渐的在弱化甚至消除,保证了存算分离的可行性。
如何实现存算分离?
最初的尝试:在云上独立部署 HDFS
从2013、2014年,行业内开始看到一些存算分离架构的尝试。最初的方案比较简单,就是独立部署 HDFS,不再和负责计算 worker 去混合部署。这个方案在 Hadoop 生态里,没有引入任何的新组件。
从下面的示意图可以看到, DataNode 节点上不再部署 Node Manager,意味着不再把计算任务发送到 DataNode 节点上。存储成为一个独立集群,计算需要用到的数据都会通过网络来传输,端到端的万兆网卡去支持,网络传输线没有在下图标出。
在这个改变里,尽管 HDFS 最巧妙的数据本地性这个设计被舍弃了,但由于网络通讯速度的提高, 给集群的配置带来更大的便利。Juicedata 创始人 Davies,2013 年在 Facebook 工作期间,团队就做了这样的实验, 发现这样的一个存算分离的改造,对整个平台性能的影响是仅仅是几个百分点,但是给集群的配置管理带来了一个还很大的便利,可以独立的部署和管理计算节点了。
但是这个尝试没有得到进一步发展,是什么原因呢?最大的一个原因,当在机房做这样的改造是可行的,但当我们去使用云上资源的时候,这个方案的弊端就显露了。
首先,源自 HDFS 的多副本机制在云上会增加企业的成本。过去,企业在机房使用裸硬盘去搭建一套 HDFS,为了解决裸硬损坏的风险, HDFS 设计了多副本的机制,来保证数据安全性;同时多副本还承载着保证数据可用性的作用。除了磁盘损坏,当某一个 DataNode 的节点临时宕机了,这个节点上的数据访问不到了?多副本机制在可靠性和可用性上都发挥作用。当数据被迁移到云上时,云提供给用户的是经过多副本机制存储的云盘,不再是裸硬盘了,企业用这块云盘去搭一个HDFS,又要做3副本,企业数据在云上要存 9 副本,成本立马飙升了好几倍。
后来,云也会提供一些有裸硬盘的机型,但是这类机型往往都非常少,比如说云上有 100 款虚拟机,云盘可以任意配置,但是有裸盘的机型只有 5~10 款,选择余地比较少,这些型号不一定能匹配企业的集群需要。
第二个原因, 这个方案不能让企业得到云上的独特价值,比如开箱即用,弹性伸缩,以及按量付费这些云上最大的优势。在云上部署 HDFS, 需要自己创建机器,手动部署和维护,自己监控和运维,而且还不能方便地扩缩容。这种情况下,HDFS 上云实现存算分离,仍然有其痛点。
第三个原因,HDFS 本身的局限。首先是,NameNode,只能垂直扩展,并不能分布式扩展说扩出更多的 NameNode 节点,限制了 HDFS 单集群去管理的文件数量。
当 NameNode 的资源占用比较多,负载又高的时候就有可能会触发 FullGC(Garbage Collection) 。一旦触发这个问题之后,它会影响到整个 HDFS 集群可用性。系统存储可能宕机,不能读,又无法干预 GC的过程,系统卡多久无法确定。这个也是 HDFS 高负载集群一直以来的痛点。
根据实际运维经验,一般在 3 亿文件以内,运维 HDFS 还是比较轻松的,3 亿文件之后运维的复杂度就会明显提升,峰值可能就在 5 亿文件左右,就达到单机群的天花板了。文件量更多,需要引入 HDFS的 Federation 联邦的机制,但是它就增加了很多的运维和管理的成本。
公有云+ 对象存储
随着云计算技术的成熟,企业存储又多了一个选项,对象存储。不同的云厂商有不同的英文缩写名,例如阿里云的对象存储服务叫做 OSS,华为云 OBS,腾讯云 COS,七牛 Kodo;对象存储适用于大规模存储非结构化数据的数据存储架构,其设计的初衷是想满足非常简单的上传下载数据,企业存储系统拥有超级强大的弹性伸缩的能力,还能保证低成本的存储。
最早从 AWS 开始,后来所有的云厂商其实都在往这个方向发展,开始推动用对象存储去替代 HDFS。这些方案首先带来了两个 HDFS 无法实现的最明显的好处:
第一,对象存储是服务化的,开箱即用,不用做任何的部署监控运维这些工作,特别省事儿。
第二,弹性伸缩,企业可以按量付费,不用考虑任何的容量规划,开一个对象存储的 bucket ,有多少数据写多少数据,不用担心写满。
这些方案相比在云上独立部署 HDFS , 运维方面是有了很大的简化。但当对象存储被用来去支持复杂的 Hadoop 这样的数据系统,就会发现如下的一些问题。
文件 Listing 的性能比较弱。Listing 是文件系统中最基础的一个操作。我们在文件系统中 List 目录,包括 HDFS 里面 List 目录,都是非常轻量快的操作。它的性能是源于在文件系统中,数据是一个树形结构。
对象存储没有树形结构的,它的整个存储结构是扁平的。当用户需要存储成千上万,甚至数亿个对象,对象存储需要做的是用 Key 去建立一份索引,Key 可以理解为文件名是该对象唯一标识符。如果用户要执行 Listing,只能在这个索引里面去搜索,搜索的性能相比树形结构的查找弱很多。
对象存储没有原子 Rename, 影响任务的稳定性和性能。在 ETL 的计算模型中,每个子任务完成会将结果写入临时目录,等到整个任务完成后,把临时目录改名为正式目录名即可。
这样的改名操作在 HDFS 和其他文件系统中是原子的,速度快,而且有事务性保证。但由于对象存储没有原生目录结构,处理 rename 操作是一个模拟过程,会包含大量系统内部的数据拷贝,会耗时很多,而且没有事务保证。
用户在使用对象存储时,常用文件系统中的路径写法作为对象的 Key,比如 “/order/2-22/8/10/detail”。改名操作时,需要搜索出所有 Key 中包含目录名的对象,用新的目录名作为 Key 复制所有的对象,此时会发生数据拷贝,性能会比文件系统差很多,可能慢一两个数量级,而且这个过程因为没有事务保证,所以过程中有失败的风险,造成数据不正确。这样看起来很细节的差异对整个任务 pipeline 的性能和稳定性都会有影响。
对象存储数据最终一致性的机制,会降低计算过程的稳定性和正确性。举个例子,比如多个客户端在一个路径下并发创建文件,这是调用 List API 得到的文件列表可能并不能包含所有创建好的文件列表,而是要等一段时间让对象存储的内部系统完成数据一致性同步。这样的访问模式在 ETL 数据处理中经常用到,最终一致性可能会影响到数据的正确性和任务的稳定性。
为了解决对象存储存在无法保持强数据一致性的问题。AWS 发布过一个名为 EMRFS 的产品。AWS EMRFS 的做法是,因为知道 Listing 结果可能不对,所以另外准备一个 DynamoDB 数据库, 比如 Spark 在写文件的时候,同时也写一份文件列表到 DynameDB 里,再建立一个机制,不断调用对象存储的 List API,和数据库里面存下来的结果做比较,直到相等了再返回。但这个机制的稳定性不好,它会受对象存储所在的区域的负载高低影响忽快忽慢,不是一个理想的解决方式。
除了上述由于文件系统和对象存储本身差异带来的问题外,在对象存储上使用 Hadoop 的另一大问题,就是对象存储对于 Hadoop 组件的兼容性相对弱。在文章开头 Hadoop 架构介绍中提到了 HDFS 是 Hadoop 生态早期几乎唯一的存储选择,上层各种各样的组件都是面向 HDFS API 开发的。而到了对象存储上,数据存储的结构变了, API 也变了。
云厂商为了能够与现有的这些 Hadoop 组件适配,一方面需要去改造组件和云对象存储之间的 connector,另一方面还需要给上层的组件去打 patch ,对于每一个组件都一一的去验证兼容性,这对公有云厂商来说意味着巨大的工作量。所以,目前公有云它提供的大数据组件里面能包含的计算组件是有是有限的,一般只能包含 Spark、 Hive、 Presto 三个常用组件,而且还只能包含少数几个版本。这样就会给将大数据平台迁移上云,或者有需要使用自己的发行版和组件需求的用户带来了挑战。
企业如何能够享受到对象存储的强大性能,同时又兼顾文件系统的准确性?
对象存储 + JuiceFS
当用户想在对象存储上去进行复杂的数据计算、分析训练这些场景的时候,对象存储确实无法满足企业的需求;这也是我们去做 JuiceFS 的一个出发点,希望能够站在对象存储之上去补充他不擅长的部分,与对象存储一起以比较低廉的价格服务好密集性的数据计算、分析、训练这些场景。
JuiceFS + 对象存储是如何工作的呢?通过下图 JuiceFS 在 Hadoop 集群中的部署方式,简单介绍原理。
从下面这个简单的示意图看到, YARN 管理的这些执行节点上,都带一个 JuiceFS Hadoop SDK, 这个 SDK 可以保证完整兼容 HDFS。图片下方可以看到, SDK 它需要访问两个部分,左侧是 JuiceFS Meta Engine,右侧是 S3 bucket。Metadata engine 就相当于 HDFS里的 NameNode,整个文件系统的元数据信息会存储在这里,元数据信息包括目录数、文件名,权限时间戳这些信息,并且相应的解决掉了 HDFS NameNode 扩展性 、GC 这些的痛点。
另外一边,数据存在 S3 bucket 里面,这里的 S3 bucket 等同于HDFS 中的 DataNode,可以将它看成一大堆海量的磁盘来用,它会管理好的数据存储和副本的相关任务。JuiceFS 就是三个组件组成,JuiceFS Hadoop SDK, Metadata Engine 和 S3 Bucket。
相较于直接使用对象存储, JuiceFS 还有哪些优势呢?
HDFS 100% 完整兼容。这得益于我们最初完整兼容 POSIX 的这个设计。POSIX API 的覆盖程度以及复杂程度是大于 HDFS的,HDFS 在设计的时候就是去简化了 POSIX,因为最先去实现复杂的 API 集,再去简化它就变得非常容易了,所以这也是 JuiceFS 能实现 100%实现 HDFS 完整兼容性的一个原因。
同时, 用户可以和 HDFS 一起使用,无需完全替换 HDFS。这也得益于 Hadoop 系统的设计,在一个 Hadoop 集群里,可以配置多个文件系统,JuiceFS 和 HDFS 可以同时使用,并不是互相替代的关系,而是可以互相合作。这样的架构给我们我们现有的集群带来的好处是用户不用完整替代现有的 HDFS 集群,完整替代的工作量和风险上都太大了。用户可以结合着业务,结合着集群的情况,分步分批的去做融合。
元数据性能强大,JuiceFS 将元数据引擎独立出来不再依赖于 S3 里面的原数据性能,保证了元数据的性能。使用 JuiceFS 的时候,对底层对象存储的调用简化到只是 get、 put、delete 这三个最基础的操作,像 listing, update 等命令都用不到,在这样的架构下,用户就避开了对象存储元数据性能弱的问题,最终一致性这些问题也都不再存在了。
原子 rename, 因为有独立的原数据引擎,JuiceFS 也可以支持原子 rename。
缓存,有效提升热数据的访问性能,提供了 data locality 特性。缓存可以让热数据缓存到执行器 worker 节点本地的一些磁盘空间上。有了缓存后,会反复访问的热数据,不需要每次都通过网络去对象存储里面读数据。而且 JuiceFS 特意实现了HDFS 特有的数据本地性的 API,让所有支持数据本地性的上层组件都能重新获得数据亲和性的感知,这会让 YARN 把自己的任务优先调度到已经建立缓存的节点上面,综合的性能可以和存储计算耦合的 HDFS 相当的。
兼容 POSIX, 与机器学习、AI 相关的任务应用结合方便。JuiceFS 还兼容 POSIX,可以和机器学习, AI相关的这些业务更便捷地融合。
小结
伴随着企业需求的更迭、基础技术的发展,存储和计算的架构在变,从最初的耦合到分离;实现存算分离方式多样,各有利弊,从直接将 HDFS 部署到云上,到使用公有云提供兼容 Hadoop的方案,再到公有云 + JuiceFS 这样的适合在云上进行复杂大数据计算和存储的方案。对于企业来说,没有银弹,结合自身需求做架构选型才是关键。
但无论选什么,保持简单都不会错。