Storm集成Kafka的Trident實現

 
原本打算將storm直接與flume直連,發現相應組件支持比較弱,topology任務對應的supervisor也不一定在哪個節點上,只能採用統一的分佈式消息服務Kafka。
 
原本打算將結構設置爲:


 
最後結構更改爲:
 


  

集成Kafka

 
storm中已經寫好了KafkaSpout用來接收Kafka中間件上的消息,併發射到Bolt中,只需要依賴 storm-kafka即可:
 
<dependency>
            <groupId>org.apache.storm</groupId>
            <artifactId>storm-kafka</artifactId>
            <version>${storm.version}</version>
        </dependency>
 
 
調用org.apache.storm.kafka.KafkaSpout,  需要傳遞一個SpoutConfig用來配置kafka對應的zookeeper以及topic:
 
String zks = "192.168.1.1xx:2181,192.168.1.1xx:2181,192.168.1.1xx:2181/kafka";
        String topic = "log-storm";

        BrokerHosts brokerHosts = new ZkHosts(zks);
        SpoutConfig spoutConfig = new SpoutConfig(brokerHosts, topic, "/"+ topic, UUID.randomUUID().toString());
        spoutConfig.scheme = new SchemeAsMultiScheme(new StringScheme());
        spoutConfig.zkServers = Arrays.asList("192.168.1.1xx","192.168.1.1xx","192.168.1.1xx");
        spoutConfig.zkPort = 2181;
 
 
建立KafkaSpout(spoutConfig)即可。
 
需要注意的是,我們在Bolt中需要對收到的消息進行主動ack/fail,否則會出現消息重複發送的情況,一般情況下Bolt的寫法類似下面,在prepare中緩存collector,executor中通過try/catch塊決定是否確認消息(以通知Spout是否需要對消息進行重發),declareOutputFields中聲明需要輸出的字段。
 
private OutputCollector collector;

    @Override
    public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
        this.collector = collector;
    }

    @Override
    public void execute(Tuple input) {
        try {
            String msgBody = input.getString(0);
            int traceIndex = msgBody.indexOf(TRACE_CONST);
            if (traceIndex >= 0) {
                String completeLog = msgBody.substring(traceIndex + TRACE_CONST.length());
                collector.emit(new Values(completeLog));
            }
            collector.ack(input);
        } catch (Exception e) {
            collector.fail(input);
        }
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
        declarer.declare(new Fields("log"));
    }
 
 
Storm中需要有一個main函數,用於構建和啓動topology,以便將spout,bolt等組件連接起來,代碼類似下面:
 
TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("kafka-reader", new KafkaSpoutNoMetrics(spoutConfig), 3);

        builder.setBolt("log-extractor", new LogExtractorBolt(), 2).shuffleGrouping("kafka-reader");
        builder.setBolt("log-splitter", new LogSplitterBolt(), 2).shuffleGrouping("log-extractor");
        builder.setBolt("memcached-store", new MemcachedBolt()).fieldsGrouping("log-splitter", new Fields("md"));

        Config config = new Config();
        String name = "LogStormProcessor";

        config.setNumWorkers(1);
        StormSubmitter.submitTopologyWithProgressBar(name, config, builder.createTopology());
 
 
 

使用Storm Trident

 
Trident在Storm上進行了高級抽象,例如事務處理和狀態管理的細節,可以讓一批tuple進行離散的事務處理,還允許topology在數據上執行函數功能、過濾和聚合操作。
 


 
 
 
使用Trident,我們需要使用TridentTopology替換原有的TopologyBuilder構造Storm的拓撲圖。在Trident中的spout引入了數據批次(batch)的概念,不像Strom中的spout,Trident Spout必須成批地發送tuple。
 
在Trident中,spout並沒有真正發射tuple,而是把這項工作分解給了BatchCoordinator和Emitter方法,Emitter方法負責發送tuple,BatchCoordinator負責管理批次和元數據,Emitter需要依靠元數據來恰當地進行批次的數據重放。
 
首先,需要根據ITridentSpout新建一個數據流,
 
       
 Stream stream = tridentTopology.newStream("event", kafkaSpout);
 
