jstorm的acker实现

在jstorm中通过acker机制来保证spout作为拓扑树的源头能够清楚知道每一条数据的处理情况。

在DefaultTopologyAssignContext中在在准备topology中,会根据之前设置的acker并行度(topology.acker.executor参数)来给topology的结构中插入相应数量的acker。

public static void add_acker(Map stormConf, StormTopology ret) {
    String key = Config.TOPOLOGY_ACKER_EXECUTORS;

    Integer ackerNum = JStormUtils.parseInt(stormConf.get(key), 0);

    // generate outputs
    HashMap<String, StreamInfo> outputs = new HashMap<>();
    ArrayList<String> fields = new ArrayList<>();
    fields.add("id");

    outputs.put(ACKER_ACK_STREAM_ID, Thrift.directOutputFields(fields));
    outputs.put(ACKER_FAIL_STREAM_ID, Thrift.directOutputFields(fields));

    IBolt ackerbolt = new Acker();

    // generate inputs
    Map<GlobalStreamId, Grouping> inputs = acker_inputs(ret);

    // generate acker which will be stored in topology
    Bolt acker_bolt = Thrift.mkBolt(inputs, ackerbolt, outputs, ackerNum);

    // add every bolt two output stream
    // ACKER_ACK_STREAM_ID/ACKER_FAIL_STREAM_ID
    for (Entry<String, Bolt> e : ret.get_bolts().entrySet()) {

        Bolt bolt = e.getValue();

        ComponentCommon common = bolt.get_common();

        List<String> ackList = JStormUtils.mk_list("id", "ack-val");

        common.put_to_streams(ACKER_ACK_STREAM_ID, Thrift.outputFields(ackList));

        List<String> failList = JStormUtils.mk_list("id");
        common.put_to_streams(ACKER_FAIL_STREAM_ID, Thrift.outputFields(failList));

        bolt.set_common(common);
    }

    // add every spout output stream ACKER_INIT_STREAM_ID
    // add every spout two intput source
    // ((ACKER_COMPONENT_ID, ACKER_ACK_STREAM_ID), directGrouping)
    // ((ACKER_COMPONENT_ID, ACKER_FAIL_STREAM_ID), directGrouping)
    for (Entry<String, SpoutSpec> kv : ret.get_spouts().entrySet()) {
        SpoutSpec bolt = kv.getValue();
        ComponentCommon common = bolt.get_common();
        List<String> initList = JStormUtils.mk_list("id", "init-val", "spout-task");
        common.put_to_streams(ACKER_INIT_STREAM_ID, Thrift.outputFields(initList));

        GlobalStreamId ack_ack = new GlobalStreamId(ACKER_COMPONENT_ID, ACKER_ACK_STREAM_ID);
        common.put_to_inputs(ack_ack, Thrift.mkDirectGrouping());

        GlobalStreamId ack_fail = new GlobalStreamId(ACKER_COMPONENT_ID, ACKER_FAIL_STREAM_ID);
        common.put_to_inputs(ack_fail, Thrift.mkDirectGrouping());
    }

    ret.put_to_bolts(ACKER_COMPONENT_ID, acker_bolt);
}

 

以上是给将acker作为bolt插入在topology结构中方法,根据所配置的acker的并行度来创建相应数量的acker,并同时给其余的spout和bolt设置相应的streamId,以确保能与相应的acker进行数据交流。

 

这里可以看到以上实现的acker类的具体实现,acker继承了IBolt类,因此可以直接作为Bolt来放入topology的结构中。

@Override
public void execute(Tuple input) {
    Object id = input.getValue(0);
    AckObject curr = pending.get(id);
    String stream_id = input.getSourceStreamId();
    if (Acker.ACKER_INIT_STREAM_ID.equals(stream_id)) {
        if (curr == null) {
            curr = new AckObject();

            curr.val = input.getLong(1);
            curr.spout_task = input.getInteger(2);

            pending.put(id, curr);
        } else {
            // bolt's ack first come
            curr.update_ack(input.getValue(1));
            curr.spout_task = input.getInteger(2);
        }

    } else if (Acker.ACKER_ACK_STREAM_ID.equals(stream_id)) {
        if (curr != null) {
            curr.update_ack(input.getValue(1));
        } else {
            // two case
            // one is timeout
            // the other is bolt's ack first come
            curr = new AckObject();
            curr.val = input.getLong(1);
            pending.put(id, curr);
        }
    } else if (Acker.ACKER_FAIL_STREAM_ID.equals(stream_id)) {
        if (curr == null) {
            // do nothing
            // already timeout, should go fail
            return;
        }
        curr.failed = true;
    } else {
        LOG.info("Unknown source stream, " + stream_id + " from task-" + input.getSourceTask());
        return;
    }

    Integer task = curr.spout_task;
    if (task != null) {
        if (curr.val == 0) {
            pending.remove(id);
            List values = JStormUtils.mk_list(id);
            collector.emitDirect(task, Acker.ACKER_ACK_STREAM_ID, values);
        } else {
            if (curr.failed) {
                pending.remove(id);
                List values = JStormUtils.mk_list(id);
                collector.emitDirect(task, Acker.ACKER_FAIL_STREAM_ID, values);
            }
        }
    }

    // add this operation to update acker stats
    collector.ack(input);

    long now = System.currentTimeMillis();
    if (now - lastRotate > rotateTime) {
        lastRotate = now;
        Map<Object, AckObject> tmp = pending.rotate();
        if (tmp.size() > 0) {
            LOG.warn("Acker's timeout item size:{}", tmp.size());
        }
    }

}

 

