Hadoop-Streaming實戰經驗及問題解決方法總結

看到一篇不錯的Hadoop-Streaming實戰經驗的文章,裏面有大部分的情景都是自己實戰中曾經遇到過的。特意轉載過來,感謝有心人的總結。

目錄

  1. Join操作分清join的類型很重要…

  2. 啓動程序中key字段和partition字段的設定…

  3. 控制hadoop程序內存的方法…

  4. 對於數字key的排序問題…

  5. 在mapper中獲取map_input_file環境變量的方法…

  6. 運行過程中記錄數據的方法…

  7. 多次運行Hadoop之是否成功的判斷…

  8. 對stdin讀取的 line的預處理…

  9. Python字符串的連接方法…

  10. 怎樣查看mapper程序的輸出…

  11. SHELL腳本中變量名的命名方法…

  12. 提前設計好流程能簡化很多重複工作…

  13. 其他一些實用經驗…

1. Join操作分清join的類型很重要

Join操作是hadoop計算中非常常見的需求,它要求將兩個不同數據源的數據根據一個或多個key字段連接成一個合併數據輸出,由於key字段數據的特殊性,導致join分成三種類型,處理方法各有不同,如果一個key在數據中可以重複,則記該數據源爲N類型,如果只能出現一次,則記爲1類型。

1) 類型1-1的join

比如(學號,姓名)和(學號,班級)兩個數據集根據學號字段進行join,因爲同一個學號只能指向單個名字和單個班級,所以爲1-1類型,處理方法是map階段加上標記後,reduce階段接收到的數據是每兩個一個分組,這樣的話只需要讀取第一行,將非key字段連到第二行後面即可。

每個學號輸出數據:1*1=1個

2) 類型1-N或者N-1的join

比如(學號,姓名)和(學號,選修的課程)兩個數據集根據學號字段的join,由於第二個數據源的數據中每個學號會對應很多的課程,所以爲1-N類型join,處理方法是map階段給第一個數據源(類型1)加上標記爲1,第二個數據源加上標記爲2。這樣的話reduce階段收到的數據以標記爲1的行分組,同時每組行數會大於2,join方法是先讀取標記1的行,記錄其非key字段Field Value 1,然後往下遍歷,每次遇到標記2的行都將Field Value 1添加到該行的末尾並輸出。

每個學號輸出數據:1*N=N*1=N個

3) 類型M-N的join

比如(學號,選修的課程)和(學號,喜歡的水果)根據學號字段做join,由於每個數據源的單個學號都會對應多個相應數據,所以爲M*N類型。處理方法是map階段給數據源小的加上標記1(目的是reduce階段的節省內存),給數據源大的加上標記2,reduce階段每個分組會有M*N行,並且標記1的全部在標記2的前面。Join方法是先初始化一個空數組,遇到標記1的行時,將非key數據都記錄在數組中,然後遇到標記2的行時,將數組中的數據添加在該行之後輸出。

每個學號輸出數據:M*N個

關於join,本博主也寫過不少文章啦。 
http://blog.csdn.net/bitcarmanlee/article/details/51694101 是介紹Hive join數據傾斜。 
http://blog.csdn.net/bitcarmanlee/article/details/51863358是介紹自己用MR實現join操作的,有興趣的同學可以自行查看。

2. 啓動程序中key字段和partition字段的設定

在join計算過程中,有兩個字段非常的重要並需要對其理解,就是排序字段key和分區字段partition的指定。

字段 字段說明

num.key.fields.for.partition

用於分區,隻影響數據被分發到哪個reduce機器,但不影響排序

stream.num.map.output.key.fields

Key的意思就是主鍵,這個主鍵會影響到數據根據前幾列的排序
org.apache.hadoop.mapred.lib.KeyFieldBasedPartitioner 如果需要對字段排序、分區,默認都得加上此設置

上面三個配置尤其會影響到join計算時的配置:

