Trident原理简析

本文深入探讨Apache Storm中的Trident框架,详细分析其高阶抽象特性,包括批处理、事务支持及数据流处理机制。重点讲解MasterBatchCoordinator、TridentSpoutCoordinator与TridentSpoutExecutor的工作原理,以及它们在数据流中的角色与交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

转载自:https://blog.csdn.net/jediael_lu/article/details/76687124
    http://www.cnblogs.com/hseagle/p/3490635.html

  Trident是storm的更高层次抽象,相对storm,它主要提供了3个方面的好处:
  (1)提供了更高层次的抽象,将常用的count,sum等封装成了方法,可以直接调用,不需要自己实现。
  (2)以批次代替单个元组,每次处理一个批次的数据。
  (3)提供了事务支持,可以保证数据均处理且只处理了一次。

Spout

  在Trident中用户定义的Spout需要实现ItridentSpout接口。ItridentSpout的定义:

public interface ITridentSpout<T> extends ITridentDataSource {
    interface BatchCoordinator<X> {
        X initializeTransaction(long txid, X prevMetadata, X currMetadata);
        void success(long txid);
        boolean isReady(long txid);
        void close();
    }
    
    interface Emitter<X> {
        void emitBatch(TransactionAttempt tx, X coordinatorMeta, TridentCollector collector);
        void success(TransactionAttempt tx);
        void close();
    }
    
    BatchCoordinator<T> getCoordinator(String txStateId, Map conf, TopologyContext context);
    Emitter<T> getEmitter(String txStateId, Map conf, TopologyContext context); 
    
    Map<String, Object> getComponentConfiguration();
    Fields getOutputFields();
}

  它有2个内部接口,BatchCoordinatorEmitter,分别是用于协调的Spout接口和发送消息的Bolt接口。实现一个Spout的主要工作就在于实现这2个接口,创建实际工作的Coordinator和Emitter。Spout中提供了2个get方法用于分别用于指定使用哪个Coordinator和Emitter类,这些类会由用户定义。
  getComponentConfiguration用于获取配置信息,getOutputFields获取输出field。

BatchCoordinator

  BatchCoordinator接口中有四个方法,下面几个是比较关键的。
  initializeTransaction方法返回一个用户定义的事务元数据。X是用户自定义的与事务相关的数据类型,返回的数据会存储到zk中。 其中txid为事务序列号,prevMetadata是前一个事务所对应的元数据。若当前事务为第一个事务,则其为空。currMetadata是当前事务的元数据,如果是当前事务的第一次尝试,则为空,否则为事务上一次尝试所产生的元数据。
  isReady方法用于判断事务所对应的数据是否已经准备好,当为true时,表示可以开始一个新事务。其参数是当前的事务号。

Emmitter

  Emmitter是消息发送节点,会接收协调spout(即MasterBatchCoordinator)的$batch$success流。
MasterBatchCoordinator(1)当收到$batch消息时,节点便调用emitBatch方法来发送消息。
MasterBatchCoordinator(2)当收到$success消息时,会调用success方法对事务进行后处理。

Spout实际的消息流

创建Spout后,它是怎么被加载到拓扑真正的Spout中呢?

1、MasterBatchCoordinator

&emsp;&emsp;org.apache.storm.trident.topology.MasterBatchCoordinator是一个数据流的真正起点:

  • 首先调用open方法完成初始化,包括读取之前的拓扑处理到的事务序列号,最多同时处理的tuple数量,每个事务的尝试次数等。
  • 然后nextTuple会改变事务的状态,或者是创建事务并发送$batch流。
  • 最后,ack方法会根据流的状态向外发送$commit流,或者是重新调用sync方法,开始创建新的事务。

  总而言之,MasterBatchCoordinator作为拓扑数据流的真正起点,通过循环发送协调信息,不断的处理数据流。MasterBatchCoordinator的真正作用在于协调消息的起点,里面所有的map,如_activeTx,_attemptIds等都只是为了保存当前正在处理的情况而已。

  来看MasterBatchCoordinator的定义:

public class MasterBatchCoordinator extends BaseRichSpout { 

