背景
前幾日在微信的大數據技術交流羣中,有水友面試深圳的某家證券公司並將部分筆試題發出來了,廣大水友就開始討論起來了;當然作爲吃瓜羣衆中的一員我不僅吃了瓜還丟了籽!嘿嘿,當然也記錄下來了,下面請看題:
題目
在關係型數據庫中的差運算在MR中是如何實現的。假設兩個表R、T,現需要計算R-T,找出R中存在T中不存在的數據。
分析
- 首先限定了是在
MR
環境中。 - “關係型數據庫中的差運算”說明 R、T兩張表可以通過某個或多個字段關聯。
- MR環境、差運算、有關聯字段;那麼我首先想起的就是
MR
的Join
操作。
實現
我們就先現在比較熱門的電商訂單表-R與商品表-T爲例,R、T通過商品id進行光關聯;所以給出以下原始測試數據。
訂單表R: 分別對應字段 訂單id,商品id,購買數量
商品表T: 分別對應字段 商品id,商品名稱
通過測試數據我們就可以發現除了1010
號訂對應的商品是不存在的;換句話說就是R中存在T中不存在的數據。
-
建立關聯實體
@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; } }
注意序列化與反序列化的順序問題
-
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階段做準備
-
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
產品名稱肯定是不存在的,以此爲判斷條件。 - 拼接商品名稱輸出結果
- 同一個商品的所的訂單進入同一個ReduceTask中,那麼R中存在T中不存在的數據數據在此時對應的
-
主類
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); } }
-
結果
我們發現如開始我所說的那樣我們輸出了除1010
訂單號之外的其他所有數據。