侯向寧
(成都理工大學 工程技術學院,四川 樂山 614007)
面對海量的車牌數(shù)據(jù),傳統(tǒng)的基于終端的車牌識別系統(tǒng)受到很大的挑戰(zhàn)。Hadoop云計算平臺具有強大的海量數(shù)據(jù)分發(fā)、存儲以及對大數(shù)據(jù)進行并行運算的能力,是大數(shù)據(jù)處理領域的首選。HDFS和MapReduce是Hadoop框架的核心,然而,HDFS設計的初衷是為了能夠存儲和分析大文件,并且在實際應用中取得好的效果,但卻不適合處理大小在10 KB~1 MB的海量車牌圖像小文件。這是因為HDFS集群下每一個小文件均占據(jù)一個Block,海量的小文件會消耗大量NameNode內(nèi)存,另外,Hadoop會為每個文件啟動一個Map任務來處理,大量的時間花費在啟動和關閉Map任務上,從而嚴重降低了執(zhí)行速率。因此,Hadoop在存儲和處理海量小文件時,存儲效率和性能會大幅下降。
文中在分析現(xiàn)有解決方案的基礎上,利用CFIF將小文件打包分片,以解決HDFS在存儲海量小文件時的瓶頸問題,以及MapReduce的執(zhí)行效率問題。
HDFS(Hadoop distributed file system)[1-3]是用Java開發(fā)的能夠運行在通用機器上的具有高容錯性、高吞吐量的分布式文件系統(tǒng)。HDFS基于M/S模式,由一個NameNode節(jié)點和若干DataNode節(jié)點構成。NameNode是主控服務器,其職責是維護HDFS命名空間,協(xié)調(diào)客戶端對文件的訪問和操作,管理元數(shù)據(jù)并記錄文件數(shù)據(jù)在每個DataNode節(jié)點上的位置和副本信息[4-6]。DataNode是數(shù)據(jù)存儲節(jié)點,在NameNode的調(diào)度下,負責客戶端的讀寫請求以及本節(jié)點的存儲管理[7-8]。
(1)NameNode內(nèi)存瓶頸。
HDFS中所有元數(shù)據(jù)信息都存儲在NameNode內(nèi)存中。HDFS在設計時,每一個文件、文件夾和Block大約占150 Byte。假設HDFS集群中有100萬個小文件,每一個小文件均占據(jù)一個Block,就至少需要3G內(nèi)存[9]。所以,海量的小文件會占用大量NameNode內(nèi)存,造成NameNode內(nèi)存的嚴重浪費,極大限制了集群中文件數(shù)量的規(guī)模,成為HDFS文件系統(tǒng)的一大瓶頸。
(2)數(shù)據(jù)訪問效率低。
在M/S模式下,所有的數(shù)據(jù)讀寫請求大都要經(jīng)過NameNode,當存儲海量小文件時,NameNode會頻繁地接受大量地址請求和處理數(shù)據(jù)塊的分配。此外,海量小文件一般分儲于不同的DataNode上,訪問時要不斷地從一個DataNode切換到另一個DataNode,嚴重影響了訪問效率和性能[10-11]。此外,客戶端在讀取海量小文件時,需要與NameNode節(jié)點頻繁通信,極大降低了元數(shù)據(jù)節(jié)點的I/O性能。最后,讀取海量小文件時,由于小文件存儲空間連續(xù)性不足,HDFS順序式文件訪問的優(yōu)勢難以發(fā)揮。
(3)MapReduce運行效率低。
Hadoop處理海量小文件時,會為每一個小文件啟動一個Map任務,因此,大量時間浪費在Map任務的啟動和關閉上,嚴重降低了執(zhí)行速率。
處理小文件一直是Hadoop的一大難題。目前,解決問題的常見方案大致有:Hadoop Archives方案、Sequence File方案、MapFile方案和HBase方案[11-13]。
Hadoop Archives即HAR,是Hadoop系統(tǒng)自帶的一種解決方案[14]。其原理是先把文件打包,然后做上標記以便查詢。HAR的缺點是兩級索引增加了系統(tǒng)搜索和處理文件的時間,HAR沒有對文件進行合并,文件的數(shù)量沒有明顯減少。這一方面會耗費NameNode內(nèi)存,另一方面MapReduce要給每個小文件各啟動一個Map任務,總的Map任務數(shù)并沒有減少,因此在MapReduce上運行時效率很低。
Sequence File基于文件合并,它把多個小文件歸并成一個大文件,通過減少文件數(shù)來減輕系統(tǒng)的內(nèi)存消耗和性能。但是它沒有設置索引方式,導致每次查找合并序列中的小文件都要從整個系統(tǒng)的磁盤中去查找,嚴重影響了系統(tǒng)的效率。
MapFile是排序后的Sequence File,MapFile由索引文件(Index)和數(shù)據(jù)文件(Data)兩大部分構成。Index作為文件的數(shù)據(jù)索引,主要記錄了每個小文件的鍵值,以及該小文件在大文件中的偏移位置。其缺點是當訪問MapFile時,索引文件會被加載到內(nèi)存,因此會消耗一部分內(nèi)存來存儲Index數(shù)據(jù)。
HBase以MapFile方式存儲數(shù)據(jù),通過文件合并與分解提高文件的存儲效率[15]。其缺點主要是:HBase規(guī)定數(shù)據(jù)的最大長度是64 KB,因此不能存儲大于64 KB的小文件。另外, HBase只支持字符串類型,要存儲圖像、音頻、視頻等類型還需用戶做相關的處理。還有,隨著文件數(shù)的增多,HBase需要進行大量的合并與分解操作,這樣既占用系統(tǒng)資源又影響系統(tǒng)性能[13]。
總之,通過對以上常用方案的分析,發(fā)現(xiàn)都不適合解決海量車牌圖像的存儲與執(zhí)行效率問題。因此,下節(jié)將采用另外一種方案來解決當前所面臨的困境。
Hadoop在新版本中引入了CombineFileInputFormat(簡稱CFIF)抽象類[16],其原理是利用CFIF將來自多個小文件的分片打包到一個大分片中,這種打包只是邏輯上的組合,讓Map以為這些小文件來自同一分片,每個大分片只啟動一個Map任務來執(zhí)行,通過減少Map任務的啟動數(shù)量,節(jié)省了頻繁啟動和關閉大量Map任務所帶來的性能損失,提高了處理海量小文件的效率。另外,CFIF在將多個小文件分片打包到一個大分片時會充分考慮數(shù)據(jù)本地性,因此節(jié)省了數(shù)據(jù)在節(jié)點間傳輸所帶來的時間開銷。
CFIF只是一個抽象類,并沒有具體的實現(xiàn),需要自定義。文中利用CFIF處理海量車牌小文件,具體流程設計如圖1所示。
由圖1可知,要利用CFIF來實現(xiàn)海量車牌圖像小文件的處理,需要做如下工作:
(1)設計Hadoop圖像接口類,因為Hadoop沒有提供圖像處理接口,不能處理圖像文件數(shù)據(jù),因此必須自己定義。
(2)繼承CFIF類,定義CombineImageInputFormat類將海量圖像小文件打包成分片,作為MapReduce的輸入格式。
(3)實現(xiàn)CombineImageRecordReader,它是CombineFileSplit的通用RecordReader,就是為來自不同文件的分片創(chuàng)建相對應的記錄讀取器,負責分片中文件的處理。因為Hadoop中已經(jīng)提供了CombineFileSplit的實現(xiàn),因此CombineFileSplit無須再設計。繼承CombineImageRecordReader類,定義ImageRecordReader實現(xiàn)對CombineFileSplit分片中單個小文件記錄的讀取。

