一、背景

  有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做。这就有点像业务系统的工作流一样,一环一环地传下

去,直到最后一部分完成。在业务系统中,我们经常会用SOA的架构来解决这种问题,每个团队在ESB(企业服务股总线)服务器上部署自己的服务,然后通过消息中间件完成调度任务。对亍分步式的多个

Hadoop集群系统的协作,同样可以用这种架构来做只要把消息中间件引擎换成支持分步式的消息中间件的引擎就行了。

  本文楼主将使用zookeeper做为分步式消息中间件构造一个大型超市的部分数据计算模型来完成各个区域利润计算的业务需求。

  由于采购和销售分别是由不同厂商进行的软件开发和维护,而且业务往来也在不同的城市和地区。 所以在每月底结算时,工作量都特别大。 比如,计算利润表: 当月利润 = 当月销售金额 - 当月采购

额 - 当月其他支出(楼主只是粗略计算)。如果采购系统是单独的系统,销售是另外单独的系统,及以其他几十个大大小小的系统, 如何能让多个系统,配合起来完成该需求?

二、系统构思

  楼主基于zookeeper来构建一个分步式队列的应用,来解决上面的功能需求。排除了ESB的部分,只保留zookeeper进行实现。

  1.   采购数据:海量数据,基于Hadoop存储和分析(楼主环境有限,只使用了很少的数据)
  2.   销售数据:海量数据,基于Hadoop存储和分析(楼主环境有限,只使用了很少的数据)
  3.   其他费用支出:为少量数据,基于文件或数据库存储和分析

  我们设计一个同步队列,这个队列有3个条件节点,分别对应采购(purchase),销售 (sell),其他费用(other)3个部分。当3个节点都被创建后,程序会自动触发计算利润, 幵创建利润(profit)节点。上面3个节点的创建,无顺序要求。每个节点只能被创建一次 。

  

  Hadoop mapreduce1,Hadoop mapreduce2 是2个独立的Hadoop集群应用。 Java App 是2个独立的Java应用 。ZooKeeper集群的有3个节点 。

  • /queue,是znode的队列目录,假设队列长度为3
  • /queue/purchase,是znode队列中,1号排对者,由Hadoop mapreduce1提交,用于统计采购金额
  • /queue/sell,是znode队列中,2号排对者,由Hadoop mapreduce2提交,用于统计销售金额
  • /queue/other,是znode队列中,3号排对者,由Java App提交,用于统计其他费用支出金额
  • /queue/profit,当znode队列中满了,触发创建利润节点。

  当/qeueu/profit被创建后,利润java app被启动,所有zookeeper的连接通知同步程序(红色线),队列已完成,所有程序结束。

三、环境准备

  1)hadoop集群。楼主用的6个节点的hadoop2.7.3集群,各位同学可以根据自己的实际情况进行搭建,但至少需要1台伪分布式的。(参考http://www.cnblogs.com/qq503665965/p/6790580.html

  2)zookeeper集群。至少三个节点。安装参考楼主这篇文章(http://www.cnblogs.com/qq503665965/p/6790580.html

  3)java开发环境。

四、mapreduce及java app程序

  计算采购金额:

 package zkqueue;
import java.io.IOException;
import java.util.HashMap;
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.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; /**
* 采购金额计算
* @author Jon_China
*
*/
public class Purchase { public static final String HDFS = "hdfs://192.168.8.101:9000";
public static final Pattern DELIMITER = Pattern.compile("[\t,]"); public static class PurchaseMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private String month = "2017-01";
private Text k = new Text(month);
private IntWritable v = new IntWritable();
private int money = 0; public void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
System.out.println(values.toString());
String[] tokens = DELIMITER.split(values.toString());//拆分源数据
if (tokens[3].startsWith(month)) {// 过滤1月份数据
money = Integer.parseInt(tokens[1]) * Integer.parseInt(tokens[2]);//计算
v.set(money);
context.write(k, v);
}
}
} public static class PurchaseReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable v = new IntWritable();
private int money = 0; @Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
for (IntWritable line : values) {
money += line.get();
}
v.set(money);
context.write(null, v);
System.out.println("Output:" + key + "," + money);
} } public static void run(Map<String, String> path) throws IOException, InterruptedException, ClassNotFoundException {
JobConf conf = config();
String local_data = path.get("purchase");
String input = path.get("input");
String output = path.get("output"); HdfsDAO hdfs = new HdfsDAO(HDFS, conf);
hdfs.rmr(input);
hdfs.mkdirs(input);
hdfs.copyFile(local_data, input); Job job = Job.getInstance(conf);
job.setJarByClass(Purchase.class); job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class); job.setMapperClass(PurchaseMapper.class);
job.setReducerClass(PurchaseReducer.class); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class); FileInputFormat.setInputPaths(job, new Path(input));
FileOutputFormat.setOutputPath(job, new Path(output)); job.waitForCompletion(true);
} public static JobConf config() {
JobConf conf = new JobConf(Purchase.class);
conf.setJobName("purchase");
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/yarn-site.xml");
return conf;
} public static Map<String,String> path(){
Map<String, String> path = new HashMap<String, String>();
path.put("purchase", Purchase.class.getClassLoader().getResource("logfile/biz/purchase.csv").getPath());// 源文件数据
path.put("input", HDFS + "/user/hdfs/biz/purchase");//hdfs存储路径
path.put("output", HDFS + "/user/hdfs/biz/purchase/output"); //hdfs输出路径
return path;
} public static void main(String[] args) throws Exception {
run(path());
} }

  销售数据计算:

 package zkqueue;

 import java.io.IOException;
