jStorm 流分发-订阅机制测试

摘要

在 Storm 的拓扑中,存在若干种流分发策略;而在拓扑的创建中,也容许一个拓扑中接收消息的为不同类型的 bolt。那么在复杂拓扑结构中,流分发机制是否可靠?本文以实验的方式模拟稍微复杂的网络拓扑,并发送数据流进行了验证,得出 jStorm 可以很好地识别 bolt 类型,不同组 bolt 订阅相同流互相不影响的结论。随后将对 jStorm 如何做到这种类似消息队列的消费者 offset 维护将进行浅析。

拓扑结构

如下图所示,TestSpout 通过 static Atomic Integer 原子操作类型进行线程安全的多线程计数,并将该值发送至两个不同处理逻辑的 Bolt。之后对两个 Bolt 的输入日志进行分析。
模拟两种类型bolt的流订阅

测试结论

  1. 不同的 Bolt 组订阅消息满足不同 groupping 的预期,即流量分发均可拿到全量数据而互不干扰;
  2. 只要有流出异常,如果两边都有 ack 机制,那么任务会被停止;
  3. 否则,如果未出异常,那么两边均正常 ack 后,继续发送下一个 tuple(从这里猜测,应该是 spout 内部维护了一个同步发送队列,即所有需要 ack 的流都被应答后,才同时广播给所有 Bolt 下一个数据)。
  4. 变换 worker 数目后发现,不同的 task(bolt/spout均被按顺序编号task-id)几乎会均匀的分配到不同的 worker(同一个worker间的task之间数据收发通过队列,不同的worker的task之间数据收发通过netty),未产生流丢失的现象,且日志分别在不同的两个worker所在机器上。

测试代码

/**
* @Description 测试spout的流订阅机制
* @param spoutSize
* @param boltSize
* @param workers
* @return
* @throws Exception
 */

    private boolean testSample(Integer spoutSize, Integer boltSize, Integer workers) throws Exception {
            TopologyBuilder builder = new TopologyBuilder();
            builder.setSpout("testSpout", new TestSpout(), spoutSize);

            builder.setBolt("TestBoltOrig", new TestBoltOrig(), 2).fieldsGrouping("testSpout",new Fields("modVal"));            
            builder.setBolt("TestBoltDouble", new TestBoltDouble(), 3).fieldsGrouping("testSpout",new Fields("modVal"));         
            conf.put(backtype.storm.Config.TOPOLOGY_WORKERS, 2); // 设置Worker的数量

            try {
                //提交测试拓扑
                StormSubmitManager.getInstance().submitTopology(builder, "testSample", conf);
                LOGGER.info("submit success!");
                return true;
            } catch (Exception e) {
                LOGGER.error("submit failed : " + e.getMessage(), e);
                throw e;
            }
        }
/**
 * @Description 测试 wordCount
 * @author cathar
 * @Date 2017年1月4日 下午8:25:07
 */

public class TestSpout extends BaseRichSpout {

    private SpoutOutputCollector collector;   
    private boolean completed = false;
    private static final Logger LOGGER = Logger.getLogger(TestSpout.class);
    //全局共享的,在spout初始化的时候,每个线程获取一次作为自己的编号
    static AtomicInteger sAtomicInteger = new AtomicInteger(0);
    //多线程共享的变量
    static AtomicInteger pendNum = new AtomicInteger(0);
    private int sqnum;


    @Override
    public void open(Map conf, TopologyContext context,
            SpoutOutputCollector collector) {
      //spout 线程编号 
        sqnum = sAtomicInteger.incrementAndGet();
      //初始化发射器  
        this.collector = collector;  
    }

    @Override
    public void nextTuple() {
   //模拟一直发送递增的全局数字
        while (true) {
            int a = pendNum.incrementAndGet();
            //如果有多个线程,这里输出的数字是连续的,共同改变
            LOGGER.info(String.format("spout %d, send pendNum %d", sqnum, a));
            this.collector.emit(new Values(a%10, a));

            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        // TODO Auto-generated method stub
        //declarer.declare(new Fields("word"));  
        declarer.declare(new Fields("modVal","val"));
    }

    /**
     * 启用 ack 机制,详情参考:https://github.com/alibaba/jstorm/wiki/Ack-%E6%9C%BA%E5%88%B6
     * @param msgId
     */
    @Override
    public void ack(Object msgId) {
        super.ack(msgId);
    }

    /**
     * 消息处理失败后需要自己处理
     * @param msgId
     */
    @Override
    public void fail(Object msgId) {
        super.fail(msgId);
        LOGGER.info("ack fail,msgId"+msgId);
    }
}
public class TestBoltOrig implements IRichBolt {
//...
@Override
    public void execute(Tuple input) {
        Integer partition = input.getIntegerByField("modVal");
        Integer val = input.getIntegerByField("val");

        LOGGER.error(String.format("TestBoltOrig received partition %d:  val %d", partition,val));
        this.collector.ack(input);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
//...
}
public class TestBoltDouble implements IRichBolt {
//...
@Override
    public void execute(Tuple input) {
        Integer partition = input.getIntegerByField("modVal");
        Integer val = input.getIntegerByField("val");
        LOGGER.error(String.format(" TestBoltDouble received partition %d:  val %d", partition,val*10));
        this.collector.ack(input);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
//...
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值