當前List操作的問題
在我們使用HDFS作為數據存儲文件系統時,恐怕最常使用到的命令就是ls命令了。我們往往先使用這個命令查找出目前我們期待的文件目錄信息,然後對查出的這些文件目錄做後續的操作。所以說,list操作的執行效率高低對用戶以及上層應用層調用程序來說就顯得十分重要了。
當前List操作的問題
這裏我們會從2個層面來展開此方面的討論:
- 一個是針使用用戶的角度,也就是CLI命令行使用方式的。
- 另一個就是針對應用程序調用底層API獲取目錄信息的。
先來看第一種情況,當使用ls命令行去查詢目錄樹比較深並且其下子文件又比較多的時候,就會出現返回信息特別緩慢的情況。給到用戶的第一感覺就是命令行執行了好幾十秒,還是沒有結果出來。這種情況,我們最直觀的優化點,就是能夠更快速地出結果。
再來看第二種情況,當然啦,第一鐘情況裏提到的情況也會包含在第二種情況裏,但是額外地,它還有另外一個問題,應用程序在list文件目錄操作時,往往不會只查找1次,往往它要根據比如說partition這種查找一系列的路徑,這個時候就會有RTT往返時延的問題,以及多次RPC請求的開銷。這時,我們可能需要一種Batch-list查詢的API。
社區解決方案
因為最近社區有這方面的解決方案,所以筆者就拿來分享一下了,想提前使用的朋友,可以先apply到自己的測試分支中,進行使用。
ls命令執行緩慢
對應上述2種場景,首先來看第一種case,命令行方式的list優化,解決的核心要點在於將同步執行過程改為客戶端多線程異步執行,用ForkJoinPool來執行遞歸的list查詢,對子任務進行拆分執行,然後進行結果的再匯總。
以下是核心的改動,在基類Command.java裏改的:
+ /** multi-thread approach to FsShell commands */
+ protected ForkJoinPool pool;
+
/** Constructor */
protected Command() {
out = System.out;
@@ -174,6 +179,13 @@ public int run(String...argv) {
"DEPRECATED: Please use ‘"+ getReplacementCommand() + "‘ instead.");
}
processOptions(args);
+ int threads = getConf(www.leyouzaixan.cn).getInt("fs.threads", -1);
+ if(threads != -www.hjd1956.com 1) {
+ pool = new ForkJoinPool(threads);
+ } else {
+ // Default is number of cores.
+ pool = new ForkJoinPool(www.233077.cn/);
+ }
processRawArguments(args);
} catch (CommandInterruptException e) {
displayError("Interrupted");
@@ -301,7 +313,7 @@ protected www.yigouyule2.cn void processPathArgument(PathData item) throws IOException {
// null indicates that the www.feifanyule.cn call is not via recursion, ie. there is
// no parent directory that was expanded
depth = 0;
- processPaths(null,www.cnzhaotai.com item);
+ pool.invoke(new ProcessPathsAction(null, Arrays.asList(item)));
這種部分全部代碼請閱讀JIRA HADOOP-15471:Hdfs recursive listing operation is very slow。
list多目錄查詢緩慢
在元數據的加載中,會涉及到List多目錄的查詢操作,這在Hadoop之上的很多應用程序,比如MR,Hive,Presto中都會調用到。但是,像目前這種方式,HDFS提供的方式只有單一的特定路徑的List查找。當然你可以說,我可以去查一個很大的路徑,然後再在返回結果中過濾出我們想要的路徑。這麽做會有幾個問題,首先你可能會得到大量無關的文件目錄信息,第二會造成不必要的性能損耗,對於NN來說。總的一句話,還是得讓每次的查詢是一次精準,有效的查詢。上文中,筆者已經提到過,這裏我們的優化點是減少不必要的RTT,以及RPC調用,從Multiple-list Call變為一次Batch-List Call。
下面用一個例子來更加具體化以上提到的改進點:
比如目前我們需要list出下面3個路徑:
/zhangsan/20180525/16
/zhangsan/20180525/17
/zhangsan/20180525/18
正常情況下,我們就會調3次get list操作,而一旦我們有了Batch-List的API,我們只需一次性傳入/zhangsan/20180525/16(17)(18),然後經過NN處理,返回多個list結果列表。這樣就完成了一次高效的查詢操作,相比於之前3次完全獨立的查詢調用。
目前此方面的改進在JIRA HDFS-13616:Batch listing of multiple directories 。
另外,根據HDFS-13616上面的討論,此部分改進在元數據的loading過程中會有近10到20倍的性能提升。
Batch-List API在DFSClient.java中的定義如下:
public BatchedDirectoryListing batchedListPaths(
String[] srcs, byte[] startAfter, boolean needLocation)
throws IOException {
checkOpen();
try {
return namenode.getBatchedListing(srcs, startAfter, needLocation);
} catch(RemoteException re) {
throw re.unwrapRemoteException(AccessControlException.class,
FileNotFoundException.class,
UnresolvedPathException.class);
在FileSystem層面的API定義如下:
@Override
public RemoteIterator<PartialListing<FileStatus>> batchedListStatusIterator(
final List<Path>www.tkcyl1.com paths)
throws IOException {
List<Path> absPaths = Lists.newArrayListWithCapacity(paths.size());
for (Path p : paths) {
absPaths.add(fixRelativePart(p));
}
return new PartialListingIterator<>(absPaths, false);
這裏的List Path就是需要list的多個path路徑。
參考資料
[1]. https://issues.apache.org/jira/browse/HADOOP-15471. Hdfs recursive listing operation is very slow
[2].https://issues.apache.org/jira/browse/HDFS-13616. Batch listing of multiple directories.
當前List操作的問題