基於Zookeeper的分步式隊列系統集成案例

轉自:http://www.aboutyun.com/thread-9004-1-1.html

本帖最後由 sstutu 於 2014-9-2 22:58 編輯
問題導讀:

1.Zookeeper在hadoop家族中作用是什麼?
2.Zookeeper如何起到分佈式中間件的作用?
3.計算某月的利潤,你認爲hadoop基於Zookeeper的程序該如何設計?





前言

軟件系統集成一直是工業界的一個難題,像10年以上的遺留系統集成,公司收購後的多系統集成,全球性的分步式系統集成等。雖然基於SOA的軟件架構,從理論上都可以解決這些集成的問題,但是具體實施過程,有些集成項目過於複雜而失敗。

隨着技術的創新和發展,對於分步式集羣應用的集成,有了更好的開源軟件的支持,像zookeeper就是一個不錯的分步式協作軟件平臺。本文將通過一個案例介紹Zookeeper的強大。

目錄
  • 項目背景:分佈式消息中間件
  • 需求分析:業務系統升級方案
  • 架構設計:搭建Zookeeper的分步式協作平臺
  • 程序開發:基於Zookeeper的程序設計
  • 程序運行

1. 項目背景:分佈式消息中間件
隨着Hadoop的普及,越來越多的公司開始構建自己的Hadoop系統。有時候,公司內部的不同部門或不同的團隊,都有自己的Hadoop集羣。這種多集羣的方式,既能讓每個團隊擁有個性化的Hadoop,又能避免大集羣的高度其中化運維難度。當數據量不是特別巨大的時候,小型集羣會有很多適用的場合。

當然,多個小型集羣也有缺點,就是資源配置可能造成浪費。每個團隊的Hadoop集羣,都要配有服務器和運維人員。有些能力強的團隊,構建的hadoop集羣,可以達到真正的個性化要求;而有一些能力比較差的團隊,搭建的Hadoop集羣性能會比較糟糕。

還有一些時候,多個團隊需要共同完成一個任務,比如,A團隊通過Hadoop集羣計算的結果,交給B團隊繼續工作,B完成了自己任務再交給C團隊繼續做。這就有點像業務系統的工作流一樣,一環一環地傳下去,直到最後一部分完成。

在業務系統中,我們經常會用SOA的架構來解決這種問題,每個團隊在ESB服務器上部署自己的服務,然後通過消息中間件完成調度任務。對於分步式的多個Hadoop集羣系統的協作,同樣可以用這種架構來做,只要把消息中間件引擎換成支持分步式的消息中間件的引擎就行了。

Zookeeper就可以做爲 分步式消息中間件,來完成上面的說的業務需求。ZooKeeper是Hadoop家族的一款高性能的分佈式協作的產品,是一個爲分佈式應用所設計的分佈的、開源的協調服務,它主要是用來解決分佈式應用中經常遇到的一些數據管理問題,簡化分佈式應用協調及其管理的難度,提供高性能的分佈式服務。Zookeeper的安裝和使用,請參考文章ZooKeeper僞分佈式集羣安裝及使用
ZooKeeper提供分佈式協作服務,並不需要依賴於Hadoop的環境。

2. 需求分析:業務系統升級方案
下面我將從一個案例出發,來解釋如何進行分步式協作平臺的系統設計。

2.1 案例介紹

某大型軟件公司,從事領域爲供應鏈管理,主要業務包括了 採購管理、應付賬款管理、應收賬款管理、供應商反覆管理、退貨管理、銷售管理、庫存管理、電子商務、系統集成等。


每塊業務的邏輯都很複雜,由單獨部門進行軟件開發和維護,部門之間的系統沒有直接通信需求,每個部門完成自己的功能就行了,最後通過數據庫來共享數據,實現各功能之間的數據交換。


隨着業務的發展,客戶對響應速度要求越來越高,通過數據庫來共享數據的方式,已經達不到信息交換的要求,系統進行了第一次升級,通過企業服務總線(ESB)統一管理公司內部所有業務。通過WebServices發佈服務,通過Message Queue實現業務功能的調度。


