storm笔记:Storm+Kafka简单应用
这几天工作须要使用storm+kafka,基本场景是应用出现错误,发送日志到kafka的某个topic。storm订阅该topic。然后进行兴许处理。场景很easy,可是在学习过程中。遇到一个奇怪的异常情况:使用KafkaSpout读取topic数据时,没有向ZK写offset数据,致使每次都从头開始读取。
纠结了两天,最终碰巧找到原因:应该使用BaseBasicBolt作为bolt的父类。而不是BaseRichBolt。
通过本文记录一下这样的情况,后文中依据上述场景提供几个简单的样例。
由于是初学storm、kafka,基础理论查看storm笔记:storm基本概念,。或查看Storm 简单介绍。
基本订阅
基本场景:订阅kafka的某个topic,然后在读取的消息前加上自己定义的字符串,然后写回到kafka另外一个topic。
从Kafka读取数据的Spout使用storm.kafka.KafkaSpout。向Kafka写数据的Bolt使用storm.kafka.bolt.KafkaBolt。
中间进行进行数据处理的Bolt定义为TopicMsgBolt。闲言少叙。奉上代码:
public class TopicMsgTopology {
public static void main(String[] args) throws Exception {
// 配置Zookeeper地址
BrokerHosts brokerHosts = new ZkHosts("zk1:2181,zk2:2281,zk3:2381");
// 配置Kafka订阅的Topic。以及zookeeper中数据节点文件夹和名字
SpoutConfig spoutConfig = new SpoutConfig(brokerHosts, "msgTopic1", "/topology/root", "topicMsgTopology");
// 配置KafkaBolt中的kafka.broker.properties
Config conf = new Config();
Properties props = new Properties();
// 配置Kafka broker地址
props.put("metadata.broker.list", "dev2_55.wfj-search:9092");
// serializer.class为消息的序列化类
props.put("serializer.class", "kafka.serializer.StringEncoder");
conf.put("kafka.broker.properties", props);
// 配置KafkaBolt生成的topic
conf.put("topic", "msgTopic2");
spoutConfig.scheme = new SchemeAsMultiScheme(new MessageScheme());
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("msgKafkaSpout", new KafkaSpout(spoutConfig));
builder.setBolt("msgSentenceBolt", new TopicMsgBolt()).shuffleGrouping("msgKafkaSpout");
builder.setBolt("msgKafkaBolt", new KafkaBolt<String, Integer>()).shuffleGrouping("msgSentenceBolt");
if (args.length == 0) {
String topologyName = "kafkaTopicTopology";
LocalCluster cluster = new LocalCluster();
cluster.submitTopology(topologyName, conf, builder.createTopology());
Utils.sleep(100000);
cluster.killTopology(topologyName);
cluster.shutdown();
} else {
conf.setNumWorkers(1);
StormSubmitter.submitTopology(args[0], conf, builder.createTopology());
}
}
}
storm.kafka.ZkHosts构造方法的參数是zookeeper标准配置地址的形式(ZooKeeper环境搭建能够查看ZooKeeper安装部署),zk1、zk2、zk3在本地配置了host。由于server使用的伪分布式模式,因此几个端口号不是默认的2181。
storm.kafka.SpoutConfig构造方法第一个參数为上述的storm.kafka.ZkHosts对象。第二个为待订阅的topic名称,第三个參数zkRoot为写读取topic时的偏移量offset数据的节点(zk node),第四个參数为该节点上的次级节点名(有个地方说这个是spout的id)。
backtype.storm.Config对象是配置storm的topology(拓扑)所须要的基础配置。
backtype.storm.spout.SchemeAsMultiScheme的构造方法输入的參数是订阅kafka数据的处理參数,这里的MessageScheme是自己定义的,代码例如以下:
public class MessageScheme implements Scheme {
private static final Logger logger = LoggerFactory.getLogger(MessageScheme.class);
@Override
public List<Object> deserialize(byte[] ser) {
try {
String msg = new String(ser, "UTF-8");
logger.info("get one message is {}", msg);
return new Values(msg);
} catch (UnsupportedEncodingException ignored) {
return null;
}
}
@Override
public Fields getOutputFields() {
return new Fields("msg");
}
}
MessageScheme类中getOutputFields方法是KafkaSpout向后发送tuple(storm数据传输的最小结构)的名字,须要与接收数据的Bolt中统一(在这个样例中能够不统一,由于后面直接取第0条数据。可是在wordCount的那个样例中就须要统一了)。
TopicMsgBolt类是从storm.kafka.KafkaSpout接收数据的Bolt,对接收到的数据进行处理,然后向后传输给storm.kafka.bolt.KafkaBolt。
代码例如以下:
public class TopicMsgBolt extends BaseBasicBolt {
private static final Logger logger = LoggerFactory.getLogger(TopicMsgBolt.class);
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String word = (String) input.getValue(0);
String out = "Message got is '" + word + "'!";
logger.info("out={}", out);
collector.emit(new Values(out));
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
declarer.declare(new Fields("message"));
}
}
此处须要特别注意的是,要使用
backtype.storm.topology.base.BaseBasicBolt对象作为父类,否则不会在zk记录偏移量offset数据。
须要编写的代码已完毕,接下来就是在搭建好的storm、kafka中进行測试:
# 创建topic
./bin/kafka-topics.sh --create --zookeeper zk1:2181,zk2:2281,zk3:2381 --replication-factor 1 --partitions 1 --topic msgTopic1
./bin/kafka-topics.sh --create --zookeeper zk1:2181,zk2:2281,zk3:2381 --replication-factor 1 --partitions 1 --topic msgTopic2
接下来须要分别对msgTopic1、msgTopic2启动producer(生产者)与consumer(消费者):
# 对msgTopic1启动producer,用于发送数据
./bin/kafka-console-producer.sh --broker-list dev2_55.wfj-search:9092 --topic msgTopic1
# 对msgTopic2启动consumer,用于查看发送数据的处理结果
./bin/kafka-console-consumer.sh --zookeeper zk1:2181,zk2:2281,zk3:2381 --topic msgTopic2 --from-beginning
然后将打好的jar包上传到storm的nimbus(能够使用远程上传或先上传jar包到nimbus节点所在server,然后本地运行):
# ./bin/storm jar topology TopicMsgTopology.jar cn.howardliu.demo.storm.kafka.topicMsg.TopicMsgTopology TopicMsgTopology
待相应的worker启动好之后,就能够在msgTopic1的producer相应终端输入数据,然后在msgTopic2的consumer相应终端查看输出结果了。
有几点须要注意的:
1. 必须先创建msgTopic1、msgTopic2两个topic。
2. 定义的bolt必须使用BaseBasicBolt作为父类,不能够使用BaseRichBolt。否则无法记录偏移量;
3. zookeeper最好使用至少三个节点的分布式模式或伪分布式模式。否则会出现一些异常情况;
4. 在整个storm下。spout、bolt的id必须唯一。否则会出现异常。5.
TopicMsgBolt类作为storm.kafka.bolt.KafkaBolt前的最后一个Bolt。须要将输出数据名称定义为message。否则KafkaBolt无法接收数据。
wordCount
简单的输入输出做完了,来点复杂点儿的场景:从某个topic定于消息,然后依据空格分词,统计单词数量。然后将当前输入的单词数量推送到还有一个topic。
首先规划须要用到的类:
1. 从KafkaSpout接收数据并进行处理的backtype.storm.spout.Scheme子类;
2. 数据切分bolt:SplitSentenceBolt;
3. 计数bolt:WordCountBolt;
4. 报表bolt:ReportBolt;
5. topology定义:WordCountTopology;
6. 最后再加一个原样显示订阅数据的bolt:SentenceBolt。
backtype.storm.spout.Scheme子类能够使用上面已经定义过的MessageScheme。此处不再赘述。
SplitSentenceBolt是对输入数据进行切割。简单的使用String类的split方法,然后将每一个单词命名为“word”,向后传输,代码例如以下:
public class SplitSentenceBolt extends BaseBasicBolt {
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word"));
}
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String sentence = input.getStringByField("msg");
String[] words = sentence.split(" ");
Arrays.asList(words).forEach(word -> collector.emit(new Values(word)));
}
}
SentenceBolt是从KafkaSpout接收数据,然后直接输出。在拓扑图上就是从输入分叉。一个进入SplitSentenceBolt。一个进入SentenceBolt。这样的结构能够应用在Lambda架构中。代码例如以下:
public class SentenceBolt extends BaseBasicBolt {
private static final Logger logger = LoggerFactory.getLogger(SentenceBolt.class);
@Override
public void execute(Tuple tuple, BasicOutputCollector basicOutputCollector) {
String msg = tuple.getStringByField("msg");
logger.info("get one message is {}", msg);
basicOutputCollector.emit(new Values(msg));
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("sentence"));
}
}
WordCountBolt是对接收到的单词进行汇总统一,然后将单词“word”及其相应数量“count”向后传输,代码例如以下:
public class WordCountBolt extends BaseBasicBolt {
private Map<String, Long> counts = null;
@Override
public void prepare(Map stormConf, TopologyContext context) {
this.counts = new ConcurrentHashMap<>();
super.prepare(stormConf, context);
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word", "count"));
}
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String word = input.getStringByField("word");
Long count = this.counts.get(word);
if (count == null) {
count = 0L;
}
count++;
this.counts.put(word, count);
collector.emit(new Values(word, count));
}
}
ReportBolt是对接收到的单词及数量进行整理,拼成json格式,然后继续向后传输。代码例如以下:
public class ReportBolt extends BaseBasicBolt {
@Override
public void execute(Tuple input, BasicOutputCollector collector) {
String word = input.getStringByField("word");
Long count = input.getLongByField("count");
String reportMessage = "{'word': '" + word + "', 'count': '" + count + "'}";
collector.emit(new Values(reportMessage));
}
@Override
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("message"));
}
}
最后是定义topology(拓扑)WordCountTopology,代码例如以下:
public class WordCountTopology {
private static final String KAFKA_SPOUT_ID = "kafkaSpout";
private static final String SENTENCE_BOLT_ID = "sentenceBolt";
private static final String SPLIT_BOLT_ID = "sentenceSplitBolt";
private static final String WORD_COUNT_BOLT_ID = "sentenceWordCountBolt";
private static final String REPORT_BOLT_ID = "reportBolt";
private static final String KAFKA_BOLT_ID = "kafkabolt";
private static final String CONSUME_TOPIC = "sentenceTopic";
private static final String PRODUCT_TOPIC = "wordCountTopic";
private static final String ZK_ROOT = "/topology/root";
private static final String ZK_ID = "wordCount";
private static final String DEFAULT_TOPOLOGY_NAME = "sentenceWordCountKafka";
public static void main(String[] args) throws Exception {
// 配置Zookeeper地址
BrokerHosts brokerHosts = new ZkHosts("zk1:2181,zk2:2281,zk3:2381");
// 配置Kafka订阅的Topic,以及zookeeper中数据节点文件夹和名字
SpoutConfig spoutConfig = new SpoutConfig(brokerHosts, CONSUME_TOPIC, ZK_ROOT, ZK_ID);
spoutConfig.scheme = new SchemeAsMultiScheme(new MessageScheme());
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout(KAFKA_SPOUT_ID, new KafkaSpout(spoutConfig));
builder.setBolt(SENTENCE_BOLT_ID, new SentenceBolt()).shuffleGrouping(KAFKA_SPOUT_ID);
builder.setBolt(SPLIT_BOLT_ID, new SplitSentenceBolt()).shuffleGrouping(KAFKA_SPOUT_ID);
builder.setBolt(WORD_COUNT_BOLT_ID, new WordCountBolt()).fieldsGrouping(SPLIT_BOLT_ID, new Fields("word"));
builder.setBolt(REPORT_BOLT_ID, new ReportBolt()).shuffleGrouping(WORD_COUNT_BOLT_ID);
builder.setBolt(KAFKA_BOLT_ID, new KafkaBolt<String, Long>()).shuffleGrouping(REPORT_BOLT_ID);
Config config = new Config();
Map<String, String> map = new HashMap<>();
map.put("metadata.broker.list", "dev2_55.wfj-search:9092");// 配置Kafka broker地址
map.put("serializer.class", "kafka.serializer.StringEncoder");// serializer.class为消息的序列化类
config.put("kafka.broker.properties", map);// 配置KafkaBolt中的kafka.broker.properties
config.put("topic", PRODUCT_TOPIC);// 配置KafkaBolt生成的topic
if (args.length == 0) {
LocalCluster cluster = new LocalCluster();
cluster.submitTopology(DEFAULT_TOPOLOGY_NAME, config, builder.createTopology());
Utils.sleep(100000);
cluster.killTopology(DEFAULT_TOPOLOGY_NAME);
cluster.shutdown();
} else {
config.setNumWorkers(1);
StormSubmitter.submitTopology(args[0], config, builder.createTopology());
}
}
}
除了上面提过应该注意的地方。此处还须要注意。
storm.kafka.SpoutConfig定义的zkRoot与id应该与第一个样例中不同(至少保证id不同,否则两个topology将使用一个节点记录偏移量)。
storm笔记:Storm+Kafka简单应用的更多相关文章
- Storm笔记——技术点汇总
目录 概况 手工搭建集群 引言 安装Python 配置文件 启动与测试 应用部署 参数配置 Storm命令 原理 Storm架构 Storm组件 Stream Grouping 守护进程容错性(Dae ...
- Storm学习笔记 - Storm初识
Storm学习笔记 - Storm初识 1. Strom是什么? Storm是一个开源免费的分布式计算框架,可以实时处理大量的数据流. 2. Storm的特点 高性能,低延迟. 分布式:可解决数据量大 ...
- 分布式流式处理框架:storm简介 + Storm术语解释
简介: Storm是一个免费开源.分布式.高容错的实时计算系统.它与其他大数据解决方案的不同之处在于它的处理方式.Hadoop 在本质上是一个批处理系统,数据被引入 Hadoop 文件系统 (HDFS ...
- 《Apache kafka实战》读书笔记-管理Kafka集群安全之ACL篇
<Apache kafka实战>读书笔记-管理Kafka集群安全之ACL篇 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 想必大家能看到这篇博客的小伙伴,估计你对kaf ...
- 【Storm】storm安装、配置、使用以及Storm单词计数程序的实例分析
前言:阅读笔记 storm和hadoop集群非常像.hadoop执行mr.storm执行topologies. mr和topologies最关键的不同点是:mr执行终于会结束,而topologies永 ...
- how tomcat works 读书笔记(二)----------一个简单的servlet容器
app1 (建议读者在看本章之前,先看how tomcat works 读书笔记(一)----------一个简单的web服务器 http://blog.csdn.net/dlf123321/arti ...
- Python编程从入门到实践笔记——变量和简单数据类型
Python编程从入门到实践笔记——变量和简单数据类型 #coding=gbk #变量 message_1 = 'aAa fff' message_2 = 'hart' message_3 = &qu ...
- Spring MVC 学习笔记10 —— 实现简单的用户管理(4.3)用户登录显示全局异常信息
</pre>Spring MVC 学习笔记10 -- 实现简单的用户管理(4.3)用户登录--显示全局异常信息<p></p><p></p>& ...
- Spring MVC 学习笔记9 —— 实现简单的用户管理(4)用户登录显示局部异常信息
Spring MVC 学习笔记9 -- 实现简单的用户管理(4.2)用户登录--显示局部异常信息 第二部分:显示局部异常信息,而不是500错误页 1. 写一个方法,把UserException传进来. ...
随机推荐
- [HNOI2007][bzoj1187] 神奇游乐园 [插头dp]
题面: 传送门 给定一个四联通棋盘图,每个格子有权值,求一条总权值最大的回路 思路: 插头dp基础教程 棋盘? 回路? n,m<=10? 当然是插头dp啦~\(≧▽≦)/~ 然后发现这道题并不是 ...
- [luogu3768] 简单的数学题 [杜教筛]
题面: 传送门 实际上就是求: 思路: 看到gcd就先反演一下,过程大概是这样: 明显的一步反演 这里设,S(x)等于1到x的和 然后把枚举d再枚举T变成先枚举T再枚举其约数d,变形: 后面其中两项展 ...
- 【BZOJ 5038 不打兔子】
Time Limit: 20 Sec Memory Limit: 256 MBSubmit: 22 Solved: 8[Submit][Status][Discuss] Description 勤 ...
- Django使用js,css等静态文件的时候,出现mime类型问题
使用adminLTE模板, return render(request, 'AdminLTE/index.html') 的时候报如下错误且页面渲染异常,css没有效果: Resource interp ...
- 星际战争(bzoj 3993)
Description 3333年,在银河系的某星球上,X军团和Y军团正在激烈地作战.在战斗的某一阶段,Y军团一共派遣了N个巨型机器人进攻X军团的阵地,其中第i个巨型机器人的装甲值为Ai.当一个巨型机 ...
- 汇编中的 imul 指令
1.这是整形乘法指令,无论是 unsigned int 还算是 signed int 实际上指令都是进行相同的运算,只不过最终的结果是由程序中的类型来做相应的解读 2.imul 指令实际上不会发生 o ...
- 【asp.net】Win7旗舰版IIS配置
1.IIS配置流程 win7 iis 的配置不需要插入安装盘,可直接在控制面板中开启该功能,步骤如下: (1)"控制面板"-->"程序和功能"--> ...
- hdu 3061 hdu 3996 最大权闭合图 最后一斩
hdu 3061 Battle :一看就是明显的最大权闭合图了,水提......SB题也不说边数多少....因为开始时候数组开小了,WA....后来一气之下,开到100W,A了.. hdu3996. ...
- 【转载】SQL Server XML Path
FOR XML PATH 有的人可能知道有的人可能不知道,其实它就是将查询结果集以XML形式展现,有了它我们可以简化我们的查询语句实现一些以前可能需要借助函数活存储过程来完成的工作.那么以一个实例为主 ...
- (一)python 简单网页爬虫
1.基于window环境,使用的工具是 Anaconda 下载地址 https://www.anaconda.com/download/ 2.所使用的包 BeautifulSoup,用来解析html代 ...