【轉】大數據【五十八】探索MapReduce過程及分組詳解

一。問題

一直對MapReduce的分區和分組理解的比較模糊和不確定。這次又遇到reduce輸出結果跟自己預想的不一樣的情況,因此決定深入進去操作一下各種情況的結果,爭取理清楚分組、分區的關係。

以前的認識
我一直以來對MapReduce的分區和分組有一個自己的理解。

分區:如果不自定義分區類,而使用默認分區時,採取的是對鍵進行哈希操作,並與reducetask任務數取餘,根據得到的值進行分區。由於默認的reducetask任務數設置爲1,因此默認情況下只有1個分區。如果自己重新定義了一個分區類,則會按照自定義的方式進行分區。

(key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;

分組:分組和分區類似,也是用來劃分數據集的,只不過更加細粒度。如果不自定義分組類而使用默認分組的話,跟默認分區相同,也是通過比較鍵來進行分組reduce()函數是按照組爲操作對象進行統計的

二。對分區和分組進行測試

(一)數據集

摩根士丹利MORGANSTANLEY,美國
工商銀行INDUSTRIALCOMMERBANKOFCHINA,中國
TalanxTALANX,德國
華爲投資控股HUAWEIINVESTMENTHOLDING,中國
天津物產TEWOOGROUP,中國
阿里巴巴ALIBABAGROUPHOLDING,中國
FomentoEconmicoMexicanoFOMENTOECONMICOMEXICANO,墨西哥
農業銀行AGRICULTURALBANKOFCHINA,中國
戴爾科技DELLTECHNOLOGIES,美國
太平洋保險股份CHINAPACIFICINSURANCE,中國
浙江吉利控股ZHEJIANGGEELYHOLDINGGROUP,中國
建設銀行CHINACONSTRUCTIONBANK,中國
電子信息產業CHINAELECTRONICS,中國
江蘇沙鋼JIANGSUSHAGANGGROUP,中國
上海汽車股份SAICMOTOR,中國
廈門建發XIAMENCD,中國
電信CHINATELECOMMUNICATIONS,中國
甲骨文ORACLE,美國
廣州汽車工業GUANGZHOUAUTOMOBILEINDUSTRYGROUP,中國
山東能源SHANDONGENERGYGROUP,中國
聯想LENOVOGROUP,中國
正威國際AMERINTERNATIONALGROUP,中國
移動通信CHINAMOBILECOMMUNICATIONS,中國
陝西延長石油責任SHAANXIYANCHANGPETROLEUM,中國
航天科工CHINAAEROSPACESCIENCEINDUSTRY,中國
富士通FUJITSU,日本
思科CISCOSYSTEMS,美國
微軟MICROSOFT,美國
北京汽車BEIJINGAUTOMOTIVEGROUP,中國

(二)測試一、使用默認分組

1. 代碼
public class HandleTopAttend {

public static class CmpnyCutry implements WritableComparable<CmpnyCutry> {
        private String company;
        private String country;
 
        public void set(String first, String second) {
            this.company = first;
            this.country = second;
        }
 
        public String getCompany() {
            return this.company;
        }
 
        public String getCountry() {
            return this.country;
        }
 
        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            company = in.readUTF();
            country = in.readUTF();
        }
 
        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeUTF(company);
            out.writeUTF(country);
        }
 
        @Override
        public int compareTo(CmpnyCutry o) {// 先按照公司比較,再按照所屬國家比較
            // TODO Auto-generated method stub
            int i = 0;
            if (o instanceof CmpnyCutry) {
                CmpnyCutry cc = (CmpnyCutry) o;
                i = this.company.compareTo(cc.company);
                if (i == 0) {
                    return this.country.compareTo(cc.country);
                }
            }
            return i;
        }
 
    }
  public static class Mapper1 extends Mapper<LongWritable, Text, CmpnyCutry, IntWritable> {
        static int count = 0;
        static Map<String, String> m;
        CmpnyCutry cmcu = new CmpnyCutry();
 
        public void setup(Context context) {
            m = new HashMap<>();
        }
 
        public void map(LongWritable index, Text line, Context context) throws IOException, InterruptedException {
            String[] cc = line.toString().split(",");
            m.put(cc[0], cc[1]);
            count++;//有多少行數據,最後count就爲幾,代表500強的公司數
        }
 
        public void cleanup(Context context) throws IOException, InterruptedException {
            Set s = m.entrySet();
            Iterator<Entry<String, String>> it = s.iterator();
            while (it.hasNext()) {
                Entry<String, String> en = it.next();
                cmcu.set(en.getKey(), en.getValue());
                context.write(cmcu, new IntWritable(count));//map輸出的key:(公司名,國家);value:500強公司數
            }
        }
    }
