Storm集成Kafka应用的开发

  咱们知道storm的做用主要是进行流式计算,对于源源不断的均匀数据流流入处理是很是有效的,而现实生活中大部分场景并非均匀的数据流,而是时而多时而少的数据流入,这种状况下显然用批量处理是不合适的,若是使用storm作实时计算的话可能由于数据拥堵而致使服务器挂掉,应对这种状况,使用kafka做为消息队列是很是合适的选择,kafka能够将不均匀的数据转换成均匀的消息流,从而和storm比较完善的结合,这样才能够实现稳定的流式计算,那么咱们接下来开发一个简单的案例来实现storm和kafka的结合html

  storm和kafka结合,实质上无非是以前咱们说过的计算模式结合起来,就是数据先进入kafka生产者,而后storm做为消费者进行消费,最后将消费后的数据输出或者保存到文件、数据库、分布式存储等等,具体框图以下:java

  

  这张图片摘自博客地址:http://www.cnblogs.com/tovin/p/3974417.html 在此感谢做者的奉献数据库

  首先咱们保证在服务器上zookeeper、kafka、storm正常运行,而后咱们开始写程序,这里使用eclipse for javaee IDEapache

  和以前同样,创建一个maven项目,在pom.xml写入以下代码:vim

 1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 2   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
 3   <modelVersion>4.0.0</modelVersion>
 4 
 5   <groupId>kafkastorm</groupId>
 6   <artifactId>kafkastorm</artifactId>
 7   <version>0.0.1-SNAPSHOT</version>
 8   <packaging>jar</packaging>
 9 
10   <name>kafkastorm</name>
11   <url>http://maven.apache.org</url>
12 
13   <properties>
14     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
15   </properties>
16 
17   <dependencies>
18     <dependency>
19       <groupId>junit</groupId>
20       <artifactId>junit</artifactId>
21       <version>3.8.1</version>
22       <scope>test</scope>
23     </dependency>
24     <dependency>
25         <groupId>org.apache.storm</groupId>
26         <artifactId>storm-core</artifactId>
27         <version>0.9.6</version>
28         <scope>provided</scope>
29     </dependency>
30     <dependency>
31         <groupId>org.apache.kafka</groupId>
32         <artifactId>kafka_2.9.2</artifactId>
33         <version>0.8.2.2</version>
34         <exclusions>
35             <exclusion>
36                 <groupId>org.apache.zookeeper</groupId>
37                 <artifactId>zookeeper</artifactId>
38             </exclusion>
39             <exclusion>
40                 <groupId>log4j</groupId>
41                 <artifactId>log4j</artifactId>
42             </exclusion>
43         </exclusions>
44     </dependency>
45     <dependency>
46         <groupId>org.apache.storm</groupId>
47         <artifactId>storm-kafka</artifactId>
48         <version>0.9.6</version>  
49     </dependency>
50   </dependencies>
51   
52   <build>
53     <plugins>
54       <plugin>
55         <artifactId>maven-assembly-plugin</artifactId>
56         <configuration>
57           <descriptorRefs>
58             <descriptorRef>jar-with-dependencies</descriptorRef>
59           </descriptorRefs>
60         </configuration>
61         <executions>
62           <execution>
63             <id>make-assembly</id> 
64             <phase>package</phase>
65             <goals>
66               <goal>single</goal>
67             </goals>
68           </execution>
69         </executions>
70       </plugin>
71     </plugins>
72   </build>
73 </project>

  主要是导入的zookeeper、storm、kafka外部依赖这些叠加起来,还有<plugin>插件便于咱们后续对程序进程maven的打包windows

  和以前同样首先编写storm消费kafka的逻辑,MessageScheme类,代码以下:api

 1 package net.zengzhiying;
 2 
 3 import java.io.UnsupportedEncodingException;
 4 import java.util.List;
 5 
 6 import backtype.storm.spout.Scheme;
 7 import backtype.storm.tuple.Fields;
 8 import backtype.storm.tuple.Values;
 9 