在使用KafkaSpout作爲TridentSpout時,其默認的輸出字段名稱爲str,
 
 
Exception in thread "main" java.lang.IllegalArgumentException: Trying to select non-existent field: 'event' from stream containing fields fields: <[str]>
    at org.apache.storm.trident.Stream.projectionValidation(Stream.java:853)
    at org.apache.storm.trident.Stream.each(Stream.java:320)
    at com.zhen.log.processor.trident.Main.main(Main.java:48)
 
 
我們原來使用的KafkaSpout,雖然可以將其直接用於newStream方法,但是運行時會出現錯誤:
 


 
 
原因就在於進行適配的過程中,註冊方法registerMetric只能夠被宰ISpout::open()方法中被調用,雖然可以進行合理地改造(由於有一些包訪問控制權限的相關依賴,新建一個同名package,並將其中的registerMetric方法刪除),但是其事務性不能得到保證,在本人測試的過程中,Kafka的消息不能被正常消費,每次重啓服務都會讀到完整的所有數據。
 
但是這並不是推薦的用法,storm-kafka中存在另外一個實現:org.apache.storm.kafka.trident.OpaqueTridentKafkaSpout,可以滿足要求:
 
TridentKafkaConfig kafkaConfig = new TridentKafkaConfig(brokerHosts, topic);
        OpaqueTridentKafkaSpout kafkaSpout = new OpaqueTridentKafkaSpout(kafkaConfig);

        TridentTopology tridentTopology = new TridentTopology();
        Stream stream = tridentTopology.newStream("event", kafkaSpout);
 
 
而使用OpaqueTridentKafkaSpout時,默認的輸出名稱爲“bytes”,其輸出格式也並不是String字符串而是byte[],需要根據編碼格式將其轉換爲String。
 
在Spout編寫完成後,就可以加入後續的運算操作,trident處理是通過創建Stream的各種operation並連接來進行處理的,比較常用的兩種運算:filter和function,例如我們下面處理流的方式,每次返回Stream都可以繼續用來創建新的數據流:
 
Stream logStream = stream.each(new Fields("bytes"), new LogExtractorFunction(), new Fields("log"))
                .each(new Fields("log"), new LogSplitterFunction(), new Fields("logObject"))
                .each(new Fields("logObject"), new LogTypeFilter("TRACE"));
 
在filter中,繼承自BaseFilter,唯一的isKeep方法會根據tuple的屬性進行相應過濾操作,需要指定對應輸入的Field,filter沒有額外輸出的多餘字段。注意filter中不能改變tuple,如果既想要過濾又想添加字段時必須使用function。
 
在function中,繼承自BaseFunction,通過execute方法來對所有的數據增加額外的字段,並不會刪除或者變更已有的字段。使用function需要指定多餘輸出的Fields,function中發射的字段數要與聲明的fields字段數據保持一致。
 
和function比較類似,aggregator允許topology組合tuple,不同的是,它會替換tuple字段和值,有三種聚合器可以被使用:CombinerAggregator,ReducerAggregator和Aggregator。這裏,我們使用的是CombinerAggregator。
 
CombinerAggregator用來將一個集合的tuple組合到一個單獨到一個單獨的字段中,定義如下:
 
public interface CombinerAggregator<T> extends Serializable {
    T init(TridentTuple tuple);
    T combine(T val1, T val2);
    T zero();
}
 
 
Storm會對每個tuple調用init方法,然後重複調用combiner方法指導一個分片的數據處理完成,傳遞給combine方法的兩個參數是局部聚合的結果,以及調用了init返回的值,如果沒有聚合結果,會直接調用zero方法返回一個自定義空值。
 
聚合一般需要首先對數據流進行groupBy操作後,在GroupedStream流上進行實際操作,一般情況下,首先根據前面的流輸出一個用於分組的鍵值用於groupBy,然後進行persistentAggregate,根據分組將數據歸併計算合併結果。
 
        logStream
                .each(new Fields("logObject"), new LogGroupFunction(), new Fields("key")).groupBy(new Fields("key"))
                .persistentAggregate(MemcachedState.nonTransactional(servers), new Fields("logObject"), new LogCombinerAggregator(),
                        new Fields("statistic"))
 
 
 