  一个Trident拓扑的真正逻辑就是从MasterBatchCoordinator开始的,先调用open方法完成一些初始化,然后是在nextTuple中发送$batch$commit流。来看一下open方法

@Override
public void open(Map conf, TopologyContext context, SpoutOutputCollector collector) {
    _throttler = new WindowedTimeThrottler((Number)conf.get(Config.TOPOLOGY_TRIDENT_BATCH_EMIT_INTERVAL_MILLIS), 1);
    // 每个MasterBatchSpout可以处理多个ITridentSpout,这里将多个spout的元数据放到_states这个Map中。
    for(String spoutId: _managedSpoutIds) {
        _states.add(TransactionalState.newCoordinatorState(conf, spoutId));
    }
    
    // 从zk中获取当前的transation事务序号,当拓扑新启动时,需要从zk恢复之前的状态。
    // 也就是说zk存储的是下一个需要提交的事务序号,而不是已经提交的事务序号。
    _currTransaction = getStoredCurrTransaction();

    _collector = collector;

    // 任何时刻中,一个spout task最多可以同时处理的tuple数量,即已经emite,但未acked的tuple数量。
    Number active = (Number) conf.get(Config.TOPOLOGY_MAX_SPOUT_PENDING);
    if(active==null) {
        _maxTransactionActive = 1;
    } else {
        _maxTransactionActive = active.intValue();
    }
    // 每一个事务的当前尝试编号,即_currTransaction这个事务序号中,各个事务的尝试次数。
    _attemptIds = getStoredCurrAttempts(_currTransaction, _maxTransactionActive);

    
    for(int i=0; i<_spouts.size(); i++) {
        String txId = _managedSpoutIds.get(i);
    // 将各个Spout的Coordinator保存在_coordinators这个List中。
        _coordinators.add(_spouts.get(i).getCoordinator(txId, conf, context));
    }
    LOG.debug("Opened {}", this);
}

  再看一下nextTuple()方法,它只调用了sync()方法:

@Override
public void nextTuple() {
    sync();
}

private void sync() {
    // note that sometimes the tuples active may be less than max_spout_pending, e.g.
    // max_spout_pending = 3
    // tx 1, 2, 3 active, tx 2 is acked. there won't be a commit for tx 2 (because tx 1 isn't committed yet),
    // and there won't be a batch for tx 4 because there's max_spout_pending tx active
    // 判断当前事务_currTransaction是否为PROCESSED状态,如果是的话,将其状态改为COMMITTING,然后发送$commit流。
    // 接收到$commit流的节点会调用finishBatch方法,进行事务的提交和后处理。
    TransactionStatus maybeCommit = _activeTx.get(_currTransaction);
    if(maybeCommit!=null && maybeCommit.status == AttemptStatus.PROCESSED) {
        maybeCommit.status = AttemptStatus.COMMITTING;
        _collector.emit(COMMIT_STREAM_ID, new Values(maybeCommit.attempt), maybeCommit.attempt);
        LOG.debug("Emitted on [stream = {}], [tx_status = {}], [{}]", COMMIT_STREAM_ID, maybeCommit, this);
    }
    
    // 用于产生一个新事务。
    // 最多存在_maxTransactionActive个事务同时运行,当前active的事务序号区间处于[_currTransaction,_currTransaction+_maxTransactionActive-1]之间。
    // 注意只有在当前事务结束之后,系统才会初始化新的事务,所以系统中实际活跃的事务可能少于_maxTransactionActive。
    if(_active) {
        if(_activeTx.size() < _maxTransactionActive) {
            Long curr = _currTransaction;
            for(int i=0; i<_maxTransactionActive; i++) {
            // 如果事务序号不存在_activeTx中,则创建新事务,并发送$batch流。当ack被调用时,这个序号会被remove掉。
                if(!_activeTx.containsKey(curr) && isReady(curr)) {
                    // by using a monotonically increasing attempt id, downstream tasks
                    // can be memory efficient by clearing out state for old attempts
                    // as soon as they see a higher attempt id for a transaction
                    Integer attemptId = _attemptIds.get(curr);
                    if(attemptId==null) {
                        attemptId = 0;
                    } else {
                        attemptId++;
                    }
                    // _activeTx记录的是事务序号和事务状态的map,而_activeTx则记录事务序号与尝试次数的map。
                    _attemptIds.put(curr, attemptId);
                    for(TransactionalState state: _states) {
                        state.setData(CURRENT_ATTEMPTS, _attemptIds);
                    }
                    // TransactionAttempt包含事务序号和尝试编号2个变量,对应于一个具体的事务。
                    TransactionAttempt attempt = new TransactionAttempt(curr, attemptId);
                    final TransactionStatus newTransactionStatus = new TransactionStatus(attempt);
                    _activeTx.put(curr, newTransactionStatus);
                    _collector.emit(BATCH_STREAM_ID, new Values(attempt), attempt);
                    LOG.debug("Emitted on [stream = {}], [tx_attempt = {}], [tx_status = {}], [{}]", BATCH_STREAM_ID, attempt, newTransactionStatus, this);
                    _throttler.markEvent();
                }
                // 如果事务序号已经存在_activeTx中,则curr递增,然后再循环检查下一个。
                curr = nextTransactionId(curr);
            }
        }
    }
}