10 public class MessageScheme implements Scheme {
11 
12     public List<Object> deserialize(byte[] arg0) {
13         try {
14             String msg = new String(arg0, "UTF-8");
15             return new Values(msg);
16         } catch (UnsupportedEncodingException e) {
17             e.printStackTrace();
18         }
19         return null;
20     }
21 
22     public Fields getOutputFields() {
23         return new Fields("msg");
24     }
25 
26 }

  逻辑很简单,就是对kafka出来的数据转换成字符串,接下来咱们想办法来处理strom清洗以后的数据,咱们为了简单就把输出保存到一个文件中,Bolt逻辑SenqueceBolt类的代码以下:服务器

 1 package net.zengzhiying;
 2 
 3 import java.io.DataOutputStream;
 4 import java.io.FileNotFoundException;
 5 import java.io.FileOutputStream;
 6 import java.io.IOException;
 7 
 8 import backtype.storm.topology.BasicOutputCollector;
 9 import backtype.storm.topology.OutputFieldsDeclarer;
10 import backtype.storm.topology.base.BaseBasicBolt;
11 import backtype.storm.tuple.Fields;
12 import backtype.storm.tuple.Tuple;
13 import backtype.storm.tuple.Values;
14 
15 public class SenqueceBolt extends BaseBasicBolt {
16 
17     public void execute(Tuple arg0, BasicOutputCollector arg1) {
18         String word = (String) arg0.getValue(0);
19         String out = "output:" + word;
20         System.out.println(out);
21         
22         //写文件
23         try {
24             DataOutputStream out_file = new DataOutputStream(new FileOutputStream("kafkastorm.out"));
25             out_file.writeUTF(out);
26             out_file.close();
27         } catch (FileNotFoundException e) {
28             // TODO Auto-generated catch block
29             e.printStackTrace();
30         } catch (IOException e) {
31             // TODO Auto-generated catch block
32             e.printStackTrace();
33         }
34         
35         arg1.emit(new Values(out));
36     }
37 
38     public void declareOutputFields(OutputFieldsDeclarer arg0) {
39         arg0.declare(new Fields("message"));
40     }
41 
42 }

  就是把输出的消息放到文件kafkastorm.out中框架

  而后咱们编写主类,也就是配置kafka提交topology到storm的代码,类名为StormKafkaTopo,代码以下:eclipse

 1 package net.zengzhiying;
 2 
 3 import java.util.HashMap;
 4 import java.util.Map;
 5 
 6 import backtype.storm.Config;
 7 import backtype.storm.LocalCluster;
 8 import backtype.storm.StormSubmitter;
 9 import backtype.storm.generated.AlreadyAliveException;