公司業務規模繼續擴大,跨國收購了多家公司。業務系統從原來的一個機房的集中式部署,變成了全球性的多機房的分步式部署。這時,Message Queue已經不能滿足多機房跨地域的業務系統的功能需求了,需要一種分步式的消息中間件解決方案,來代替原有消息中間件的服務。
系統進行了第二次升級,採用Zookeeper作爲分步式中間件調度引擎。


通過上面的描述,我們可以看出,當一個公司從小到大,從國內業務發展到全球性業務的時候。
爲了配合業務發展,IT系統也是越來越複雜的,從最早的主從數據庫設計,到ESB企業系統總線的擴展,再到分步式ESB配合分步式消息系統,每一次的升級都需要軟件技術的支撐。

2.2 功能需求
全球性採購業務和全球性銷售業務,讓公司在市場中處於競爭優勢。但由於採購和銷售分別是由不同部門進行的軟件開發和維護,而且業務往來也在不同的國家和地區。所以在每月底結算時,工作量都特別大。
比如,計算利潤表 (請不要糾結於公式的準確性)
當月利潤 = 當月銷售金額 - 當月採購金額 - 當月其他支出
這樣一個非常簡單的計算公式,但對於跨國公司和部門來說,一點也不簡單的。
從系統角度來看,採購部門要統計採購數據(海量數據),銷售部門統計銷售數據((海量數據),其他部門統計的其他費用支出(彙總的少量數據),最後系統計算得到當月的利潤。

這裏要說明的是,採購系統是單獨的系統,銷售是另外單獨的系統,及以其他幾十個大大小小的系統,如何能讓多個系統,配合起來做這道計算題呢??

3. 架構設計:搭建Zookeeper的分步式協作平臺
接下來,我們基於zookeeper來構建一個分步式隊列的應用,來解決上面的功能需求。下面內容,排除了ESB的部分,只保留zookeeper進行實現。
  • 採購數據,爲海量數據,基於Hadoop存儲和分析。
  • 銷售數據,爲海量數據,基於Hadoop存儲和分析。
  • 其他費用支出,爲少量數據,基於文件或數據庫存儲和分析。
我們設計一個同步隊列,這個隊列有3個條件節點,分別對應採購(purchase),銷售(sell),其他費用(other)3個部分。當3個節點都被創建後,程序會自動觸發計算利潤,並創建利潤(profit)節點。上面3個節點的創建,無順序要求。每個節點只能被創建一次。


系統環境
  • 2個獨立的Hadoop集羣
  • 2個獨立的Java應用
  • 3個Zookeeper集羣節點

圖標解釋:
  • Hadoop App1,Hadoop App2 是2個獨立的Hadoop集羣應用
  • Java App3,Java App4 是2個獨立的Java應用
  • zk1,zk2,zk3是ZooKeeper集羣的3個連接點
  • /queue,是znode的隊列目錄,假設隊列長度爲3
  • /queue/purchase,是znode隊列中,1號排對者,由Hadoop App1提交,用於統計採購金額。
  • /queue/sell,是znode隊列中,2號排對者,由Hadoop App2提交,用於統計銷售金額。
  • /queue/other,是znode隊列中,3號排對者,由Java App3提交,用於統計其他費用支出金額。
  • /queue/profit,當znode隊列中滿了,觸發創建利潤節點。
  • 當/qeueu/profit被創建後,app4被啓動,所有zk的連接通知同步程序(紅色線),隊列已完成,所有程序結束。
補充說明:
  • 創建/queue/purchase,/queue/sell,/queue/other目錄時,沒有前後順序,程序提交後,/queue目錄下會生成對應該子目錄
  • App1可以通過zk2提交,App2也可通過zk3提交。原則上,找最近路由最近的znode節點提交。
  • 每個應用不能重複提出,直到3個任務都提交,計算利潤的任務纔會被執行。
  • /queue/profit被創建後,zk的應用會監聽到這個事件,通知應用,隊列已完成!
這裏的同步隊列的架構更詳細的設計思路,請參考文章 ZooKeeper實現分佈式隊列Queue

4. 程序開發:基於Zookeeper的程序設計
最終的功能需求:計算2013年01月的利潤。
4.1 實驗環境
在真正企業開發時,我們的實驗環境應該與需求是一致的,但我的硬件條件有限,因些做了一個簡化的環境設置。
  • 把zookeeper的完全分步式部署的3臺服務器集羣節點的,改爲一臺服務器上3個集羣節點。
  • 把2個獨立Hadoop集羣,改爲一個集羣的2個獨立的MapReduce任務。
開發環境:
  • Win7 64bit
  • JDK 1.6
  • Maven3
  • Juno Service Release 2
  • IP:192.168.1.10
Zookeeper服務器環境:
  • Linux Ubuntu 12.04 LTS 64bit
  • Java 1.6.0_29
  • Zookeeper: 3.4.5
  • IP: 192.168.1.201
  • 3個集羣節點
Hadoop服務器環境:
  • Linux Ubuntu 12.04 LTS 64bit
  • Java 1.6.0_29
  • Hadoop: 1.0.3
  • IP: 192.168.1.210

4.2 實驗數據
3組實驗數據:
  • 採購數據,purchase.csv
  • 銷售數據,sell.csv
  • 其他費用數據,other.csv

4.2.1 採購數據集
一共4列,分別對應 產品ID,產品數量,產品單價,採購日期。

  1. 1,26,1168,2013-01-08
  2. 2,49,779,2013-02-12
  3. 3,80,850,2013-02-05
  4. 4,69,1585,2013-01-26
  5. 5,88,1052,2013-01-13
  6. 6,84,2363,2013-01-19
  7. 7,64,1410,2013-01-12
  8. 8,53,910,2013-01-11
  9. 9,21,1661,2013-01-19
  10. 10,53,2426,2013-02-18
  11. 11,64,2022,2013-01-07
  12. 12,36,2941,2013-01-28
  13. 13,99,3819,2013-01-19
  14. 14,64,2563,2013-02-16
  15. 15,91,752,2013-02-05
  16. 16,65,750,2013-02-04
  17. 17,19,2426,2013-02-23
  18. 18,19,724,2013-02-05
  19. 19,87,137,2013-01-25
  20. 20,86,2939,2013-01-14
  21. 21,92,159,2013-01-23
  22. 22,81,2331,2013-03-01
  23. 23,88,998,2013-01-20
  24. 24,38,102,2013-02-22
  25. 25,32,4813,2013-01-13
  26. 26,36,1671,2013-01-19

  27. //省略部分數據
複製代碼


4.2.2 銷售數據集
一共4列,分別對應 產品ID,銷售數量,銷售單價,銷售日期。

  1. 1,14,1236,2013-01-14
  2. 2,19,808,2013-03-06
  3. 3,26,886,2013-02-23
  4. 4,23,1793,2013-02-09
  5. 5,27,1206,2013-01-21
  6. 6,27,2648,2013-01-30
  7. 7,22,1502,2013-01-19
  8. 8,20,1050,2013-01-18
  9. 9,13,1778,2013-01-30
  10. 10,20,2718,2013-03-14
  11. 11,22,2175,2013-01-12
  12. 12,16,3284,2013-02-12
  13. 13,30,4152,2013-01-30
  14. 14,22,2770,2013-03-11
  15. 15,28,778,2013-02-23
  16. 16,22,874,2013-02-22
  17. 17,12,2718,2013-03-22
  18. 18,12,747,2013-02-23
  19. 19,27,172,2013-02-07
  20. 20,27,3282,2013-01-22
  21. 21,28,224,2013-02-05
  22. 22,26,2613,2013-03-30
  23. 23,27,1147,2013-01-31
  24. 24,16,141,2013-03-20
  25. 25,15,5343,2013-01-21
  26. 26,16,1887,2013-01-30
  27. 27,12,2535,2013-01-12
  28. 28,16,469,2013-01-07
  29. 29,29,2395,2013-03-30
  30. 30,17,1549,2013-01-30
  31. 31,25,4173,2013-03-17

  32. //省略部分數據
複製代碼


4.2.3 其他費用數據集
一共2列,分別對應 發生日期,發生金額

  1. 2013-01-02,552
  2. 2013-01-03,1092
  3. 2013-01-04,1794
  4. 2013-01-05,435
  5. 2013-01-06,960
  6. 2013-01-07,1066
  7. 2013-01-08,1354
  8. 2013-01-09,880
  9. 2013-01-10,1992
  10. 2013-01-11,931
  11. 2013-01-12,1209
  12. 2013-01-13,1491
  13. 2013-01-14,804
  14. 2013-01-15,480
  15. 2013-01-16,1891
  16. 2013-01-17,156
  17. 2013-01-18,1439
  18. 2013-01-19,1018
  19. 2013-01-20,1506
  20. 2013-01-21,1216
  21. 2013-01-22,2045
  22. 2013-01-23,400
  23. 2013-01-24,1795
  24. 2013-01-25,1977
  25. 2013-01-26,1002
  26. 2013-01-27,226
  27. 2013-01-28,1239
  28. 2013-01-29,702
  29. 2013-01-30,1396

  30. //省略部分數據
複製代碼


4.3 程序設計
我們要編寫5個文件:
  • 計算採購金額,Purchase.java
  • 計算銷售金額,Sell.java
  • 計算其他費用金額,Other.java
  • 計算利潤,Profit.java
  • Zookeeper的調度,ZookeeperJob.java

4.3.1 計算採購金額
採購金額,是基於Hadoop的MapReduce統計計算。

  1. public class Purchase {

  2. public static final String HDFS = "hdfs://192.168.1.210:9000";
  3. public static final Pattern DELIMITER = Pattern.compile("[\t,]");

  4. public static class PurchaseMapper extends Mapper {

  5. private String month = "2013-01";
  6. private Text k = new Text(month);
  7. private IntWritable v = new IntWritable();
  8. private int money = 0;

  9. public void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
  10. System.out.println(values.toString());
  11. String[] tokens = DELIMITER.split(values.toString());
  12. if (tokens[3].startsWith(month)) {// 1月的數據
  13. money = Integer.parseInt(tokens[1]) * Integer.parseInt(tokens[2]);//單價*數量
  14. v.set(money);
  15. context.write(k, v);
  16. }
  17. }
  18. }

  19. public static class PurchaseReducer extends Reducer {
  20. private IntWritable v = new IntWritable();
  21. private int money = 0;

  22. @Override
  23. public void reduce(Text key, Iterable values, Context context) throws IOException, InterruptedException {
  24. for (IntWritable line : values) {
  25. // System.out.println(key.toString() + "\t" + line);
  26. money += line.get();
  27. }
  28. v.set(money);
  29. context.write(null, v);
  30. System.out.println("Output:" + key + "," + money);
  31. }

  32. }

  33. public static void run(Map path) throws IOException, InterruptedException, ClassNotFoundException {
  34. JobConf conf = config();
  35. String local_data = path.get("purchase");
  36. String input = path.get("input");
  37. String output = path.get("output");

  38. // 初始化purchase
  39. HdfsDAO hdfs = new HdfsDAO(HDFS, conf);
  40. hdfs.rmr(input);
  41. hdfs.mkdirs(input);
  42. hdfs.copyFile(local_data, input);

  43. Job job = new Job(conf);
  44. job.setJarByClass(Purchase.class);

  45. job.setOutputKeyClass(Text.class);
  46. job.setOutputValueClass(IntWritable.class);

  47. job.setMapperClass(PurchaseMapper.class);
  48. job.setReducerClass(PurchaseReducer.class);

  49. job.setInputFormatClass(TextInputFormat.class);
  50. job.setOutputFormatClass(TextOutputFormat.class);

  51. FileInputFormat.setInputPaths(job, new Path(input));
  52. FileOutputFormat.setOutputPath(job, new Path(output));

  53. job.waitForCompletion(true);
  54. }

  55. public static JobConf config() {// Hadoop集羣的遠程配置信息
  56. JobConf conf = new JobConf(Purchase.class);
  57. conf.setJobName("purchase");
  58. conf.addResource("classpath:/hadoop/core-site.xml");
  59. conf.addResource("classpath:/hadoop/hdfs-site.xml");
  60. conf.addResource("classpath:/hadoop/mapred-site.xml");
  61. return conf;
  62. }

  63. public static Map path(){
  64. Map path = new HashMap();
  65. path.put("purchase", "logfile/biz/purchase.csv");// 本地的數據文件
  66. path.put("input", HDFS + "/user/hdfs/biz/purchase");// HDFS的目錄
  67. path.put("output", HDFS + "/user/hdfs/biz/purchase/output"); // 輸出目錄
  68. return path;
  69. }

  70. public static void main(String[] args) throws Exception {
  71. run(path());
  72. }

  73. }
