storm之ack实现机制及源码分析

一. 前言

        最近公司因为内部项目调动,我们组接收一个维护storm的任务,看代码流程都是通过ACK机制来保证从spout发出tuple在后面的各个环节已经全部执行成功。自己也好奇是怎么实现的呢? 毕竟storm程序同时处理很多个tuple,更要命的是一个tuple可能被切分成多个tuple,而且会切分成多少个tuple并不知道(e.g. 根据某个标识符进行split("特殊标识")等)

       查看官网说明:http://storm.apache.org/releases/1.2.3/Guaranteeing-message-processing.html (1.2.3版本)以及各个blog,感觉写的蛮简单,看了不是很明白(毕竟storm 1.x及之前的版本是用Clojure 语言,该语言用户量不会很大,从2.x开始用java来重写),于是打算自己看2.0.0版本的storm代码,理解其中的原理。

二.ACK原理分析

      案例:

       ack原理的本质:是一个数 异或 “自己本身” 肯定是等于 0 的。设想我们有这样一个通用程序  :一个为A的spout 发送了一个tuple;boltB接收到A发送的tuple,逻辑处理后,又发送了一个新的tuple并且ack表示处理成功;boltC接收到boltB发送的tuple,逻辑处理并且ack    即: spout -A( 业务逻辑处理,emit a tuple) --> bolt -B(业务逻辑处理,emit a tuple   ,  ack ) --> bolt-C(业务逻辑逻辑,ack)

我们先假设spout或者bolt  emit 一个tuple时会产生一个64位bit的随机数,其次 有一个全局异或值:VAL(初始值为0),  spout执行emit 或者 bolt执行ack方法的时候会触发 VAL 异或 与自己生成随机数 相关的值(后面流程图会讲)。

      根据流程图:

      spout-A  emit a tuple:   生成一个随机数(用random-A 表示),并且执行异或,VAL = VAL ^ random-A,因为VAL初始值0,结果VAL变成了random-A

      bolt-B eimt a tuple 并且 ack操作: emit a tuple时生成一个随机数random-B,然后再执行ack,通过anchors机制,可以获取上游spout-A的信息,获取random-A。执行 random-temp-B  = random -A ^ random-B(random-temp-B 就是上述红线标注的 和自己生成随机数相关的值) ,random-temp-B再与全局异或值VAL异或。 即 VAL = VAL ^ random-temp-B ,替换变量就是VAL = random-A ^ (random-A ^ random-B)

     bolt-C  ack 操作:  因为botl-C没有emit a tuple,即不会产生随机数。 同样的道理 random-temp-C = random-B (anchors机制从上游组件的tuple拿出其生成的随机数),调用ack方法后和全局异或值VAL异或。即 VAL  = VAL ^ random-temp-C,  替换变量后

VAL = random-A  ^ (random-A ^ random-B) ^ random-B. 当然最终的值VAL 肯定为0

(上述过程可以大致理解为storm实现的机制,当然真实肯定比这要复杂一点,比如上游的anchors有多个 等情况)

 

      源码分析:

      storm 版本为storm 2.0.0版本,这里仅仅分析了 列出了核心的类以及方法,并对重点步骤进行代码注释 (还是比较简单的,有兴趣的同学可以自己看看)

     1. 在spout.emit时候,SpoutOutputCollectorImpl.sendSpoutMsg方法会被调用

private List<Integer> sendSpoutMsg(String stream, List<Object> values, Object messageId, Integer outTaskId) throws
        InterruptedException {
         /**..此处代码被删除..**/

        //判断是否需要 ACK机制
        final boolean needAck = (messageId != null) && hasAckers;

        final List<Long> ackSeq = needAck ? new ArrayList<>() : null;

        //当执行全局VAL异或的时候,用于标识 哪些tuple划分为同一次“完全处理”
        final long rootId = needAck ? MessageId.generateId(random) : 0;

        for (int i = 0; i < outTasks.size(); i++) { // perf critical path. don't use iterators.
            Integer t = outTasks.get(i);
            MessageId msgId;
            if (needAck) {
                // 当emit发送一个tuple时,需要生成一个随机值
                long as = MessageId.generateId(random);
                msgId = MessageId.makeRootId(rootId, as);
                ackSeq.add(as);
            } else {
                msgId = MessageId.makeUnanchored();
            }

            //构建一个tuple,并发送出去(注意这里的this.taskId,一个tuple需要被哪个task执行,这里其实已经记录好了)
            final TupleImpl tuple =
                new TupleImpl(executor.getWorkerTopologyContext(), values, executor.getComponentId(), this.taskId, stream, msgId);
            AddressedTuple adrTuple = new AddressedTuple(t, tuple);
            executor.getExecutorTransfer().tryTransfer(adrTuple, executor.getPendingEmits());
        }
        

        if (needAck) {
            boolean sample = executor.samplerCheck();
            TupleInfo info = new TupleInfo();
            info.setTaskId(this.taskId);
            info.setStream(stream);
            info.setMessageId(messageId);
            pending.put(rootId, info);
           //构建参与和全局VAL 异或的值,Utils.bitXorVals(ackSeq) 这里等于上面emit a tuple时生成的随机值,并发送出去。其中ack类型为Acker.ACKER_INIT_STREAM_ID
            List<Object> ackInitTuple = new Values(rootId, Utils.bitXorVals(ackSeq), this.taskId);
            taskData.sendUnanchored(Acker.ACKER_INIT_STREAM_ID, ackInitTuple, executor.getExecutorTransfer(), executor.getPendingEmits());
        } 
        /** 代码省略 **/
        return outTasks;
    }

   2. 我们知道storm内部有个ACK 程序实时处理spout emit的tuple是否已经完全成功处理。该ACK其实也是一个bolt(特殊的bolt)。我们直接查看Acker.execute

