jStorm 流分发-订阅机制测试

来源:互联网 发布:读博士 知乎 编辑:程序博客网 时间:2024/05/21 08:47

摘要

在 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 0
原创粉丝点击