MapReduce的Join操作

背景

前幾日在微信的大數據技術交流羣中,有水友面試深圳的某家證券公司並將部分筆試題發出來了,廣大水友就開始討論起來了;當然作爲吃瓜羣衆中的一員我不僅吃了瓜還丟了籽!嘿嘿,當然也記錄下來了,下面請看題:

題目

在關係型數據庫中的差運算在MR中是如何實現的。假設兩個表R、T,現需要計算R-T,找出R中存在T中不存在的數據。

分析

  1. 首先限定了是在 MR環境中。
  2. “關係型數據庫中的差運算”說明 R、T兩張表可以通過某個或多個字段關聯。
  3. MR環境、差運算、有關聯字段;那麼我首先想起的就是MRJoin操作。

實現

我們就先現在比較熱門的電商訂單表-R與商品表-T爲例,R、T通過商品id進行光關聯;所以給出以下原始測試數據。

訂單表R: 分別對應字段 訂單id,商品id,購買數量
在這裏插入圖片描述

商品表T: 分別對應字段 商品id,商品名稱
在這裏插入圖片描述
通過測試數據我們就可以發現除了1010號訂對應的商品是不存在的;換句話說就是R中存在T中不存在的數據

  1. 建立關聯實體

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class TableBean implements Writable {
    
        /**
         * 訂單id
         */
        private String order_id;
        /**
         * 產品id
         */
        private String p_id;
        /**
         * 產品數量
         */
        private int amount;
    
        /**
         * 產品名稱
         */
        private String pname;
        /**
         * 表的標記
         */
        private String flag;
    
        @Override
        public void write(DataOutput out) throws IOException {
            out.writeUTF(order_id);
            out.writeUTF(p_id);
            out.writeInt(amount);
            out.writeUTF(pname);
            out.writeUTF(flag);
        }
    
        @Override
        public void readFields(DataInput in) throws IOException {
            this.order_id = in.readUTF();
            this.p_id = in.readUTF();
            this.amount = in.readInt();
            this.pname = in.readUTF();
            this.flag = in.readUTF();
        }
        @Override
        public String toString() {
            return order_id + "\t" + p_id + "\t" + amount +"\t"+ pname;
        }
    }
    

    注意序列化與反序列化的順序問題

  2. Mapper過程

    public class TableMapper extends Mapper<LongWritable, Text, Text, TableBean> {
        TableBean bean = new TableBean();
        Text k = new Text();
        String fileName = null;
        /**
         * maptask在做數據處理時,會先調用一次setup() 釣完後纔對每一行反覆調用map()
         */
        @Override
        protected void setup(Mapper<LongWritable, Text, Text, TableBean>.Context context) {
            // 2 獲取輸入切片
            FileSplit inputSplit = (FileSplit) context.getInputSplit();
            fileName = inputSplit.getPath().getName();
            System.out.println("數據切片----》" + fileName);
        }
    
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            // 2 獲取輸入數據
            String line = value.toString();
    
            //3 不同文件分別處理
            // 訂單相關信息處理
            if (fileName.contains("order")) {
                // 切割
                String[] fields = line.split("\t");
    
                // 封裝bean對象 1001	01	1
                bean.setOrder_id(fields[0]);
                bean.setP_id(fields[1]);
                bean.setAmount(Integer.parseInt(fields[2]));
    
                bean.setPname("");
                bean.setFlag("0");
    
                // 設置key值
                k.set(fields[1]);
    
                // 產品表信息處理     01	小米
            } else {
    
                // 切割
                String[] fields = line.split("\t");
    
                // 封裝bean對象
                bean.setOrder_id("");
                bean.setP_id(fields[0]);
                bean.setAmount(0);
                bean.setPname(fields[1]);
                bean.setFlag("1");
    
                // 設置key值
                k.set(fields[0]);
            }
    
    //		4 封裝bean對象輸出
            context.write(k, bean);
        }
    }
    
    
    • 以 商品 id作爲Key輸出,這樣同一個商品的所有訂單就在一個ReduceTask處理了
    • 重寫 setup函數,maptask在做數據處理時,會先調用一次setup() 後纔對每一行反覆調用map(),在此獲取數據的來源是R、還是T。
    • 在map的過程中通過 flag字段區分數據來源,爲reduce階段做準備
  3. Reduce過程

    public class TableReducer extends Reducer<Text, TableBean, TableBean, NullWritable> {
        @Override
        protected void reduce(Text key, Iterable<TableBean> values, Context context) throws IOException, InterruptedException {
            // 1準備存儲訂單的集合
            ArrayList<TableBean> orderBeans = new ArrayList<>();
            // 2 準備bean對象
            TableBean pdBean = new TableBean();
    
            for (TableBean bean : values) {
                // 訂單表
                if ("0".equals(bean.getFlag())) {
                    // 拷貝傳遞過來的每條訂單數據到集合中
                    TableBean orderBean = new TableBean();
                    try {
                        BeanUtils.copyProperties(orderBean, bean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
    
                    orderBeans.add(orderBean);
                } else {// 產品表
                    try {
                        // 拷貝傳遞過來的產品表到內存中
                        BeanUtils.copyProperties(pdBean, bean);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
    
            // 3 表的拼接
            for (TableBean bean : orderBeans) {
                if (!StringUtils.isBlank(pdBean.getPname())) {
                    bean.setP_id(pdBean.getPname());
    
                    // 4 數據寫出去
                    context.write(bean, NullWritable.get());
                }
            }
        }
    }
    
    • 同一個商品的所的訂單進入同一個ReduceTask中,那麼R中存在T中不存在的數據數據在此時對應的 pname產品名稱肯定是不存在的,以此爲判斷條件。
    • 拼接商品名稱輸出結果
  4. 主類

    public class JobSubmit {
        public static void main(String[] args) throws Exception {
            // 1 獲取配置信息,或者job對象實例
            Configuration configuration = new Configuration();
            Job job = Job.getInstance(configuration);
    
            // 2 指定本程序的jar包所在的本地路徑
            job.setJarByClass(JobSubmit.class);
    
            // 3 指定本業務job要使用的mapper/Reducer業務類
            job.setMapperClass(TableMapper.class);
            job.setReducerClass(TableReducer.class);
    
            // 4 指定mapper輸出數據的kv類型
            job.setMapOutputKeyClass(Text.class);
            job.setMapOutputValueClass(TableBean.class);
    
            // 5 指定最終輸出的數據的kv類型
            job.setOutputKeyClass(TableBean.class);
            FileUtils.deleteDirectory(new File("F:/hadoop/orderjoin/outpath"));
            job.setOutputValueClass(NullWritable.class);
    
            // 6 指定job的輸入原始文件所在目錄
            FileInputFormat.setInputPaths(job, new Path("F:/hadoop/orderjoin/inpath"));
            FileOutputFormat.setOutputPath(job, new Path("F:/hadoop/orderjoin/outpath"));
            // 7 將job中配置的相關參數,以及job所用的java類所在的jar包, 提交給yarn去運行
            boolean result = job.waitForCompletion(true);
            System.exit(result ? 0 : 1);
    
        }
    }
    
  5. 結果
    在這裏插入圖片描述
    我們發現如開始我所說的那樣我們輸出了除 1010訂單號之外的其他所有數據。

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