HDFS中的集中式缓存管理是一种显式的缓存机制,允许用户指定需要由HDFS缓存的路径。NameNode将与磁盘上存有所需数据块的DataNodes通信,并指示它们将这些数据块缓存在堆外缓存中。
HDFS中的集中式缓存管理具有许多显著优势。
显式固定可以防止频繁使用的数据从内存中被逐出。当工作集的大小超过主内存大小时,这一点尤为重要,而这对于许多HDFS工作负载来说很常见。
由于DataNode缓存由NameNode管理,应用程序在做出任务放置决策时可以查询缓存块位置集。将任务与缓存块副本共置可提高读取性能。
当数据块已被DataNode缓存时,客户端可以使用一种新的、更高效的零拷贝读取API。由于缓存数据的校验和验证由DataNode一次性完成,客户端在使用此新API时基本不会产生额外开销。
集中式缓存可以提高集群整体内存利用率。当依赖每个DataNode上的操作系统缓冲区缓存时,重复读取一个块会导致该块的所有n个副本都被加载到缓冲区缓存中。通过集中式缓存管理,用户可以显式地固定仅n个副本中的m个,从而节省n-m的内存。
HDFS在Linux平台上支持非易失性存储类内存(SCM,也称为持久内存)缓存。用户可以为DataNode启用DRAM缓存或SCM缓存。DRAM缓存和SCM缓存可以在DataNodes之间共存。此外,SCM缓存支持缓存持久化。如果dfs.datanode.pmem.cache.recovery
设置为true,在DataNode启动时将恢复保存在SCM中的缓存状态。否则,之前持久化的缓存将被丢弃,数据需要重新缓存。
集中式缓存管理对于需要重复访问的文件非常有用。例如,Hive中经常用于连接的小型事实表就非常适合缓存。另一方面,缓存年度报告查询的输入可能用处不大,因为历史数据可能只会被读取一次。
集中式缓存管理对于具有性能SLA的混合工作负载也非常有用。缓存高优先级工作负载的工作集可确保其不会与低优先级工作负载竞争磁盘I/O资源。
在此架构中,NameNode负责协调集群中所有DataNode的堆外缓存。NameNode会定期从每个DataNode接收一份缓存报告,该报告描述了特定DN上缓存的所有数据块。NameNode通过借助DataNode心跳机制附加缓存和取消缓存命令来管理DataNode缓存。
NameNode会查询其缓存指令集合以确定哪些路径应被缓存。缓存指令会持久化存储在fsimage和编辑日志中,并可通过Java和命令行API进行添加、删除和修改。NameNode还存储着一组缓存池,这些是用于将缓存指令分组以便进行资源管理和权限控制的管理实体。
NameNode会定期重新扫描命名空间和活动缓存指令,以确定哪些块需要缓存或取消缓存,并将缓存工作分配给DataNodes。用户操作(如添加或删除缓存指令、移除缓存池等)也可以触发重新扫描。
我们目前不会缓存正在构建中、损坏或不完整的块。如果缓存指令覆盖了一个符号链接,则不会缓存该符号链接的目标。
目前缓存是在文件或目录级别进行的。块和子块缓存是未来的工作项目。
一个缓存指令定义了应该被缓存的路径。路径可以是目录或文件。目录以非递归方式缓存,意味着仅缓存该目录第一层级中的文件。
指令还指定了额外的参数,例如缓存复制因子和过期时间。复制因子定义了要缓存的块副本数量。如果多个缓存指令引用同一个文件,将应用最大的缓存复制因子。
过期时间在命令行中指定为生存时间(TTL),即未来的相对过期时间。当缓存指令过期后,NameNode在做出缓存决策时将不再考虑该指令。
一个缓存池是用于管理缓存指令组的管理实体。缓存池具有类似UNIX的权限,用于限制哪些用户和组可以访问该池。写入权限允许用户向池中添加和移除缓存指令。读取权限允许用户列出池中的缓存指令以及其他元数据。执行权限未被使用。
缓存池也用于资源管理。池可以强制执行最大限制,这会限制该池中指令可以聚合缓存的字节数。通常,池限制的总和大约等于集群上为HDFS缓存保留的聚合内存量。缓存池还会跟踪一些统计信息,以帮助集群用户确定当前缓存的内容以及应该缓存的内容。
池还可以强制执行最大生存时间。这限制了添加到池中的指令的最大过期时间。
cacheadmin
命令行界面在命令行中,管理员和用户可以通过hdfs cacheadmin
子命令与缓存池和指令进行交互。
缓存指令由一个唯一的、不重复的64位整数ID标识。即使缓存指令后来被移除,ID也不会被重复使用。
缓存池通过唯一的字符串名称进行标识。
用法:hdfs cacheadmin -addDirective -path
添加一个新的缓存指令。
|
缓存路径。该路径可以是目录或文件。 |
|
该指令将被添加到的缓存池。您必须拥有该缓存池的写入权限才能添加新指令。 |
-force | 跳过缓存池资源限制的检查。 |
|
使用的缓存副本因子。默认为1。 |
|
指令的有效时长。可以按分钟、小时和天指定,例如30m、4h、2d。有效单位是[smhd]。"never"表示永不过期的指令。如果未指定,则指令永不过期。 |
用法:hdfs cacheadmin -removeDirective
移除缓存指令。
|
要移除的缓存指令的ID。您必须对该指令所属的池具有写权限才能移除它。要查看缓存指令ID列表,请使用-listDirectives命令。 |
用法:hdfs cacheadmin -removeDirectives
移除指定路径下的所有缓存指令。
|
要移除的缓存指令路径。您必须对该指令所在存储池拥有写入权限才能移除它。要查看缓存指令列表,请使用-listDirectives命令。 |
用法:hdfs cacheadmin -listDirectives [-stats] [-path
列出缓存指令。
|
仅列出具有此路径的缓存指令。请注意,如果我们没有读取权限的缓存池中存在针对path的缓存指令,则该指令不会被列出。 |
|
仅列出该资源池中的路径缓存指令。 |
-stats | 列出基于路径的缓存指令统计信息。 |
用法:hdfs cacheadmin -addPool
添加一个新的缓存池。
|
新资源池的名称。 |
|
资源池所有者的用户名。默认为当前用户。 |
|
池的所属组。默认为当前用户的主组名。 |
|
池的UNIX风格权限。权限以八进制形式指定,例如0755。默认情况下,此值设置为0755。 |
|
该池中指令可缓存的字节数上限(总计值)。默认情况下不设置限制。 |
|
添加到池中的指令允许的最大生存时间。可以以秒、分钟、小时和天为单位指定,例如120s、30m、4h、2d。有效单位为[smhd]。默认情况下不设置最大值。"never"值表示没有限制。 |
用法:hdfs cacheadmin -modifyPool
修改现有缓存池的元数据。
|
要修改的池名称。 |
|
资源池所有者的用户名。 |
|
资源池所属组的组名。 |
|
以八进制表示的池的Unix风格权限。 |
|
该池可以缓存的最大字节数。 |
|
添加到池中的指令所允许的最大生存时间。 |
用法:hdfs cacheadmin -removePool
移除一个缓存池。这也会解除与该池关联的路径缓存。
|
要移除的缓存池名称。 |
用法:hdfs cacheadmin -listPools [-stats] [
显示一个或多个缓存池的信息,例如名称、所有者、组、权限等。
-stats | 显示额外的缓存池统计信息。 |
|
如果指定,则仅列出指定名称的缓存池。 |
用法:hdfs cacheadmin -help
获取关于某个命令的详细帮助。
|
需要获取详细帮助的命令名称。如果未指定命令,则打印所有命令的详细帮助信息。 |
为了将块文件锁定在内存中,DataNode依赖于Windows上libhadoop.so
或hadoop.dll
中的原生JNI代码。如果您正在使用HDFS集中式缓存管理,请确保启用JNI。
目前,持久内存缓存有两种实现方式。默认的是纯Java实现,另一种是原生实现,利用PMDK库来提升缓存写入和读取的性能。
要启用基于PMDK的实现,请按照以下步骤操作。
安装PMDK库。详细信息请参考官方网站http://pmem.io/。
构建支持PMDK的Hadoop。请参考源代码中BUILDING.txt
文件里的"PMDK库构建选项"章节。
要验证Hadoop是否正确检测到PMDK,请运行hadoop checknative
命令。
请务必为DRAM缓存或持久内存缓存配置以下属性之一。请注意,在DataNode上DRAM缓存和持久缓存不能共存。
dfs.datanode.max.locked.memory
该参数决定DataNode用于缓存的最大内存量。在类Unix系统上,还需要相应提高DataNode用户的"锁定内存大小"ulimit值(ulimit -l
)以匹配此参数(参见下方操作系统限制章节)。设置此值时,请记住内存中还需要为其他用途预留空间,例如DataNode和应用程序JVM堆以及操作系统页面缓存。
该设置与延迟持久化写入功能共享。DataNode将确保延迟持久化写入和集中式缓存管理使用的总内存不超过dfs.datanode.max.locked.memory
中配置的值。
dfs.datanode.pmem.cache.dirs
该属性指定持久内存的缓存卷。对于多个卷,应使用","分隔,例如"/mnt/pmem0, /mnt/pmem1"。默认值为空。如果配置了此属性,将自动检测卷容量,且无需再配置dfs.datanode.max.locked.memory
。
以下属性不是必需的,但可以指定用于调优:
dfs.namenode.path.based.cache.refresh.interval.ms
NameNode将使用此参数作为路径缓存重新扫描之间的毫秒间隔。该参数用于计算需要缓存的块以及包含该块副本且应进行缓存的每个DataNode。
默认情况下,该参数设置为30000,即三十秒。
dfs.datanode.fsdatasetcache.max.threads.per.volume
DataNode将使用此参数作为每个卷用于缓存新数据的最大线程数。
默认情况下,此参数设置为4。
dfs.cachereport.intervalMsec
DataNode将使用此参数作为向NameNode发送完整缓存状态报告之间的时间间隔(毫秒)。
默认情况下,该参数设置为10000,即10秒。
dfs.namenode.path.based.cache.block.map.allocation.percent
我们将分配给缓存块映射的Java堆内存百分比。缓存块映射是一个使用链式哈希的哈希表。如果缓存块数量较大,较小的映射表可能导致访问速度变慢;较大的映射表则会消耗更多内存。默认值为0.25%。
dfs.namenode.caching.enabled
该参数用于启用/禁用NameNode中的集中式缓存功能。当集中式缓存被禁用时,NameNode将不再处理缓存报告或存储集群中块缓存位置的信息。需要注意的是,NameNode仍会在文件系统元数据中存储基于路径的缓存位置信息,但在缓存功能启用前不会对这些信息采取行动。该参数的默认值为true(即启用集中式缓存)。在当前实现中,集中式缓存会引入额外的写锁开销(参见CacheReplicationMonitor#rescan),即使没有指定要缓存的路径也是如此,因此我们建议在不使用时禁用此功能。在后续版本中,我们将默认禁用集中式缓存。
dfs.datanode.pmem.cache.recovery
该参数用于确定在DataNode启动时是否恢复持久内存中先前缓存的状态。如果启用,DataNode将恢复持久内存中先前缓存的数据状态,从而避免重新缓存。如果未启用此属性,DataNode将丢弃持久内存中的缓存(如果有)。此属性仅在启用持久内存缓存时生效,即配置了dfs.datanode.pmem.cache.dirs
的情况下。
如果遇到错误“无法启动数据节点,因为配置的最大锁定内存大小...超过了数据节点可用的RLIMIT_MEMLOCK ulimit限制”,这意味着操作系统对可锁定内存量的限制低于您的配置值。要解决此问题,您必须调整DataNode运行时使用的ulimit -l值。通常,该值在/etc/security/limits.conf
文件中配置。但具体配置方式会根据您使用的操作系统和发行版而有所不同。
当您能在shell中运行ulimit -l
并返回高于您通过dfs.datanode.max.locked.memory
配置的值,或返回"unlimited"字符串(表示无限制)时,即可确认该值已正确配置。请注意,ulimit -l
通常以KB为单位输出内存锁定限制,但dfs.datanode.max.locked.memory必须以字节为单位指定。
此信息不适用于Windows上的部署。Windows没有直接等同于ulimit -l
的功能。