HDFS3核心特性之Router使用(二)
上一篇主要介绍了从Federation诞生, 引出ViewFS, 到最新的Router(RBF)的基础, 这篇主要说一下Router的具体实践, 以及一些关键点的测试.
未完待续
0x00. 脉络
这篇有两个主要的核心:
- 一个是Router的具体使用, 测试/生产环境中的配置
- 另一个是针对一些关键问题的测试和小结.
先来看看基础的Router使用配置.
0x01. Router使用
1. 测试环境
Router的全部配置官方单独抽取到hdfs-rbf-default.xml, 同样参考官方文档搭建最简的测试环境, 这里测试环境采用的是基于之前federation构建好后:
- 两个NN, NN1和NN2, 不启用HA, 对应两个Subcluster/Namespace
- 两个Router, R1和R2, 和NN1/NN2位于一台机器 (一对一)
- 两个DN给NNs共享使用(federation)
- 集群HDFS版本分别测试
3.4-trunk/3.1.2
, 也就是最新开发分支和稳定release分支. (对比区别)
更新: 为了更贴近生产环境, 补充了3.1.2的版本, 会启用HA, 以及跨集群(不同federation)的截图, 会单独说明.
2. 测试配置和使用
Router主要的端口有3个:
dfs.federation.router.rpc-address
: Router的默认RPC端口8888, Client发送RPC到此dfs.federation.router.admin-address
: Router的默认routeradmin命令端口8111dfs.federation.router.http-address
: Router的默认UI地址50071
A. NN配置修改
默认端口不改, 剩下需要手动添加的是: (federation基于前篇不再贴了)
1 | <!-- 添加Router感知 --> |
Router的启动/停止方式和启动DN/NN类似, 作为一个独立进程, 也有自己单独的日志 :
1 | # 第一次启动后router会进入safemode, 注意观察日志 |
关键在于我们需要添加路由表, 常见的方式是通过routeradmin
命令增删改查:
1 | # 核心是add(添加)命令, 有这些选项: |
如果你没配置ZK, 也没有修改配置文件换成本地/HDFS存储映射, 启动router后尝试添加记录, 会提示Cached State Store not initialized
. 那选本地它存在哪呢?
这个配置官方文档没说, 从源码里可得知是dfs.federation.router.store.driver.file.directory
, 如果不配置, 则会写到临时目录/tmp
下(时间戳开头), 当然正式使用目前一般都是用ZK存储, 后续官方也会支持DBMS类似Mysql存储, 本地存储的结构是:
1 | # 以刚操作的映射具体为例 |
ZK的配置方式见[3. 生产使用](#3. 生产使用和排错), 都尝试一下印象会更深
B. 客户端配置
下面先来说一下客户端的配置, 它是访问Router的主体, 如果和NN一台机器, 建议单独复制包分开, 不要在NN配置里添加客户端的配置 (有冲突)
1 | <!-- hdfs-site.xml--> |
那之前viewFS
时需要修改core-site.xml
的fs.defaultFS
参数, 现在还需要修改么? 应该配置成什么呢?
1 | <property> |
3. 生产使用和排错
A. 生产环境
准备好ZK的环境, 这里不复述过程了. 假设目前已经获得了ZK的地址, 配置文件应该添加
1 | <!--使用ZK时无需指定, 但是默认值难免有变, 写一下--> |
配置好启动后, 可以在zk的路径下确认查看结构(zookeeper/bin/zkCli.sh
), 同本地FS存储格式, 不再重复.
其他的router-admin
命令:
1 | # 1. 开启/关闭NS (后跟NS名) |
B. 排错
router虽然很好, 但是因为官方的文档很多地方说的不够清楚, 或者直接没说, 对初上手的同学是不太友好的, 后续看看能不能优化一下:)
实验过程中遇到过这些错, 记录一下供大家参考:
1 | # 1.Unknown Host异常, 需加上对应proxy.provider配置 (详见配置文件) |
- 配置HDFS存储暂未测试 (后续测试应使用HDFS, 因为本地存储不能共享, 失去了跨NS的功能)
4. UI界面
Router的UI风格基本是和NN界面一样, 几个主要模块:
Overview(概览): 参考官方文档即可, 没啥特别的
SubClusters(子集群信息)
Routers(所有Router的信息)
Datanodes(DN信息)
Mount Table(挂载映射信息)
最关键的就两个, 一个是Subclusters
页面, 另一个是MountTable
页面, 对应整个大集群的所有NN, 和挂载表信息.
0x02. Router测试
主要是测试几个关键的问题:
1. 单挂载可用性
常见增删改查无问题
2. 多挂载可用性和效果
类似viewFS-nfly的延伸, 但又有不小区别, 主要在于前者更像是一个文件写多份, 单纯的跨NS做备份, 那Router的挂载是什么情况呢? 先来简单看下源码定义:
1 | /* 多挂载的默认策略是HASH, 关键点在于如何读写: |
然后简单测试来看:
- 写入都只会写到一个NS上, 都有负载均衡的意义
- 目前来看, 所有策略都会给全部NS发送读请求, 只是有一个发送RPC的第一优先级而已, 那意义是找到了就不给后续NS发? (存疑)
- 如果第二点的结论正确, 那么一致性hash其实目前退化为了随机策略其实, 不知道是何考量? (存疑)
总之, 可以看出, 它和viewFS提出的NFly多写/多删/单读的设计是不一样的, 应用场景也完全不同. 它的核心是写一份, 但是读的时候能通过某种方式优先找到之前写的文件. 实现一个多NS间负载均衡的问题. 至于细节待后面补充.
3. 是否兼容默认hdfs://
类访问方式
router目前常见的default.fs
配置是 hdfs://rsName
, 所以本身没有引入新的schema
, 原本的hdfs://
或者hdfs:///
方式访问是可以兼容的, 但是上层业务的写法是否完成覆盖, 还待全面的测试.
4. 跨Federation的时候, Router是否可用? (ViewFS是可以的)
这也是个比较关键的点, 如果Router也可以支持跨大集群(Federation), 那Router是否可以单独抽取一层呢?
从而引申出一个问题, 那就是为什么官方推荐每个NN上起一个Router对应? 是为了降低读本地NN时的那点延时么? 还是其它考虑呢..
5. 低版本客户端可以直接访问Router么?
- 2.7客户端: 可以直接访问, 测试普通读写正常, 其他功能尚未细测
- 0.2/1.x客户端: (不可以)提示
Server IPC version 9 cannot communicate with client version 3
6. Router1挂掉后, 会自动切换Router2/3么?
理论上应该是会自动切换的, 但是我测试中失败切换后没有生效, 之后看看是不是需要单独配置哪, 待确认….
0x03. Router问题
整理了一下, Router有几个主要的问题大家都比较关心, 这里列一下目前的结论 (主要参考自社区和业界反馈)
1. 功能开发进度? 可以完全取代ViewFS么?
核心功能分为两个大的汇总, 在V3.2已经近乎实现完成
- (V3.0) 核心功能V1阶段 [HDFS-10467]
- (V3.2) 核心功能V2阶段 [HDFS-12615]
它可以完全取代ViewFS, 那只是它最基本的功能之一, 而且因为它不需要使用新的schema
前缀, 业务使用原有hdfs://
或hdfs:///
访问都是正常的.
2. 它能解决多NS负载均衡的问题么? 怎么用 (重要)
核心思路已实现, 对应MultipleDestinationMountTableResolver
, 目前有多个策略:
- 随机选择(RANDOM): 写入随机选择一个NS, 读取会给所有NS发送RPC, 显示汇总结果
- 优先本地(LOCAL): 优先选择和Client位置更近的NS写入, 读也只读这个么? (需要维护一套
ip --> nsId
的映射么?) - 完全哈希(HASH_ALL): 使用一致性hash算法, 对所有文件进行hash打散, 不同于随机, 它的读写路径是固定的. (但增减节点是个问题?)
- 局部哈希(HASH): 继承自
HASH_ALL
, 不同之处在于只取第一个子目录, 比如/a/b/c/d
, 只对/a/b
进行hash,/a/b
下的子孙都属于一个NS [默认值] - 优选剩余空间更大的子集群(SPACE): 写入时大概率(权重)选取剩余空间最多的NS, 读的时候也是优先找空闲么? (待确定)
这里核心的疑问其实在于这个优先级的意义, 所有策略本质都只是修改了原本多个NS的排序, 比如/path/a
挂载到ns1, ns2, ns3
上, 选择SPACE
策略, 若ns3是最空闲的, 那么发送RPC顺序就是ns3, ns1, ns2
, 这里需要后续看源码确认, RPC是串行发送么?
- 如果RPC串行发送, 得到结果后不给其他NS发, 那既可以节省RPC, 又能在一致性hash实现下不搬运数据, 可是性能?
- 如果RPC是并行发送, 那不仅让排序失去了意义, 反而会增加NS的RPC压力, 似乎说不通.
关于默认的一致性Hash结构设计, 见后面单独的[补充](#番外篇: 一致性Hash).
3. 它的可用性如何? 生产环境是否能使用 (进度)
核心的稳定性有两个主要的汇总JIRA:
- (V3.3) 稳定性V1阶段 [HDFS-13891]
- (完成50%) 稳定性V2阶段 [HDFS-14603]
核心的稳定性问题已经修复, 但是一些进阶功能, 比如跨集群mv/cp
的方案, DBMS
支持等还未GA. 生产环境需要实际集群测试, 目前运维资料较少, 采用V3.3.x的稳定版为宜.
4. 使用它对已有业务的影响
暂未细查, Hive之前有相关issue, 后fixed, 其他未知
5. 它目前的不足 or 缺失功能
以下汇总自社区和分享:
Router带来的延时影响, 参考社区分享图
跨子集群的balancer/mv相关, 把数据匀到新的集群的合理方式 (进行ing)
“通过在各集群之上提供一个负载均衡工具,RBF能支持在子集群/NS间数据无缝迁移,从而降低某个子集群上的负载,还能够实现分层(冷热)存储。RBF层会记录当前全局命名空间的状态,并且可以同时启用多个路由器来将用户请求合理映射到不同的子集群当中。” —–Uber
多挂载实现存在一些限制和稳定性问题
Router的安全性问题, 详情参考RBF-安全模块, 暂略
然后上述问题都有相对的PR在进行中了, 也有对应的设计文档, 之后有需要可以再细看.
番外篇: 一致性Hash
在router的多挂载中, 默认/核心的一个策略是HASH/HASH_ALL
,它本质使用了一致性HASH算法做实现, 这里补充点一致性hash的内容, 关于普通hash可以参考此篇, 就不再说了, 一致性hash常用来做负载均衡, 也就是说M个鸡蛋, 怎么均分到N个桶里. 常在分布式DB/缓存系统中看到它的应用.
1. 前身
传统hash实现中, 最常见/简单的一个方式就是取模%
, 常见的hash底层就是个数组, 也可以说是N个桶, 通过取模的方式, 把任意key放到同一个桶中, 如下所示:

上面就是一个0~12
作为12个桶, 任意数字取余12, 得到的结果放入对应桶中的实现, 公式表示就是 index = key % N
, 从而实现经典的hash表核心.
但是这里有个关键问题: 作为取余数的N, 如果发生了改变, 就会导致之前的绝大部分数据的下标发生变化, 原本的hash值就不可用了. (比如N减为3, 或者N增到100), 需要全部重新计算一次, 所以hash表中, 数组(桶)的扩容代价是非常昂贵的, 不可随意变动.
但在实际应用场景, 特别是分布式环境中, 例如把100万个文件(key), 放在10台机器上(桶), 那机器当然会出现故障, 也会随着数据的增加扩容, 这样原本的hash算法, 就无法再适用, 我们期望的是, 就算10台机器变成了9台, 或者增到了20台, 不用花巨大代价去重新计算大部分文件, 也就由1997年MIT引出了一致性hash这个特殊的hash算法, 它的核心不再是传统的数组打底, 而变成了一个巨大的”hash环“
2. Hash环结构与生产实现
之前取余用桶的数目作为取余数, 而hash环默认的取余数是2^32^, 取值范围是[0, 2^32^ -1], 也就是起点/终点是同一个点的一个大圆, 接下来把之前的节点(桶)做一次普通hash(比如md5)后, 再放入hash环中, 相当于划分了多个(环)区间, 这就是Hash环的核心设计. (当然实际实现基本不需要管这个上限, 也不一定会对2^32^ 取余, 而是更简单的思路, 大家理解核心思想, 别死记硬背)
然后接下来就有两种实现, 带虚拟节点和不带虚拟节点的, 生产环境基本都用的是带虚拟节点的, 否则节点少时, 数据分散可能严重不均. 它的核心实现很简单:
- 初始化两个核心数据结构
- Hash环 (
TreeMap
–有序rbtree) - 实节点 –> 虚拟节点个数映射 (
HashMap<string, int>
)
- Hash环 (
- 遍历所有实际节点, 依次执行
- 确定每个实节点对应多少个虚拟节点, 比如100个, 存入map映射中
- 从
0~100
依次遍历, 把虚拟节点的md5-hash
值放入hash环中- 虚拟节点一般基于实节点, 加一个后缀递增, 例如实际节点名
ns
, 虚拟节点名ns/0
,ns/1
…ns/99
, 作为value值 - 把虚拟节点名做一次普通hash, 比如
md5("ns/1")
, 作为hash环的key值, 保证key是离散的 - K-V值都有了, 把它们依次放入hash环中, 比如
("10e...91", "ns/0")
,("e30..cd","ns/99")
- 虚拟节点一般基于实节点, 加一个后缀递增, 例如实际节点名
这样就实现了一个生产环境带虚拟节点的Hash环, 简洁易懂, 下面再看看如何把”鸡蛋”(元素)放入环中, 核心思想就是顺时针找到离它最近的虚拟节点:
- 例如, 对传入文件做一次
md5("/path/a")
, - 在hash环中查找距离文件hash值, 最近的一个虚拟节点hash, 取出对应虚拟节点名(例如
ns/5
) - 逆向处理
ns/5
, 返回实际的节点ns
.
整个设计和实现都很简单, 和论文版hash环有两个主要区别:
- 没有使用2^32^ 作为底数, 然后对它取余,
- 没有把实际节点加入环中, 所有环中节点都是虚拟节点, 读写时统一转换
核心问题:
一致性hash的设计和理解, 不管是否加入虚拟节点, 其实都是好理解的, 关键问题在于增删节点(服务器)时, 数据怎么搬运? 绝大部分文章只说了数据结构的设计
比如在HDFS-Router的场景中, 我们可以通过一致性hash让每次增/减节点映射变化尽可能的小, 但是还是得搬数据呀, 原本只有NS1, NS2, 新增一个NS3, 近乎要搬运50%的数据, 怎么搬呢? 不比缓存说丢就丢, FS上的数据搬运都会耗费大量的IO和时间, 所以推测这里不支持搬数据时, 可能是这样考虑的:
- 每次读请求所有的NS, 这样一致性Hash的读性能就大打折扣了, 本来只需要读一个NS. 那就退化为随机策略了其实..
- 也有可能是, 根据策略优先选出一个NS, 然后如果找到了, 就不给后续NS发RPC了(似乎比较合理), 但是随机策略是特例么?
- 或者先不允许增/减NS, router上挂载好后就不再调整, 等支持数据搬运后再说?
后续这部分单独抽取出来, 暂时放这.
0x04. 未来和展望
上面提到的一系列不足和缺失的功能, 以及结构上的展望:
- 进一步统筹规划Routers结构, 现在每个router相对较为松散, 考虑提供一个Router_Master? (没太明白, 待确定含义. 是说routers前面挂个负载均衡器么?)
- 当前federation中加入新的NN, 所有的DN都需要重启更新配置, 考虑未来通过Routers让DN加入集群
最后附上Uber在分享中规划的蓝图, RBF应该被视为一个负载均衡层, 负责所有客户端的请求分发, 不仅可以同一NS内做读写分离, 还可以智能分流到不同NS.
篇幅原因, 第三篇来单独说一下RBF的另一个尚未GA的核心特性, 如何让多个NS间实现真正的均衡负载, 也就是俗称的ns-balance
.这也是大家很关注的一个点.
注: 学习和上手整个Router, 大部分图和配置参考自官方文档 & HDFS社区的PR和设计文档, 向前辈们表示感谢和敬意.
参考资料:
- (doc) Router-Official-Doc
- (pr) HDFS-10467 Router-Based Federation
- (blog) HDFS Router Based FederationV2 (LinYiQun)
- (blog) RBF supports mount point across multiple NS/subcluster (LinYiQun)
- (share) HDFS-Federation-enhancement (Meituan)
- (share) HDFS-Federation-Beike (InfoQ)
- (slide) HDFS-RBF & Storage Tiering-2019(Uber)