文章转载来源于数据仓库与Python大数据
到目前为止,我们已经知道Storm的作用主要是进行流式计算,对于源源不断的均匀数据流流入处理是非常有效的,而现实生活中大部分场景并不是均匀的数据流,而是时而多时而少的数据流入,这种情况下显然用批量处理是不合适的,如果使用Storm做实时计算的话可能因为数据拥堵而导致服务器挂掉,应对这种情况,使用Kafka作为消息队列是非常合适的选择,Kafka可以将不均匀的数据转换成均匀的消息流,从而和Storm比较完善的结合,这样才可以实现稳定的流式计算,那么接下来开发一个简单的案例来实现Storm和Kafka的结合。
Storm和Kafka结合,实质上无非是之前我们说过的计算模式结合起来,就是数据先进入Kafka生产者,然后Storm作为消费者进行消费,最后将消费后的数据输出或者保存到文件、数据库、分布式存储等等,具体框如下图1所示:
图1 集成Kafka与Storm的框架
Storm从Kafka中接收数据
为了实现Kafka与Storm的集成,我们需要添加以下的依赖信息。
<dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka_2.9.2</artifactId>
<version>0.8.2.2</version>
<exclusions>
<exclusion>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
</exclusion>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.storm</groupId>
<artifactId>storm-kafka</artifactId>
<version>1.0.3</version>
</dependency>
修改之前的WordCountTopology主程序,创建一个新的KafkaSpout组件,并将其作为WordCountTopology任务的输入。这KafkaSpout将从Kafka中接收消息,作为Kafka消息的Consumer使用。下面是创建这个KafkaSpout组件的核心代码程序_大数据培训。
//支持从Kakfa消息系统中读取数据
private static KafkaSpout createKafkaSpout() {
//指定ZooKeeper的地址信息
BrokerHosts brokerHosts = new ZkHosts("kafka101:2181");
//创建KafkaSpout的配置信息。
SpoutConfig spoutConfig = new SpoutConfig(brokerHosts,
"mytopic1", "/mytopic1",
UUID.randomUUID().toString());
spoutConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
spoutConfig.startOffsetTime = kafka.api.OffsetRequest.LatestTime();
//返回一个KafkaSpout
return new KafkaSpout(spoutConfig);
}
注意:Storm默认是从头开始读取Kafka的消息,如果要从Kafka最新的便宜地址开始读取数据,需要添加如下代码:
conf.startOffsetTime = kafka.api.OffsetRequest.LatestTime();
另一方面,由于应用程序将从Kafka中接收消息,由于Kafka发送来的消息中并没有“sentence”字段,所以需要修改一下WordCountSplitBolt的代码,如下:
//进行单词拆分
public class WordCountSplitBolt extends BaseRichBolt{
private OutputCollector collector;
@Override
public void execute(Tuple tuple) {
//如何处理上一级组件发来的Tuple
//取出数据: I love Beijing
//String data = tuple.getStringByField("sentence");
String data = tuple.getString(0);
String[] words = data.split(" ");
for(String w:words) { //k2 v2
this.collector.emit(new Values(w,1));
}
}
@Override
public void prepare(Map arg0, TopologyContext arg1, OutputCollector collector) {
// OutputCollector该级组件的输出流
this.collector = collector;
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declare) {
declare.declare(new Fields("word","count"));
}
}
注意:这里的第10行代码和第11行代码的区别。
为了测试的方便,我们将WordCountTopology任务运行在本地模式下。下面为大家展示了完整的代码程序。
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.StormSubmitter;
import org.apache.storm.generated.AlreadyAliveException;
import org.apache.storm.generated.AuthorizationException;
import org.apache.storm.generated.InvalidTopologyException;
import org.apache.storm.generated.StormTopology;
import org.apache.storm.kafka.BrokerHosts;
import org.apache.storm.kafka.KafkaSpout;
import org.apache.storm.kafka.SpoutConfig;
import org.apache.storm.kafka.StringScheme;
import org.apache.storm.kafka.ZkHosts;
import org.apache.storm.spout.SchemeAsMultiScheme;
import org.apache.storm.topology.IRichBolt;
import org.apache.storm.topology.IRichSpout;
import org.apache.storm.topology.TopologyBuilder;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.ITuple;
public class WordCountTopology {
public static void main(String[] args) {
//创建一个Topology
TopologyBuilder builder = new TopologyBuilder();
//指定任务的spout组件
//builder.setSpout("myspout", new WordCountSpout());
builder.setSpout("myspout", createKafkaSpout());
//指定拆分单词的bolt,是随机分组
builder.setBolt("mysplit",
new WordCountSplitBolt())
.shuffleGrouping("myspout");
//指定单词计数的bolt
builder.setBolt("mytotal",
new WordCountTotalBolt())
.fieldsGrouping("mysplit",
new Fields("word"));
//创建任务
StormTopology topology = builder.createTopology();
//配置参数
Config conf = new Config();
//本地模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("MyWC", conf, topology);
}
private static IRichSpout createKafkaSpout() {
BrokerHosts zkHost = new ZkHosts("kafka101:2181");
SpoutConfig conf = new SpoutConfig(zkHost,
"mytopic1",
"/mytopic1",
"mygroupID");
//指定反序列化机制
conf.scheme = new SchemeAsMultiScheme(new StringScheme());
conf.startOffsetTime = kafka.api.OffsetRequest.LatestTime();
return new KafkaSpout(conf);
}
}
测试Kafka与Storm的集成
(1) 在kafka101、kafka102和kafka103上启动ZooKeeper集群,执行下面的命令 :
zkServer.sh start
(2) 启动Kafka集群
(3) 执行下面的命令,启动Kafka Producer Console
bin/kafka-console-producer.sh --broker-list kafka101:9092--topic mytopic1
(4) 启动Storm应用程序,如下图2所示:
图2 启动Storm 应用程序
(5)在Kafka Producer Console输入一些字符串,并回车发送。观察Storm应用程序的结果。可以看到当在Kafka Producer Console上输入了数据,在Storm的应用程序中将实时接收消息,并处理消息。如下图3所示:
图3 Storm 应用程序接收Kafka的消息
Storm将数据输出到Kafka
在前面的例子中,我们集成了Storm和Kafka。将Kafka作为Storm的Spout,Storm将从Kafka中接收数据,并处理数据。其实还有另一种情况,就是Storm处理完成后,也可以将数据输出到Kafka中,下图描述了这一过程。
图4 Storm 应用程序发送消息到Kafka
在了解上面的过程后,我们可以改造一下之前的Topology主程序的代码,创建一个新的Bolt任务,将任务处理完成的数据输出到Kafka中,下面列出了改造后的代码程序。
private static IRichBolt createKafkaBolt() {
Properties props = new Properties();
//broker的地址
props.put("bootstrap.servers", "kafka101:9092");
//说明了使用何种序列化方式将用户提供的key和vaule值序列化成字节。
props.put("key.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("value.serializer",
"org.apache.kafka.common.serialization.StringSerializer");
props.put("acks", "1");
KafkaBolt<String, String> bolt = new KafkaBolt<String, String>();
//指定Kafka的配置信息
bolt.withProducerProperties(props);
//指定Topic的名字
bolt.withTopicSelector(new DefaultTopicSelector("mytopic1"));
//指定将上一级Bolt处理完成后的Key和Value//
//KafkaBolt将会按照这里指定的Key和Value将数据发送到Kakfa
bolt.withTupleToKafkaMapper(new FieldNameBasedTupleToKafkaMapper
<String, String>("word","total"));
return bolt;
}
创建好了新的Bolt组件后,可以将其添加进入Topology任务中,下面列出了改造后的主程序代码。
public static void main(String[] args) {
//创建一个Topology
TopologyBuilder builder = new TopologyBuilder();
//指定任务的spout组件
//builder.setSpout("myspout", new WordCountSpout());
builder.setSpout("myspout", createKafkaSpout());
//指定拆分单词的bolt,是随机分组
builder.setBolt("mysplit",
new WordCountSplitBolt())
.shuffleGrouping("myspout");
//指定单词计数的bolt
builder.setBolt("mytotal",
new WordCountTotalBolt())
.fieldsGrouping("mysplit",
new Fields("word"));
//创建KafkaBolt,将结果发送到Kafka中
builder.setBolt("kafkabolt", createKafkaBolt())
.shuffleGrouping("mytotal");
//创建任务
StormTopology topology = builder.createTopology();
//配置参数
Config conf = new Config();
//本地模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("MyWC", conf, topology);
}
注意:这里的第21行代码和第22行代码,我们使用了之前的createKafkaBolt方法,将KafkaBolt任务添加进了Topology任务中。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。