圖1 海量車牌圖像小文件處理流程
(4)配置MapReduce以實現(xiàn)對海量車牌圖像的處理。
Hadoop沒有提供專用的圖像接口,因此不能直接處理車牌圖像數(shù)據(jù)。Hadoop的Writable接口定義了一種二進制輸入流方法和一種二進制輸出流方法,這兩種方法可以實現(xiàn)數(shù)據(jù)的序列化和反序列化。因此,要處理圖像數(shù)據(jù),需要繼承Writable接口,定義一個圖像類接口Image類,并在Image類中重寫Writable類的readFields和write兩種方法,分別用于寫入和讀出圖像的相關數(shù)據(jù)信息。經(jīng)過設計的Image類圖如圖2所示。

圖2 Image類圖
CFIF作為Mapper的輸入格式,將來自多個小文件的分片打包到一個大的輸入分片中,因此需要從CFIF類繼承一個CombineImageInputFormat類,并在CombineImageInputFormat類中創(chuàng)建CombineImageRecordReader。CombineFileRecordReader是針對CombineFileSplit的通用RecordReader,是為來自不同文件的分片創(chuàng)建相對應的記錄讀取器,負責分片中文件的處理,即讀取CombineFileSplit中的文件Block并轉化為
(1)CombineImageRecordReader類。
從RecordReader繼承一個CombineImageRecordReader類,把CombineFileSplit中的每一個小文件分片轉化為
CombineImageRecordReader extends RecordReader
CombineImageRecordReader(CombineFileSplit,split,TaskAttemptContext context,Integer index) {CombineFileSplit ThisSplit=split; //獲取文件分片
//對緩沖區(qū)中的圖像解碼,img用作鍵值對的值
img=new Image(BufferedImage)};
//獲取當前輸入文件分片的路徑,用作鍵值對的鍵
filePath=ThisSplit.getPath(index);}}
(2)CombineImageInputFormat類。
繼承CFIF類,定義CombineImageInputFormat作為Mapper的圖像輸入格式,具體代碼如下:
CombineImageInputFormat extends CFIF
//設置RecordReader為CombineImageRecordReader
RecordReader
return newCombineImageRecordReader
//不對單個圖像文件分片
isSplitable(JobContext context,Path file){return false;}}
在Mapper階段,從CombineImageRecordReader那里得到
在Reducer階段,新key表示輸出路徑,其中圖像文件以“原主文件名(車牌號).jpg”的形式命名,并把image特征圖以新key為路徑保存。
采用偽分布式搭建Hadoop云計算平臺,該云計算平臺由一個NameNode節(jié)點和三個DataNode節(jié)點組成。各節(jié)點的配置如表1所示,所需軟件配置如表2所示。

