Apache Storm的ACK机制

Apache Storm通过ACK机制确保消息完整性,当开启ACK后,若Bolt未返回结果,30s超时会触发失败。如果有Bolt处理失败,所有数据将失败。即使数据处理到中间Bolt不再进入下一个,只要返回成功仍视为成功。出现未处理异常,程序会退出。本文通过修改Spout和Bolt,验证了ACK机制的各种情况。
摘要由CSDN通过智能技术生成

一、概要

1、先上结论

  • 开启ACK机制,对于spout产生的每一个tuple, storm都会进行跟踪。
  • 开启ACK后,不返回,默认30s超时,触发失败。
  • 数据处理有多个Bolt时,只要有Bolt返回失败,结果触发失败。
  • 数据处理到中间Bolt不再进入下一个Bolt,且已返回成功,结果为成功。
  • 在处理过程中出现未处理异常,程序退出。
  • 失败数据重新发送,需自行实现相关逻辑。

2、ACK介绍

Storm中为保证消息完整性,设计了ACK机制。Storm会监控一条从Spout发出的tuple,每个Bolt逻辑处理中需返回ACK结果,在Spout中判定数据返回结果,成功会调用ack()方法,失败调用fail()方法。

要使用AC机制,Spout传递tuple的时候必须指定messageId,设置acker数大于0,config.setNumAckers(1);


二、验证过程

1. 开启ACK后,不返回

1.1 Spout修改

nextTuple方法修改

num++;
//不使用ACK机制
//this.collector.emit(new Values(num,"My First Project"));
//使用ACK机制,messageId使用num
this.collector.emit(new Values(num,"My First Project"),num);
System.out.println("spout send "+num);
Thread.sleep(5000); //发送间隔改为5s

重新ack和fail回调方法

    @Override
    public void ack(Object msgId) {
        System.out.println("ack success at "+msgId);
    }

    @Override
    public void fail(Object msgId) {
        System.out.println("ack fail at "+msgId);
    }

1.2 Bolt01修改

 excute方法修改

        String msg = tuple.getStringByField("message");
        System.out.println("Bolt01 accepts:"+ tuple.getInteger(0)+" "+msg);
        if (msg!=null && msg.length()>0){
            //单词切分
            String[] words = msg.split(" ");
            //将切分后的单词发送到下一个Bolt
            for(String word : words){
                //不使用ACK
                //this.collector.emit(new Values(word));
                //使用ACK机制
                this.collector.emit(tuple,new Values(word));
            }
        }

1.3 运行结果及结论

...
words statistics:33
spout send 12
Bolt01 accepts:12 My First Project
words statistics:34
words statistics:35
words statistics:36
ack fail at 6
ack fail at 2
ack fail at 3
ack fail at 1
ack fail at 4
ack fail at 5
spout send 13

...

 开启ACK后,Bolt中不返回结果,在1分钟时,调用了这1分钟内超时时间超过30秒的所有数据失败回调函数,即调用Spout中的fail()方法6次,messageId分别为1,2,3,4,5,6。

2.多个Bolt时,有Bolt返回失败

2.1 Bolt01修改

 excute方法最后添加

this.collector.fail(tuple);

2.2 Bolt02修改

 excute方法最后添加

this.collec.ack(tuple);

2.3 结果及结论 

spout send 1
Bolt01 accepts:1 My First Project
words statistics:1
words statistics:2
words statistics:3
ack fail at 1
spout send 2
Bolt01 accepts:2 My First Project
words statistics:4
words statistics:5
words statistics:6 

 只要有Bolt返回失败,结果触发Spout中的fail()方法。如果Bolt01不返回任何结果,最后到达超时时间,依然触发fail()方法。

3. 数据处理到中间Bolt不再进入下一个Bolt

3.1 Bolt01,Bolt02修改

 excute方法中,全部返回成功

//修改ack返回,ack(tuple)为返回成功
//this.collector.fail(tuple);
this.collector.ack(tuple);