public static class CountryPartitioner extends Partitioner<CmpnyCutry, IntWritable> {
        public int getPartition(CmpnyCutry key, IntWritable count, int numPartitions) {
            if(key.getCountry().equals("中國")){
                return 0;
            }else{
                return 1;
            }
        }
    }
public static class Reducer1 extends Reducer<CmpnyCutry, IntWritable, Text, IntWritable> {
        int groupCount = 0;//統計當前分區中有多少個分組
        public void reduce(CmpnyCutry cc, Iterable<IntWritable> count, Context context)
                throws IOException, InterruptedException {
            int sum = 0;//統計當前分組的集合中有多少個元素
            IntWritable iw = new IntWritable();
            Iterator<IntWritable> i = count.iterator();
            while (i.hasNext()) {
                iw = i.next();
                sum++;
            }
            //輸出:國家,公司數,第幾組,500強公司數
            context.write(new Text(cc.getCountry() + "," + sum+","+groupCount++), iw);
        }
    }
 public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        conf.set("mapred.textoutputformat.ignoreseparator","true");  
        conf.set("mapred.textoutputformat.separator",","); 
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if (otherArgs.length != 2) {
            System.err.println("Usage: wordcount <in> <out>");
            System.exit(2);
        }
 
        Job job = Job.getInstance(conf, "topAttend");
        job.setJarByClass(HandleTopAttend.class);
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
        job.setMapperClass(Mapper1.class);
        job.setMapOutputKeyClass(CmpnyCutry.class);
        job.setMapOutputValueClass(IntWritable.class);
 
        job.setPartitionerClass(CountryPartitioner.class);
        job.setNumReduceTasks(2);
        job.setReducerClass(Reducer1.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        System.exit(job.waitForCompletion(true) ? 0 : 1);
 
    }

   
}

 

public class HandleTopAttend {

public static class CmpnyCutry implements WritableComparable<CmpnyCutry> {
        private String company;
        private String country;
 
        public void set(String first, String second) {
            this.company = first;
            this.country = second;
        }
 
        public String getCompany() {
            return this.company;
        }
 
        public String getCountry() {
            return this.country;
        }
 
        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            company = in.readUTF();
            country = in.readUTF();
        }
 
        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeUTF(company);
            out.writeUTF(country);
        }
 
        @Override
        public int compareTo(CmpnyCutry o) {// 先按照公司比較,再按照所屬國家比較
            // TODO Auto-generated method stub
            int i = 0;
            if (o instanceof CmpnyCutry) {
                CmpnyCutry cc = (CmpnyCutry) o;
                i = this.company.compareTo(cc.company);
                if (i == 0) {
                    return this.country.compareTo(cc.country);
                }
            }
            return i;
        }
 
    }

  public static class Mapper1 extends Mapper<LongWritable, Text, CmpnyCutry, IntWritable> {
        static int count = 0;
        static Map<String, String> m;
        CmpnyCutry cmcu = new CmpnyCutry();
 
        public void setup(Context context) {
            m = new HashMap<>();
        }
 
        public void map(LongWritable index, Text line, Context context) throws IOException, InterruptedException {
            String[] cc = line.toString().split(",");
            m.put(cc[0], cc[1]);
            count++;//有多少行數據,最後count就爲幾,代表500強的公司數
        }
 
        public void cleanup(Context context) throws IOException, InterruptedException {
            Set s = m.entrySet();
            Iterator<Entry<String, String>> it = s.iterator();
            while (it.hasNext()) {
                Entry<String, String> en = it.next();
                cmcu.set(en.getKey(), en.getValue());
                context.write(cmcu, new IntWritable(count));//map輸出的key:(公司名,國家);value:500強公司數
            }
        }
    }

public static class CountryPartitioner extends Partitioner<CmpnyCutry, IntWritable> {
        public int getPartition(CmpnyCutry key, IntWritable count, int numPartitions) {
            if(key.getCountry().equals("中國")){
                return 0;
            }else{
                return 1;
            }
        }
    }