以上是,acker的excute()方法,实现的比较长。

 

首先会根据传入的tuple的messageId在自己以messageId和AckObject为键值对的Map中去寻找对应的AckObject来对应此次数据流对应的ack状态。

支持了ack的spout都会在发送消息的时候带上相应的messageId,以此类推,那么没有带上messageId的数据都将不会在acker中做处理。

在init中,会首先根据messageId来寻找是否存在对应的ackObjcet,如果不存在,说明这里是spout自发送数据后,第一次ack,那么将会创建一个ackObjcet,并且将val赋为所带着的随机数,并设置好spout的taskId,并在map中存放。

那么这个随机数是在什么时候设置的呢,把目光放在专门用来支持ack机制的spout,ACKTransactionSpout类里实现了AckSpoutOutputCollector,其ack()方法在发送ack消息之前,会随机生成一个随机数。

public List<Integer> emit(String streamId, List<Object> tuple, Object messageId) {
    if (messageId != null) {
        addPendingTuple(currBatchId, streamId, messageId, tuple);
        tuple.add(Utils.generateId(random));
    } else {
        // for non-anchor tuples, use 0 as default rootId
        tuple.add(0l);
    }
    return delegate.emit(streamId, tuple, null);
}

 

这里会给tuple携带一个随机数,也就是赋值在ackObject里的val字段里的数据。

 

在init中,如果已经存在了相应的ackObject,说明这是第一次从bolt向ack提交,那么ackObject则会调用update_ack()方法,来对其进行相应的操作。

public void update_ack(Object value) {
    synchronized (this) {
        val = JStormUtils.bit_xor(val, value);
    }
}

 

这个方法很简单,只是进行相应的异或操作,那么这里的bolt传来的rootId是怎样的呢?可以看到实现在了AckTransactionBolt里的AckOutputCollecror的ack()方法。

 

@Override
public void ack(Tuple input) {
    Pair<Long, Integer> pendingBatch = tracker.getPendingBatch(((TupleImplExt) input).getBatchId(), input.getSourceStreamId());
    if (pendingBatch != null) {
        long rootId = getRootId(input);
        if (rootId != 0)
            pendingBatch.setFirst(JStormUtils.bit_xor(pendingBatch.getFirst(), rootId));
    }
    delegate.ack(input);
}

 

这里会将之前产生的随机数rootId与之前的rootId异或的结果,传给acker。

 

由此可以看到,如果第一次spout传给acker的val为x,第一次bolt产生的rootId为y,那么bolt将会把x和y异或的结果传给acker,而把y单独传给下一个bolt做异或操作。此时,acker里对应这个消息树的val的值为x与y与x异或的结果,也就是y。

那么看到streamId为ack的处理,也变得很简单,就是对rootId简单的异或操作。

如果streamId为fail,那么则会导致把这个ackObject的状态赋为fail。

由此可以看到,假设一个消息树存在一个spout,三个不同的bolt,能保证其完整的状态量为0的时候,是经过a^b^a^c^b^c^d^d。那么结果就是0,确保消息完成。

之后如果ackObject的val已经为0则通知spout已经处理完毕,如果不是,则继续,但如果状态已经是fail,则需要通知spout该消息已经失败。

 

可以看到,在更新了一次ackObject之后,如果此时更新的时间与上一次更新时间之间大于了规定的timeout,则其map则会对最后没有更新过的ack数据进行清理,通过RotatingMap的rotate()方法。

主要存放ackObject的map是一个RotatingMap,其构造如下。

private Deque<Map<K, V>> buckets;

 

其中是一个map组成的队列,队列的大小取决于acker的timeout_bucket_num参数。看到其put()方法。

 

@Override
public void put(K key, V value) {
    Iterator<Map<K, V>> it = buckets.iterator();
    Map<K, V> bucket = it.next();
    bucket.put(key, value);
    while (it.hasNext()) {
        bucket = it.next();
        bucket.remove(key);
    }
}

 

也就是说,新加入的acker数据都将处于队列的第一个位置,而其rotate()方法则如下会清理队列最后一个map,并在队列顶部加入一个新的map。由此可以得出,如果一个ack数据进入,如果该acker在timeout_bucket_num * timeout时间里没有被更新过,则在下一次别的ack数据进来时将会被清除。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值