陈昱康,携程架构师,对分布式计算和存储、调度、查询引擎、在线离线混部、高并发等方面有浓厚兴趣。
本文将分享携程Hadoop跨机房架构实践,包含Hadoop在携程的发展情况,整个跨机房项目的背景,我们跨机房的架构选型思路和落地实践,相关的改造和对未来的展望,希望给大家一些启迪。
一、Hadoop在携程的落地及发展情况
携程Hadoop是从2014年引进的,基本上每年较前一年以两倍的速度在增长,我们对Hadoop集群做了大量性能方面的改造和优化。
1)目前,HDFS存储层面拥有数百PB的数据,数千的节点,分为4个namespace做Federation,自研了namenode proxy来路由rpc到对应的namespace,2019年初上了一套基于Hadoop 3的Erasure Code集群来做对用户透明的冷热存储分离,目前已迁移几十PB数据到EC集群,节省一半的存储资源。
2)计算层面,搭建了两套离线和一套在线Yarn集群做Federation,总量15万+core,每天30万+ Hadoop作业,其中90%为spark。所有节点分布在四个机房,其中离线集群部署在其中两个机房,在线集群部署在三个机房。、
二、跨机房项目背景
来看下整个项目的背景。之前我们的Hadoop机器部署在金和福两个机房,95%的机器在福。去年底,携程自建了日机房,同时福机房的机架数达到了物理上限,没办法继续扩容。另外按照目前计算和存储的增速来看,预计2024年底集群规模会达到万台,新采购的机器只能加在日机房,我们需要多机房架构和部署的能力。
这其中的难点在于,两个机房的带宽仅200Gbps,正常情况下网络延迟在1ms,当带宽打满情况下,延迟会达到10ms,同时会有10%的丢包率。我们需要尽可能减少跨机房的网络使用带宽。
2.1 原生Hadoop架构问题
看下原生Hadoop的问题。网络IO开销主要来自两方面,Shuffle读写和HDFS读写。
1)先看shuffle,MR和Spark作业的前一个stage会将中间临时文件刷到磁盘,下一个stage通过网络来Fetch。如果分配的map task在机房1,reducetask在机房2,就会产生跨机房流量开销。
2)其次HDFS层面,对于读场景,三个副本存放在不同的节点,客户端会从namenode拿到按照距离排好序的副本信息,优先从最近的副本所在的节点读取。但是当三个副本都和客户端不在一个机房的情况下,就会产生跨机房读网络IO开销。写场景的话,HDFS采用Pipeline写,选择副本时只考虑到机架层面的存放策略,会将三个副本放在两个机架,如果选择的两个机架跨机房了,也会有跨机房网络写开销。
2.2 可选的方案
当时我们讨论下来有两种架构解决方案,多机房多集群和多机房单集群,两种各有利弊。
2.2.1 多机房多集群
多机房多集群方案的优势是不需要修改源代码,可以直接部署。缺点是:
1)对用户不透明,用户需要修改配置,指定提交到某个集群;
2)运维成本较高,每个机房有独立的集群,配置管理麻烦;
3)最重要的是第三点,数据一致性难以保证。有些公共数据需要被多个事业部访问的话,只能跨机房读取,这个IO无法省掉,而如果用distcp,在本机房也放一些副本以省掉这部分流量开销的话,又会由于副本是通过不同的namenode管理的,导致数据可能会有不一致的问题。
2.2.2 多机房单集群
再来看多机房单集群架构,劣势是需要改Hadoop源代码,因为动了BlockManager的核心代码逻辑,会有风险,需要做好完备的测试和验证。但是好处也很明显。
1)对用户透明,用户不需要关心作业提交到了哪个机房,副本存放在哪里,无感知;
2)运维部署简单;
3)因为是由一个namenode来管理副本状态,所以可以保证多机房副本的一致性。
主要由于第一和第三点优势,我们希望保证用户使用时的透明性和一致性,最终选择了多机房单集群方案。
三、先期尝试——在线离线混部跨机房
其实对于第一种多机房多集群方案,我们之前在在线离线混部项目中采用过。当时的场景是,离线集群的资源在凌晨高峰打满,白天低峰较空。而在线k8s集群恰恰相反,我们希望利用k8s凌晨的计算资源帮我们减轻负担。而k8s集群部署在金和欧机房,数据没有本地性。所以我们希望将一些cpu密集,但是对IO压力又不大的作业,能分配到在线集群。
我们在k8s上部署了一套Yarn集群,并开发了一套作业资源画像系统,主要是采集作业的vcore/memory使用,shuffle,hdfs读写等metrics。由于zeus调度系统提交的作业一般不怎么修改,每个作业的历史执行时间和所消耗资源都有趋同性,我们按照zeus jobid聚合,根据历史多次执行情况分析出每个作业的资源使用趋势。下次作业启动时zeus会将shuffle量和hdfs读写量较低的作业分配到在线集群跑。
另外由于在线集群也跨了两个机房,我们在FairScheduler上开发了基于label的调度,一个label对应一个机房,会根据每个label的负载,动态分配作业到所属的label,一个app所有的task只会固定在一个label内执行,这样机房间不会产生shuffle流量。该方案上线后,可以缓解离线集群8%的计算压力。
四、多机房单集群方案
我们规划一个事业部对应的一个默认机房,数据尽可能在同机房内流动。由此对于多机房单集群架构改造主要包括四个方面:多机房单HDFS集群,多机房多Yarn集群,自动化数据和作业迁移工具,跨机房带宽监控和限流。
4.1 多机房单HDFS架构
先来看HDFS改造,我们改造了namenode源码,在机架感知之上,增加了机房感知,NetworkTopology形成了<机房,机架,Datanode>三元组。这样客户端读block时,计算出来和副本所在节点的距离,本地机房肯定小于跨机房,会优先读本地机房数据。
另外我们在namenode中增加了跨机房多副本管理能力,可以设置目录的多机房副本数,比如只在机房1设置3个副本,或者机房1和机房2各设置三个副本,对于没有设置跨机房副本的路径,我们会在zookeeper和内存中维护一个用户对应默认机房的mapping关系,写文件addBlock的时候,根据ugi找到对应的机房,在该机房内选择节点。
Decommission或者掉节点时候会有大量的副本复制操作,极易容易导致跨机房带宽被打爆。对此,我们修改了ReplicationMonitor线程的逻辑,在副本复制的时候,会优先选择和目标节点相同机房的源节点来进行复制,降低跨机房带宽。
为了持久化跨机房路径副本信息,我们增加Editlog Op来保存每一次跨机房副本设置变更记录,fsimage中新增了跨机房副本Section,这样namenode只会保存一份元数据,failover切换到standby的时候也能加载出来,没有其他外部依赖。
4.2 改造Balancer&Mover&EC
HDFS层面还有其他一些改造,比如Balancer,我们支持了多实例部署,每个Balancer增加IP范围列表,每个机房会起一个,只balance本机房IP的datanode的数据。对于Mover,我们也支持了多机房多实例部署,因为mover是在客户端选择目标副本节点的,所以需要改造按照目录的跨机房副本放置策略在客户端来选择合适的节点。
这边要注意一点的是,尽量保证proxy节点和target节点在同一个机房,因为真正迁移的网络IO是在这两个节点发生的。另外我们在新的日机房部署了一套基于Hadoop 3的Erasure Code集群,会将一部分历史冷数据迁移过去,目前这块没有做跨机房的代码改造,我们的EC迁移程序只会迁移那些已经被迁移到日机房的BU的冷数据到EC集群。
4.3 副本修正工具-Cross FSCK
由于我们有多个namespace,跨机房版本的HDFS是一个一个ns灰度上线的,灰度过程中,其他ns的副本放置还没有考虑机房维度,所以我们开发了Cross IDC Fsck工具,可以感知跨机房配置策略,来修正不正确放置的副本。
因为需要不停的读取副本信息,会产生大量的getBlockLocations rpc请求,我们将请求改成从standby namenode读,一旦发现不匹配会调用reportBadBlocks rpc给active namenode,BlockManager会删除错误的副本,重新选择新的副本。由于这个操作比较重,高峰时间对HDFS会有影响,所以我们在客户端加了rpc限流,控制调用次数。
4.4 多机房多Yarn集群
下面来看下Yarn的改造,我们在每个机房独立部署一套Yarn集群,自研了ResourceManager Proxy,它维护了用户和机房的mapping关系,这个信息是和namenode共用的,都是内存和zookeper各一份。
修改了Yarn Client,用户提交的Yarn作业会首先经过rmproxy,然后再提交到对应Yarn集群。这样一个app所有的Task只会在一个机房内调度,不会产生跨机房Shuffle。如果要切换用户账号对应的机房和集群也很方便,会立马通过zookeeper通知到所有rmproxy,修改内存中的mapping关系。
rmproxy可以多实例部署,互相独立,同时在Yarn Client做了降级策略,在本地定期缓存一份完整的mapping关系,一旦所有rmproxy都挂了,client也能在这段时间做本地路由提交到对应集群。
adhoc和分析报表大量使用了Sparkthrift service,presto,hive service来做计算。对这块常驻服务也做了改造,每个机房各部署一套,客户端之前都是通过jdbc直连对应的thrift service,改造后接入rmproxy,会先从rmproxy中拿到用户对应机房的服务jdbc url,再连接,这块同样对用户透明。
4.2 改造Balancer&Mover&EC
HDFS层面还有其他一些改造,比如Balancer,我们支持了多实例部署,每个Balancer增加IP范围列表,每个机房会起一个,只balance本机房IP的datanode的数据。对于Mover,我们也支持了多机房多实例部署,因为mover是在客户端选择目标副本节点的,所以需要改造按照目录的跨机房副本放置策略在客户端来选择合适的节点。
这边要注意一点的是,尽量保证proxy节点和target节点在同一个机房,因为真正迁移的网络IO是在这两个节点发生的。另外我们在新的日机房部署了一套基于Hadoop 3的Erasure Code集群,会将一部分历史冷数据迁移过去,目前这块没有做跨机房的代码改造,我们的EC迁移程序只会迁移那些已经被迁移到日机房的BU的冷数据到EC集群。
4.3 副本修正工具-Cross FSCK
由于我们有多个namespace,跨机房版本的HDFS是一个一个ns灰度上线的,灰度过程中,其他ns的副本放置还没有考虑机房维度,所以我们开发了Cross IDC Fsck工具,可以感知跨机房配置策略,来修正不正确放置的副本。
因为需要不停的读取副本信息,会产生大量的getBlockLocations rpc请求,我们将请求改成从standby namenode读,一旦发现不匹配会调用reportBadBlocks rpc给active namenode,BlockManager会删除错误的副本,重新选择新的副本。由于这个操作比较重,高峰时间对HDFS会有影响,所以我们在客户端加了rpc限流,控制调用次数。
4.4 多机房多Yarn集群
下面来看下Yarn的改造,我们在每个机房独立部署一套Yarn集群,自研了ResourceManager Proxy,它维护了用户和机房的mapping关系,这个信息是和namenode共用的,都是内存和zookeper各一份。
修改了Yarn Client,用户提交的Yarn作业会首先经过rmproxy,然后再提交到对应Yarn集群。这样一个app所有的Task只会在一个机房内调度,不会产生跨机房Shuffle。如果要切换用户账号对应的机房和集群也很方便,会立马通过zookeeper通知到所有rmproxy,修改内存中的mapping关系。
rmproxy可以多实例部署,互相独立,同时在Yarn Client做了降级策略,在本地定期缓存一份完整的mapping关系,一旦所有rmproxy都挂了,client也能在这段时间做本地路由提交到对应集群。
adhoc和分析报表大量使用了Sparkthrift service,presto,hive service来做计算。对这块常驻服务也做了改造,每个机房各部署一套,客户端之前都是通过jdbc直连对应的thrift service,改造后接入rmproxy,会先从rmproxy中拿到用户对应机房的服务jdbc url,再连接,这块同样对用户透明。
五、自动化迁移工具
由于日机房的节点会按采购到货情况逐步往上加,所以需要按照计算和存储的容量来规划该迁移哪些账号,这是一个漫长的过程,希望能尽量做到自动化迁移,以BU->账号的粒度进行迁移,我们梳理了迁移流程,分为如下四步:
1)批量设置BU对应Hive账号开始迁移(初始为3:0,即福机房3份,日机房0份)
2)按照Hive账号下的DB和用户Home目录依次设置3:3,数据复制到日机房
3)账号和队列迁移到日机房
4)观察跨机房流量,回收福机房的计算和存储资源(设置0:3)
迁移时间过程中有些注意点:
1)迁移过程会耗费大量跨机房网络带宽,需要在集群低峰时间执行,我们是放在早上10点到晚上11点之间,其他时间会自动暂停迁移,否则会影响线上报表和ETL作业的SLA。
2)即使在白天迁移,也需要控制迁移的速率,一方面是减少namenode本身的处理压力,另一方面也是降低带宽,白天也会有一些ETL和adhoc查询需要跨机房访问数据,若打满的话也会有性能影响。迁移中我们会实时监控namenode的UnderReplicatedBlocks和跨机房流量metrics,根据这些值动态调整迁移速率。
3)实时监控被迁移机房的hdfs可用容量,包括不同的StorageType的,防止磁盘打爆。还有有些hive DB库目录设置了hdfs quota,也会由于迁移设置3:3超过quota而报错,我们会自动暂时调高quota,等迁移整体完成后再把quota调回去。
4)公共库表由于被多个BU都有访问依赖,需要提前设置多机房的副本,我们有个白名单功能,可以手动设置,一般设为2:2,每个机房各放两份。
六、跨机房带宽监控&限流
实践中有些BU的表,会被当做公共表来使用,我们需要识别出来,设置跨机房多副本策略。目前的hdfs audit log中,没有dfsclient访问datanode,datanode和datanode传输数据的实际流量audit信息,而我们需要这部分信息来看实际的路径和block访问情况,做进一步数据分析,另外当流量打爆的况下,需要有一个限流服务按照作业优先级提供一定的SLA保障,优先让高优先级作业获取到带宽资源。
对此我们开发了限流服务,在dfsclient和datanode代码中埋点实时向限流服务汇报跨机房读写路径,block读写大小,zeus作业id等信息, 限流服务一方面会记录流量信息并吐到ES和HDFS做数据分析,另一方面会根据作业的优先级和当前容量决定是否放行,客户端只有获得限流服务的Permit,才能继续执行跨机房读写操作,否则sleep一段时间后再次尝试申请。
有了实际的流量信息后,通过离线数据分析,就很容易知道哪些表会被其他BU大量读,通过自动和手动结合方式设置这部分表的跨机房副本数2:2。设置后跨机房Block读请求量下降到原来的20%。跨机房带宽原来是打满的,现在下降到原来的10%。
七、总结与未来规划
总结一下,本文主要介绍了携程Hadoop跨机房实践,主要做了如下改造:
1)实现单hdfs集群机房感知功能,跨机房副本设置
2)实现基于rm proxy和yarn federation的计算调度
3)实时自动化存储和计算迁移工具
4)实现跨机房流量监控和限流服务
目前整套系统已在线上稳定运行了半年,迁移了40%的计算作业和50%的存储数据到新机房,跨机房带宽流量也在可控范围之内,迁移常态化,用户完全不需要感知。
未来我们希望能智能决定该迁移哪些账号,大多数公共路径设置为2:2四个副本,比通常会多加一个副本的物理存储量,现在是设置在表层面,希望能进一步细化到分区层面,因为分析出来大多数下游作业都是只依赖最近一天或者一周的分区。所以一旦过了时间,完全可以将历史分区设置回三副本来减少存储开销。最后是将跨机房的改造也应用到基于Hadoop 3的EC集群,也支持跨机房的能力。