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();
        }
    }
//...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章