天天看點

Hadoop專業解決方案-第5章 開發可靠的MapReduce應用

本章主要内容:

1、利用mrunit建立mapreduce的單元測試。

2、mapreduce應用的本地執行個體。

3、了解mapreduce的調試。

4、利用mapreduce防禦式程式設計。

在wox.com下載下傳本章源代碼

到目前為止,你應該對mapreduce體系結構,應用程式設計,和定制mapreduce擴充程式很熟悉了。本章讨論如何對測試程式通過有影響力(leveraging)的單元測試和基于hadoop的裝置建立可靠的mapreduce代碼。你也可以從中學到不同的防禦式程式設計技術來處理部分被破壞的(corrupted)資料的方法。

mapreduce應用的單元測試

缺陷(bugs)總在代碼中存在,這是一個不争的事實——你寫的代碼越多,你也會遇到越多的缺陷(bugs)。即使是最偉大的程式員也極少寫沒有缺陷(bug)的代碼。這就是為什麼測試成為了代碼開發中完整中的(integral)一部分,是以許多開發人員越來越傾向于測試性驅動的開發(test-driven development,tdd)。

注:這裡所讨論的mrunit和它在mapreduce工作的單元測試中的使用是由諾基亞的同僚michael spicuzza富有創造性地實作的。

測試性驅動的程式開發(test-driven development)

測試性驅動程式開發是一種基于開發實際代碼的同時編寫自動化測試代碼的程式設計技術。這保證了你即時的測試你的代碼,并保證你快速而容易地重複測試你的代碼,因為這個過程是自動的。

測試性驅動程式開發涉及到了如下簡短、重複的開發流程:

1、在寫任何代碼之前,你都要首先寫它的自動化測試代碼。當你寫自動化測試代碼的時候,你需要考慮到他所有可能的輸入,錯誤,以及輸出。這樣,你需要在實際編寫代碼前設計你的代碼習慣

2、第一次運作你的測試代碼,這個測試應該是失敗的——表明代碼還沒有準備好。

3、然後,你應該開始程式設計。因為這裡已經有測試代碼了,隻要這個代碼仍舊失敗,那麼就意味着還沒有準備好。這個代碼可以一直修複直到它通過所有的斷言為止。

4、一旦這個代碼通過了這個測試,然後通過重構,你可以将它清除幹淨。隻要這個代碼仍舊能通過測試,它意味着它仍舊能工作。你不必當心改變會引起新的缺陷(bugs)。

5、在其他的方法和程式中重新開始整個事情。

測試性驅動程式設計的一個基石是單元測試。盡管通過利用足夠多的模拟類,利用junit可以從技術上測試大量的mapreduce應用程式,這裡仍有一個可以選擇的方法能夠提供可以增加的覆寫水準。mrunit是專門為hadoop服務的測試架構。它是從包含對hadoop的cloudera的分布式開源實作開始的,它現在是apache的一個子項目。mrunit是基于junit的,并允許單元測試成為映射式(mappers),簡化式(reducers),和其他mapper-reducer交叉的交叉性測試,伴随着合并,客戶計數,和拆分。

如第三章的解釋,eclipse提供了mapreduce的一個非常好的開發工具。在這裡的讨論中,你能學會如何建立包含了mapreduce程式依賴的所有需求的pom.xml檔案。eclipse也提供了基于mrunit的單元測試平台。

為了利用mrunit,你應該繼承在第三章中增加的mrunit依賴的标準mapreudce maven中的pom檔案,如清單5-1所示:

注:mrunit的jar檔案,和,是以,maven的依賴項,有如下兩個版本:

mrunit-0.9.0-incubating-hadoop1.jar 是hadoop的mapreduce的第一個版本,而mrunit-0.9.0-incubating-hadoop2.jar是工作在hadoop的mapreudce新的版本下的。這個新版本是指從cloudere的cdh 4而來的hadoop-2.0版本。

       有了這個工具,你可以實作包含mapreduce應用的主要元素的單元測試。第三章的單詞計數的執行個體在這裡用作測試的執行個體。這意味着這個執行個體中的mapper和reducer會作為參數傳遞給測試程式。