public static class Reducer1 extends Reducer<CmpnyCutry, IntWritable, Text, IntWritable> {
        int groupCount = 0;//統計當前分區中有多少個分組
        public void reduce(CmpnyCutry cc, Iterable<IntWritable> count, Context context)
                throws IOException, InterruptedException {
            int sum = 0;//統計當前分組的集合中有多少個元素
            IntWritable iw = new IntWritable();
            Iterator<IntWritable> i = count.iterator();
            while (i.hasNext()) {
                iw = i.next();
                sum++;
            }
            //輸出:國家,公司數,第幾組,500強公司數
            context.write(new Text(cc.getCountry() + "," + sum+","+groupCount++), iw);
        }
    }

 public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        conf.set("mapred.textoutputformat.ignoreseparator","true");  
        conf.set("mapred.textoutputformat.separator",","); 
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if (otherArgs.length != 2) {
            System.err.println("Usage: wordcount <in> <out>");
            System.exit(2);
        }
 
        Job job = Job.getInstance(conf, "topAttend");
        job.setJarByClass(HandleTopAttend.class);
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
        job.setMapperClass(Mapper1.class);
        job.setMapOutputKeyClass(CmpnyCutry.class);
        job.setMapOutputValueClass(IntWritable.class);
 
        job.setPartitionerClass(CountryPartitioner.class);
        job.setNumReduceTasks(2);
        job.setReducerClass(Reducer1.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        System.exit(job.waitForCompletion(true) ? 0 : 1);
 
    }

   
}


2. 查看輸出日誌

    因爲只有一個輸入文件,且文件很小不足一個splits(分片),因此map tasks=1。同時由於設置了兩個分區和兩個reducetask,因此reduce tasks=2。關鍵是看Reduce input groups的數量,可以看到groups數量爲29(源數據是29行),也就是原始數據集的記錄數。因爲自定義了組合鍵,每條記錄的組合鍵都不相同,因此這個結果證明了分組是按照鍵進行的。

下面是輸出的part-r-00000文件內容

   這個結果,證明了reducer類和其中的reduce()函數各自的處理範圍。reducer類處理整個分區的數據其操作對象是區,一個區調用一次reducer類。而reduce()函數的操作對象是組,也就是分區中有 幾個分組就調用幾次reduce()函數reduce()函數對分組對應的集合進行處理。結合輸出的第二個分區文件part-r-00001可以進一步佐證。

下面是輸出的part-r-00001文件內容

(三)測試二、使用自定義分組

    現在想要實現的是,將相同國家的數據分到一個組中進行整合,也就是按照“國家”進行分組。因此要自定義分組類。

1. 代碼

public class HandleTopAttend {
    public static class CmpnyCutry implements WritableComparable<CmpnyCutry> {
        private String company;
        private String country;
 
        public void set(String first, String second) {
            this.company = first;
            this.country = second;
        }
 
        public String getCompany() {
            return this.company;
        }
 
        public String getCountry() {
            return this.country;
        }
 
        @Override
        public void readFields(DataInput in) throws IOException {
            // TODO Auto-generated method stub
            company = in.readUTF();
            country = in.readUTF();
        }
 
        @Override
        public void write(DataOutput out) throws IOException {
            // TODO Auto-generated method stub
            out.writeUTF(company);
            out.writeUTF(country);
        }
 
        @Override
        public int compareTo(CmpnyCutry o) {// 先按照公司比較,再按照所屬國家比較
            // TODO Auto-generated method stub
            int i = 0;
            if (o instanceof CmpnyCutry) {
                CmpnyCutry cc = (CmpnyCutry) o;
                i = this.company.compareTo(cc.company);
                if (i == 0) {
                    return this.country.compareTo(cc.country);
                }
            }
            return i;
        }
 
    }
 
    public static class Mapper1 extends Mapper<LongWritable, Text, CmpnyCutry, IntWritable> {
        static int count = 0;
        static Map<String, String> m;
        CmpnyCutry cmcu = new CmpnyCutry();
 
        public void setup(Context context) {
            m = new HashMap<>();
        }
 
        public void map(LongWritable index, Text line, Context context) throws IOException, InterruptedException {
            String[] cc = line.toString().split(",");
            m.put(cc[0], cc[1]);
            count++;//有多少行數據,最後count就爲幾,代表500強的公司數
        }
 
        public void cleanup(Context context) throws IOException, InterruptedException {
            Set s = m.entrySet();
            Iterator<Entry<String, String>> it = s.iterator();
            while (it.hasNext()) {
                Entry<String, String> en = it.next();
                cmcu.set(en.getKey(), en.getValue());
                context.write(cmcu, new IntWritable(count));//map輸出的key:(公司名,國家);value:500強公司數
            }
        }
    }
 
