HDFS 中心缓存管理
中心缓存管理器(CacheManager)和缓存块监控服务(CacheReplicationMonitor)
适用场景、中心缓存管理的原理、命令使用。
1.HDFS 缓存适用场景
- 公共资源文件
- 短期临时的热 数据文件
2.HDFS 缓存的结构设计
图 HDFS缓存总结构关系
CacheDirective是缓存的基本单位,可以是目录或文件。
CachePool管理信息有:缓存池名称,所属用户名、组名,缓存池权限最大缓存字节数,过期时间,缓存对象列表等信息。
3.HDFS 缓存管理机制分析
- CacheAdmin CLI 命令在 CacheManager 的实现
三种映射关系:
//CacheDirective id对CacheDirective 的映射关系
private final TreeMap<Long, CacheDirective> directivesByid =
new TreeMap<Long, CacheDirective>();
//缓存路径对 CacheDirective 列表的映射关系,说明一个文件/目录路径可以同时被多次缓存
private final TreeMap<String , List<CacheDirective> directivesByPath =
new TreeMap<String, List<CacheDirective>();
//缓存池名称对 Cache Pool 的映射
private final TreeMap<String, CachePool> cachePools =
new TreeMap<String, CachePool>();
CacheManager通过id到CacheDirective,路径到CacheDirective列表和名称到CachePool的多个映射关系,使得原本逻辑上的父子关系结构平级化了,方便了多条件地灵活查询。
- CacheReplicationMonitor 缓存监控服务
《Hadoop HDFS深度剖析》见第二章中1.5中的内容
4.HDFS 中心缓存疑问点
两个疑问点:
第一个疑问点, CacheManager#checkLimit 方法在运行的时候默认副本数总是为1。在checkLimit 的前面部分用副本数计算字节需求量,但其实在 computeNeeded 方法中副本数并没有被用上。
在上面注释的地方如果没有用上副本数,就会导致后面异常时输出的信息有误。执行过程如下:
private void checkLimit(CachePool pool , Str ng path,
short replication) throws InvalidRequestException {
//副本数作为参数传入,用以计算统计值,但是没有被用上
CacheDirectiveStats stats = computeNeeded(path, replication);
if (pool.getLimit() == CachePoolinfo.LIMIT_UNLIMITED) {
return;
}
//因为副本数没用上,在这里需要乘上副本系数
if (pool.getBytesNeeded() + (stats. getBytesNeeded() * replication) > pool.getLimit()) {
//但是在下面的输出中又除了副本数,因此副本数并没有乘上
throw new InvalidRequestException ( " Caching path " + path +" of size
+ stats.getBytesNeeded() / replication + " bytes at replication "
+ replication +" would exceed pool " + pool.getPoolName()
+ " 's remaining capacity of "
+ (pool.getLimit() - pool.getBytesNeeded()) + " bytes.");
}
}
这个问题已确认是一个 bug ,在社区上已有相应 JIRA : HDFS-10448 ( CacheManager#
checkLimit always assumes a replication factor of 1).
第二个疑问点, CacheReplicationMonitor 在缓存目录的时候没有考虑到子目录的情况,只是处理了直接孩子文件的情况,代码如下:
private void rescanCacheDirectives() {
FSDirectory fsDir = namesystem.getFSDirectory();
final long now= new Date().getTime();
//遍历缓存管理器中需要缓存的基本单元
for (CacheDirective directive : cacheManager.getCacheDirectives()) {
// ...
if(node == null) {
LCG debug ("Directive {} : No inode found at {} ”, directive.getid(), path);
} else if (node.isDirectory()) {
//如果此对象包含的工Node 是目录的话 则遍历孩子节点
INodeDirectory = node.asDirectory();
ReadOnlyList<INode> children = dir.getChildrenList(Snapshot.CURRENT_STATE_ID);
for (INode child : children) {
if (child.isFile()) {
rescanFile(directive, child.asFile());
}
//这里缺少了孩子是目录的情况
}
} else if (node.isFile()) {
//如果此对象包含的INode是纯文件,则直接进行处理
rescanFile(directive, node.asFile());
} else {
LOG.debug("Directive{} : ignor ng non-directive on-file inode {}",directive.getid(), node);
}
}
}
后来笔者看了官网的介绍,目前的版本中只对缓存路径下的第 层孩 做处理,并没有做到对路径的递归处理。递归处理缓存路径是对这种情况的一个优化,目前社区已有相应的 JIRA 来完善这一点: HDFS-10594 ( HDFS-4949 should support recursive cache directives ),感兴趣的同学可以关注一下这个 JIRA。
5.HDFS CacheAdmin 命令使用
$hdfs cacheadmin
Usage: bin/hdfs cacheadmin [COMMAND]
[-addDirective -path <path> -pool <pool-name> [-force] [-replication <replication>] [-ttl <time-to-live>]]
[-modifyDirective -id <id> [-path <path>] [-force] [-replication <replication>] [-pool <pool-name>] [-ttl <time-to-live>]]
[-listDirectives [-stats] [-path <path>] [-pool <pool>] [-id <id>]
[-removeDirective <id>]
[-removeDirectives -path <path>]
[-addPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]
[-modifyPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]]
[-removePool <name>]
[-listPools [-stats] [<name>]]
[-help <command-name>]
图 HDFS CacheAdmin命令分类图
需要DataNode开启缓存功能开关,默认是关闭的,值为0.
<property>
<name>dfs.datanode.max.locked.memory</name>
<value>0</value>
<description> DataNode 用来缓存块的最大内存空间大小 单位用字节表示 系统变量RLIMIT MEMLOCK 至少需要设置得比此配置值要大 否则 DataNode 会出现启动失败的现象 在默认的情况下,此配置值为 表明默认关闭内存缓存的功能</description>
</property>