  nextTuple()主要完成了以下功能:

  • 如果事务状态是PROCESSED,则将其状态改为COMMITTING,然后发送commit流。接收到commit流。接收到commit流的节点会调用finishBatch方法,进行事务的提交和后处理 .
  • 如果_activeTx.size()小于_maxTransactionActive,则新建事务,放到_activeTx中,同时向外发送$batch流(_collector.emit(BATCH_STREAM_ID, new Values(attempt), attempt);),等待Coordinator的处理。( 当ack方法被 调用时,这个事务会被从_activeTx中移除)。

  注意:当前处于acitve状态的应该是序列在[_currTransaction,_currTransaction+_maxTransactionActive-1]之间的事务。

  继续往下,看看ack方法:

@Override
public void ack(Object msgId) {
    // 获取事务的状态
    TransactionAttempt tx = (TransactionAttempt) msgId;
    TransactionStatus status = _activeTx.get(tx.getTransactionId());
    LOG.debug("Ack. [tx_attempt = {}], [tx_status = {}], [{}]", tx, status, this);
    if(status!=null && tx.equals(status.attempt)) {
    // 如果当前状态是PROCESSING,则改为PROCESSED
        if(status.status==AttemptStatus.PROCESSING) {
            status.status = AttemptStatus.PROCESSED;
            LOG.debug("Changed status. [tx_attempt = {}] [tx_status = {}]", tx, status);
        } else if(status.status==AttemptStatus.COMMITTING) {
            // 如果当前状态是COMMITTING,则将事务从_activeTx及_attemptIds去掉,并发送$success流。
            _activeTx.remove(tx.getTransactionId());
            _attemptIds.remove(tx.getTransactionId());
            _collector.emit(SUCCESS_STREAM_ID, new Values(tx));
            _currTransaction = nextTransactionId(tx.getTransactionId());
            for(TransactionalState state: _states) {
                state.setData(CURRENT_TX, _currTransaction);                    
            }
            LOG.debug("Emitted on [stream = {}], [tx_attempt = {}], [tx_status = {}], [{}]", SUCCESS_STREAM_ID, tx, status, this);
        }
        // 由于有些事务状态已经改变,需要重新调用sync()继续后续处理,或者发送新tuple。
        sync();
    }
}

  还有fail方法和declareOutputFileds方法, isReady方法。

@Override
public void fail(Object msgId) {
    TransactionAttempt tx = (TransactionAttempt) msgId;
    TransactionStatus stored = _activeTx.remove(tx.getTransactionId());
    LOG.debug("Fail. [tx_attempt = {}], [tx_status = {}], [{}]", tx, stored, this);
    if(stored!=null && tx.equals(stored.attempt)) {
        _activeTx.tailMap(tx.getTransactionId()).clear();
        sync();
    }
}
@Override
public void declareOutputFields(OutputFieldsDeclarer declarer) {
    // in partitioned example, in case an emitter task receives a later transaction than it's emitted so far,
    // when it sees the earlier txid it should know to emit nothing
    declarer.declareStream(BATCH_STREAM_ID, new Fields("tx"));
    declarer.declareStream(COMMIT_STREAM_ID, new Fields("tx"));
    declarer.declareStream(SUCCESS_STREAM_ID, new Fields("tx"));
}
private boolean isReady(long txid) {
    if(_throttler.isThrottled()) return false;
    //TODO: make this strategy configurable?... right now it goes if anyone is ready
    for(ITridentSpout.BatchCoordinator coord: _coordinators) {
        if(coord.isReady(txid)) return true;
    }
    return false;
}
2、TridentSpoutCoordinator

  TridentSpoutCoordinator接收来自MasterBatchCoordinator的$success流与$batch流,并通过调用用户代码,实现真正的逻辑。此外还向TridentSpoutExecuter发送$batch流,以触发后者开始真正发送业务数据流。

public class TridentSpoutCoordinator implements IBasicBolt {