測試mappers

使用mrunit測試mappers非常直接,代碼清單5-2非常明顯的展示了其特性。

注:mrunit支援老的(從mapred包而來)和新的(從mapreduce包而來)mapreduce apis。當測試你的代碼時,注意保證應用合适的mapdriver對象的執行個體。相同的reducedriver和mapreducedriver對象,在以後的章節中描述。

寫一個基于mrunit的測試單元非常簡單。這種簡單的标志性的加強是基于流利的api風格的。為了寫你的測試程式,你需要做如下的事情:

1.在測試map程式中安裝mapdriver類作為map确切的參數。

2.通過使用withmapper調用來增加一個你正在測試的執行個體。第三章單詞計數的程式的mapper程式應用在了這裡。

3. 你可以使用一個可選的withconfiguration方法,将所需的配置傳遞給mapper。

4. 該withinput調用,你可以通過所需的鍵和值 - 在這種情況下,用一個任意值,并且包含一個長文本對象如“貓,貓,狗“

5. 使用withoutput調用中得到預計輸出。在這個例子中,是“貓”,“貓”和“狗”三個文本對象與其出現的相應i的intwritable值 – 所有的值都為1。

6. 如果一個mapper遞增計數器可選。 withcounter (組名稱, expectedvalue ) (清單5-2中沒有顯示),使你能夠指定計數器的期望值。

7.最後一次調用,runtest,回報進入mapper中指定的輸入值,比較實際的輸出和通過withoutput方法得到的期望輸出值。

對于mapdriver類的缺陷是對于每個側是你最後隻能有單個的輸入和輸出。如果你想,你可以調用withinput和withoutput多次,是以,你在任何時間隻是測試一組輸入/輸出。為了指定多個輸入,你必須使用mapreducerdriver對象(将在這章稍後介紹)。

reducers 測試

       測試reducer和測試mapper是一樣的。可以參看清單5-3:

清單5-3

@test

public void testreducer() throws exception {

list<intwritable> values = new arraylist<intwritable>();

values.add(new intwritable(1));

new reducedriver<text, intwritable, text, intwritable>()

.withreducer(new wordcount.reduce())

.withconfiguration(new configuration())

.withinput(new text("cat"), values)

.withoutput(new text("cat"), new intwritable(2))

.runtest();

}

以下是這段代碼詳細的解釋:

reducer被建立的時候一個intwritable對象清單作為輸入對象。

一個reducerdriver需要被執行個體化。如同mapprtdriver一樣,這也真是作為參數在reducer中測試。

你想要的一個reducer的執行個體是通過withreducer調用的。這個在第三章的word count執行個體中使用的執行個體将在這裡使用。

一個可選的withconfiguration方法

withinput調用允許你傳遞輸入之值給reducer。這裡,你傳遞“cat”健和在一開始通過intwritable建立的清單。

你可以通過withoutput指定希望的輸出結果。這裡,你指定相同的健“cat”和intwritable代替單詞“cat”的個數。

如果一個reducer是一個遞增的計數器,一個可選的計數組合(組,名,期待值)(在5-3清單中未列出)可以讓你指定希望得到的計數值。

最後,你調用runtest,其中回報了reducer的指定輸出,并和期望輸出作對比。

reducerdricver和mapperdriver存在相同的限制,不能接受超過一個的輸入/輸出對。

到目前為止,這一章節已經向你展示了如何分開測試mapper和reducer的方法,但是,也可能需要一起對它們進行交叉測試。你可以利用mapreducedriver類來實作。mapreducedriver類也被用來測試聯合使用的問題。

交叉測試