10 import backtype.storm.generated.InvalidTopologyException;
11 import backtype.storm.spout.SchemeAsMultiScheme;
12 import backtype.storm.topology.TopologyBuilder;
13 import backtype.storm.utils.Utils;
14 import storm.kafka.BrokerHosts;
15 import storm.kafka.KafkaSpout;
16 import storm.kafka.SpoutConfig;
17 import storm.kafka.ZkHosts;
18 import storm.kafka.bolt.KafkaBolt;
19 
20 public class StormKafkaTopo {
21     public static void main(String[] args) {
22         BrokerHosts brokerHosts = new ZkHosts("192.168.1.216:2181/kafka");
23         
24         SpoutConfig spoutConfig = new SpoutConfig(brokerHosts, "topic1", "/kafka", "kafkaspout");
25         
26         Config conf = new Config();
27         Map<String, String> map = new HashMap<String, String>();
28         
29         map.put("metadata.broker.list", "192.168.1.216:9092");
30         map.put("serializer.class", "kafka.serializer.StringEncoder");
31         conf.put("kafka.broker.properties", map);
32         conf.put("topic", "topic2");
33         
34         spoutConfig.scheme = new SchemeAsMultiScheme(new MessageScheme());
35         
36         TopologyBuilder builder = new TopologyBuilder();
37         builder.setSpout("spout", new KafkaSpout(spoutConfig));
38         builder.setBolt("bolt", new SenqueceBolt()).shuffleGrouping("spout");
39         builder.setBolt("kafkabolt", new KafkaBolt<String, Integer>()).shuffleGrouping("bolt");
40         
41         if(args != null && args.length > 0) {
42             //提交到集群运行
43             try {
44                 StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
45             } catch (AlreadyAliveException e) {
46                 e.printStackTrace();
47             } catch (InvalidTopologyException e) {
48                 e.printStackTrace();
49             }
50         } else {
51             //本地模式运行
52             LocalCluster cluster = new LocalCluster();
53             cluster.submitTopology("Topotest1121", conf, builder.createTopology());
54             Utils.sleep(1000000);
55             cluster.killTopology("Topotest1121");
56             cluster.shutdown();
57         }
58         
59         
60         
61     }
62 }

  注意上面代码的配置,和以前单独运行storm和kafka代码不太同样,配置也很简单,注意区别便可,若是细心的话会注意到这里建了两个topic一个是topic1,一个是topic2,topic1的含义kafka接收生产者过来的数据所须要的topic,topic2是KafkaBolt也就是storm中的bolt生成的topic,固然这里topic2这行配置能够省略,是没有任何问题的,相似于一个中转的东西,另外咱们此次测试是上传到服务器执行,本地模式的代码没有执行到,固然原理是同样的

  以前通常网上的教程到这里就完毕了,这样咱们会引发一种没有生产者的误区,注意:上面3个类实现的功能是kafka消费者输出的数据被storm消费!生产者的代码能够当作独立的其余来源,能够写在其余项目中,根据数据源的状况来,下面咱们为了示例,编写一个类来进行生产,代码和以前kafka单独的同样:

 1 package net.zengzhiying;
 2 
 3 import java.util.ArrayList;
 4 import java.util.List;
 5 import java.util.Properties;
 6 
 7 import kafka.javaapi.producer.Producer;
 8 import kafka.producer.KeyedMessage;
 9 import kafka.producer.ProducerConfig;
10 
11 public class DataProducerInsert {
12     private static Producer<Integer,String> producer;
13     private final Properties props=new Properties();
14     public DataProducerInsert(){
15             //定义链接的broker list
16             props.put("metadata.broker.list", "192.168.1.216:9092");
17             //定义序列化类 Java中对象传输以前要序列化
18             props.put("serializer.class", "kafka.serializer.StringEncoder");
19             //props.put("advertised.host.name", "192.168.1.216");
20             producer = new Producer<Integer, String>(new ProducerConfig(props));
21     }
22     public static void main(String[] args) {
23             DataProducerInsert sp=new DataProducerInsert();
24             //定义topic
25             String topic="topic1";
26             //开始时间统计
27             long startTime = System.currentTimeMillis();
28             //定义要发送给topic的消息
29             String messageStr = "This is a message";
30             List<KeyedMessage<Integer, String>> datalist = new ArrayList<KeyedMessage<Integer, String>>();
31            
32             //构建消息对象
33             KeyedMessage<Integer, String> data = new KeyedMessage<Integer, String>(topic, messageStr);
34             datalist.add(data);
35             
36             //结束时间统计
37             long endTime = System.currentTimeMillis();
38             KeyedMessage<Integer, String> data1 = new KeyedMessage<Integer, String>(topic, "用时" + (endTime-startTime)/1000.0);
39             datalist.add(data1);
40             
41             //推送消息到broker
42             producer.send(data);
43             producer.close();
44     }
45 }

  注意,这里咱们定义的topic是topic1,正好和前面的topic1数据源对应,是整个kafka保持一致的topic,也就是说kafka生产者topic和消费者topic是必须名称相同才能够响应,下面简单添加了一点时间统计的代码,也很简单

  另外还要注意kafka配置文件host.name尽可能改为ip,和以前说过的同样

  到如今项目就编写完成了,而后咱们使用maven命令对项目打包,首先得保证咱们windows上安装好了maven,咱们运行cmd,进入到当前项目目录下,执行命令: mvn assembly:assembly 进行打包,打包的前提就是以前pom.xml的全部配置,执行后maven会自动下载相应的依赖并完成打包,须要耐心等待一会:

  

  看到如图所示的BUILD SUCCESS返回以后,那么打包就成功了,如今进入项目目录下的target目录中,会看到2个jar包

  

  其中后面那个文件名较长的大小也比较大,是包含相关依赖的包,接下来咱们将这个包上传到服务器,而后使用storm执行jar包将咱们的topology上传到集群中运行,注意是使用storm执行jar包,而不是java

