1 简介
随着苏宁大数据平台的规模越来越大,HDFS集群Namenode逐渐出现性能瓶颈,特别是在凌晨任务的高并发期,Namenode的RPC响应延迟较高,单次写RPC请求甚至超过1s,严重影响了集群的计算性能。因此解决HDFS的扩展性问题,势在必行。
本文将介绍在苏宁我们是怎么解决这个问题的,主要从以下几个方面来展开:
- 单一的HDFS集群存在的问题和挑战,以及原因分析;
- 将单一的集群拆分成多集群需要考虑的问题,以及多集群的方案对比;
- 如何利用Alluxio的统一命名空间来实现HDFS多集群的统一入口;
2 单一HDFS集群的瓶颈和挑战
下图为苏宁大数据平台的软件栈,与其他各家友商的平台大同小异。HDFS作为苏宁大数据平台的底层存储系统,集群达到1500+台Datanode(48TB),已使用30+PB的空间,存储了1.8亿的文件和1.8亿的块,每天运行的任务量接近40万。在凌晨计算高峰期,对Namenode最大并发请求量为3万+。
苏宁大数据平台软件栈
我们使用的Hadoop-2.4.0的版本,下图是HDFS RPC请求及处理的流程图。
HDFS RPC请求及处理流程
从图中可以看到,导致NameNode RPC响应延迟高的原因主要有:
- 所有对Namenode元数据的写请求都需要获取FSNameSystem的写锁,并发高的情况下,等待锁资源会消耗较长的时间;
- EditLog和Auditlog的同步写;
下图是我们HDFS集群某一天写请求响应时间的统计图。从图中可以看到,在凌晨3点到5点,RPC的响应时间持续在500ms左右,对上层的任务影响较大。
HDFS Namenode 写请求的RPC响应时间
针对高峰期Namenode RPC响应延迟高的问题,我们做了如下优化:
- 优化任务,减小对HDFS的读写次数,特别是使用动态分区的任务;
- 将audit log由同步改成异步;
- 设置fsImage的合并时间为1小时;
- disable掉文件系统access time的更新功能;
通过上面的优化,性能有所提升,但是并没有达到我们预期,高峰期RPC延迟仍然很高,我们开始考虑多集群方案。
3 多集群方案调研
设计多集群方案时,我们主要考虑的问题有:
- 如何做到对用户透明;
- 如何做到跨集群的数据访问;
- 集群按照何种维度进行切分;
- 系统的稳定性;
- 运维的复杂度;
带着以上问题,我们对社区已有方案进行了调研和对比。
3.1 Federation+Viewfs
该方案在Hadoop-2.4.0版本中已经存在,是基于客户端的配置来实现的。
优点是:
- 可以做到对用户无感知;
- 通过客户端的Viewfs可以实现跨集群的数据访问;
- 很多公司在生产环境中使用,稳定性有保证;
缺点是:
- 基于客户端配置实现,集群规模越大,账户越多,运维的复杂度会越来越高。
3.2 HDFS Router
在Hadoop-2.9.0的版本中,发布了一个新的feature -- HDFS Router。该方案将路由表做在了服务端,所有的RPC请求都先到Router,Router根据路由表的解析结果对RPC请求进行转发。
该方案虽然可以解决Federation + Viewfs的运维复杂度问题,但没有经过大规模生产验证,稳定性无法保证,不敢轻易使用。
3.3 Alluxio 统一命名空间
Alluxio的统一命名空间特性,支持对不同集群的HDFS路径进行挂载,为用户提供统一的访问入口。经过调研该方案,得出如下结论:对用户访问透明;在服务端进行挂载,运维成本可接受;Alluxio已经被多家一线互联网公司应用于生产环境,成熟度和稳定性有保证。因此,我们决定采用Alluxio统一命名空间来实现多HDFS集群方案。
在下面的章节中,我们会介绍在设计和实施过程中遇到过哪些问题,踩过哪些坑。
4 Alluxio多集群统一入口的设计和实现
我们首先来解决一个问题:集群如何切分?只有在确定了集群的切分维度之后,才能基于该维度去做路由的设计。我们采取的切分策略是:按照账号进行切分,一个账号不能跨两个集群。
如何实现对用户透明?采用同名挂载的方式可以实现对用户的透明,比如集群A的/user/bi路径挂载到Alluxio空间中也是/user/bi。
确认了切分和挂载方案后,我们对Alluxio进行了测试,发现如下问题:
- 存储同样体量的元数据时,Alluxio Master相较于HDFS Namenode会消耗更多的内存空间。因此,做多集群的统一入口时,Alluxio Master的内存会先达到瓶颈;
- Alluxio采用的Thrift RPC框架,Alluxio客户端和Master采用的是长连接;在凌晨计算高峰期,Master端的服务线程会被打满,从而导致客户端的请求都被堵住;
- Alluxio Client是以plugin的形式进行部署的,和其他组件的jar包存在兼容性问题;
针对以上问题,我们分别给出了自己的解决方案,下面会分小节做详细介绍。首先来看下我们的总体架构图。
Alluxio多集群统一入口的结构图
客户端通过多集群统一入口访问HDFS集群的流程如下:
- HDFS的客户端利用Alluxio客户端去Alluxio Master请求路由表;
- HDFS客户端根据返回的路由表对访问的路径进行解析,获取对应的NameService;
- 请求相应NameService的Namenode,获取文件的元数据信息;
- HDFS客户端根据返回的元数据信息,和对应的Datanode进行交互;
4.1 元数据量问题
针对Alluxio Master内存瓶颈的问题,我们采取的解决方案是:Alluxio和HDFS各自管理自己的元数据;下图为各个空间的文件和元数据的示例图。
文件内存元数据管理示例图
从图中可知:
- File A:表示只存储在Alluxio的数据,没有写到HDFS中,该文件的元数据信息只会保存在Alluxio Master中;
- File B:表示只存储在HDFS的数据,没有load到Alluxio的内存中,该文件的元数据信息只会保存在HDFS的Namenode中;
- File C:表示文件同时存储在Alluxio和HDFS中,此时C文件的元数据信息会同时出现在Alluxio Master中和HDFS Namenode中;
元数据分开管理会存在数据一致性的问题,但在我们当前的使用场景中,并不会使用Alluxio进行实际的数据存储,算是从应用场景上规避了这个问题。
4.2 Alluxio客户端和Master之间的长连接问题
针对client和Master存在长连接的问题,我们的解决方案是:client主动去关闭,在需要时再重建连接。关闭重连的消耗时间如下图所示,针对离线集群来说,1ms左右的性能损耗是可以接受的。
客户端关闭重连的压测结果
4.3 jar包兼容性问题
Alluxio客户端jar包和其他组件的jar包冲突问题,我们采用了业界常用的方案:利用maven的shaded插件将Alluxio客户端runtime相关的jar全部shaded住。
5 性能测试
在上线之前,我们对该方案进行了压测。模拟20个和50个账户,并发对单集群和多集群(2个集群)分别进行压测,结果如下:
备注:压测的读写比例请求是按照生产的实际读写比例的请求来进行的;1 Transaction =mkdir:create:listStatus:getfileinfo:open:rename:delete=1:4:5:71:40:3:1,因此1TPS对应底层125个RPC请求。
从压测数据来看,在采用两个集群的方案中,HDFS的TPS增加了40%,响应时间下降了22%。相信随着苏宁的业务的不断增多,多集群的解决方案会取得更好的效果。
6 后续优化
在使用的Alluxio做多集群统一入口的时候,我们发现如下问题需要去优化,我们将在Q2解决:
- Client需要保存各个集群Namenode的地址信息,不利于客户端的运维。可以将HDFS的配置信息也存储在Alluxio的Master端;
- Alluxio Master不能感知底层HDFS Namenode的Active和Standby状态,导致客户端在访问Namenode的时候,会按照配置文件的先后顺序会去尝试;
7 后记
在今年的3月份我们将HDFS升级到Hadoop-2.8.2的版本中,得益于新版本对HDFS写流程的优化,升级之后,Namenode的RPC响应延迟大幅下降,写请求的峰值时间下降到50ms以下。
但该版本中Namenode的内存使用量和FSNameSystem的writeLock问题依然没有得到解决。随着业务和数据量的持续增长,仍然会存在较严重的性能问题。因此对于可以预见的大规模使用场景,提前做多集群方案,仍然非常必要。