注意,使用Trident時也是可以分成多個流的,只需要在特定的節點上,保存本地變量,就可以在其上執行多次each,分出多條路徑流進行獨立處理(也可以對多條輸入流進行合併,這裏沒有使用到這樣高級的功能)。
 
Stream logStream = stream.each(new Fields("bytes"), new LogExtractorFunction(), new Fields("log"))
                .each(new Fields("log"), new LogSplitterFunction(), new Fields("logObject"))
                .each(new Fields("logObject"), new LogTypeFilter("TRACE"));

        logStream.each(new Fields("log"), new LocalFileSaveFunction(), new Fields());

        logStream
                .each(new Fields("logObject"), new LogGroupFunction(), new Fields("key")).groupBy(new Fields("key"))
                .persistentAggregate(MemcachedState.nonTransactional(servers), new Fields("logObject"), new LogCombinerAggregator(),
                        new Fields("statistic"))
        ;
 
 
在使用任何function,aggregator時,都可以通過聲明Fields的方式來設置使用到的字段名稱,Combiner中可以不使用任何定義的Fields,此時傳遞給Trident的Tuple中將不會包含任何字段(一般代碼示例中如此)。使用聚合時,還需要持續存儲聚合的Trident狀態,持久化操作從狀態管理開始,Trident對狀態有底層的操作原語,可以參考State接口的方法。
 
Storm中用State來持久化存儲信息,有三種狀態類型:非事務型,重複事務型以及不透明事務型,在分佈式環境下,數據可能被重放,爲了支持計數和狀態更新,Trident將狀態更新操作進行序列化,使用不同的狀態更新模式對重放和錯誤數據進行容錯。
 
我們存儲中間數據狀態使用了memcached作爲媒介,關於trident與memcached進行事務處理相關代碼,可以參考工程(storm創建者編寫)
 
 
其中調用了twitter中定義的所以使用改造過的客戶端:
<dependency>
            <groupId>com.twitter</groupId>
            <artifactId>finagle-memcached_2.9.2</artifactId>
            <version>6.20.0</version>
        </dependency>
 
但是將源碼copy到工程中並添加對應的maven依賴(多數是twitter相關的依賴),其中的twitter依賴始終找不到:
 
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal on project log-storm-processor: Could not resolve dependencies for project com.zhen:log-storm-processor:jar:1.0.0-SNAPSHOT: The following artifacts could not be resolved: com.twitter.common.zookeeper:server-set:jar:1.0.83, com.twitter.common.zookeeper:client:jar:0.0.60, com.twitter.common.zookeeper:group:jar:0.0.78: Failure to find com.twitter.common.zookeeper:server-set:jar:1.0.83 in http://192.168.1.14:8081/nexus/content/repositories/releases/ was cached in the local repository, resolution will not be reattempted until the update interval of nexus-releases has elapsed or updates are forced -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
 
 
因此只能將其進行改造,使用com.whalin對應的memcached客戶端jar包,以滿足從Storm存儲到memcached的需求。
 
storm trident的結構:
 


 
 
 
storm中還存在其他的工具,Storm-contribute項目地址:https://github.com/nathanmarz/storm-contrib,同樣是作者所寫,加入了支持Redis,Kafka,MongoDB等數據源。
  • 17bfb1a9-d467-3593-b14c-d08dc5fd621c-thumb.png
  • 大小: 101.5 KB
  • ea0c8a6d-24a9-33d7-a651-f04701199e34-thumb.png
  • 大小: 87.9 KB
  • 8703cd93-7987-321a-8a00-7143e5170bb7-thumb.png
  • 大小: 56.5 KB
  • f663c88b-c3ba-31e2-b3d7-34a2ab142c94-thumb.png
  • 大小: 43.9 KB
  • fe242c03-f6d3-38eb-9c70-022720d3470a-thumb.png
  • 大小: 37.4 KB
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章