    public static class CountryPartitioner extends Partitioner<CmpnyCutry, IntWritable> {
        public int getPartition(CmpnyCutry key, IntWritable count, int numPartitions) {
            if(key.getCountry().equals("中國")){
                return 0;
            }else{
                return 1;
            }
        }
    }
//自定義分組類,重寫compare()方法
    public static class GroupingComparator extends WritableComparator {
        protected GroupingComparator() {
            super(CmpnyCutry.class, true);
        }
 
        @SuppressWarnings("rawtypes")
        public int compare(WritableComparable cc1, WritableComparable cc2) {
            CmpnyCutry ip1 = (CmpnyCutry) cc1;
            CmpnyCutry ip2 = (CmpnyCutry) cc2;
            String l = ip1.getCountry();
            String r = ip2.getCountry();
            return l.compareTo(r);// 比較兩個字符串的大小
        }
    }
 
    public static class Reducer1 extends Reducer<CmpnyCutry, IntWritable, Text, IntWritable> {
        int groupCount = 0;//統計當前分區中有多少個分組
        public void reduce(CmpnyCutry cc, Iterable<IntWritable> count, Context context)
                throws IOException, InterruptedException {
            int sum = 0;//統計當前分組的集合中有多少個元素
            IntWritable iw = new IntWritable();
            Iterator<IntWritable> i = count.iterator();
            while (i.hasNext()) {
                iw = i.next();
                sum++;
            }
            //輸出:國家,公司數,第幾組,500強公司數
            context.write(new Text(cc.getCountry() + "," + sum+","+groupCount++), iw);
        }
    }
 
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        Configuration conf = new Configuration();
        conf.set("mapred.textoutputformat.ignoreseparator","true");  
        conf.set("mapred.textoutputformat.separator",","); 
        String[] otherArgs = new GenericOptionsParser(conf, args).getRemainingArgs();
        if (otherArgs.length != 2) {
            System.err.println("Usage: wordcount <in> <out>");
            System.exit(2);
        }
 
        Job job = Job.getInstance(conf, "topAttend");
        job.setJarByClass(HandleTopAttend.class);
        FileInputFormat.addInputPath(job, new Path(otherArgs[0]));
        FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));
        job.setMapperClass(Mapper1.class);
        job.setMapOutputKeyClass(CmpnyCutry.class);
        job.setMapOutputValueClass(IntWritable.class);
 
        job.setPartitionerClass(CountryPartitioner.class);
        job.setGroupingComparatorClass(GroupingComparator.class);//爲job配置自定義的分組類
        job.setNumReduceTasks(2);
        job.setReducerClass(Reducer1.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);
        System.exit(job.waitForCompletion(true) ? 0 : 1);
 
    }
}


2. 查看輸出的日誌結果

   可以看到groups數由之前的29變爲了5,說明自定義分組其作用了。進一步分析,自定義分組規則是按照國家進行分組的。此處顯示5,說明數據集中總共應該有5個國家,查看上面測試一輸出的兩個文件,發現果然總共有5個國家。進一步分析,第一個分區是“中國”,並且因爲在這裏設置了按國家分組,因此輸出的part-r-00000文件應該只有一行記錄,而part-r-00001應該有4條記錄。

查看輸出文件part-r-00000

查看輸出文件part-r-00001

   進一步分析每條記錄的信息,可以更加佐證測試一的結論。也就是,關於reducer類和reduce()函數的作用範圍的結論。

(四)總結

很高興的是,實際結果證明自己以前的認識基本是正確的!!!!!

    reducer類的操作對象是分區,一個分區初始化一次reducer類。reduce()函數的操作對象是組,一個分區中有幾個分組就調用幾次reduce()函數。

   分組默認採用通過比較鍵的方式來實現。當自定義組合鍵時,往往需要根據組合鍵中的某一個屬性進行分組統計,此時就用到自定義分組類。通過重寫其中的compare()方法定義分組規則。

不足 

    由於輸出結果中沒有將整個組合鍵輸出,因此沒有體現出排序的過程。實際上,排序應該是在map端執行溢寫的時候進行的操作,操作的代碼就是自定義組合鍵中的compareTo()方法。到達溢寫條件時,先鎖定要溢寫的數據,然後對其進行分區,然後在分區內進行排序。如果設置了combiner類,還會在排序後執行combine操作。最後纔將結果寫出到臨時文件中。