mrunit提供了mapreducerdriver類來讓你測試mapper和reducer共同使用的情況。mapreducerdriver類不同于mapperdriver和reducerdriver類被參數化。首先,你參數化mapper類的輸入和輸出類型,然後是reducer的輸入和輸出類型。因為mapper的輸出類型通常是和reducer的輸入類型互相比對的,你最終得到三對參數對。補充一下,你可以提供多組的輸入和指定多組的期望輸出。清單5-4列出了一些執行個體代碼:

清單5-4 一起測試mapper和reducer

public void testmapreduce() throws exception {

new mapreducedriver<longwritable, text, text, intwritable, text,

intwritable>()

.withmapper(new wordcount.map())

.withinput(new longwritable(1), new text("dog cat dog"))

.withinput(new longwritable(2), new text("cat mouse"))

.withoutput(new text("dog"), new intwritable(2))

.withoutput(new text("mouse"), new intwritable(1))

正如你在上面的代碼中所看到的,這個安裝程式和mapdriver/reducedriver用到的類是很相似的。你傳遞執行個體執行個體到mapper和reducer中。(第三章涉及到的單詞計數的執行個體在這裡也用到了。)可選的是,你可以利用withconfiguration和withcombiner來測試配置和需要的合并。

mapreducedrive類讓你能夠傳遞多個不同的鍵值。這裡,你傳遞兩個記錄——第一個含有一個lonwritable的随意值和以惡文本對象包含一行“dog cat dog”,第二個longwritable對象包含一個任意值和一個文本對象包含一行“cat mouse”。

你也可以利用withoutput方法指定一個期望的輸出。這裡,你指定三個關鍵值——“cat”,“dog”,”mouse”——伴随着一緻的計數2,2,和1.最後,如果mapper/reducer 是一個遞增的計數器,一個可選的是,withcounter(group,name,experctedvalue)(在清單5-4中沒有列出來)可以讓你指定這個計數器期望的值。

如果一個測試失敗了,mrunit會産生一個和清當5-5相類似的指定輸出,告訴你出現了什麼錯誤。

清單5-5:mrunit不成功時的輸出結果

13/01/05 09:56:30 error mrunit.testdriver: missing expected output (mouse, 2)

at position 2.

13/01/05 09:56:30 error mrunit.testdriver: received unexpected output (mouse,

1)   at position 2.

如果測試結果是成功的,你會活得一個小小的自信,mapper和reducer協同工作是成功的。

盡管mrunit使mapper和reducer代碼的單元測試變得簡單了,在這裡涉及的mapper和reducer執行個體是比較簡單的。如果你的map和/或者reduce代碼開始變得很複雜,從hadoop架構獲得支援分開處理,并單獨測試業務邏輯是一個好的設計方法(也就是說,需要應用程式定制)。就像在交叉測試中使用mapreducedriver一樣,在你不再測試你的代碼的時候也是很容易得到一個點的,而不是已經做了這件事的hadoop架構本身。

這裡所設計的單元測試是一種典型的在實作發現bugs的方法,但是這些測試不會測試基于hadoop的已經完成的mapreduce任務。本地任務運作,在下一節中描述,能讓你在本地運作hadoop程式,在一個java虛拟機中,使如果mapreduce任務失敗了更加容易調試。

用eclipse進行本地程式測試

利用eclipse進行hadoop開發提供了運作完整的mapreduce本地應用程式的能力——在一個單例模式下。hadoop分布式(hadoop-core)伴随着本地任務遠行,能讓你在本地計算機上運作hadoop,在單個的jvm中。在這種情況下,你能在map或者reduce方法内部設定斷點,利用eclipse調試器,和單步執行代碼來檢驗程式的錯誤。

在本地的eclipse中運作mapreduce程式不許要一些特别的配置或者設定。如圖5-1,隻需要右鍵類,選擇run as(或者debug as)再選擇java application就行了。

圖5-1:在本地eclipse中運作mapreduce程式

注意:盡管一個本地的任務執行可以運作完整的程式,但是它也很多限制。例如,它不能運作超過一個的reducer。(它不支援0reducer的情況。)通常,這不是問題,因為許多程式能夠隻用一個reducer執行。需要注意的問題是,即使你設定了多個reducer,本地任務也會忽略這些設定,并用單個的reducer。也要注意所有的本地mapper是順序執行的。

一個基于eclipse的本地可執行任務可以在linux和windows中執行。(如果在windows中使用mapreduce程式,你需要安裝cygwin。)預設情況下,一個本地運作的任務會使用本地的檔案系統進行讀和寫資料。

注意這個,預設情況,本地hadoop執行程式使用本地檔案系統。(如同在第二章中描述的,hdfs實作了對本地檔案系統提供支援。)這意味着,所有用于測試的資料都需要拷貝到本地,産生的結果也是本地的。

如果這個是不可選的,你可以配置本地運作程式去操作叢集資料(包括hbasse)。為了通過本地執行程式進入叢集,你必須要使用一個配置檔案,如清單5-6中所示:

清單5-6:hadoop通路叢集資料的配置檔案

<?xml version="1.0" encoding="utf-8"?>

<configuration>

<!-- hbase access -->

<property>

<name>hbase.zookeeper.quorum</name>

<value>comma separated list of zookeeper nodes</value>

</property>

<name>hbase.zookeeper.property.clientport</name>

<value>zookeeper port</value>

<!-- hdfs -->

<name>fs.default.name</name>

<value>hdfs://<url>/</value>

<!-- impersonation -->

<name>hadoop.job.ugi</name>

<value>hadoop, hadoop</value>

</configuration>

這個配置檔案定義了三個主要的元件——hbase的配置(定義了指向zookeeper的連接配接數),hdfs的配置(定義了hbase的url),和安全模拟(這需要如果你開發的機器和hadoop叢集屬于不同的安全域,或者在你本機和hadoop叢集中你使用不同的登陸名)。将這個配置檔案加到可執行的應用中使相當簡單的,如同清單5-7中所示。

清單5-7:加載叢集資訊的配置檔案

configuration.adddefaultresource("hadoop properiies");

configuration conf = new configuration();

盡管利用本地執行任務進行測試相對于單元測試來說會比較徹底,一個你必須記住的就是測試hadoop程式必須關注計算規模,不論你在本地運作多少次,直到你使用真實的資料測試代碼的時候,你都不會确定它是正确工作的。

實際上,許多測驗都不能被驗證,包括下面幾項:

1、 在程式運作的時候有多少的mapper被建立,資料在它們之間是如何被拆分的?

2、 多少真實的重洗和排序?是否必要去實作一個聯合器?是否一個駐記憶體的聯合器是可選的?

3、 是什麼樣的硬體/軟體/網絡環境?是否需要調整應用程式/叢集的參數?

這意味着為了保證應用程式正常工作,測試本地任務必須在hadoop叢集上用真實的資料測試。

mapreduce可執行程式的高并發性和它依賴于大量的資料,使得測試mapreduce代碼變得比較具有挑戰性。在下一節,你會學到如何利用hadoop的日志來加強hadoop執行程式的調試。

利用日志檔案測試hadoop

日志檔案被軟體工程廣泛的使用了,并包含如下幾個重要的目标:

1、 建立一個可執行的測試應用,例如,為了執行分析,或者得到潛在的提升。

2、 收集執行的各項名額,能夠用來進行實時的和事後的分析,并且能自動測試,錯誤校驗,等等。

mapreduce本身已經記錄了程式執行過程中的各項日志。本地的這些檔案是受hadoop的配置檔案控制的。預設情況下,它們存放在hadoop版本檔案夾下的logs子目錄下。對于單個程式最重要的日志檔案是tasktracker的日志。mapreduce任務抛出的任何異常資訊都會在這些日志檔案中記載。

這個log檔案目錄下還有一個userlogs的子目錄,它包含了每個任務的日志。每個任務都會記錄它的stdout和stderr資訊到在這個目錄下的這兩個檔案中。每一個應用指定的日志資訊包括使用者的代碼也存放在這些檔案裡。在一個多節點的hadoop叢集上,這些日志檔案沒有集中彙總,你必須檢視每個節點下的logs/userlosgs目錄。

一個通路一個任務的所有日志的友善的方法是通過jobtracker的網頁。如圖5-2所示。它能讓你看到這個任務的mappers和reducers的所有日志。

圖5-2 任務網頁

所有任務的日志資訊可以通過tasktracker的任務網頁進入(任務的設定和清除日志,也包括mapper和reducer的日志一緻性網頁)。從這些頁面,你可以導航到任務的配置頁面,如圖5-3所示.

圖5-3 任務配置頁面

這個任務配置檔案包含了配置對象的文本。如果你使用了大量的自己寫的配置檔案(例如,當你從你的驅動器傳遞參數到mappers和reducers的時候)。這些頁面允許你配置他們期望的值。

另外,任務的安裝和清理日志,包括mapper和reducer的頁面,被連結到統一的日志檔案頁面。如圖5-4所示,日志檔案包括stdout,stderr和syslog三個日志檔案。

圖5-4 map的日志檔案

任何應用程式指定的日志都應該在這個頁面上顯示。因為這個頁面可以實時的重新整理。它可以有效的檢視單個執行任務的程序(假設你的日志檔案記錄了合适的資訊)。

通過使用mapreduce架構來運作使用者提供的調試腳本會使日志運作的更加有效。這些使用者指定的日志資訊是對于問題資訊更加重要的記載。這些腳本允許從任務的輸出檔案(stdout和stderr),系統日檔案,和任務配置檔案中挖掘資料。這些從腳本的标準輸出檔案的到的檔案可以利用任務提供的接口來使用。

你可以對失敗的任務提供map和reduce分開的腳本。你可以通過對mapred.map.task.debug.script(為了調試map任務)和mapred.reduce.task.debug.script(為了調試reduce任務)屬性設定合适的值來送出調試腳本。你可以通過api來設定這些屬性。對于這些腳本的參數就是任務的stdout,stderr,syslog和jobconf檔案。

當決定什麼樣的東西需要在你的日志檔案中出現,使你的決定在一個有目的的情況下進行。對于調試日志檔案有如下幾點建議:

1. 異常或者錯誤代碼資訊應該一直輸出異常資訊。

2. 任何不期望的變量的值(例如,空值)應該在執行的過程中記錄日志。

3. 不可預料的執行路徑應該記錄日志。

4. 如果異常是發生在被包含的裡面的,那麼在主函數塊中應該記錄相關日志。

5. 太多的日志檔案反而使日志無效。盡量使相關的資訊放在同一個日志檔案中。

盡管利用jobtracker可以很友善的檢視指定任務的日志檔案,但它不事後自動記錄日志和挖掘資料。下一節将描述适合自動記錄日志的方法。

進行中的程式日志

你可以使用廣泛的方法來解決日志檔案的問題,利用指定的軟體(例如,适合hadoopops的splunk)和一個定制的日志處理程式。為了實作定制的日子處理程式,所有map和reduce産生的日志檔案都應該集中到一個檔案總來。你可利用清單5-8(代碼檔案:hadoopjoblogscraper類)所展示的那樣來處理,它允許你将所有相關的任務的日志集中起來,并将它們存放到單個的檔案中去。

注意:這個解決方案是一個叫dmitry mikhelson的諾基亞同僚提供的。

listing 5-8: simple log screen scraper

public class hadoopjoblogscraper{

private string _trackerurl = null;

public static void main(string[] args) throws ioexception{

if (args.length != 2){

system.err.println("usage: <jobtracker url>, <job id>");

string jobid = args[1];

string trackerurl = args[0];

hadoopjoblogscraper scraper = new hadoopjoblogscraper(trackerurl);

scraper.scrape(jobid, jobtype.map);

scraper.scrape(jobid, jobtype.reduce);

system.out.println("done");

public enum jobtype{

map("map"), reduce("reduce");

private string urlname;

private jobtype(string urlname){

this.urlname = urlname;

public string geturlname(){

return urlname;

private pattern taskdetailsurlpattern = pattern.compile("<a

href=\"(taskdetails\\.jsp.*?)\">(.*?)</a>");

private pattern logurlpattern = pattern.compile("<a

href=\"([^\"]*)\">all</a>");

public hadoopjoblogscraper (string trackerurl){

_trackerurl = trackerurl;

public void scrape(string jobid, jobtype type) throws ioexception{

system.out.println("scraping " + jobid + " - " + type);

string jobtasksurl = _trackerurl + "/jobtasks.jsp?jobid=" + jobid +

"&type=" + type.geturlname() + "&pagenum=1";

string jobtaskshtml = ioutils.tostring(new

url(jobtasksurl).openstream());

matcher taskdetailsurlmatcher =

taskdetailsurlpattern.matcher(jobtaskshtml);

file dir = new file(jobid);

if (!dir.exists()){

dir.mkdir();

file outfile = new file(dir, type.geturlname());

bufferedwriter out = new bufferedwriter(new filewriter(outfile));

while (taskdetailsurlmatcher.find()){

out.write(taskdetailsurlmatcher.group(2) + ":\n");

string taskdetailsurl = new string(_trackerurl + "/" +

taskdetailsurlmatcher.group(1));

string taskdetailshtml = ioutils.tostring(new

url(taskdetailsurl).openstream());

matcher logurlmatcher = logurlpattern.matcher(taskdetailshtml);

while (logurlmatcher.find()){

string logurl = logurlmatcher.group(1) +

"&plaintext=true&filter=stdout";

out.write(ioutils.tostring(new url(logurl).openstream()));

out.flush();

out.close();

注意:這個解決方案是基于screen scraping的,也是繼承自unreliable的。在頁面上的任何改動都有可能打斷這個實作。

從一個作業監控url和作業的id通過主方法對這個作業建立螢幕抓取。讓後利用它從mapper和reducer中抓取日志檔案。這個抓取程式利用正規表達式來解析所有的mapper和reducer頁面,讀取這些頁面,并列印出他們的文本。

日志不是擷取mapreduce執行情況的唯一方式。接下來,你可以找到另外一種方式來擷取執行情況-工作計數器。

利用工作計數器進行報表度量

另外一個hadoop指定的調試和測試的方法是利用定制度量-工作計數器。正如第三章的描述,計數器是在hadoop中輕量級的對象可以讓你追蹤你感興趣的事件在map和reduce中。

mapreduce自身記錄了它每次運作的度量計數器,包括輸入的記錄數由mapper和reducer提供的,它從hdfs中讀取的或者寫入的位元組數,等等。因為工作頁面(圖 5-1)自動更新(通過預設值,每30秒),這個計數器能夠用來追蹤執行的情況。這個計數器也能被用作,例如,所有輸入的記錄都被讀取和處理了。

表5-1展示了分組名和計數器名包含在了目前hadoop支援的個人組别中。

table 5-1: hadoop’s built-in counters

group name counter name

org.apache.hadoop.mapred.task$counter

map_input_records

map_output_records

map_skipped_records

map_input_bytes

map_output_bytes

combine_input_records

combine_output_records

reduce_input_groups

reduce_shuffle_bytes

reduce_input_records

reduce_output_records

reduce_skipped_groups

reduce_skipped_records

org.apache.hadoop.mapred.jobinprogress$counter

total_launched_maps

rack_local_maps

data_local_maps

total_launched_reduces

filesystemcounters

file_bytes_read

hdfs_bytes_read

file_bytes_written

hdfs_bytes_written

你可以通過這些計數器來獲得更多任務執行情況的資訊——例如,mapper的input/outpu計數(map_input_records/map_input_records),hdfs讀寫的位元組數(hdfs_bytes_read/ hdfs_bytes_written),等等。補充一下,你可定制應用程式的計數器-指定值-例如,中間計算的數值,或者代碼分支的數量(可以在以後程式的測試和調試過程中有用)。

給mapper和reducer類傳遞的文本對象可以被用來更新計數器。相同的計數器變量(基于名稱)被所有的mapper和reducer執行個體,并且通過叢集的master節點合并計數,是以在這種方法下他們是“線程安全的”。5-9清單中展示了簡單的代碼片段顯示如何來建立和使用定制計數器。

listing 5-9: updating counters

……………………………………………………………………………

private static string countergroup = "debuggroup";

private static string debug1 = "debug1";

………………………………………………………………

context.getcounter(countergroup, debug1).increment(1);

在清單5-9中,如果這是第一次使用計數器,合适的計數對象會被建立為初始值為0。

每個工作的計數器個數

計數器存放在jobtracker中,這意味着如果一個工作嘗試着建立一百萬的計數器,jobtracker将會生成“超過記憶體空間”的錯誤。(參考第三章mapreduce推薦的設計。)為了避免這個錯誤,每一個工作可以建立的計數器個數被hadoop架構所限制。

這裡是hadoop 1.0的計數器類的一些代碼片段:

/** limit on the size of the name of the group **/

private static final int group_name_limit = 128;

/** limit on the size of the counter name **/

private static final int counter_name_limit = 64;

private static final jobconf conf = new jobconf();

/** limit on counters **/

public static int max_counter_limit =

conf.getint("mapreduce.job.counters.limit", 120);

/** the max groups allowed **/

static final int max_group_limit = 50;

注意每個計數器組是沒有配置的,然而計數器是配置的(在基本的cluster-wide中)。

在hadoop 2.0中,所有的參數都是配置的。下面是mrjobconfig類的代碼片段:

public static final string counters_max_key =

"mapreduce.job.counters.max";

public static final int counters_max_default = 120;

public static final string counter_group_name_max_key =

"mapreduce.job.counters.group.name.max";

public static final int counter_group_name_max_default = 128;

public static final string counter_name_max_key =

"mapreduce.job.counters.counter.name.max";

public static final int counter_name_max_default = 64;

public static final string counter_groups_max_key =

"mapreduce.job.counters.groups.max";

public static final int counter_groups_max_default = 50;

如果一個工作嘗試建立比指定的更多地計數器,如下的一個異常将會在運作的時候抛出:

org.apache.hadoop.mapred.counters$countersexceededexception: error:

exceeded limits on number of counters - counters=xxx limit=xxx

定制的計數器可以通過jobtracker的配置頁面來指定(如圖5-2),表5-10展示了簡單的代碼片段顯示了如何列印出計數器的文本無論是運作結束的還是開始的工作。

listing 5-10: printing job’s counters

// now lets get the counters put them in order by job_id and then print

// them out.

counters c = job.getcounters();

// now walk through counters adding them to a sorted list.

iterator<countergroup> i = c.iterator();

while (i.hasnext()){

countergroup cg = i.next();

system.out.println("counter group =:"+cg.getname());

iterator<counter> j = cg.iterator();

while (j.hasnext()){

counter cnt = j.next();

system.out.println("\tcounter: "+cnt.getname()+

"=:"+cnt.getvalue());

本章中介紹的日志檔案和計數器都提供了工作任務執行的概況的資訊。它們對于你查找那裡出現了問題時有力的工具。它們被用來測試和調試使用者代碼,不幸的是,即使是完全正确的hadoop應用程式也可能失敗因為資料的中斷。防禦式程式設計幫助我們提供能夠部分應對中斷的方法。

在mapreduce中的防禦式程式設計

應為hadoop工作在一個大量資料輸入的環境中(許多資料可以中斷),經常的殺掉一個工作程序每次mapper不能處理輸入的資料時或者應為資料本身的中斷,或者因為map函數的bug(例如,在第三方庫中,源代碼是不可見的)。在這種情況下,一個标準的hadoop恢複機制将會非常有幫助。不論你多少次嘗試着閱讀壞的記錄,最後的結果将是相同的-map執行程式将會失敗。

如果一個應用程式可以接受略過某些資料,像這樣正确執行解決方案進而使整個應用程式更加穩健和可維護。不幸的是,這樣的一個應用程式不是普通的一個應用程式。在這種情況下,一個異常可能發生在reader負責讀取資料的過程,也可能發生在mapper處理資料的過程。想正确的處理這個情況需要如下的幾點要求:

1、 注意在讀取資料時候的錯誤,讓所有讀取時候的錯誤都能正确處理,然後檔案指針移動到下一個位置。

2、 一種向mapper發送reader錯誤資訊的機制保證mapper能正确輸出資訊。

3、 注意在mapper中的錯誤資訊,保證所有的錯誤都能正确的處理。

4、 一個定制的outputformat(類似于第四章描述的内容)能夠将錯誤資訊輸出到一個錯誤字典中。

幸運的是,hadoop允許你實作一個單純的應用提供一個能略過一些記錄當你确信它會引起任務中斷的時候。如果這個略過模式開啟的話,一個任務在這種模式下會被嘗試執行多次。一旦在這種模式下,tasktrcker決定那條記錄引起這個失敗。tasktracker然後重新開機這個任務,但是會略過這些壞的記錄。

應用程式可以通過skipbadrecords類來控制這種特性,這個類提供了許多靜态方法。工作驅動必須調用如下一個或者兩個方法來打開map和reduce任務的略過記錄功能:

setmappermaxskiprecords(configuration conf,long maxskiprecs)

setreducermaxskipgroups(configuration conf,long maxskipgrps)

如果最大略過數設定為0(預設情況),略過記錄功能不可用。略過數依賴于程式中計數器自增記錄數。你應當在每次記錄被處理之後來增加計數器。如果這個不能做到(許多程式會分開來進行處理),這個架構可能會圍繞壞記錄來增加記錄。

hadoop利用分治法找到需要略過的記錄,它每次分開執行這個有略過的任務,并決定另外一半包含壞的記錄數。這個過程會疊代進行直到略過的範圍在可接受的之内。這是一個相當耗費資源的操作,尤其是略過的最大值非常小的時候。它可能需要必要的增加最大值的設定來讓正常的hadoop任務恢複機制接受額外的嘗試。

如果略過功能是開啟的,當任務失敗的時候任務會向tasktracker報告正在處理的傳回記錄,然後tasktracker會再次嘗試這個任務,并略過引起失敗的記錄。由于網絡故障或者是對錯誤記錄處理的失敗,略過模式會再任務兩次錯誤之後開啟。

對于在一個記錄中一緻性失敗的任務,tasktracker運作多次任務嘗試如下的結果:

1、 沒有指定動作的失敗的嘗試(兩次)。

2、 被tasktracker存儲的失敗的記錄的失敗的嘗試。

3、 在新的略過壞的記錄失敗的情況下,利用skipbadrecords類中的setattmptstostartskippint(int attemps)方法你能修改任務失敗記錄的數量來觸發略過模式。hadoop會将略過的記錄存儲在hdfs中,以便以後的分析使用。他們在_log/skip檔案下以序列的方式寫入。

總結

這章讨論了建造可靠的mapreduce應用程式的标準。你學到了如何利用有利的mrunit元件來測試mapreduce應用程式的方法,并利用本地的hadoop工作任務來調試已經完成的應用程式。你也學到了利用日志和程式計數器來檢視mapreduce的執行情況。最後,你學到了如何設計,實作,和調試mapreduce,第六章讨論如何利用apache oozie将mapreduce程式結合在一起。