現在已有的很多博客demo都是以wordcount爲例,衆所周知這是一個非常簡單的功能,但凡遇到一些高階一點的操作我都會大腦一片空白,今天正好有相關的需求,就來學習了一下。
http://www.zhangdongshengtech.com/article-detials/236
上面的鏈接是記錄頻次的demo,寫的非常的好,相信各位看了它就會了解mapreduce核心的寫法
Intro:wordcount
說在前面:mapreduce程序的調試可以單獨分別運行mapper和reducer,直接在命令行輸入你指定好的輸入格式,就會打印出輸出
mapper.py
輸入文件的形式就是
word1
word2
word1
word3
# coding=utf-8
import sys
for line in sys.stdin:
words = line.strip()
if not word: continue
print(word)
reducer.py
這裏實現的就是一個簡單的計數並把頻次寫到文件中的操作。
如果你只需要實現計數操作,那麼只用修改mapper.py的print的值即可
# coding=utf-8
import sys
count = 0
key = ""
current_key = ""
for line in sys.stdin:
line = line.rstrip()
if not line:
sys.stderr.write("data is wrong")
sys.exit(1)
line = line.rstrip()
items = line.split("\t")
current_key = items[3]
cur_timestamp = items[2]
if current_key == key:
if cur_timestamp < timestamp:
print "%s\t%d" % (key, count)
count = 0
key = current_key
count += 1
if key:
print "%s\t%d" % (key, count)
run.sh
運行環境配置這一塊我可能沒有辦法講清楚,因爲是別人寫好的腳本,我只修改了上面2個代碼
在這裏修改你的輸入路徑和輸出路徑
#!/bin/bash
HADOOP_bin='/your/path/hadoop-2.7.3/bin/hadoop'
INPUT_PATH="input_data"
OUTPUT_PATH="test"
$HADOOP_bin fs -rmr $OUTPUT_PATH
$HADOOP_bin jar /your/path/hadoop-2.7.3/share/hadoop/tools/lib/hadoop-streaming-2.7.3.jar\
-D mapred.job.priority="VERY_HIGH"\
-D mapred.reduce.tasks=200\
-D mapreduce.job.queuename=root.online.default\
-D mapred.job.map.capacity=400\
-D mapred.job.reduce.capacity=100\
-D mapred.job.name="test"\
-D mapred.textoutputformat.ignoreseparator="true"\
-input ${INPUT_PATH} \
-output ${OUTPUT_PATH} \
-file ./mapper.py\
-file ./reducer.py\
-partitioner "org.apache.hadoop.mapred.lib.HashPartitioner"\
-mapper "python mapper.py"\
-reducer "python reducer.py"\
-inputformat "org.apache.hadoop.mapred.TextInputFormat"\
-outputformat "org.apache.hadoop.mapred.TextOutputFormat"\
Advance:有條件的合併內容
下面來實現把輸入按某一個值進行合併
輸入形式:
key1 value1 value2
key2 value1 value2
key1 value3 value4
輸出形式:
key1 value1 value2 value3 value4…………
key2 value1 value2 …………
mapper.py
#coding=utf8
import json
import sys
#f = open('part-07198', 'r') #調試用,因爲我的mapreduce任務配置在python2下,調試的時候sys.stdin接收不到輸入,所以直接讀文件
for line in sys.stdin:
line = line.strip()
if not line:
continue
data = line.split('\t', 2) #只區分key,後面的values不做區分
if len(data) <= 1:
continue
print data
這裏如果怕數據有問題可以寫在try except裏,如果還需要對每一行的數據做什麼處理都放在mapper裏處理,當把數據預處理成可以根據某一項進行合併時就print輸出,丟給reducer
reducer.py
import json
import sys
from operator import itemgetter
from itertools import groupby
def read_mapper_output(file, separator='\t'):
for line in file:
yield line.rstrip().split(separator, 2)
def main(separator='\t'):
#f = open('part-07198', 'r') #調試用
# input comes from STDIN (standard input)
data = read_mapper_output(sys.stdin, separator=separator)
for name, group in groupby(data, itemgetter(0)):
val = []
for values in group:
for v in values[1]:
val.append(v)
print "%s\t%s"% (values[0], json.dumps(v))
if __name__ == "__main__":
main()
這裏有兩個函數非常重要,搞懂了它們你就能搞懂如何寫reducer,再複雜的功能你都能變着花實現
groupby
https://blog.csdn.net/Together_CZ/article/details/73042997
key, group= groupby(iterator, key=func())
將key函數作用於原循環器的各個元素。根據key函數結果,將擁有相同函數結果的元素分到一個新的循環器。每個新的循環器以函數返回結果爲標籤。
這就好像一羣人的身高作爲循環器。我們可以使 用這樣一個key函數: 如果身高大於180,返回"tall";如果身高底於160,返回"short";中間的返回"middle"。最終,所有身高將分爲三個循環器, 即"tall", “short”, “middle”。
這個函數的意思就是說把原來的迭代器中的值按照某一個key聚合
再來複習一下mapreduce的原理:
hadoop框架會自動的將相同的key分配到同一個reducer上,這個key,默認的就是上一個mapper輸出數據的以\t,或者\001分割後的第一部分
看到這裏大概應該就明白了,只要我們把所需要合併的key在mapper中變換到第一位輸出,這樣就能用groupby直接進行聚合
那麼,groupby的輸出是什麼呢?
groupby的輸出有兩部分,一部分是key,另一部分就是同一key下的所有data,這裏的data同樣含有key這個字段
看一下https://blog.csdn.net/LY_ysys629/article/details/72553273這個實例應該就能對groupby的輸出有直觀感受了。
itemgetter
我覺得這篇博客的理解寫的非常好https://blog.csdn.net/qq_22022063/article/details/79019294
作用:itemgetter 用於獲取對象的哪些位置的數據,參數即爲代表位置的序號值,
也就是說itemgetter的參數等於i,就取data[i]的數據,並且它是一個函數,因此可以直接用作groupby的key傳參。
中文字符的處理
我已經被這個坑了兩次了,記錄一下
(1)文件開頭一定要記得#coding=utf8
(2)如果保存的文件是’\u’開頭的,解析的時候用json.loads能直接解析出中文
(3)如果保存的文件是’\x’開頭的。。。可能在解析的時候要decode(‘utf-8’)吧。。。