不知道有沒有不對的地方,因爲都是自己的認識,不是正規軍。希望各位大佬指正!互相學習!

   寫博客不容易,轉載請註明出處,謝謝!!!https://blog.csdn.net/ASN_forever/article/details/81778972

三。進一步補充

真的是剛做完上面的實驗,就又發現問題了。。。。。

雖然上面通過自定義分組類之後,貌似數據集的輸出結果確實是實現分組了。但當數據集換成下面這個時,情況就又變啦。。。

(一). 新數據集

華爲投資控股HUAWEIINVESTMENTHOLDING,中國
天津物產TEWOOGROUP,中國
日本瑞穗金融MIZUHOFINANCIALGROUP,日本
阿里巴巴ALIBABAGROUPHOLDING,中國
英國葛蘭素史克GLAXOSMITHKLINE,英國
戴爾科技DELLTECHNOLOGIES,美國
騰訊控股TENCENTHOLDINGS,中國
浙江吉利控股ZHEJIANGGEELYHOLDINGGROUP,中國
通用汽車GENERALMOTORS,美國
國家電網STATEGRID,中國
電子信息產業CHINAELECTRONICS,中國
美國銀行BANKOFAMERICACORP,美國
江蘇沙鋼JIANGSUSHAGANGGROUP,中國
上海汽車股份SAICMOTOR,中國
廈門建發XIAMENCD,中國
電信CHINATELECOMMUNICATIONS,中國
OrangeORANGE,法國
廣州汽車工業GUANGZHOUAUTOMOBILEINDUSTRYGROUP,中國
聯合信貸UNICREDITGROUP,意大利
山東能源SHANDONGENERGYGROUP,中國
興業銀行INDUSTRIALBANK,中國
SAPSAP,德國
富士通FUJITSU,日本
微軟MICROSOFT,美國
北京汽車BEIJINGAUTOMOTIVEGROUP,中國


(二)問題

當用測試二中的代碼處理這個新數據集時,得到的打印日誌和輸出文件信息分別如下:

打印日誌

part-r-00000文件

part-r-00001文件

  分析這些信息,首先是groups=10。新數據集中明明總共只有7個不同的國家,按道理來說,根據國家分組後應該得到groups=7纔對。先不管,接着看輸出的兩個文件。

  第一個文件是中國區的統計數據,15表示這個分區中總共有15條記錄,0表示這個分區中只有一個分組,都跟預計的結果一致。

   再看第二個文件,發現問題了!竟然沒有將相同國家的聚合到相同分組中???雖然不知道哪裏出了問題,但是也會發現一點貓膩。就是第一個美國分組中,顯示有2條記錄。而後接着就是日本分組了。因此做如下猜想:因爲reduce端是先將所有map端對應的分區數據copy過來,然後合併成一個大文件。此後對這個大文件中的記錄進行排序操作,而這個排序規則也是根據自定義鍵類中的compareTo()方法實現的,而這個比較規則是先比較公司後比較國家。也就是說,相同國家的公司中間很可能被其他國家的公司隔開了。而分組操作是從上往下一條一條記錄進行比對來進行分組的。如果上下兩條記錄對應的組合鍵中的國家相同,則合併到一個組,如果不同,則上下各分一個組。這樣就能解釋part-r-00001的結果了。

(三)進一步證明

爲了進一步證明猜想,需要在輸出中增加公司名,並且取消分組,看看輸出結果跟猜想的符不符合。

下面是增加公司名,且取消分組時的輸出文件part-r-00001:

完美有沒有!!!完美證明了猜想的正確性!

四。再總結

分組詳解:

    分組前先對合並後的分區文件中的記錄進行排序,排序後再進行分組。分組是通過對排序後的記錄從上往下遍歷比對進行的。如果上下兩個比對結果爲0,則分到同一個組,否則各分一個組。後面的分組與前面的分組無關,只與緊挨着的上一條記錄有關。也就是說,就算前面有一個“美國”分組,但是中間隔了一個“日本”分組,則後面再出現“美國”時也不會合併到前面的分組中!!!因此,如果想按照國家分組的話,應該將國家作爲組合鍵的第一個屬性,這樣在reduce端排序後得到的就是相同的國家上下挨着了。

關於MapReduce的shuffle過程,請看這篇文章:https://mp.csdn.net/postedit/81233547


————————————————
版權聲明:本文爲CSDN博主「ASN_forever」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/asn_forever/article/details/81778972

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章