  可见TridentSpoutCoordinator是一个bolt,在创建TridentSpoutCoordinator时,需要传递一个ITridentSpout对象:

public TridentSpoutCoordinator(String id, ITridentSpout<Object> spout) {
    _spout = spout;
    _id = id;
}

  在prepare方法中使用这个对象来获取到用户定义的Coordinator:

@Override
public void prepare(Map conf, TopologyContext context) {
    _coord = _spout.getCoordinator(_id, conf, context);
    _underlyingState = TransactionalState.newCoordinatorState(conf, _id);
    _state = new RotatingTransactionalState(_underlyingState, META_DIR);
}

  _state和_underlyingState保存了zk中的元数据信息。

  在execute方法中,TridentSpoutCoordinator接收$success流与$batch流:

@Override
public void execute(Tuple tuple, BasicOutputCollector collector) {
    TransactionAttempt attempt = (TransactionAttempt) tuple.getValue(0);

    if(tuple.getSourceStreamId().equals(MasterBatchCoordinator.SUCCESS_STREAM_ID)) {
        _state.cleanupBefore(attempt.getTransactionId());
        _coord.success(attempt.getTransactionId());
    } else {
        long txid = attempt.getTransactionId();
        Object prevMeta = _state.getPreviousState(txid);
        Object meta = _coord.initializeTransaction(txid, prevMeta, _state.getState(txid));
        _state.overrideState(txid, meta);
        collector.emit(MasterBatchCoordinator.BATCH_STREAM_ID, new Values(attempt, meta));
    }     
}

  接收到$success流时,清理了zk中的数据,然后调用用户定义的Coordinator中的success方法。
  接收到$batch流时,初始化一个事务并将其发送出去。由于在trident中消息有可能是重放的,因此需要prevMeta。注意,trident是在bolt中初始化一个事务的。

3、TridentSpoutExecutor

  TridentSpoutExecutor也是一个bolt,接收来自TridentSpoutCoordinator的消息流,包括$commit,$success$batch流,前面2个分别调用用户定义的emmitter的commit与success方法,$batch流时则调用emmitter的emitBatch方法,开始向外发送业务数据。
  核心的execute方法:

@Override
public void execute(BatchInfo info, Tuple input) {
    // there won't be a BatchInfo for the success stream
    TransactionAttempt attempt = (TransactionAttempt) input.getValue(0);
    if(input.getSourceStreamId().equals(MasterBatchCoordinator.COMMIT_STREAM_ID)) {
        if(attempt.equals(_activeBatches.get(attempt.getTransactionId()))) {
            ((ICommitterTridentSpout.Emitter) _emitter).commit(attempt);
            _activeBatches.remove(attempt.getTransactionId());
        } else {
             throw new FailedException("Received commit for different transaction attempt");
        }
    } else if(input.getSourceStreamId().equals(MasterBatchCoordinator.SUCCESS_STREAM_ID)) {
        // valid to delete before what's been committed since 
        // those batches will never be accessed again
        _activeBatches.headMap(attempt.getTransactionId()).clear();
        _emitter.success(attempt);
    } else {            
        _collector.setBatch(info.batchId);
        _emitter.emitBatch(attempt, input.getValue(1), _collector);
        _activeBatches.put(attempt.getTransactionId(), attempt);
    }
}
4、总结

  MasterBatchCoordinator才是真正的spout,另外2个都是bolt。
  MasterBatchCoordinator会调用用户定义的BatchCoordinator的isReady()方法,返回true的话,则会在nextTuple()发送一个id为batch的消息流,作为整个数据流的起点。MasterBatchCoordinator会先判断正在处理的事务数是否少于maxTransactionActive,是的话就继续向外发送batch流。
  TridentSpoutCoordinator接到MasterBatchCoordinator的batch流后,在execute()方法中会调用BatchCoordinator的initialTransaction()生成meta消息,并继续向外发送 batch流。
  TridentSpoutExecutor接到TridentSpoutCoordinator转发的batch后,会调用用户Emitter类中的emitBatch()方法,开始发送实际的业务数据。