/usr/storm/apache-storm-0.9.6/bin/storm jar kafkastorm-0.0.1-SNAPSHOT-jar-with-dependencies.jar net.zengzhiying.StormKafkaTopo kafkagostorm

  前面是storm的绝对路径,参数jar执行jar包,后面跟的是上传topology的主类,最后kafkagostorm是咱们上传拓扑的名称

  

  这里执行完以后会回到命令行,如今就在后台集群中开始分发运行了,这就是集群模式,以前咱们讲的storm案例会不断滚动大量数据,那个属于本地模式,若是咱们如今开启ui界面的话,那么访问咱们的地址http://192.168.1.216:8080/能够看到正在运行的Topology

  

  能够看到状态是active正在运行了,咱们上面代码中kafkabolt建立了一个topic2的消息,咱们如今能够测试一下,消费者这里只是简单地原样输出,咱们进入kafka目录,执行下面命令:

bin/kafka-console-consumer.sh --zookeeper localhost:2181/kafka --topic topic2 --from-beginning

  后面参数--from-beginning不添加也是能够的,添加包括旧信息,不添加就是新的输出

  如今界面卡住,待会咱们来观察输出,如今咱们新开一个窗口,仍是使用storm执行刚才的生产者类DataProducerInsert来生产一条消息,命令以下:

/usr/storm/apache-storm-0.9.6/bin/storm jar kafkastorm-0.0.1-SNAPSHOT-jar-with-dependencies.jar net.zengzhiying.DataProducerInsert

  回车以后,等待界面滚动2s程序跑完以后,咱们看到另外一个窗口输出了消息:

  

  而后,咱们的输出文件在哪呢,刚才咱们使用storm执行的生产者代码,因此输出的kafkastorm.out就在storm的安装目录下,咱们使用cat或者vim均可以看到文件内容,若是有时间统计的话两行内容显示可能会有点问题,由于后续要进行简单的转换,去掉时间统计代码只输出消息的内容以下,这里使用vim打开的:

  

  另外注意,上传拓扑时全部的代码都加载到集群了,因此修改代码版本时,必定要先在storm目录下执行 bin/storm kill topo_name 结束拓扑,修改代码后从新上传便可再次运行,不然可能会出现错误,在集群上的时候kafka配置文件的host.name注释便可,默认为localhost,最后代码中用到的参数比较多,很容易出错,因此写代码时仍是要仔细点

  这样storm集成kafka的测试案例就完成了,而且实现了必定的功能,只要咱们灵活掌握了怎么写kafka和storm结合的总体拓扑结构,那么主要的代码就集中在数据源也就是kafka生产者的发送和storm消费后的存储问题,这全部的代码都是在storm和kafka给好的方法内写逻辑,而不用关心底层,这样使开发更加简单快捷,好比咱们消费后的数据既能够写到文件、数据库还能够索引到solr,存到Hbase等,这样就能够灵活运用了;其实最关键的仍是要了解这些框架底层的实现原理,这样遇到问题才能够知其然知其因此然

相关文章
相关标签/搜索