複製代碼


4.3.2 計算銷售金額
銷售金額,是基於Hadoop的MapReduce統計計算。

  1. public class Sell {

  2. public static final String HDFS = "hdfs://192.168.1.210:9000";
  3. public static final Pattern DELIMITER = Pattern.compile("[\t,]");

  4. public static class SellMapper extends Mapper {

  5. private String month = "2013-01";
  6. private Text k = new Text(month);
  7. private IntWritable v = new IntWritable();
  8. private int money = 0;

  9. public void map(LongWritable key, Text values, Context context) throws IOException, InterruptedException {
  10. System.out.println(values.toString());
  11. String[] tokens = DELIMITER.split(values.toString());
  12. if (tokens[3].startsWith(month)) {// 1月的數據
  13. money = Integer.parseInt(tokens[1]) * Integer.parseInt(tokens[2]);//單價*數量
  14. v.set(money);
  15. context.write(k, v);
  16. }
  17. }
  18. }

  19. public static class SellReducer extends Reducer {
  20. private IntWritable v = new IntWritable();
  21. private int money = 0;

  22. @Override
  23. public void reduce(Text key, Iterable values, Context context) throws IOException, InterruptedException {
  24. for (IntWritable line : values) {
  25. // System.out.println(key.toString() + "\t" + line);
  26. money += line.get();
  27. }
  28. v.set(money);
  29. context.write(null, v);
  30. System.out.println("Output:" + key + "," + money);
  31. }

  32. }

  33. public static void run(Map path) throws IOException, InterruptedException, ClassNotFoundException {
  34. JobConf conf = config();
  35. String local_data = path.get("sell");
  36. String input = path.get("input");
  37. String output = path.get("output");

  38. // 初始化sell
  39. HdfsDAO hdfs = new HdfsDAO(HDFS, conf);
  40. hdfs.rmr(input);
  41. hdfs.mkdirs(input);
  42. hdfs.copyFile(local_data, input);

  43. Job job = new Job(conf);
  44. job.setJarByClass(Sell.class);

  45. job.setOutputKeyClass(Text.class);
  46. job.setOutputValueClass(IntWritable.class);

  47. job.setMapperClass(SellMapper.class);
  48. job.setReducerClass(SellReducer.class);

  49. job.setInputFormatClass(TextInputFormat.class);
  50. job.setOutputFormatClass(TextOutputFormat.class);

  51. FileInputFormat.setInputPaths(job, new Path(input));
  52. FileOutputFormat.setOutputPath(job, new Path(output));

  53. job.waitForCompletion(true);
  54. }

  55. public static JobConf config() {// Hadoop集羣的遠程配置信息
  56. JobConf conf = new JobConf(Purchase.class);
  57. conf.setJobName("purchase");
  58. conf.addResource("classpath:/hadoop/core-site.xml");
  59. conf.addResource("classpath:/hadoop/hdfs-site.xml");
  60. conf.addResource("classpath:/hadoop/mapred-site.xml");
  61. return conf;
  62. }

  63. public static Map path(){
  64. Map path = new HashMap();
  65. path.put("sell", "logfile/biz/sell.csv");// 本地的數據文件
  66. path.put("input", HDFS + "/user/hdfs/biz/sell");// HDFS的目錄
  67. path.put("output", HDFS + "/user/hdfs/biz/sell/output"); // 輸出目錄
  68. return path;
  69. }

  70. public static void main(String[] args) throws Exception {
  71. run(path());
  72. }

  73. }
