ZooKeeper分布式队列实现MapReduce任务集成

来源:互联网 发布:考研辅导班知乎 编辑:程序博客网 时间:2024/05/17 14:18
        随着Hadoop的普及,越来越多的公司在构建自己的Hadoop的集群,一赶大数据不可阻挡之趋势,虽然大数据的发展的确是不可阻挡的。随着业务的延展,有时候公司内部不同部门或团队之间就会出现归属自己的Hadoop集群,这种多集群的方式,既让不同业务板块的Hadoop集群实现个性化、差异化,以更好的为自身业务场景所服务,与此同时也会不可避免的出现需要协调多个Hadoop集群共同完成某件任务的场景。下面我们以公司经营利润的计算为例来说明。
需求说明
        公司一年经营利润的计算,需要由采购团队计算采购的支出(purchase),销售团队计算销售的收入(sell),然后还包括其他部门费用支付的计算(other),则:
一年的:
           利润(profit)=销售收入-采购支出-其他业务花费,我们暂且将业务场景定义的如此简单。
        从系统角度来看,采购部门要统计采购数据(海量数据),销售部门统计销售数据((海量数据),其他部门统计的其他费用支出(汇总的少量数据),最后系统计算得到当月的利润。
这里要说明的是,采购系统是单独的系统,销售是另外单独的系统,及以其他很多大大小小的系统,如何能让多个系统,配合起来做这道计算题呢??
        计算方式我们采用基础的MapReduce进行,则profit的计算需要purchase、sell、other三个任务同时完成后,才能触发。我们此处探索,使用ZooKeeper来进行任务的协调,当然还有其他很好的方式比如,使用消息总线或者Oozie等工具。
     
架构设计:
  1. 数据存储:
  • 采购数据,为海量数据,基于Hadoop存储和分析;
  • 销售数据,为海量数据,基于Hadoop存储和分析;
  • 其他费用支出,为少量数据,基于文件或数据库存储和分析;
    2.程序设计:
设计一个同步队列,这个队列有3个条件节点,分别对应采购(purchase),销售(sell),其他费用(other)3个部分。当3个节点都被创建后,程序会自动触发计算利润,并创建利润(profit)节点。上面3个节点的创建,无顺序要求。每个节点只能被创建一次:


           说明
  • 2个独立的Hadoop集群
  • 2个独立的Java应用
  • 3个Zookeeper集群几点
            
           /queue是队列的目录;
            /queue/purchase是队列的采购排队节点,对应Hadoop App1完成任务后在ZK上创建;
            /queue/sell 是队列的销售排队节点,对应Hadoop App2完成计算后在ZK上创建;
            /queue/other 是队列的其他费用节点,对应Java程序 App3完成计算后在ZK上创建;
            /queue/profit是队列的利润节点,当前三个节点都创建成功后,触发该节点的创建,完成利润计算;

            创建/queue/purchase,/queue/sell,/queue/other目录时,没有前后顺序,程序提交后,/queue目录下会生成对应该子目录;
                /queue/profit被创建后,zk的应用会监听到这个事件,通知应用,队列已完成。
            (PS:向下的红色箭头代表,利润节点创建完成后,删除业务几点zk1、zk2、zk3释放对应Hadoop计算节点资源)

        3.实验环境:
          开发环境: Win7 64bit、JDK1.6、Maven3、Eclipse Luna;
          Zookeeper服务器:三台服务器几点,CentOS 6.5、zookeeper-3.4.5、JDK1.6  
          Hadoop集群:  CentOS 6.5、JDK1.6、Hadoop-1.2.1
         提前完成Hadoop集群和Zookeep集群的搭建,并启动;  

        4.实验数据:
  • 采购数据:purchase.csv,格式示例:
             一共4列,分别对应 产品ID,产品数量,产品单价,采购日期,
1,26,1168,2013-01-08
2,49,779,2013-02-12
3,80,850,2013-02-05
4,69,1585,2013-01-26
5,88,1052,2013-01-13
6,84,2363,2013-01-19
7,64,1410,2013-01-12
8,53,910,2013-01-11
9,21,1661,2013-01-19
10,53,2426,2013-02-18
  • 销售数据:sell.csv,格式示例:
一共4列,分别对应 产品ID,销售数量,销售单价,销售日期 ,
1,14,1236,2013-01-14
2,19,808,2013-03-06
3,26,886,2013-02-23
4,23,1793,2013-02-09
5,27,1206,2013-01-21
6,27,2648,2013-01-30
7,22,1502,2013-01-19
8,20,1050,2013-01-18
9,13,1778,2013-01-30
10,20,2718,2013-03-14
              其他费用数据集:other.csv,格式示例:
            一共2列,分别对应 发生日期,发生金额
2013-01-02,552
2013-01-03,1092
2013-01-04,1794
2013-01-05,435
2013-01-06,960
2013-01-07,1066
2013-01-08,1354
2013-01-09,880
2013-01-10,1992
2013-01-11,931
5.程序开发:
  • 使用Maven构建Java Project,myZookeeper,目录机构如下:
     

  •   项目使用Maven进行依赖包的管理,pom.xml文件引入:
   <dependencies>
           <dependency>
                <groupId>org.apache.hadoop</groupId>
                <artifactId>hadoop-core</artifactId>
                <version>1.2.1</version>
           </dependency>
           <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.4</version>
                <scope>test</scope>
           </dependency>
           <dependency>
                <groupId>org.apache.zookeeper</groupId>
                <artifactId>zookeeper</artifactId>
                <version>3.4.6</version>
           </dependency>
     </dependencies>
    
  • 类说明,总共新建6个Java类,其中:
       HdfsDao.java操作HDFS的工具类,实现本地对HDFS文件的基本操作,常规操作,有兴趣可以参考项目源码;               Purchase.java:基于MapReduce的采购金额的计算:(一个简单的MR任务)
 
package org.bd.ytg.zookeeper;import java.io.IOException;import java.util.HashMap;import java.util.Iterator;import java.util.Map;import java.util.regex.Pattern;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IntWritable;import org.apache.hadoop.io.LongWritable;import org.apache.hadoop.io.Text;import org.apache.hadoop.mapred.FileInputFormat;import org.apache.hadoop.mapred.FileOutputFormat;import org.apache.hadoop.mapred.JobClient;import org.apache.hadoop.mapred.JobConf;import org.apache.hadoop.mapred.MapReduceBase;import org.apache.hadoop.mapred.Mapper;import org.apache.hadoop.mapred.OutputCollector;import org.apache.hadoop.mapred.Reducer;import org.apache.hadoop.mapred.Reporter;import org.apache.hadoop.mapred.TextInputFormat;import org.apache.hadoop.mapred.TextOutputFormat;import org.bd.ytg.hdfs.HdfsDao;/** * 计算2013年1月的采购金额 * @author gaoyongtao * * 2017年11月8日 */public class Purchase {public static final String HDFS_HOST = "hdfs://192.168.203.10:9000/";public static final Pattern DELIMITER = Pattern.compile("[\t,]");public  static class  PurchaseMapper extends MapReduceBase implements Mapper<LongWritable, Text, Text, IntWritable>{//一共4列,分别对应 产品ID,产品数量,产品单价,采购日期//1,26,1168,2013-01-08//2,49,779,2013-02-12//3,80,850,2013-02-05//4,69,1585,2013-01-26//5,88,1052,2013-01-13public static final String MONTH = "2013-01";// 输出的Keystatic Text oneMonth = new Text(MONTH);// 输出的valueIntWritable money = new IntWritable();@Overridepublic void map(LongWritable key, Text value,OutputCollector<Text, IntWritable> outputCollector, Reporter reporter)throws IOException {// hadoop的输入 这个value是1行数据,是的;每一行数据列之间以'/t'进行分割System.out.println("PurchaseMapper excete in map,key=:"+key+",line=:"+value.toString());//PurchaseMapper excete in map,key=:0,line=:1,26,1168,2013-01-08String[] datas = DELIMITER.split(value.toString());if(datas.length>=3 && datas[3].startsWith(MONTH)){int sum = 0;sum = Integer.parseInt(datas[1]) * Integer.parseInt(datas[2]);money.set(sum);outputCollector.collect(oneMonth, money);}}}public static class PurchaseReducer extends  MapReduceBase implements Reducer<Text, IntWritable, Text, IntWritable>{private IntWritable v = new IntWritable();Text myKey = new Text();    private int totalMoney = 0;         @Override    public void reduce(Text key, Iterator<IntWritable> values,OutputCollector<Text, IntWritable> outputCollector, Reporter reporter)throws IOException {while(values.hasNext()){int money = values.next().get();System.out.println("PurchaseReducer excete in reduce,key=:"+key+",values.next().get()=:"+money);totalMoney += money;}//myKey.set(string);v.set(totalMoney);//outputCollector.collect(key, v);  //如果此处输出带上key,则MR的输出即为2013-01,XXXXoutputCollector.collect(null, v); // 不带key,则MR输出为XXXX,只有一个金额System.out.println("Output:" + key + "," + totalMoney);}} public static void runPurchase(Map<String, String> path) throws IOException, InterruptedException, ClassNotFoundException {        JobConf conf = getHadoopConfig();        String local_data = path.get("purchase");        String input = path.get("input");        String output = path.get("output");        // 初始化HDFS访问层        HdfsDao hdfs = new HdfsDao(HDFS_HOST, conf);        hdfs.rmr(input);//        hdfs.rmr(output);        hdfs.mkdirs(input);        hdfs.copyFile(local_data, input);    conf.setOutputKeyClass(Text.class);conf.setOutputValueClass(IntWritable.class);conf.setMapperClass(PurchaseMapper.class);conf.setReducerClass(PurchaseReducer.class);conf.setInputFormat(TextInputFormat.class);conf.setOutputFormat(TextOutputFormat.class);FileInputFormat.setInputPaths(conf, new Path(input));FileOutputFormat.setOutputPath(conf, new Path(output));JobClient.runJob(conf);    }  public static JobConf getHadoopConfig() {JobConf conf = new JobConf();conf.setJobName("purchaseJob");conf.addResource("classpath:/hadoop/core-site.xml");conf.addResource("classpath:/hadoop/hdfs-site.xml");conf.addResource("classpath:/hadoop/mapred-site.xml");conf.addResource("classpath:/hadoop/masters");conf.addResource("classpath:/hadoop/slaves");return conf;}  public static Map<String,String> pathConfigMap(){        Map<String, String> path = new HashMap<String, String>();        path.put("purchase", "logfile/biz/purchase.csv");// 本地的数据文件        path.put("input", HDFS_HOST + "dataguru/hdfs/purchase/");// HDFS的目录        path.put("output", HDFS_HOST + "purchaseresult"); // 输出目录        return path;    }    public static void main(String[] args) throws Exception {      runPurchase(pathConfigMap());    }}

       Sell.javaMapReduce的销售金额的计算:
        Other.java :其他费用的Java App计算;
        ProfitCaculate.java:利润计算的Java App;
        ZookeeperJob.java :ZK任务调度类,各个业务节点,在完成自身节点创建完成后,判断队列创建是否完成(/queue的子节点个数是否等于3),如果是,则触发对PROFIT        节点的创建,生成利润节点,进行利润的计算:
     
package org.bd.ytg.zookeeper;import java.io.IOException;import java.util.List;import org.apache.zookeeper.CreateMode;import org.apache.zookeeper.KeeperException;import org.apache.zookeeper.WatchedEvent;import org.apache.zookeeper.Watcher;import org.apache.zookeeper.ZooDefs.Ids;import org.apache.zookeeper.ZooKeeper;/** * ZooKeeper任务调度类: * 各个业务节点,在完成自身节点创建完成后,判断队列创建是否完成(/queue的子节点个数是否等于3), * 如果是,则触发对PROFIT节点的创建,生成利润节点,进行利润的计算 * @author gaoyongtao * * 2017年11月8日 */public class ZooKeeperJob {    final public static String QUEUE = "/queue";   //父節點    final public static String PURCHASE = "/queue/purchase";    final public static String SELL = "/queue/sell";    final public static String OTHER = "/queue/other";        final public static String PROFIT = "/queue/profit";        // 创建一个与服务器的连接,监控节点创建事件    public static ZooKeeper connection(String host) throws IOException {        ZooKeeper zk = new ZooKeeper(host, 60000, new Watcher() {            // 监控所有被触发的事件            public void process(WatchedEvent event) {                if (event.getType() == Event.EventType.NodeCreated && event.getPath().equals(PROFIT)) {                    System.out.println("Queue has Completed!!!");                }            }        });        return zk;    }        // 初始化隊列    public static void initQueue(ZooKeeper zooKeeper) throws KeeperException, InterruptedException{    System.out.println("WATCH => " + PROFIT);        // 如果這個節點存在    zooKeeper.exists(QUEUE, true);        // 節點不存在,則創建該節點    if (zooKeeper.exists(QUEUE, false) == null) {            System.out.println("create " + QUEUE);            zooKeeper.create(QUEUE, QUEUE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);        } else {            System.out.println(QUEUE + " is exist!");        }    }       // 判斷隊列的節點是否全部創建完成    public static void isCompleted(ZooKeeper zk) throws Exception {    // 共三個節點:採購、銷售、其他        int size = 3;        List<String> children = zk.getChildren(QUEUE, true);        int length = children.size();        System.out.println("Queue Complete:" + length + "/" + size);        if (length >= size) {            System.out.println("create " + PROFIT);                        String profit = String.valueOf(ProfitCaculate.profit());                        System.out.println(profit);                        zk.create(PROFIT, profit.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);                        List<String> queueChildren = zk.getChildren(QUEUE, true);                        for (String child : queueChildren) {            System.out.println("完成利润节点的创建,Queue的子节点有:");System.out.print(child+" ");}                       try {   String profitMoney = new String(zk.getData(PROFIT, null, null));      System.out.println(profitMoney);} catch (Exception e) {System.out.println("获取PROFIT值异常");e.printStackTrace();}                     /*   for (String child : children) {// 清空节点,釋放服務器對該業務節點的監控,保留利润节点            if(!PROFIT.equals(QUEUE + "/" + child)){            zk.delete(QUEUE + "/" + child, -1);            }            }*/        }    }        // 如果隊列上不存在採購節點,則執行採購金額計算MR任務,并創建採購節點,完成后判斷隊列節點是否創建完成    public static void doPurchase(ZooKeeper zk) throws Exception {        if (zk.exists(PURCHASE, false) == null) {                        Purchase.runPurchase(Purchase.pathConfigMap());                        System.out.println("create " + PURCHASE);            zk.create(PURCHASE, PURCHASE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);        } else {            System.out.println(PURCHASE + " is exist!");        }        isCompleted(zk);    } // 如果隊列上不存在銷售節點,則執行銷售金額計算MR任務,并創建銷售節點,完成后判斷隊列節點是否創建完成    public static void doSell(ZooKeeper zk) throws Exception {        if (zk.exists(SELL, false) == null) {                        Sell.runSell(Sell.pathConfigMap());                        System.out.println("create " + SELL);            zk.create(SELL, SELL.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);        } else {            System.out.println(SELL + " is exist!");        }        isCompleted(zk);    }   // 如果隊列上不存在其他費用節點,則執行其他費用金額計算任務,并創建採購節點,完成后判斷隊列節點是否創建完成    public static void doOther(ZooKeeper zk) throws Exception {        if (zk.exists(OTHER, false) == null) {                        Other.calcOther(Other.file);                        System.out.println("create " + OTHER);            zk.create(OTHER, OTHER.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);        } else {            System.out.println(OTHER + " is exist!");        }        isCompleted(zk);    }        public static void doAction(int client) throws Exception {        String host1 = "192.168.203.10:2181";        String host2 = "192.168.203.10:2181";        String host3 = "192.168.203.10:2181";        ZooKeeper zk = null;        switch (client) {        case 1:            zk = connection(host1);            initQueue(zk);            doPurchase(zk);            break;        case 2:            zk = connection(host2);            initQueue(zk);            doSell(zk);            break;        case 3:            zk = connection(host3);            initQueue(zk);            doOther(zk);            break;        }    }        public static void main(String[] args) throws Exception {        doAction(Integer.parseInt("1"));            doAction(Integer.parseInt("2"));            doAction(Integer.parseInt("3"));                }}


     6.程序运行:
      启动Hadoop集群,启动ZK集群,检查应用进程均正常:(master既作为Hadoop的主节点,也作为Zookeeper的主节点,salve1和slave2作为从节点)
  
       分别进行调试,完成两个MapReduce任务单独运行成功(确保MR任务无bug,毕竟此处这不是我们的重点);
         
        运行过MR任务后,需要对HDFS进行初始化,还原到最初的环境,人生若只如初见:
      
        
       下面,运行ZookeeperJob.java中的main方法,进行集群协调的验证,运行结果:
        实验数据文件由本地上传至HDFS:
      
           
       采购MapReduce任务金额的计算:
      

       销售MapReduce任务金额的计算:
       
       其他费用为本地Java 应用计算金额:
        

       查看Zookeeper的queue队列:(此处为查看计算结果,暂未删除ZK节点释放资源):
      
        查看Zookeeper上的profit节点:
      
       Eclipse输出日志:
       

  7.小结:       
      通过同步的分步式队列自动启动了计算利润的程序,并在日志中打印了2013年1月的利润为-6693765,以此模拟实现这个分布式队列的Demo完成。 当然程序中还有许多不严谨的地方,以待继续优化完善,不知情所起,一往情深。



附:完整项目代码参考:https://gitee.com/tonnygao/JiYuZooKeeperShiXianFenBuShiDuiLieXiTongShiXianMapReduceRenWuJiCheng.git


















原创粉丝点击