  当整个消息被成功处理完后,会调用MasterBatchCoordinator的ack()方法,ack方法会将事务的状态从PROCESSING改为PROCESSED,然后将其改为COMMITTING的状态,并向外发送id为commit的流。当然,如果fail掉了,则会调用fail()方法。
  TridentSpoutCoordinator收到commit流的节点会开始提交操作,但trident会按事务号顺序提交事务的,所以由提交bolt来决定是否现在提交,还是先缓存下来之后再提交。当commit流处理完后,MasterBatchCoordinator的ack()方法会被再次调用,同时向外发送success流。
  TridentSpoutExecutor收到$success流时,会调用Emitter的success方法。至此整个流程全部完成。

Bolt

(一)概述
1、组件的基本关系

  Trident拓扑最终会转化为一个spout和多个bolt,每个bolt对应一个SubTopologyBolt,它通过TridentBoltExecutor适配成一个bolt。而每个SubTopologyBolt则由很多节点组成,具体点说这个节点包括(Stream|Node)两部分,注意,Node不是Stream自身的成员变量,而是一个具体的处理节点。Stream定义了哪些数据流,Node定义和如何进行操作,Node包含了一个ProjectedProccessor等处理器,用于定义如何进行数据处理。
  一个SubTopologyBolt包含多个Group,但大多数情况下是一个Group。看TridentTopology#genBoltIds()的代码。在一个SubTopologyBolt中,含有多个节点组是可能的。例如在含有DRPC的Topology中,查询操作也存储操作可以被分配到同一个SubTopologyBolt中。于是该bolt可能收到来自2个节点组的消息。
  一个Group有多个Node。符合一定条件的Node会被merge()成一个Group,每个Node表示一个操作。
  每个Node与一个Stream一一对应。注意Stream不是指端到端的完整流,而是每一个步骤的处理对象,所有的Stream组合起来才形成完整的流。
  每个Node可能有多个父stream,但多个的情况只在merge()调用multiReduce()时使用。每个Stream与node之间创建一条边。见TridentTopology#addSourceNode()方法。

2、用户视角与源码视角

  在用户角度来看,通过newStream(),each(),filter()对Stream进行操作。而在代码角度,这些操作会被转化为各种Node节点,它些节点组合成一个SubTopologyBolt,然后经过TridentBoltExecutor适配后成为一个bolt。
  从用户层面来看TridentTopology,有两个重要的概念一是Stream,另一个是作用于Stream上的各种Operation。在实现层面来看,无论是stream,还是后续的operation都会转变成为各个Node,这些Node之间的关系通过重要的数据结构图来维护。具体到TridentTopology,实现图的各种操作的组件是jgrapht。
  说到图,两个基本的概念会闪现出来,一是结点,二是描述结点之间关系的边。要想很好的理解TridentTopology就需要紧盯图中结点和边的变化。
  TridentTopology在转换成为普通的StormTopology时,需要将原始的图分成各个group,每个group将运行于一个独立的bolt中。TridentTopology又是如何知道哪些node应该在同一个group,哪些应该处在另一个group中的呢;如何来确定每个group的并发度(parallismHint)的呢。这些问题的解决都与jgrapht分不开。
  在用户看来,所有的操作就是各种各样的数据流与operation的组合,这些组合会被封装成一个Node(即一个Node包含输入流+操作+输出流),符合一定规则的Node会被组合与一个组,组会被放到一个bolt中。
  一个blot节点中可能含有多个操作,各个操作间需要进行消息传递

(二)基础类
1、Stream

  Stream主要定义了数据流的各种操作,如each(),project()等。

public class Stream implements IAggregatableStream, ResourceDeclarer<Stream> {
    final Node _node;
    final String _name;
    private final TridentTopology _topology;

  三个成员变量:

  • Node对象,这表明Stream与Node是一一对应的,每个节点对应一个Stream对象。
  • name:这个Stream的名称,也等于是这这个Node的名称。
  • TridentTopology: 这个Stram所属的拓扑,使用这个变量,可以调用addSourceNode()等方法。

  stream中定义了各种各样的trident操作。
  projectionValidation()这个方法用于检查field是否存在。project()是只保留指定的fileds:

public Stream project(Fields keepFields) {
    projectionValidation(keepFields);
    return _topology.addSourcedNode(this, new ProcessorNode(_topology.getUniqueStreamId(), _name, keepFields, new Fields(), new ProjectedProcessor(keepFields)));
}

