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

          随着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个节点的创建,无顺序要求。每个节点只能被创建一次:
ZooKeeper分布式队列实现MapReduce任务集成_第1张图片

           说明
  • 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,目录机构如下:
      ZooKeeper分布式队列实现MapReduce任务集成_第2张图片

  •   项目使用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{
		//一共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
		
		public static final String MONTH = "2013-01";
		
		// 输出的Key
		static Text oneMonth = new Text(MONTH);
		
		// 输出的value
		IntWritable money = new IntWritable();
		
		@Override
		public void map(LongWritable key, Text value,OutputCollector 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-08
			String[] 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{

		private IntWritable v = new IntWritable();
		
		Text myKey = new Text();
		
	    private int totalMoney = 0;
	     
	    @Override
	    public void reduce(Text key, Iterator values,OutputCollector 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,XXXX
			outputCollector.collect(null, v); // 不带key,则MR输出为XXXX,只有一个金额
			System.out.println("Output:" + key + "," + totalMoney);
		}
	}
	
	 public static void runPurchase(Map 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 pathConfigMap(){
	        Map path = new HashMap();
	        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 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 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作为从节点)
ZooKeeper分布式队列实现MapReduce任务集成_第3张图片  
         分别进行调试,完成两个MapReduce任务单独运行成功( 确保MR任务无bug,毕竟此处这不是我们的重点 );
           
          运行过MR任务后,需要对HDFS进行初始化,还原到最初的环境,人生若只如初见:
      
        
       下面,运行 ZookeeperJob.java中的main方法,进行集群协调的验证,运行结果:
        实验数据文件由本地上传至HDFS:
      
           
       采购MapReduce任务金额的计算:
      

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

       查看 Zookeeper的queue队列: (此处为查看计算结果,暂未删除ZK节点释放资源):
      
        查看Zookeeper上的profit节点:
       ZooKeeper分布式队列实现MapReduce任务集成_第4张图片
       Eclipse输出日志:
        ZooKeeper分布式队列实现MapReduce任务集成_第5张图片

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



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


















你可能感兴趣的:(ZooKeeper分布式队列实现MapReduce任务集成)