public void execute(Tuple input) {
        if (TupleUtils.isTick(input)) {
            Map<Object, AckObject> tmp = pending.rotate();
            LOG.debug("Number of timeout tuples:{}", tmp.size());
            return;
        }

        boolean resetTimeout = false;
        String streamId = input.getSourceStreamId();

        //spout 源码分析中的rootId
        Object id = input.getValue(0);
        AckObject curr = pending.get(id);
        if (ACKER_INIT_STREAM_ID.equals(streamId)) {  
            if (curr == null) {
               //我们所说的全局异或变量VAL,就是AckObject中的一个字段,初始话值是0
                curr = new AckObject();
                pending.put(id, curr);
            }
            //VAL 和 传过来的值进行异或  同时  VAL更新为 异或后的值 
            curr.updateAck(input.getLong(1));
            curr.spoutTask = input.getInteger(2);
        } else if (ACKER_ACK_STREAM_ID.equals(streamId)) {  //调用ack的时候
            //和上面同理
            if (curr == null) {
                curr = new AckObject();
                pending.put(id, curr);
            }
            curr.updateAck(input.getLong(1));
        } else if (ACKER_FAIL_STREAM_ID.equals(streamId)) { 
            // For the case that ack_fail message arrives before ack_init
            if (curr == null) {
                curr = new AckObject();
            }
            curr.failed = true;
            pending.put(id, curr);
        } else if (ACKER_RESET_TIMEOUT_STREAM_ID.equals(streamId)) {
            resetTimeout = true;
            if (curr == null) {
                curr = new AckObject();
            }
            pending.put(id, curr);
        } else if (Constants.SYSTEM_FLUSH_STREAM_ID.equals(streamId)) {
            collector.flush();
            return;
        } else {
            LOG.warn("Unknown source stream {} from task-{}", streamId, input.getSourceTask());
            return;
        }
        /***代码省略**/
    
        collector.ack(input);
    }

    3.  Bolt进行emit 新的tuple 以及 执行ack操作 。会相应执行BoltOutputCollectorImpl类中的boltEmit方法以及ack方法

private List<Integer> boltEmit(String streamId, Collection<Tuple> anchors, List<Object> values,
                                   Integer targetTaskId) throws InterruptedException {
        List<Integer> outTasks;
        if (targetTaskId != null) {
            outTasks = task.getOutgoingTasks(targetTaskId, streamId, values);
        } else {
            outTasks = task.getOutgoingTasks(streamId, values);
        }

        for (int i = 0; i < outTasks.size(); ++i) {
            Integer t = outTasks.get(i);
            MessageId msgId;
            if (ackingEnabled && anchors != null) {
                final Map<Long, Long> anchorsToIds = new HashMap<>();
                //上游可能有多个anchors
                for (Tuple a : anchors) {  
                     //这里的a表示为anchor上游的tuple
                    Set<Long> rootIds = a.getMessageId().getAnchorsToIds().keySet();
                    if (rootIds.size() > 0) {
                       //需要发送新的tuple,生成一个随机数
                        long edgeId = MessageId.generateId(random);
                      //构建的tuple实例有一个字段_outAckVal, updateAckVal(edgeId)代码为 _outAckVal = _outAckVal ^ val,初始值为0,更新_outAckVal。 主要处理上游的tuple可能被发送到多个下游的实例
                        ((TupleImpl) a).updateAckVal(edgeId);
                        for (Long root_id : rootIds) {
                            putXor(anchorsToIds, root_id, edgeId);
                        }
                    }
                }
                msgId = MessageId.makeId(anchorsToIds);
            } else {
                msgId = MessageId.makeUnanchored();
            }
            //构建一个tuple实例,并transter到下游
            TupleImpl tupleExt = new TupleImpl(
                executor.getWorkerTopologyContext(), values, executor.getComponentId(), taskId, streamId, msgId);
            xsfer.tryTransfer(new AddressedTuple(t, tupleExt), executor.getPendingEmits());
        }
        
        return outTasks;
    }

   

public void ack(Tuple input) {
        if (!ackingEnabled) {
            return;
        }
        //上游anchors 过来的tuple
        long ackValue = ((TupleImpl) input).getAckVal();

        //anchorsToIds 中的key就是spout.emit阶段生成的rootid
        Map<Long, Long> anchorsToIds = input.getMessageId().getAnchorsToIds();
        for (Map.Entry<Long, Long> entry : anchorsToIds.entrySet()) {
           //Utils.bitXor(entry.getValue(), ackValue) ,entry.getValues (就是上游anchors tuple实例中与生成随机数相关的值,注意是相关)  ackValue:上游anchors tuple实例中的_outAckVal字段(正常实例化时值为0,在emitBolt会和当前tuple生成的随机数异或) 
            task.sendUnanchored(Acker.ACKER_ACK_STREAM_ID,
                                new Values(entry.getKey(), Utils.bitXor(entry.getValue(), ackValue)),
                                executor.getExecutorTransfer(), executor.getPendingEmits());
        }
        /**代码省略**/
        }
    }

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值