  首先检查一下需要project的field是否存在。然后就在TridentTopology中新增一个节点。
  topology.addSourcedNode方法第一个参数就是Stream自身,第二个参数是一个Node的子类ProcessorNode。创建ProcessorNode时,最后一个参数ProjectedProcessor用于指定如何对流进行操作。
  addSourcedNode把source和node同时添加进一个拓扑,即一个流与一个节点。注意这里的节点不是source这个Stream自身的成员变量_node,而是一个新建的节点,比如在project()方法中的节点就是一个使用ProjectedProcessor创建的ProcessorNode。

2、Node SpoutNode PartitionNode ProcessorNode

  Node表示拓扑中的一个节点,后面3个均是其子类。
  事实上拓扑中的节点均用于产生数据或者对数据进行处理。一个拓扑有多个spout/bolt,每个spout/bolt有一个或者多个Group,一个Group有多个Node。

3、Group

  节点组是构建SubTopologyBolt的基础,也是Topology中执行优化的基本操作单元,Trident会通过不断的合并节点组来达到最优处理的目的。Group中包含了一组连通的节点。

public class Group {
    public final Set<Node> nodes = new HashSet<>();
    private final DirectedGraph<Node, IndexedEdge> graph;
    private final String id = UUID.randomUUID().toString();

  nodes表示节点组中含有的节点,graph表示拓扑的有向图(是整个拓扑的构成的图),id用于唯一标识一个group。
  初始状态时,每个Group只有一个Node。outgoingNodes()方法是通过遍历组中节点的方式来获取该节点组所有节点的子节点。incommingNodes()
  用于获取该节点组中所有节点的父节点。

4、GraphGrouper

  GraphGrouper提供了对节点组进行操作及合并的基本方法。

public class GraphGrouper {
    final DirectedGraph<Node, IndexedEdge> graph;
    final Set<Group> currGroups;
    final Map<Node, Group> groupIndex = new HashMap<>();

  graph:与Group相同,即这个拓扑的整个图。
  currGroups:当前graph对应的节点组。节点组之间是没有交集的。
  groupIndex:是一个反向索引,用于快速查询每个节点所在的节点组。

  mergeFully是GraphGrouper的核心算法,它用来计算何时可以对2个节点组进行合并。基本思想是:如果一个节点组只有一个父节点组,那么将这个节点组与父节点组合并;如果一个节点组只有一个子节点组,那么将子节点组与自身节点组合并。反复进行这个过程。

在TridentTopology中设置Spout、Bolt

  从TridentTopology到vanilla topology(普通的topology)由三个层次组
成:
  1. 面向最终用户的概念stream, operation
  2. 利用planner将tridenttopology转换成vanilla topology
  3. 执行vanilla topology

  从TridentTopology到基本的Topology有三层,下图是一个全局视图。

  如何将Stream与原有的Spout及Bolt联系起来呢?关键在于TridentTopology#build方法。
  在创建TridentTopology时,newStream及其后的函数实际上创建了一个含有三大类节点的List, 这三类节点分别是operation, partition, spout。在TridentTopology#build函数中将节点分类分别加入到boltNodes或spoutNodes,创建一个有向非循环图(DAG)。注意此处的spout或bolt不能等同于普通的spout和bolt。
  TridentTopology#build中会调用TridentTopologyBuilder::buildTopology,该方法利用在build函数中创建的boltNodes,spoutNodes及生成的graph来创建vanilla topology所需要的bolt及spout。在buildTopology中会看到类似的代码片段:

SpoutDeclarer masterCoord = builder.setSpout(masterCoordinator(batch), new MasterBatchCoordinator(commitIds, batchesToSpouts.get(batch)));
BoltDeclarer scd =
      builder.setBolt(spoutCoordinator(id), new TridentSpoutCoordinator(c.commitStateId, (ITridentSpout) c.spout))
        .globalGrouping(masterCoordinator(c.batchGroupId), MasterBatchCoordinator.BATCH_STREAM_ID)
        .globalGrouping(masterCoordinator(c.batchGroupId), MasterBatchCoordinator.SUCCESS_STREAM_ID);
BoltDeclarer bd = builder.setBolt(id,
        new TridentBoltExecutor(
          new TridentSpoutExecutor(
            c.commitStateId,
            c.streamName,
            ((ITridentSpout) c.spout)),
            batchIdsForSpouts,
            specs),
        c.parallelism);

  最终生成的普通Topology,与普通Topology中的Spout相对应的是MasterBatchCoordinator,而在创建TridentTopology使用的spout则成了Bolt,使用于Stream上的各种Operation也存在于多个普通Bolt中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值