1) 如果是單key的join,因爲要加上標記字段排序,所以設定key=2,同時設定partition=1對第一個字段分區來保證同Key的數據都在同一臺機器上;

2) 如果是N個聯合key的join,首先需要加上標記字段,所以設定key=N+1,用來對其進行排序,然後需要partition爲N來對其按key分區。

關於上述參數的使用,本博主也寫過相關的文章 
http://blog.csdn.net/bitcarmanlee/article/details/51881699 
同學們可以參考。

3. 控制hadoop程序內存的方法

Hadoop程序是針對海量數據的,因此任何一個保存變量的操作都會在內存中造成N倍的存儲,如果嘗試用一個數組記錄每一行或某些行的單個字段,用不到程序運行結束,hadoop平臺就會爆出137內存超出的錯誤而被kill掉。

控制內存的方法就是少用變量、尤其數組來記錄數據,最終實現當前行的處理與數據總規模的無關,彙總、M*N的join等處理不得不記錄歷史數據,對這種處理要做到用後及時釋放,同時儘量記錄在單變量而不是數組中,比如彙總計算可以每次記錄累加值,而不是先記錄所有的元素最後才彙總。

注:這個技巧很實用。其實不光是hadoop streaming裏,用Java寫原生MR的時候也需要要注意這個點。

4. 對於數字key的排序問題

如果不加以處理,排序處理過程中數字1會排在10之後,處理方法是需要在數字前面補0,比如如果全部有2位,就將個位數補1個零,讓01和10比較,最終reduce輸出的時候,再轉回來,需要先預測數字的位數。 
在mapper.py中:

Print ‘%010d\t%s’%(int(key),value)

其中key既然是數字,就需要用數字的格式化輸出%010d表示將輸出10位的字符串,如果不夠10位,前面補0。 
在reducer.py中,最終輸出時,使用轉int的方法去掉前面的0:

Print ‘%d\t%s’%(int(key),value)

5.在mapper中獲取map_input_file環境變量的方法

在mapper中,有時候爲了區分不同的數據文件來源,這時候可以用map_input_file變量來記錄當前正在處理的腳本的文件路徑。以下是兩種判別方法:

a) 用文件名判斷

Import os

filepath = os.environ[“map_input_file”]
filename = os.path.split(filepath)[-1]

if filename==”filename1”:

\#process 1

elif filename==”filename2”:

\#process2
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

b) 用文件路徑是否包含確定字符串判斷

filepath = os.environ[“map_input_file”]

if filepath.find(sys.argv[2])!=-1:

\#process
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

本博主也寫過一遍文章來闡述這個問題:http://blog.csdn.net/bitcarmanlee/article/details/51735053

6.運行過程中記錄數據的方法

Hadoop程序不同於本地程序的調試方法,可以使用錯誤日誌來查看錯誤信息,提交任務前也可以在本地用cat input | mapper.py | sort | reducer.py > output這種方法來先過濾基本的錯誤,在運行過程中也可以通過以下方法記錄信息:

1) 可以直接將信息輸出到std output,程序運行結束後,需要手工篩選記錄的數據,或者用awk直接查看,但是會污染結果數據

2) 大多采用的是用錯誤輸出的方法,這樣運行後可以在stderr日誌裏面查看自己輸出的數據:sys.stderr.write(‘filename:%s\t’%(filename))

7. 多次運行Hadoop之是否成功的判斷

如果要運行多次的hadoop計算,並且前一次的計算結果是下一次計算的輸入,那麼如果上一次計算失敗了,下一次很明顯不需要啓動計算。因此在shell文件中可以通過$?來判斷上一次是否運行成功,示例代碼

if [ $? –ne 0 ];then
   exit 1
fi
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

很常見很實用的技巧,不解釋。

8. 對stdin讀取的 line的預處理

Mapper和reducer程序都是從標準輸入讀取數據的,然而如果直接進行split會發現最後一個字段後面跟了個’\n’,解決方法有兩種:

1)  datas = line[:-1].split(‘\t’)

2)  datas=line.strip().split(‘\t’)
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

第一種方法直接去除最後一個字符\n,然後split,第二種方法是去除行兩邊的空格 (包括換行),然後split。個人喜歡用第二種,因爲我不確定是否所有行都是\n結尾的,但是有些數據兩邊會有空格,如果strip掉的話就會傷害數據,所以可以根據情景選用。

基本上每個streaming代碼都能見到的處理技巧,不解釋。

9. Python字符串的連接方法

Mapper和reducer的輸出或者中間的處理經常需要將不同類型的字符串結合在一起,python中實現字符串連接的方法有格式化輸出、字符串連接(加號)和join操作(需要將每個字段轉化成字符類型)。

使用格式化輸出:’%d\t%s%(inti,str)
使用字符串的+號進行連接:’%d\t’%i+’\t’.join(list)
寫成元祖的\t的Join:’\t’.join((‘%d%i, ‘\t’.join(list)))
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3

10. 怎樣查看mapper程序的輸出

一般來說,mapper程序經過處理後,會經過排序然後partition給不同的reducer來做下一步的處理,然而在開發過程中常常需要查看當前的mapper輸出是否是預期的結果,對其輸出的查看有兩種需求。

需求一,查看mapper的直接輸出:

在運行腳本中,不設定-reducer參數,也就是沒有reducer程序,然後把-D mapred.reduce.tasks=0,即不需要任何reduce的處理,但是同時要設定-output選項,這樣的話,在output的目錄中會看到每個mapper機器輸出的一個文件,就是mapper程序的直接輸出。

需求二,查看mapper的輸出被partition並排序後的內容,即reducer的輸入是什麼樣子:在運行腳本中,不設定-reducer參數,也就是沒有自己的reducer程序,然後把-D mapred.reduce.tasks=1或者更大的值,即有reduce機器,但是沒有reducer程序,hadoop會認爲有reducer是存在的,因此會繼續對mapper的輸出調用shuffle打亂和sort操作,這樣的話就在output目錄下面看到了reducer的輸入文件,並且數目等於reducer設定的tasks個數。

此技巧也特別常見特別實用。尤其是調試階段,有時候找不出問題在哪,試着將mapper階段單獨輸出,往往能收到奇效。

11. SHELL腳本中變量名的命名方法

如果遇到很多的輸入數據源和很多輸出的中間結果,每個hadoop的輸出都會用到下一步的輸入,並且該人物也用到了其他的輸出,這樣的話最好在一個統一的shell配置文件中配置所有的文件路徑名字,同時一定避免InputDir1、InputDir2這樣的命名方法,變量命名是一種功力,一定要多練直觀並且顯而易見,這樣隨着程序規模的增加不會變的越來越亂。

12. 提前設計好流程能簡化很多重複工作

近期自己接到一個較爲複雜的hadoop數據處理流程,大大小小的處理估算的話得十幾個hadoop任務才能完成,不過幸好沒有直接開始寫代碼,而是把這些任務統一整理了一下,最後竟然發現很多個問題可以直接合併成一類代碼處理,過程中同時將整個任務拆分成了很多小任務並列了個順序,然後挨個解決小任務非常的快。Hadoop處理流程中如果任務之間錯綜複雜並相互依賴對方的處理結果,都需要事先設計好處理流程再開始事先。

13. 其他一些實用經驗

1) Mapper和reducer腳本寫在同一個Python程序,便於對比和查看; 
2) 獨立編寫數據源的字段信息和位置映射字典,不容易混淆; 
3) 抽取常用的如輸出數據、讀入數據模塊爲獨立函數; 
4) 測試腳本及數據、run腳本、map-reduce程序分目錄放置;

原文鏈接地址:http://www.crazyant.net/1122.html 
本文在原文基礎上又稍許改動

發佈了391 篇原創文章 · 獲贊 11 · 訪問量 39萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章