看到一篇不錯的Hadoop-Streaming實戰經驗的文章,裏面有大部分的情景都是自己實戰中曾經遇到過的。特意轉載過來,感謝有心人的總結。
目錄
-
Join操作分清join的類型很重要…
-
啓動程序中key字段和partition字段的設定…
-
控制hadoop程序內存的方法…
-
對於數字key的排序問題…
-
在mapper中獲取map_input_file環境變量的方法…
-
運行過程中記錄數據的方法…
-
多次運行Hadoop之是否成功的判斷…
-
對stdin讀取的 line的預處理…
-
Python字符串的連接方法…
-
怎樣查看mapper程序的輸出…
-
SHELL腳本中變量名的命名方法…
-
提前設計好流程能簡化很多重複工作…
-
其他一些實用經驗…
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
本文在原文基礎上又稍許改動