import java.util.HashMap;
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.JobConf;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat; /**
* 销售数据计算
* @author Jon_China
*
*/
public class Sell { public static final String HDFS = "hdfs://192.168.8.101:9000";
public static final Pattern DELIMITER = Pattern.compile("[\t,]"); public static class SellMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private String month = "2013-01";
private Text k = new Text(month);
private IntWritable v = new IntWritable();
private int money = 0; public void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
System.out.println(values.toString());
String[] tokens = DELIMITER.split(values.toString());
if (tokens[3].startsWith(month)) {// 1月的数据
money = Integer.parseInt(tokens[1]) * Integer.parseInt(tokens[2]);//单价*数量
v.set(money);
context.write(k, v);
}
}
} public static class SellReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
private IntWritable v = new IntWritable();
private int money = 0; @Override
public void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
for (IntWritable line : values) {
money += line.get();
}
v.set(money);
context.write(null, v);
System.out.println("Output:" + key + "," + money);
} } public static void run(Map<String, String> path) throws IOException, InterruptedException, ClassNotFoundException {
JobConf conf = config();
String local_data = path.get("sell");
String input = path.get("input");
String output = path.get("output"); // 初始化sell
HdfsDAO hdfs = new HdfsDAO(HDFS, conf);
hdfs.rmr(input);
hdfs.mkdirs(input);
hdfs.copyFile(local_data, input); Job job = Job.getInstance(conf);
job.setJarByClass(Sell.class); job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class); job.setMapperClass(SellMapper.class);
job.setReducerClass(SellReducer.class); job.setInputFormatClass(TextInputFormat.class);
job.setOutputFormatClass(TextOutputFormat.class); FileInputFormat.setInputPaths(job, new Path(input));
FileOutputFormat.setOutputPath(job, new Path(output)); job.waitForCompletion(true);
} public static JobConf config() {// Hadoop集群的远程配置信息
JobConf conf = new JobConf(Purchase.class);
conf.setJobName("purchase");
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/yarn-site.xml");
return conf;
} public static Map<String,String> path(){
Map<String, String> path = new HashMap<String, String>();
path.put("sell", Sell.class.getClassLoader().getResource("logfile/biz/sell.csv").getPath());// 本地的数据文件
path.put("input", HDFS + "/user/hdfs/biz/sell");// HDFS的目录
path.put("output", HDFS + "/user/hdfs/biz/sell/output"); // 输出目录
return path;
} public static void main(String[] args) throws Exception {
run(path());
} }

  其他金额计算:

 package zkqueue;

 import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.regex.Pattern; public class Other { public static String file = "/logfile/biz/other.csv";
public static final Pattern DELIMITER = Pattern.compile("[\t,]");
private static String month = "2017-01"; public static void main(String[] args) throws IOException {
calcOther(file);
} public static int calcOther(String file) throws IOException {
int money = 0;
BufferedReader br = new BufferedReader(new FileReader(new File(file))); String s = null;
while ((s = br.readLine()) != null) {
String[] tokens = DELIMITER.split(s);
if (tokens[0].startsWith(month)) {// 1月的数据
money += Integer.parseInt(tokens[1]);
}
}
br.close();
System.out.println("Output:" + month + "," + money);
return money;
}
}

  计算利润:

  

 package zkqueue;

 import java.io.IOException;

 /**
* 利润计算
* @author Jon_China
*
*/
public class Profit { public static void main(String[] args) throws Exception {
profit();
} public static void profit() throws Exception {
int sell = getSell();
int purchase = getPurchase();
int other = getOther();
int profit = sell - purchase - other;
System.out.printf("profit = sell - purchase - other = %d - %d - %d = %d\n", sell, purchase, other, profit);
} public static int getPurchase() throws Exception {
HdfsDAO hdfs = new HdfsDAO(Purchase.HDFS, Purchase.config());
return Integer.parseInt(hdfs.cat(Purchase.path().get("output") + "/part-r-00000").trim());
} public static int getSell() throws Exception {
HdfsDAO hdfs = new HdfsDAO(Sell.HDFS, Sell.config());
return Integer.parseInt(hdfs.cat(Sell.path().get("output") + "/part-r-00000").trim());
} public static int getOther() throws IOException {
return Other.calcOther(Other.file);
} }

  zookeeper任务调度:

 package zkqueue;

 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调度
