01 Abstract
Hadoop 社区为了解决 HDFS 横向扩展的问题,早前的版本中实现了基于 ViewFs 的 Federation 架构,而在最新的 Hadoop 版本中,社区又实现了基于 Router 的 Federatio n架构,并且在这个架构之上还实现了许多增强集群管理能力的特性。Router 将挂载表从 Client 中抽离了出来,解决了挂载表不一致的问题,本篇文章就会介绍 HDFS Router-based Federation 的架构和特性。
02 Background
在 HDFS 单集群的架构中,随着集群规模的扩大,Block Manager 和Namespace 会消耗掉 NameNode 越来越多的资源,最终导致NameNode 难以提供可靠的服务。于是就提出了 Federation 架构。
Federation 架构是指由多个子集群联合构成一个 Federation 集群,通常的做法是这些子集群会共享 Datanode.然后由挂载表来维护Federation Namespace 到子集群 Namespace 间的映射关系,这个挂载表存在客户端本地的配置文件里,由客户端来解析从而访问到正确的子集群。在社区的实现中,用了一个新的协议 viewfs:// 来访问Federation Namespace.
小米内部对 Federation 实现做了很多优化。为了提高用户易用性,让用户配合集群迁移,我们尽可能的对用户屏蔽细节,实现访问Federation 集群和普通集群不需要用户修改代码甚至配置,
但是 Federation 架构也带来了一些问题,
于是,社区提出了新的 Federation 架构:Router-based Federation
03 Router
为了对用户屏蔽 Federation 的实现细节,将挂载表的配置和实现从客户端中剥离出来,一个自然的想法引入新的代理服务,客户端直接请求代理服务,再由其解析挂载表后将请求转发给正确的子集群。我们将这个代理服务叫做 Router.
我们首先来看 Router 是如何代理转发RPC的。
图中标识的是调用关系。Router 会启动 RouterRpcServer 服务,这个类和 NameNodeRpcServer 一样实现了 ClientProtocol,也就是说 Client 不需要改实现,就可以把 Router 当作 Namenode 来访问。当然,Router 也实现了其他的协议用以管理员来管理 Router 或集群状态。 Router 在通过 RouterRpcServe 收到 RPC 后,显示通过解析挂载表得到对应的子集群和其路径,再通过 ConnectionManager 构造出对应NameNode 的 RPC Client,利用 Client 转发这个 RPC. ConnectionManager 维护了一组连接池,每个 RPC 的UserGroupInformation,NameNode Address 和 Protocol 共同构成了连接池的 Key.连接池在构造时会创建一定数量的 RPC Client,随后对于每一个过来的 RPC,在连接池里找一个空闲的 RPC Client 用以发送RPC.当空闲的 RPC Client 不够时,由后台的 Creator 线程异步的构造新的连接,同时有后台的 Cleaner 线程负责清理连接池。 Router 如何代理用户的信息将在后面的 Router Security 章节说明。
04 MountTableResolver
在 Router 中,每一条 Federation Namespace 到子集群 Namespace的映射对应一个 MountTable,所有的 MountTable 就是集群的挂载表。在 MountTableResolver 中,由类型为 TreeMap<String, MountTable>的成员来管理,Key 为 Federation Namespace 下的路径,Value 为对应的MountTable.在解析的时候,会从这个路径向它的父目录找最近的挂载点,也就是最长匹配,这一点和社区原本的 ViewFs 的实现不同,Router 支持了嵌套挂载表。 社区还实现了一个支持把一个路径挂在多个集群下的 Resolver,它可以根据指定的规则例如一致性哈希来决定把子目录映射到哪个子集群上。 挂载表由管理员通过命令来设定,但是为了让所有的 Router 都能读到最新的挂载表,以及 Router 重启后不需要重新设定挂载表,这个挂载表应该持久化存在哪里呢?
05 State Store
为了更方便的管理 Router 的配置和状态,我们引入了 State Store,这是对于我们存储 Router 状态的存储服务的一个抽象,目前社区有基于文件系统和基于 Zookeeper 的两种实现。
负责与 State Store 通信的是 StateStoreDriver,定义了一些基本的GET/PUT 接口,由 StateStoreConnectionMonitorService 维护。StateStoreService 是 Router 管理 State Store 的服务,负责从 State Store 拉取数据,更新注册进来的 RecordStore 的缓存。在 State Store 上存储的叫做Record,目前只有基于 protobuf 的序列化实现。 举例来说,上面我们提到的挂载表就是一个 RecordStore 的实现,每条 Mount Table 就是一个 Record,他们被 protobuf 序列化后存储在State Store 上。
06 Router Security
有了上面的架构,Router 就可以作为一个无状态的代理层来工作了。可是 Client 不再直接与NameNode通信,非 RBF 集群的安全认证方案就失效了,所以就有了 Router 层的安全认证方案。 HDFS实践中用到的认证方案有两个,Kerberos 和 Delegation Token,这两种是针对不同应用同时在使用的。 我们先来看 Kerberos 如何在 Router 层实现。
显然,我们可以将 Router 作为 Service 注册到 Kerberos,由 Router来认证 Client.同时,Router 由作为 HDFS 的超级用户来代理 Client 的用户信息,在代码中可以这样简单的实现
UserGroupInformation routerUser = UserGroupInformation.getLoginUser();
connUGI = UserGroupInformation.createProxyUser(
ugi.getUserName(), routerUser);
Delegation Token 相对就没这么容易实现了。
按照社区现在的实现,是由 Router 来构造 Delegation Token,认证Client.为了让所有的 Router 能同步已构造的 Delegation Token,需要将其存到 State Store 来让 Router 间进行同步。这样做的好处实现简单,不需要和所有的 NameNode 进行通信就可以在 Route r层完成认证。坏处是需要 Router 间进行同步,这可能会导致性能问题,以及由于 Zookeeper 并非保证强一致性,Router 可能会读不到另一个 Router构造的 Delegation Token,结果 Client 认证失败。
07 Other Features
在这样的架构下,社区还实现了很多有趣的特性
08 Community Practice
社区的 RBF 架构图如下
在社区文档中提到的推荐实践是,在每一个 NameNode 的机器上启动一个 Router 服务,State Store 是 Zookeeper 实现,挂载表的映射关系中 Federation Namespace 和子集群 Namespace 上的路径相同。
09 Future Works