3.2 添加Bolt03

 创建Bolt03,并添加到拓扑关系中

builder.setBolt("bolt03",new Bolt03()).shuffleGrouping("bolt02");

3.3 结论 

数据处理到中间Bolt不再进入下一个Bolt,且已返回成功,结果为成功。

4. 处理过程中出现未处理异常

4.1 Bolt01修改

 excute方法最后添加

        if (tuple.getInteger(0)/5  == 1)
        throw new RuntimeException("运行异常");

4.2 结果及结论 

...

spout send 5
Bolt01 accepts:5 My First Project
25076 [Thread-15-bolt01-executor[2 2]] ERROR o.a.s.util - Async loop died!
java.lang.RuntimeException: java.lang.RuntimeException: 运行异常
    at org.apache.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:452) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.utils.DisruptorQueue.consumeBatchWhenAvailable(DisruptorQueue.java:418) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.disruptor$consume_batch_when_available.invoke(disruptor.clj:73) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.daemon.executor$fn__7953$fn__7966$fn__8019.invoke(executor.clj:847) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.util$async_loop$fn__625.invoke(util.clj:484) [storm-core-1.0.1.jar:1.0.1]
    at clojure.lang.AFn.run(AFn.java:22) [clojure-1.7.0.jar:?]
    at java.lang.Thread.run(Thread.java:748) [?:1.8.0_202]
Caused by: java.lang.RuntimeException: 运行异常
    at com.huber.topologys.Bolt01.execute(Bolt01.java:36) ~[classes/:?]
    at org.apache.storm.daemon.executor$fn__7953$tuple_action_fn__7955.invoke(executor.clj:728) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.daemon.executor$mk_task_receiver$fn__7874.invoke(executor.clj:461) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.disruptor$clojure_handler$reify__7390.onEvent(disruptor.clj:40) ~[storm-core-1.0.1.jar:1.0.1]
    at org.apache.storm.utils.DisruptorQueue.consumeBatchToCursor(DisruptorQueue.java:439) ~[storm-core-1.0.1.jar:1.0.1]

 

处理过程中出现未处理异常,程序退出。

5. 失败数据重新发送

原理为Spout中创建Map<messageId,message>集合,nextTuple()发送的数据加入到Map中。Spout回调ack()时,根据messageId删除Map中对应数据;Spout回调fail()时,根据messageId找到Map中对应数据并重新发送。实现代码如下:

5.1 创建消息类HuberMsg

public class HuberMsg {
    private Integer num;
    private String msg;

    public HuberMsg(Integer num, String msg) {
        this.num = num;
        this.msg = msg;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

5.2 FirstSpout 产生数据并发送

public class FirstSpout extends BaseRichSpout {
    private SpoutOutputCollector collector;
    //存储发送的数据,Map<messageId,HuberMsg>
    private static Map<Integer, HuberMsg> ackMap = new HashMap<Integer, HuberMsg>();
    private int num = 0;

    public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
        this.collector = spoutOutputCollector;
    }

    public void nextTuple() {

        try {
            num++;
            HuberMsg huberMsg = new HuberMsg(num,"My First Project");
            this.collector.emit(new Values(huberMsg),num);
            //发送数据加入Map
            ackMap.put(num,huberMsg);
            System.out.println("spout send "+num);
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
        outputFieldsDeclarer.declare(new Fields("huberMsg"));
    }

    @Override
    public void ack(Object msgId) {
        System.out.println("ack success at "+msgId);
        //删除Map中处理成功数据
        ackMap.remove(msgId);
    }

    @Override
    public void fail(Object msgId) {
        System.out.println("ack fail at "+msgId);
        //失败数据重新发送
        this.collector.emit(new Values(ackMap.get(msgId)),msgId);
    }
}

5.3 Bolt01 接收数据

HuberMsg huberMsg = (HuberMsg)tuple.getValue(0);
System.out.println("Bolt01 accepts:"+ huberMsg.getMsg());

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值