* @author Jon_China
*
*/
public class QueueZookeeper {
//设置队列目录树
final public static String QUEUE = "/queue";
final public static String PROFIT = "/queue/profit";
final public static String PURCHASE = "/queue/purchase";
final public static String SELL = "/queue/sell";
final public static String OTHER = "/queue/other"; public static void main(String[] args) throws Exception {
if (args.length == 0) {
System.out.println("Please start a task:");
} else {
doAction(Integer.parseInt(args[0]));
}
}
public static void doAction(int client) throws Exception {
//zookeeper地址
String host1 = "192.168.8.104:2181";
String host2 = "192.168.8.105:2181";
String host3 = "192.168.8.106:2181"; ZooKeeper zk = null;
switch (client) {//1,2,3分别将不同任务加入队列
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 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;
}
/**
* 初始化队列
* @param zk
* @throws KeeperException
* @throws InterruptedException
*/
public static void initQueue(ZooKeeper zk) throws KeeperException, InterruptedException {
System.out.println("WATCH => " + PROFIT);
zk.exists(PROFIT, true); if (zk.exists(QUEUE, false) == null) {
System.out.println("create " + QUEUE);
zk.create(QUEUE, QUEUE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} else {
System.out.println(QUEUE + " is exist!");
}
}
/**
* 采购任务
* @param zk
* @throws Exception
*/
public static void doPurchase(ZooKeeper zk) throws Exception {
if (zk.exists(PURCHASE, false) == null) { Purchase.run(Purchase.path()); 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);
}
/**
* 销售任务
* @param zk
* @throws Exception
*/
public static void doSell(ZooKeeper zk) throws Exception {
if (zk.exists(SELL, false) == null) { Sell.run(Sell.path()); 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);
}
/**
* 其他计算任务
* @param zk
* @throws Exception
*/
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);
}
/**
* 检测完成情况
* @param zk
* @throws Exception
*/
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);
Profit.profit();
zk.create(PROFIT, PROFIT.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); for (String child : children) {// 清空节点
zk.delete(QUEUE + "/" + child, -1);
} }
}
}

四、运行结果

  在最后一步,统计其他费用数据程序运行后,从日志中看到3个条件节点都已满足要求 。然后,通过同步的分步式队列自动启动了计算利润的程序,幵在日志中打印了2017 年1月的利润为-6693765。

  示例代码地址:https://github.com/LJunChina/hadoop/tree/master/distributed_mq