複製代碼


4.3.3 計算其他費用金額
其他費用金額,是基於本地文件的統計計算。

  1. public class Other {

  2. public static String file = "logfile/biz/other.csv";
  3. public static final Pattern DELIMITER = Pattern.compile("[\t,]");
  4. private static String month = "2013-01";

  5. public static void main(String[] args) throws IOException {
  6. calcOther(file);
  7. }

  8. public static int calcOther(String file) throws IOException {
  9. int money = 0;
  10. BufferedReader br = new BufferedReader(new FileReader(new File(file)));

  11. String s = null;
  12. while ((s = br.readLine()) != null) {
  13. // System.out.println(s);
  14. String[] tokens = DELIMITER.split(s);
  15. if (tokens[0].startsWith(month)) {// 1月的數據
  16. money += Integer.parseInt(tokens[1]);
  17. }
  18. }
  19. br.close();

  20. System.out.println("Output:" + month + "," + money);
  21. return money;
  22. }
  23. }
複製代碼


4.3.4 計算利潤
利潤,通過zookeeper分步式自動調度計算利潤。

  1. public class Profit {

  2. public static void main(String[] args) throws Exception {
  3. profit();
  4. }

  5. public static void profit() throws Exception {
  6. int sell = getSell();
  7. int purchase = getPurchase();
  8. int other = getOther();
  9. int profit = sell - purchase - other;
  10. System.out.printf("profit = sell - purchase - other = %d - %d - %d = %d\n", sell, purchase, other, profit);
  11. }

  12. public static int getPurchase() throws Exception {
  13. HdfsDAO hdfs = new HdfsDAO(Purchase.HDFS, Purchase.config());
  14. return Integer.parseInt(hdfs.cat(Purchase.path().get("output") + "/part-r-00000").trim());
  15. }

  16. public static int getSell() throws Exception {
  17. HdfsDAO hdfs = new HdfsDAO(Sell.HDFS, Sell.config());
  18. return Integer.parseInt(hdfs.cat(Sell.path().get("output") + "/part-r-00000").trim());
  19. }

  20. public static int getOther() throws IOException {
  21. return Other.calcOther(Other.file);
  22. }

  23. }
