ODPS MapReduce入門
轉載地址:
MapReduce 原理簡介
??(上圖引用自 INTRODUCTION TO HADOOP )
??以MapReduce中最經典的wordcount應用為例,來分析一下MapReduce的全過程。這裡我們要統計檔案中每個單詞出現的次數。
- Input就是我們要處理的原始資料,一共有3行。
- Splitting步驟是分配任務,這裡把任務分給3臺機器同時處理,每臺機器只負責處理一行的資料。
- Mapping步驟就是這3臺機器具體要做的事情。在這裡每臺機器要做的就是統計一行文字裡的單詞頻率。這裡就涉及到比較重要的一個概念,就是key和value。這裡key就是單詞,value就是這個單詞在這一行出現的次數。
- Shuffling步驟就是對Mapping步驟產生的9行資料,按照key進行分組。這裡分成了4組,每組交給一臺電腦去處理。
- Reducing,把相同key對應的value相加,每個key最終只輸出一行,依然是key,value的形式輸出。
- Final result,把Reducing的輸出合併。
??(注:這裡Mapping工作交給3臺電腦,Reducing工作交給4臺電腦的說法其實是不嚴謹的,具體要用多少資源來完成MapReduce由系統根據任務的狀況決定,通常一臺電腦需要完成多個Mapping與Reducing的工作。)
??為何要如此設計?簡單來說,因為MapReduce為的是能實現分散式運算,涉及到多臺機器同時運算的步驟有Mapping和Reducing,參與Mapping工作的機器可以完全獨立工作而不需要知道其他機器上有什麼資料;參與Reducing步驟的機器,由於資料之前已經按照key進行了分組,因此其他機器上有什麼資料與他毫無關係。參與計算的機器都是互相獨立,完全不依賴其他機器的資料,這樣就可以很方便寫程式碼,因為所有參與Mapping工作的機器使用一模一樣的程式碼,所有參與Reducing工作的機器也使用一模一樣的程式碼。
??我們要在ODPS上要實現MapReduce,就需要寫兩類程式碼,一類稱為Mapper,另一類稱為Reducer。拋開前面所說的原理,我們只需要記住以下兩點:
- Mapper每次只處理一行資料。即Mapper的Input是資料庫中的一條記錄。
- Reducer每次要處理的是相同key下的所有記錄,通常會是多行的。
目標
??透過MapReduce計算對每個user每天對不同品牌產生的4種行為(點選、購買、收藏、購物車)的次數進行統計,並且計算在某天某user對某個brand的累計點選次數:
input table
user_id | brand_id | type | visit_datetime |
---|---|---|---|
101 | 20001 | 0 | 06-01 |
101 | 20001 | 0 | 06-01 |
101 | 20001 | 0 | 06-02 |
101 | 20002 | 0 | 06-02 |
101 | 20002 | 1 | 06-02 |
101 | 20003 | 0 | 06-02 |
101 | 20001 | 0 | 06-03 |
101 | 20003 | 2 | 06-03 |
101 | 20003 | 3 | 06-03 |
101 | 20003 | 1 | 06-04 |
output table
user_id | brand_id | visit_datetime | clicks | buy | collect | basket | cum_clicks |
---|---|---|---|---|---|---|---|
101 | 20001 | 06-01 | 2 | 0 | 0 | 0 | 2 |
101 | 20001 | 06-02 | 1 | 0 | 0 | 0 | 3 |
101 | 20001 | 06-03 | 1 | 0 | 0 | 0 | 4 |
101 | 20002 | 06-02 | 1 | 1 | 0 | 0 | 1 |
101 | 20003 | 06-02 | 1 | 0 | 0 | 0 | 1 |
101 | 20003 | 06-03 | 0 | 0 | 1 | 1 | 1 |
101 | 20003 | 06-04 | 0 | 1 | 0 | 0 | 1 |
??比如output table裡第三行的意思是,user_101在06-03這天一共點了brand_20001 1次,從user_101第一次接觸brand_20001以來,已經累計點了4次。
??由於我們想要實現累計求和,因此我們可以在Mapping步驟中,使用(user_id,brand_id)作為key,而(type,visit_datetime)作為value。
??這麼一來,在Reducing步驟中,每個Reducer就能接受到某個user對某個brand的所有互動資訊,這樣就能衍生出我們所需的新的value,即(visit_datetime, clicks, buy, collect, basket cum_clicks)。
??在ODPS中需要達到上述目標,需要動手實現3個類,這裡我把他們命名為TestMapper,TestReducer,TestDriver,其中TestDriver用來進行一些任務的配置。下面來具體看看如何實現。
TestDriver
??Driver主要用來進行一些格式設定。在此之前你需要在eclipse中新建一個ODPS專案。然後在專案的src上右鍵->new->other,在Aliyun Open Data Processing Service下選擇MapReduce Driver…
??接著eclipse會幫我們生成一個Driver的模板。官方寫的很清楚了,所有的TODO部分是需要我們進行修改的。先來看第一個TODO:
// TODO: specify map output types
job.setMapOutputKeySchema(SchemaUtils.fromString("user_id:string,brand_id:string"));
job.setMapOutputValueSchema(SchemaUtils.fromString("type:string,visit_datetime:string"));
這是用來設定Mapper輸出的時候,key與value的格式。按照之前說的,以(user_id,brand_id)為key,(type,visit_datetime)為value。
??第二個TODO:
InputUtils.addTable(TableInfo.builder().tableName(args[0]).build(),job);
OutputUtils.addTable(TableInfo.builder().tableName(args[1]).build(),job);
設定input table與output table。這裡把待會兒命令列呼叫中,第一個引數(args[0])設為input table,第二個引數(args[1])設為output table。待會兒會透過下面的命令在odps console中啟動MapReduce任務。(暫時不需要搞清楚的地方都用*代替)
jar *** TestDriver t_alibaba_bigdata_user_brand_total_1 tb_output
其中args[0]就指代t_alibaba_bigdata_user_brand_total_1,args[1]就指代tb_output。
??第三個TODO:
// TODO: specify a mapper
job.setMapperClass(TestMapper.class)
job.setReducerClass(TestReducer.class)
告訴系統這次任務要用的Mapper和Reducer是誰,按照上面的設定之後,系統就會通知所有負責Mapping工作的電腦待會兒使用TestMapper中的程式碼進行運算,通知所有負責Reducing工作的電腦待會兒使用TestReducer中的程式碼進行運算。
??TestDriver完整程式碼如下(省略開頭import的部分)
public class TestDriver {
public static void main (String[] args) throws OdpsException {
JobConf job = new JobConf();
// TODO: specify map output types
job.setMapOutputKeySchema(SchemaUtils.fromString("user_id:string,brand_id:string"));
job.setMapOutputValueSchema(SchemaUtils.fromString("type:string,visit_datetime:string"));
// TODO: specify input and output tables
InputUtils.addTable(TableInfo.builder().tableName(args[0]).build(),job);
OutputUtils.addTable(TableInfo.builder().tableName(args[1]).build(),job);
// TODO: specify a mapper
job.setMapperClass(TestMapper.class)
job.setReducerClass(TestReducer.class)
RunningJob rj = JobClient.runJob(job);
rj.waitForCompletion();
}
}
TestMapper
??之前說過,Mapper的任務就是對讀入的一行資料,接著輸出key和value。key和value都屬於Record類,並且key和value都可以由單個或者多個欄位構成,在我們這個任務中,key由(user_id,brand_id)兩個欄位構成,value由(type,visit_datetime)構成。
??與建立TestDriver的步驟類似,使用官方的模板建立一個名為TestMapper的java程式碼,同樣官方模板把大多數程式碼都生成好了。
public void setup(TaskContext context) throws IOException {
key = context.createMapOutputKeyRecord();
value = context.createMapOutputValueRecord();
}
setup當中是對key和value進行初始化。其中key使用createMapOutputKeyRecord()進行初始化,value使用createMapOutputValueRecord()進行初始化。
??在map函式中,record代表讀入的一行資料,比如101, 20001, 0, 06-01,我們可以透過record.get(n)方法獲取該行記錄第n列的資料。並且方便的是,這裡可以直接對讀入的資料進行一個型別轉換。例如record.getString()會把讀入的資料轉為字串,record.getBigInt()則會把讀入的資料轉為Long型整數。
??在TestDriver的設定當中,我們已經把Mapper輸出的key定為(user_id,brand_id),value定為(type,visit_datetime),在map函式中,我們可以使用key.set()與value.set()來分別賦予這4個值。
??最後context.write(key, value)的意思是輸出這條key-value,如果不寫這行,Mapper就什麼都不輸出。一個Mapper可以有0個或多個key-value的輸出,每呼叫一次context.write(key,value)就會輸出一行。
??TestMapper完整程式碼如下(省略import部分)
public class TestMapper extends MapperBase{
Record key;
Record value;
@Override
public void setup(TaskContext context) throws IOException {
key = context.createMapOutputKeyRecord();
value = context.createMapOutputValueRecord();
}
@Override
public void map(long recordNum, Record record, TaskContext context)
throws IOException {
key.set("user_id", record.getString(0));
key.set("brand_id", record.getString(1));
value.set("type", record.getString(2));
value.set("visit_datetime", record.getString(3));
context.write(key, value);
}
}
TestReducer
??通常Driver和Mapper方面都很簡單,大多情況下,計算工作都在Reducing步驟完成,因此Reducer的程式碼會略多一些。同樣按照前面的方法生成名為TestReducer的Reducer類。
??因為我們要按天來彙總四類行為出現的次數,因此這裡我使用一個TreeMap來儲存每天四種行為出現的次數。
Map
這裡又再囉嗦一遍,Mapper每次只處理一行資料,而Reducer通常處理的不止一行,而是會處理屬於相同key的所有資料。翻到文章開頭的那張圖片,圖中第二個Reducer,所有key為Car的記錄,全部交給該一個Reducer處理。
??因此reduce函式當中的values引數是一個Iterator,透過呼叫values.next()來讀取所有屬於該key的記錄。每讀取一行記錄,都對計數器typeCounter進行對應type的累加操作。
??Reducer的output是一個Record類,可以透過output.set(n)來設定該output第n列的數值,同樣使用context.write(output)輸出一行資料。在本文的例子中,對於一個key(user_id,brand_id)而言,有N個不同的visit_datetime,最終就會輸出N行資料。因此可以看到TestReducer的context.write(output)是寫在一個for-loop裡的,會被呼叫多次,每次會輸出一行。類似的操作如果使用SQL實現,就非常的費神了,而使用MapReduce,反而簡單許多。
??TestReducer的完整程式碼(省略模板中的import部分)
import java.util.Map;
import java.util.TreeMap;
public class TestReducer extends ReducerMap{
Record output;
@Override
public void setup(TaskContext context) throws IOException {
output = context.createOutputRecord();
}
@Override
public void reduce(Record key, Iterator
throws IOException {
Map
while (values.hasNext()) {
Record val = values.next();
String date = val.getString("visit_datetime");
int type = Integer.parseInt(val.getString("type"));
if (typeCounter.containsKey(date)) {
typeCounter.get(date)[type]++;
} else {
Long[] counter = new Long[]{0L, 0L, 0L, 0L};
counter[type]++;
typeCounter.put(date, counter);
}
}
output.set(0, key.getString("user_id"));
output.set(1, key.getString("brand_id"));
Long cumClicks = 0L;
for (String date : typeCounter.keySet()){
output.set(2, date);
output.set(3, typeCounter.get(date)[0]);
output.set(4, typeCounter.get(date)[1]);
output.set(5, typeCounter.get(date)[2]);
output.set(6, typeCounter.get(date)[3]);
cumClicks += typeCounter.get(date)[0];
output.set(7, cumClicks);
context.write(output);
}
}
}
打包、上傳、建表、執行
??1. 在Package Explorer中你之前建立的ODPS專案下的src上右鍵,選擇Export,然後選擇Java底下的JAR file。接著設定下JAR包存放的位置與檔名。這裡假設我們放在C:\TOOLS\test.jar,然後點Finish。
??2. 開啟odps console,新建一個resource。
odps:tianchi_1234> create resource jar C:/tools/test.jar -f
3. 在實際執行之前,需要先建立一個表,作為結果輸出的位置。這裡我們就叫它tb_output好了。
??進入sql,建立表格
odps:tianchi_1234> sql
odps:sql:tianchi_1234> drop table if exists tb_output;
odps:sql:tianchi_1234> create table tb_output (user_id string, brand_id string, visit_datetime string, clicks bigint,
buy bigint, collect bigint, basket bigint, cum_clicks bigint);
4. 在odps console下,執行MapReduce任務
odps:tianchi_1234> jar -resources test.jar --classpath c:/tools/test.jar TestDriver t_alibaba_bigdata_user_brand_total_1 tb_output;
結尾
- 本人幾乎是第一次寫Java,當中如有一些不規範的地方,希望您不吝賜教。
- 因為每次MapReduce的執行時間都會很長,建議每次做MapReduce任務的時候,可以先產生一份非常小的table,先拿這個小table做實驗,確定結果正確後,再對整張表進行操作。
- odps官方文件的例子中,Mapper,Reducer,Driver是寫在同一個檔案下的,這樣做也可以,但不建議這樣寫。之前我嘗試這麼做,當Mapper或者Reducer有錯誤的時候,無法提示錯誤在第幾行。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/26613085/viewspace-1330078/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Hadoop 專欄 - MapReduce 入門Hadoop
- MapReduce入門及核心流程案例
- MapReduce(一):入門級程式wordcount及其分析
- 大資料入門:MapReduce基本原理大資料
- Hadoop框架:MapReduce基本原理和入門案例Hadoop框架
- HDFS+MapReduce+Hive+HBase十分鐘快速入門Hive
- odps平臺將資料匯入到hdfs
- ODPS insert overwrite/into
- odps dship客戶端使用客戶端
- 入門入門入門 MySQL命名行MySql
- ODPS許可權管理命令集合
- 帶你入坑大資料(三) --- MapReduce介紹大資料
- 如何入CTF的“門”?——所謂入門就是入門
- 何入CTF的“門”?——所謂入門就是入門
- MapReduce初探
- MapReduce理解
- scala 從入門到入門+
- makefile從入門到入門
- ACM入門之新手入門ACM
- ODPS輸出到動態分割槽(DYNAMIC PARTITION)
- ODPS初始篇--客戶端配置和使用客戶端
- 【小入門】react極簡入門React
- gRPC(二)入門:Protobuf入門RPC
- MapReduce: 提高MapReduce效能的七點建議[譯]
- MapReduce 簡介
- MapReduce之topN
- MapReduce InputFormat——DBInputFormatORM
- Mongodb MapReduce使用MongoDB
- Lab 1: MapReduce
- 《Flutter 入門經典》之“Flutter 入門 ”Flutter
- 新手入門,webpack入門詳細教程Web
- Android入門教程 | RecyclerView使用入門AndroidView
- linux新手入門――shell入門(轉)Linux
- MyBatis從入門到精通(一):MyBatis入門MyBatis
- SqlSugar ORM 入門到精通【一】入門篇SqlSugarORM
- Storm入門指南第二章 入門ORM
- MapReduce 示例:減少 Hadoop MapReduce 中的側連線Hadoop
- VUE入門Vue