实现分布式队列ZooKeeper的实现的更多相关文章

  1. 分布式队列ZooKeeper的实现

    一.背景 有一些时候,多个团队需要共同完成一个任务,比如,A团队将Hadoop集群计算的结果交给B团队继续计算,B完成了自己任务再交给C团队继续做.这就有点像业务系统的工作流一样,一环一环地传下 去, ...

  2. [转载] ZooKeeper实现分布式队列Queue

    转载自http://blog.fens.me/zookeeper-queue/ 让Hadoop跑在云端系列文章,介绍了如何整合虚拟化和Hadoop,让Hadoop集群跑在VPS虚拟主机上,通过云向用户 ...

  3. ZooKeeper 实现分布式队列

    使用场景  在传统的单进程编程中,我们使用队列来存储数据结构,用来在多线程之间共享或者传递数据.在分布式环境下,同样需要一个类似单进程的组件, 用来实现跨进程.跨主机.跨网络的数据共享和数据传递.这就 ...

  4. 一种基于zookeeper的分布式队列的设计与实现

    package com.ysl.zkclient.queue; import com.ysl.zkclient.ZKClient; import com.ysl.zkclient.exception. ...

  5. ZooKeeper实现分布式队列Queue

    ZooKeeper实现分布式队列Queue 让Hadoop跑在云端系列文章,介绍了如何整合虚拟化和Hadoop,让Hadoop集群跑在VPS虚拟主机上,通过云向用户提供存储和计算的服务. 现在硬件越来 ...

  6. 【分布式】Zookeeper应用场景

    一.前言 在上一篇博客已经介绍了Zookeeper开源客户端的简单实用,本篇讲解Zookeeper的应用场景. 二.典型应用场景 Zookeeper是一个高可用的分布式数据管理和协调框架,并且能够很好 ...

  7. 【分布式】Zookeeper与Paxos

    一.前言 在学习了Paxos在Chubby中的应用后,接下来学习Paxos在开源软件Zookeeper中的应用. 二.Zookeeper Zookeeper是一个开源的分布式协调服务,其设计目标是将那 ...

  8. 分布式助手Zookeeper(一)

    分布式助手Zookeeper(一)博客分类: Zookeeper   Zookeeper最早是Hadoop的一个子项目,主要为Hadoop生态系统中一些列组件提供统一的分布式协作服务,在2010年10 ...

  9. 分布式队列神器 Celery

    Celery 是什么? Celery 是一个由 Python 编写的简单.灵活.可靠的用来处理大量信息的分布式系统,它同时提供操作和维护分布式系统所需的工具. Celery 专注于实时任务处理,支持任 ...

随机推荐

  1. 前端开发框架简介:angular和react

    作者:vienwu react是facebook推出一个用来构建用户界面的js库.官方介绍的三大特性如下: just the ui 把react只当作一个ui组件就好,等同于传统mvc中的view. ...

  2. mysql数据库开启日志

    旧版 #开启慢查询 slow_query_log # (超过2秒的SQL语法记录起来,设短一点来记录除错也是一种方法.) long_query_time = 2 log-slow-queries=D: ...

  3. Mac IDEA插件——protobuf 插件

    最近在搞PB的版本的升级,历史的PB的版本是2.4,现在是打算升级到3.2,当面PB的版本肯定有很多变化了,就不再这里多说了,这里重点说一说,采用IDEA的插件方便执行PB的文件的JAVA编译,这样的 ...

  4. bzoj4031 [HEOI2015]小Z的房间

    Description 你突然有了一个大房子,房子里面有一些房间.事实上,你的房子可以看做是一个包含n*m个格子的格状矩形,每个格子是一个房间或者是一个柱子.在一开始的时候,相邻的格子之间都有墙隔着. ...

  5. edge animate从入门到放弃

    一.什么是edge animate edge animate这是一款方便网页设计师和前端工程师实现动画交互的一款工具,虽然是adobe出品的,但是属于Flash和H5时代的过渡产物,这一款产品在201 ...

  6. [ext4]空间管理 - 与分配相关的关键数据结构

     在块分配机制中,涉及到几个主要的数据结构. 通过ext4_allocation_request描述块请求,然后基于块查找结果即上层需求来决定是否执行块分配操作. 在分配过程中,为了更好执行分配, ...

  7. String转int数字格式异常问题

     写在前面的话 差不多一年前就计划写博客,可因为种种原因一直没有写,反而我身边的一些同学在我建议他们写博客不久之后就写了,比如张博同学,基本每次总结一个知识点就写一篇,这样不但方便自己以后查看翻阅,也 ...

  8. ECMAScript迭代语句

    迭代语句又叫循环语句,声明一组要反复执行的命令,直到满足某些条件为止. 循环通常用于迭代数组的值(因此而得名),或者执行重复的算术任务. do-while, while, for, for-in -- ...

  9. PS不能存储,因为程序错误

    当PS中遇到不能存储文件,因为程序错误时,可以这样: http://www.zcool.com.cn/article/ZMTgwOTQw.html

  10. 服务器数据库挂掉 Can 't connect to local MySQL server through socket '/var/lib/mysql/mysql.sock '(2) ";

    刚刚遇到这个报错,我无语了,我这么个菜逼,咋解决,还好师兄(付付)解决了,付付真棒,在此记录一下,以供学习 Can 't connect to local MySQL server through s ...