Spout是Storm数据流的入口,在设计拓扑时,一件很重要的事情就是需要考虑消息的可靠性,如果消息不能被处理而丢失是很严重的问题。
通过此项目我们可得知,如果在第一个bolt处理的时候出现异常,我们可以让整个数据进行重发,但是如果在第二个bolt处理的时候出现了异常,那么我们也会让对应的spout里的数据重发,这样就会出现事务的问题,我们就需要进行判断或者是进行记录。 如果是数据入库的话,可以与原ID进行比对。 如果是事务的话在编写代码时,尽量就不要进行拆分tuple。 或者使用storm的Trident框架。
MessageSpout
package com.xnmzdx.storm.spout;
import java.util.Map;
import backtype.storm.spout.SpoutOutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichSpout;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Values;
/**
* 体会消息确认机制
* @author zyt
*
*/
public class MessageSpout implements IRichSpout {
private static final long serialVersionUID = 7271404214963078223L;
//spout向外发送数据的连接器collector
private SpoutOutputCollector collector;
private int index = 0;
//定义需要被发送的数据(一些单词,以数组的方式存储)
private String[] subjects = new String[]{
"groovy,oeacnbase",
"openfire,restful",
"flume,activite",
"hadoop,hbase",
"spark,sqoop"
};
//初始化spout的collector
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
this.collector = collector;
}
/**
* 轮询
* 遍历存储数据的数组,将每个元素挨个发送出去
*/
public void nextTuple() {
if(index < subjects.length) {
String sub = subjects[index];
//发送消息时,添加发送了messageId
collector.emit(new Values(sub),index);//collector.emit(tuple, messageId)
//因为添加了messageId,则ack和fail方法当Tuple被正确地处理了或发生了错误时将会被调用
index++;
}
}
/**
* 定义发送的数据的字段
*/
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//以字段"subjects"发送数据
declarer.declare(new Fields("subjects"));
}
/**
* 消息确认机制的体现
* 消息发送成功的回调方法
*/
public void ack(Object msgId) {
System.out.println("【消息发送成功!!!】(mesgId=" + msgId + ")");
}
/**
* 消息确认机制的体现
* 消息tuple处理失败后的回调方法
*/
public void fail(Object msgId) {
System.out.println("【消息发送失败!!!】(msgId=" + msgId + ")");
System.out.println("【重发进行中...】");
//一旦某个bolt中数据处理出现异常,也就是消息发送失败,spout就会重新发送数据
collector.emit(new Values(subjects[(Integer)msgId]),msgId);
System.out.println("【重发成功!!!】");
}
public void close() {
}
public void activate() {
}
public void deactivate() {
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
SpliterBolt
package com.xnmzdx.storm.bolt;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Fields;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
public class SpliterBolt implements IRichBolt{
private static final long serialVersionUID = 323068030016246800L;
//Bolt向下发送数据的collector
private OutputCollector collector;
/**
* 初始化
*/
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
//初始化Bolt的collector
this.collector = collector;
}
private boolean flag = false;
public void execute(Tuple input) {
try {
//按字段"subjects"接收MessageSpout发出的数据
String subjects = input.getStringByField("subjects");
//以下这段是制造错误,用以验证消息确认机制
// if(!flag && subjects.equals("flume,activite")) {
// flag = true;
// int a = 1/0;
// }
//按逗号分割接收到的数据,放入数组中
String[] words = subjects.split(",");
//遍历数组,将每一个单词发送出去
for(String word : words) {
//注意这里循环发送消息,要携带Tuple对象(也就是input),用于处理异常时重发策略
collector.emit(input,new Values(word));
}
//描记。若发送成功,则调用消息发送成功的回调方法 ack()方法
collector.ack(input);
}catch(Exception e) {
e.printStackTrace();
//描记。发现异常,即发送失败,则调用消息发送失败的回调方法 fail()方法
collector.fail(input);
}
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
//按字段"word"发送数据
declarer.declare(new Fields("word"));
}
public void cleanup() {
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
WriteBolt
package com.xnmzdx.storm.bolt;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Map;
import backtype.storm.task.OutputCollector;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.IRichBolt;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.tuple.Tuple;
import backtype.storm.tuple.Values;
public class WriteBolt implements IRichBolt {
private static final long serialVersionUID = -2447898411180546403L;
//Bolt向下发送数据的连接器collector
private OutputCollector collector;
//字符写流
private FileWriter writer;
/**
* 初始化Bolt,即初始化它定义的所有属性
*/
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
this.collector = collector;
try {
//将管道插入文件D:\\message.txt中,即将数据写入该文件中
writer = new FileWriter("D:\\message.txt");
} catch (IOException e) {
e.printStackTrace();
}
}
private boolean flag = false;
public void execute(Tuple input) {
//接收SpliterBolt发出的数据,这里不是按字段接收的,而是取出每次收到的第一条数据(每次也就收到一条数据)
String word = input.getString(0);
try {
//以下是人为制造Bolt处理数据出错的情况,来测试消息确认机制
if(!flag && word.equals("hadoop")) {
flag = true; //让它变成true,是为了spout重新发送数据后,不再进入这个故意制造的错误中,Bolt可以正常处理数据
int a = 1/0;//此处出错,抛异常
}
writer.write(word);
writer.write("\n");
writer.flush();
}catch(Exception e) {
e.printStackTrace();
collector.fail(input);//程序运行至此说明抛出异常,即Bolt在处理数据时出现了问题,所以应该调用消息发送失败的回调方法,spout就会重新发送消息
}
collector.emit(input,new Values("word"));//这一句我觉得没有用,为什么还要向下发送,这已经是最后一个Bolt了?!将其注释掉后,程序仍能正常运行!!!
collector.ack(input);//消息发送成功(即Bolt正常处理了数据),调用此方法
}
public void cleanup() {
}
public void declareOutputFields(OutputFieldsDeclarer declarer) {
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
MessageTopology
package com.xnmzdx.storm.topology;
import com.xnmzdx.storm.bolt.SpliterBolt;
import com.xnmzdx.storm.bolt.WriteBolt;
import com.xnmzdx.storm.spout.MessageSpout;
import backtype.storm.Config;
import backtype.storm.LocalCluster;
import backtype.storm.topology.TopologyBuilder;
public class MessageTopology {
public static void main(String[] args) throws InterruptedException {
//以下是一个topology的常规配置,记住就行!!!
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new MessageSpout());
builder.setBolt("split-bolt", new SpliterBolt()).shuffleGrouping("spout");
builder.setBolt("write-bolt", new WriteBolt()).shuffleGrouping("split-bolt");
Config config = new Config();
config.setDebug(true);
//本地集群模式
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("message", config, builder.createTopology());
Thread.sleep(10000);
cluster.killTopology("message");
cluster.shutdown();
}
}