複製代碼


4.3.5 Zookeeper調度
調度,通過構建分步式隊列系統,自動化程序代替人工操作。

  1. public class ZooKeeperJob {

  2. final public static String QUEUE = "/queue";
  3. final public static String PROFIT = "/queue/profit";
  4. final public static String PURCHASE = "/queue/purchase";
  5. final public static String SELL = "/queue/sell";
  6. final public static String OTHER = "/queue/other";

  7. public static void main(String[] args) throws Exception {
  8. if (args.length == 0) {
  9. System.out.println("Please start a task:");
  10. } else {
  11. doAction(Integer.parseInt(args[0]));
  12. }
  13. }

  14. public static void doAction(int client) throws Exception {
  15. String host1 = "192.168.1.201:2181";
  16. String host2 = "192.168.1.201:2182";
  17. String host3 = "192.168.1.201:2183";

  18. ZooKeeper zk = null;
  19. switch (client) {
  20. case 1:
  21. zk = connection(host1);
  22. initQueue(zk);
  23. doPurchase(zk);
  24. break;
  25. case 2:
  26. zk = connection(host2);
  27. initQueue(zk);
  28. doSell(zk);
  29. break;
  30. case 3:
  31. zk = connection(host3);
  32. initQueue(zk);
  33. doOther(zk);
  34. break;
  35. }
  36. }

  37. // 創建一個與服務器的連接
  38. public static ZooKeeper connection(String host) throws IOException {
  39. ZooKeeper zk = new ZooKeeper(host, 60000, new Watcher() {
  40. // 監控所有被觸發的事件
  41. public void process(WatchedEvent event) {
  42. if (event.getType() == Event.EventType.NodeCreated && event.getPath().equals(PROFIT)) {
  43. System.out.println("Queue has Completed!!!");
  44. }
  45. }
  46. });
  47. return zk;
  48. }

  49. public static void initQueue(ZooKeeper zk) throws KeeperException, InterruptedException {
  50. System.out.println("WATCH => " + PROFIT);
  51. zk.exists(PROFIT, true);

  52. if (zk.exists(QUEUE, false) == null) {
  53. System.out.println("create " + QUEUE);
  54. zk.create(QUEUE, QUEUE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  55. } else {
  56. System.out.println(QUEUE + " is exist!");
  57. }
  58. }

  59. public static void doPurchase(ZooKeeper zk) throws Exception {
  60. if (zk.exists(PURCHASE, false) == null) {

  61. Purchase.run(Purchase.path());

  62. System.out.println("create " + PURCHASE);
  63. zk.create(PURCHASE, PURCHASE.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  64. } else {
  65. System.out.println(PURCHASE + " is exist!");
  66. }
  67. isCompleted(zk);
  68. }

  69. public static void doSell(ZooKeeper zk) throws Exception {
  70. if (zk.exists(SELL, false) == null) {

  71. Sell.run(Sell.path());

  72. System.out.println("create " + SELL);
  73. zk.create(SELL, SELL.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  74. } else {
  75. System.out.println(SELL + " is exist!");
  76. }
  77. isCompleted(zk);
  78. }

  79. public static void doOther(ZooKeeper zk) throws Exception {
  80. if (zk.exists(OTHER, false) == null) {

  81. Other.calcOther(Other.file);

  82. System.out.println("create " + OTHER);
  83. zk.create(OTHER, OTHER.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  84. } else {
  85. System.out.println(OTHER + " is exist!");
  86. }
  87. isCompleted(zk);
  88. }

  89. public static void isCompleted(ZooKeeper zk) throws Exception {
  90. int size = 3;
  91. List children = zk.getChildren(QUEUE, true);
  92. int length = children.size();

  93. System.out.println("Queue Complete:" + length + "/" + size);
  94. if (length >= size) {
  95. System.out.println("create " + PROFIT);
  96. Profit.profit();
  97. zk.create(PROFIT, PROFIT.getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);

  98. for (String child : children) {// 清空節點
  99. zk.delete(QUEUE + "/" + child, -1);
  100. }
  101. }
  102. }
  103. }
複製代碼


5. 運行程序最後,我們運行整個的程序,包括3個部分。
  • zookeeper服務器
  • hadoop服務器
  • 分步式隊列應用
5.1 啓動zookeeper服務
啓動zookeeper服務器集羣:

  1. ~ cd toolkit/zookeeper345

  2. # 啓動zk集羣3個節點
  3. ~ bin/zkServer.sh start conf/zk1.cfg
  4. ~ bin/zkServer.sh start conf/zk2.cfg
  5. ~ bin/zkServer.sh start conf/zk3.cfg

  6. ~ jps
  7. 4234 QuorumPeerMain
  8. 5002 Jps
  9. 4275 QuorumPeerMain
  10. 4207 QuorumPeerMain
複製代碼

查看zookeeper集羣中,各節點的狀態

  1. # 查看zk1節點狀態
  2. ~ bin/zkServer.sh status conf/zk1.cfg
  3. JMX enabled by default
  4. Using config: conf/zk1.cfg
  5. Mode: follower

  6. # 查看zk2節點狀態,zk2爲leader
  7. ~ bin/zkServer.sh status conf/zk2.cfg
  8. JMX enabled by default
  9. Using config: conf/zk2.cfg
  10. Mode: leader

  11. # 查看zk3節點狀態
  12. ~ bin/zkServer.sh status conf/zk3.cfg
  13. JMX enabled by default
  14. Using config: conf/zk3.cfg
  15. Mode: follower
複製代碼

啓動zookeeper客戶端:

  1. ~ bin/zkCli.sh -server 192.168.1.201:2181

  2. # 查看zk
  3. [zk: 192.168.1.201:2181(CONNECTED) 0] ls /
  4. [queue, queue-fifo, zookeeper]

  5. # /queue路徑無子目錄
  6. [zk: 192.168.1.201:2181(CONNECTED) 1] ls /queue
  7. []
複製代碼

5.2 啓動Hadoop服務


  1. ~ hadoop/hadoop-1.0.3
  2. ~ bin/start-all.sh

  3. ~ jps
  4. 25979 JobTracker
  5. 26257 TaskTracker
  6. 25576 DataNode
  7. 25300 NameNode
  8. 12116 Jps
  9. 25875 SecondaryNameNode
複製代碼


5.3 啓動分步式隊列ZookeeperJob
5.3.1 啓動統計採購數據程序,設置啓動參數1
只顯示用戶日誌,忽略系統日誌。

  1. WATCH => /queue/profit
  2. /queue is exist!
  3. Delete: hdfs://192.168.1.210:9000/user/hdfs/biz/purchase
  4. Create: hdfs://192.168.1.210:9000/user/hdfs/biz/purchase
  5. copy from: logfile/biz/purchase.csv to hdfs://192.168.1.210:9000/user/hdfs/biz/purchase
  6. Output:2013-01,9609887
  7. create /queue/purchase
  8. Queue Complete:1/3
複製代碼

在zk中查看queue目錄

  1. [zk: 192.168.1.201:2181(CONNECTED) 3] ls /queue
  2. [purchase]
複製代碼


5.3.2 啓動統計銷售數據程序,設置啓動參數2
只顯示用戶日誌,忽略系統日誌。

  1. WATCH => /queue/profit
  2. /queue is exist!
  3. Delete: hdfs://192.168.1.210:9000/user/hdfs/biz/sell
  4. Create: hdfs://192.168.1.210:9000/user/hdfs/biz/sell
  5. copy from: logfile/biz/sell.csv to hdfs://192.168.1.210:9000/user/hdfs/biz/sell
  6. Output:2013-01,2950315
  7. create /queue/sell
  8. Queue Complete:2/3
複製代碼
在zk中查看queue目錄



  1. [zk: 192.168.1.201:2181(CONNECTED) 5] ls /queue
  2. [purchase, sell]
複製代碼


5.3.3 啓動統計其他費用數據程序,設置啓動參數3
只顯示用戶日誌,忽略系統日誌。

  1. WATCH => /queue/profit
  2. /queue is exist!
  3. Output:2013-01,34193
  4. create /queue/other
  5. Queue Complete:3/3
  6. create /queue/profit
  7. cat: hdfs://192.168.1.210:9000/user/hdfs/biz/sell/output/part-r-00000
  8. 2950315

  9. cat: hdfs://192.168.1.210:9000/user/hdfs/biz/purchase/output/part-r-00000
  10. 9609887

  11. Output:2013-01,34193
  12. profit = sell - purchase - other = 2950315 - 9609887 - 34193 = -6693765
  13. Queue has Completed!!!
複製代碼

在zk中查看queue目錄

  1. [zk: 192.168.1.201:2181(CONNECTED) 6] ls /queue
  2. [profit]
複製代碼


在最後一步,統計其他費用數據程序運行後,從日誌中看到3個條件節點都已滿足要求。然後,通過同步的分步式隊列自動啓動了計算利潤的程序,並在日誌中打印了2013年1月的利潤爲-6693765。
本文介紹的源代碼,已上傳到github:https://github.com/bsspirit/maven_hadoop_template/tree/master/src/main/java/org/conan/myzk/hadoop
通過這個複雜的實驗,我們成功地用zookeeper實現了分步式隊列,並應用到了業務中。當然,實驗中也有一些不是特別的嚴謹的地方,請同學邊做邊思考。






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章