2121SC@SDUSC
storm-executor-spout(1)
在设计拓扑结构时,很重要事情就是消息的可靠性。当有无法处理的消息时,需要决定该怎么办,以及作为一个整体的拓扑结构该做些什么,高可靠性的拓扑必须管理丢失的消息,必然消耗更多资源;可靠性较低的拓扑可能会丢失一些消息,占用的资源也相应更少。
要在spout中管理可靠性,可以在分发时包含一个元组的消息ID(collector.emit(new Values(…),tupleId))。
在一个元组被正确的处理时调用ack方法,而在失败时调用fail方法。
当一个元组被所有的靶bolt和锚bolt处理过,即可判定元组处理成功
发生下列情况之一时为元组处理失败:
1、提供数据的spout调用collector.fail(tuple)
2、处理时间超过配置的超时时间
需求如下:
如果事务失败了,重新发送消息
如果失败了太多次,终结拓扑运行
创建一个spout和一个bolt,spout随机发送100个事务ID,有80%的元组不会被bolt收到。实现spout时利用Map分发事务消息元组,这样就比较容易实现重发消息。
public void nextTuple() {
if(!toSend.isEmpty()){
for(Map.Entry<Integer, String> transactionEntry : toSend.entrySet()){
Integer transactionId = transactionEntry.getKey();
String transactionMessage = transactionEntry.getValue();
collector.emit(new Values(transactionMessage),transactionId);
}
toSend.clear();
}
}
如果有未发送的消息,得到每条事务消息和它的关联ID,把它们作为一个元组发送出去,最后清空消息队列。调用map的clear是安全的,因为nextTuple失败时,只有ack方法会修改map,而它们都运行在一个线程内。
维护两个map用来跟踪待发送的事务消息和每个事务的失败次数。ack方法只是简单的把事务从每个列表中删除。
public void ack(Object msgId) {
messages.remove(msgId);
failCounterMessages.remove(msgId);
}
fail方法决定应该重新发送一条消息,还是已经失败太多次而放弃它。
注:如果使用全部数据流组,而拓扑里的所有bolt都失败了,spout的fail方法才会被调用。
public void fail(Object msgId) {
Integer transactionId = (Integer) msgId;
//检查事务失败次数
Integer failures = transactionFailureCount.get(transactionId) + 1;
if(failes >= MAX_FAILS){
//失败数太高了,终止拓扑
throw new RuntimeException("错误, transaction id 【"+
transactionId+"】 已失败太多次了 【"+failures+"】");
}
//失败次数没有达到最大数,保存这个数字并重发此消息
transactionFailureCount.put(transactionId, failures);
toSend.put(transactionId, messages.get(transactionId));
LOG.info("重发消息【"+msgId+"】");
}
首先,检查事务失败次数。如果一个事务失败次数太多,通过抛出RuntimeException终止发送此条消息的工人。否则,保存失败次数,并把消息放入待发送队列(toSend),它就会再次调用nextTuple时得以重新发送。
注:Storm节点不维护状态,因此如果你在内存保存信息,而节点又不幸挂了,就会丢失所有缓存的消息。
Storm是一个快速失败的系统。拓扑会在抛出异常时挂掉,然后再由Storm重启,恢复到抛出异常前的状态。
获取数据
一、直接连接
在一个直接连接的架构中,spout直接与一个消息分发器连接
这个架构很容易实现,尤其是在消息分发器是已知设备或已知设备组时。已知设备满足:拓扑从启动时就已知道该设备,并贯穿拓扑的整个生命周期保持不变。未知设备就是在拓扑运行期添加进来的。已知设备组就是从拓扑启动时组内所有设备都是已知的。
spout从配置对象得到连接参数(track,user,password),并连接到API(在这个例子中使用Apache的DefaultHttpClient)。它一次读一行数据,并把数据从JSON转化成Java对象,然后发布它。
public void nextTuple() {
//创建http客户端
client = new DefaultHttpClient();
client.setCredentialsProvider(credentialProvider);
HttpGet get = new HttpGet(STREAMING_API_URL+track);
HttpResponse response;
try {
//执行http访问
response = client.execute(get);
StatusLine status = response.getStatusLine();
if(status.getStatusCode() == 200){
InputStream inputStream = response.getEntity().getContent();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String in;
//逐行读取数据
while((in = reader.readLine())!=null){
try{
//转化并发布消息
Object json = jsonParser.parse(in);
collector.emit(new Values(track,json));
}catch (ParseException e) {
LOG.error("Error parsing message from twitter",e);
}
}
}
} catch (IOException e) {
LOG.error("Error in communication with twitter api ["+get.getURI().toString()+"],
sleeping 10s");
try {
Thread.sleep(10000);
} catch (InterruptedException e1) {}
}
}
注:如果锁定nextTuple方法,永远也不会执行ack和fail方法。
知识点参考:https://ifeve.com/getting-started-with-storm-4/