表1 Hadoop集群節(jié)點配置

表2 軟件配置
為了測試CFIF方式在內(nèi)存消耗與運行時間方面的性能,特意設計兩組對比實驗,準備6組(1 000,2 000,3 000,4 000,5 000,6 000)車牌圖像文件,在不同方案下,分別通過分布式環(huán)境運行每組等量的車牌識別任務。
(1)測試HDFS(即傳統(tǒng)的單文件單Map任務)、Hadoop Archives(HAR)、MapFile(簡稱MF)及CFIF方案下,處理6組車牌圖像文件的完成時間。
(2)測試HDFS、HAR、MF和CFIF四種方案下NameNode節(jié)點所占用的內(nèi)存百分比。
兩組實驗的測試結果分別如圖3、圖4所示。

圖3 完成時間對比
由圖3可見,在運行效率方面,CFIF在四種方案中表現(xiàn)最好,原因在于CFIF方案把小文件分片打包成了大分片,減少了Map任務的數(shù)量,從而避免了頻繁開啟和關閉Map所帶來的時間損耗。MapFile的處理效率稍遜于CFIF,原因是CFIF在將多個小文件的分片打包到一個大分片時,充分考慮了數(shù)據(jù)的本地性原則,即盡量將同一節(jié)點的小文件打包,因此節(jié)省了數(shù)據(jù)在節(jié)點間傳輸所帶來的時間開銷,而MF方案在合并文件時沒有考慮這點。HAR與HDFS方案在運行效率上相當,雖然HAR將多個圖像小文件打包作為MapReduce的輸入,但HAR沒有對文件進行合并,MapReduce還是給每個圖像小文件各啟動了一個Map任務,這并沒有減少總的Map任務數(shù),因此HAR并不比HDFS在運行效率上有效。

圖4 NameNode內(nèi)存占用率對比
由圖4可見,在NameNode內(nèi)存消耗方面,HAR、MF和CFIF遠低于HDFS,這是因為HDFS集群下每一個小文件均占據(jù)一個Block,海量的小文件會消耗大量NameNode內(nèi)存。從圖中還可以看出,HAR、MF和CFIF的表現(xiàn)不相上下,原因是三者通過對文件打包及合并,減少了元數(shù)據(jù)所占用的NameNode內(nèi)存空間,因此NameNode內(nèi)存消耗不明顯。
文中深入分析了Hadoop存儲及處理海量小文件時所引發(fā)的性能問題,并對現(xiàn)有的幾種解決方案的缺點進行了詳細的分析。在此基礎上,采用CFIF抽象類將多個小文件分片打包到大分片中,以減少Map任務的啟動數(shù)量,從而提高處理海量小文件的效率。對CFIF抽象類給出了具體實現(xiàn),并通過實驗與常規(guī)HDFS、HAR和MF方案在NameNode內(nèi)存空間和運行效率方面進行了對比。實驗結果表明,CFIF在NameNode內(nèi)存占用率和運行效率方面都有很好的表現(xiàn)。