分散式系統監控(五)- 日誌分析

weixin_33866037發表於2018-08-23

背景

前面一章節介紹瞭如何使用rocketmq來歸集資料資訊,下面將介紹如何使用storm從rocketmq中獲取日誌資訊,並將訊息轉換為流資訊進行統計分析。

storm簡介

Storm叢集和Hadoop叢集表面上看很類似。但是Hadoop上執行的是MapReduce jobs,而在Storm上執行的是拓撲(topology),這兩者之間是非常不一樣的。一個關鍵的區別是: 一個MapReduce job最終會結束, 而一個topology永遠會執行(除非你手動kill掉)。
在Storm的叢集裡面有兩種節點: 控制節點(master node)和工作節點(worker node)。控制節點上面執行一個叫Nimbus後臺程式,它的作用類似Hadoop裡面的JobTracker。Nimbus負責在叢集裡面分發程式碼,分配計算任務給機器,並且監控狀態。
每一個工作節點上面執行一個叫做Supervisor的節點。Supervisor會監聽分配給它那臺機器的工作,根據需要啟動/關閉工作程式worker。每一個工作程式執行一個topology的一個子集;一個執行的topology由執行在很多機器上的很多工作程式worker組成。


11553600-a6009ed0bd86c98c.png
storm-run-map.png

Nimbus和Supervisor之間的所有協調工作都是通過Zookeeper叢集完成。另外,Nimbus程式和Supervisor程式都是快速失敗(fail-fast)和無狀態的。所有的狀態要麼在zookeeper裡面, 要麼在本地磁碟上。這也就意味著你可以用kill -9來殺死Nimbus和Supervisor程式, 然後再重啟它們,就好像什麼都沒有發生過。這個設計使得Storm異常的穩定。

Topology

計算任務Topology是由不同的Spouts和Bolts,通過資料流(Stream)連線起來的圖。下面是一個Topology的結構示意圖:


11553600-ca40ad1d969ed145.png
storm-base.png

其中包含有:

Spout

Storm中的訊息源,用於為Topology生產訊息(資料),一般是從外部資料來源(如Message Queue、RDBMS、NoSQL、Realtime Log)不間斷地讀取資料併傳送給Topology訊息(tuple元組)。Spout可以是可靠的,也可以是不可靠的。如果這個tuple沒有被Storm完全處理,可靠的訊息源可以重新發射一個tuple,但是不可靠的訊息源一旦發出一個tuple就不能重發了。(可靠性會在下面介紹)
Spout類裡面最重要的方法是nextTuple。要麼發射一個新的tuple到topology裡面或者簡單的返回(如果已經沒有新的tuple)。要注意的是nextTuple方法不能阻塞,因為storm在同一個執行緒上面呼叫所有訊息源spout的方法。
另外兩個比較重要的spout方法是ack和fail。storm在檢測到一個tuple被整個topology成功處理的時候呼叫ack,否則呼叫fail。storm只對可靠的spout呼叫ack和fail。

Bolt

Storm中的訊息處理者,用於為Topology進行訊息的處理,Bolt可以執行過濾, 聚合, 查詢資料庫等操作,而且可以一級一級的進行處理。

storm安裝及開發

下載的包解壓即可。

storm開發

開發環境搭建。

建立一個常規的java工程或maven工程就可以,並引入storm包的依賴。

        <dependency>
            <groupId>org.apache.storm</groupId>
            <artifactId>storm-core</artifactId>
            <version>${storm.version}</version>
            <scope>provided</scope>
        </dependency>

spout開發

由於我們的日誌歸集元件使用的RocketMq,所以我們需要使用RocketMq開發spout類,在storm執行環境中執行RocketMq的消費端,獲取指定主題的訊息佇列資料。由於storm發行釋出的穩定版預設沒有整合RocketMq元件,所以需要將RocketMq整合到storm中,使用RocketMq定義的spout來獲取rocketmq的中訊息資訊。
由於storm最新開發的主幹上已經整合了RocketMq,但中因為與1.2.2版本的核心介面不一致。所以需要將工程原始碼下載下來進行修改一下。
修改步驟為:
1.匯入\external\storm-rocketmq工程
2.修改工程的pom.xml檔案,將storm-core的依賴版本修改為1.2.2
3.修復編譯異常,主是是將介面中Map<String,Object>修改為Map。
4.為了能在Spout中輸出的Tuple中獲取RocketMq的tags和keys屬性資訊,將org.apache.storm.rocketmq.RocketMqUtils.java進行了重構。

    /**
     * Generate Storm tuple values by Message and Scheme.
     * @param msg RocketMQ Message
     * @param scheme Scheme for deserializing
     * @return tuple values
     */
    public static List<Object> generateTuples(Message msg, Scheme scheme) {
        List<Object> tup;
        String rawKey = msg.getKeys();
        ByteBuffer body = ByteBuffer.wrap(msg.getBody());
        if (rawKey != null && scheme instanceof KeyValueScheme) {
            ByteBuffer key = ByteBuffer.wrap(rawKey.getBytes(StandardCharsets.UTF_8));
            tup = ((KeyValueScheme)scheme).deserializeKeyAndValue(key, body);
//ADD BEGIN
        } if (scheme instanceof MessageScheme) {
            tup = ((MessageScheme)scheme).deserializeValue(msg);
//ADD END
        }  else {
            tup = scheme.deserialize(body);
        }
        return tup;
    }

並新增一個介面MessageScheme:

package org.apache.storm.rocketmq.spout.scheme;

import java.util.List;

import org.apache.rocketmq.common.message.Message;
import org.apache.storm.spout.Scheme;

public interface MessageScheme extends Scheme {
    List<Object> deserializeValue(Message msg);
}

和一個實現類 DefaultMessageScheme:

package org.apache.storm.rocketmq.spout.scheme;

import java.nio.ByteBuffer;
import java.util.List;

import org.apache.rocketmq.common.message.Message;
import org.apache.storm.tuple.Fields;
import org.apache.storm.tuple.Values;

public class DefaultMessageScheme extends StringScheme implements MessageScheme {
    public static final String FIELD_TAGS = "Tags";
    public static final String FIELD_TEYS = "Keys";
    public static final String FIELD_BODY = "Body";
    public static final Fields DEFAULT_FIELDS = new Fields("Tags", "Keys", "Body");

    @Override
    public List<Object> deserializeValue(Message msg) {
        ByteBuffer body = ByteBuffer.wrap(msg.getBody());
        String bodyStr = deserializeString(body);
        return new Values(msg.getTags(), msg.getKeys(), bodyStr);
    }

    @Override
    public Fields getOutputFields() {
        return DEFAULT_FIELDS;
    }
}

最終將工程編譯的jar包放在storm的lib目錄下,並且在工程中引入該元件包。

        <dependency>
                     <!-- 由storm最新主幹的\external\storm-rocketmq工程改造而來  -->
            <groupId>com.going.saas</groupId>
            <artifactId>going-storm-rocketmq</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba.rocketmq</groupId>
            <artifactId>rocketmq-client</artifactId>
            <version>3.6.2.Final</version>
        </dependency>

到這一步就可以直接使用RocketMqSpout了,首先建立一個Properties類,設定建立RocketMq訊息端所需要的引數。

        Properties properties = new Properties();
        properties.setProperty(SpoutConfig.NAME_SERVER_ADDR, "10.209.8.126:9876");
        properties.setProperty(SpoutConfig.CONSUMER_GROUP, "RESOURCE_INFOS_CONSUMER_GRP");
        properties.setProperty(SpoutConfig.CONSUMER_TOPIC, "RESOURCES_INFO_TOPIC");
        properties.setProperty(SpoutConfig.SCHEME,SpoutConfig.MESSAGE_SCHEME);

再根據引數建立RocketMqSpout物件即可。

new RocketMqSpout(properties)

Bolt開發

該例子中開發一個Bolt,處理從Spout中獲取轉為Tuple物件的訊息資訊。將資訊存在Redis中。

import org.apache.storm.redis.bolt.AbstractRedisBolt;
import org.apache.storm.redis.common.config.JedisPoolConfig;
import org.apache.storm.rocketmq.spout.scheme.DefaultMessageScheme;
import org.apache.storm.topology.OutputFieldsDeclarer;
import org.apache.storm.tuple.Tuple;

import redis.clients.jedis.JedisCommands;

/**
 * 獲取終端發來的資源利用資訊, 將新獲取的資源資訊替換舊的資源資訊。
 * 
 * @author Administrator
 *
 */
public class ResourcesInfoSplitBolt extends AbstractRedisBolt {

    private static final String REDIS_KEY_PREFIX = "RESOURCE_INFO_";
    
    public ResourcesInfoSplitBolt(JedisPoolConfig config) {
        super(config);
    }

    @Override
    public void declareOutputFields(OutputFieldsDeclarer declarer) {
    }

    @Override
    protected void process(Tuple tuple) {
        JedisCommands jedisCommands = null;
        try {
            String body = tuple.getStringByField(DefaultMessageScheme.FIELD_BODY);
            String tags = tuple.getStringByField(DefaultMessageScheme.FIELD_TAGS).trim();
            
            jedisCommands = getInstance();
            jedisCommands.lpush(REDIS_KEY_PREFIX+tags, body);

        } finally {
            if (jedisCommands != null) {
                returnInstance(jedisCommands);
            }
            this.collector.ack(tuple);
        }
    }

Topology開發

Spout和Bolt開發完成以後,就需要開發Topology將它們串連起來,具體示例程式碼如下:

import java.util.Properties;
import org.apache.storm.Config;
import org.apache.storm.LocalCluster;
import org.apache.storm.redis.common.config.JedisPoolConfig;
import org.apache.storm.rocketmq.SpoutConfig;
import org.apache.storm.rocketmq.spout.RocketMqSpout;
import org.apache.storm.topology.TopologyBuilder;
import com.going.storm.bolt.ResourcesInfoSplitBolt;

public class ResourceInfosTopology {

    public static void main(String[] args) throws InterruptedException {
        

        Properties properties = new Properties();
        properties.setProperty(SpoutConfig.NAME_SERVER_ADDR, "10.209.8.126:9876");
        properties.setProperty(SpoutConfig.CONSUMER_GROUP, "RESOURCE_INFOS_CONSUMER_GRP");
        properties.setProperty(SpoutConfig.CONSUMER_TOPIC, "RESOURCES_INFO_TOPIC");
        properties.setProperty(SpoutConfig.SCHEME,SpoutConfig.MESSAGE_SCHEME);
        // RocketMqSpout spout = new RocketMqSpout(properties);
        
        properties.setProperty("REDIS_HOST", "10.209.8.126");
        properties.setProperty("REDIS_PORT", "6379");

        // 定義拓撲
        TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("resouece-info-reader", new RocketMqSpout(properties));
        JedisPoolConfig poolConfig = new JedisPoolConfig.Builder()//
                .setHost(properties.getProperty("REDIS_HOST"))//
                .setPort(Integer.valueOf(properties.getProperty("REDIS_PORT")))//
                .build();
         builder.setBolt("word-normalizer", new  ResourcesInfoSplitBolt(poolConfig)).shuffleGrouping("resouece-info-reader");
         
        // builder.setBolt("word-counter", new WordCounter(),2).fieldsGrouping("word-normalizer", new Fields("word"));

        // 配置
        Config conf = new Config();
        // conf.put("wordsFile", args[0]);
        conf.setDebug(false);

        // 執行拓撲
        conf.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
        LocalCluster cluster = new LocalCluster();
        cluster.submitTopology("Getting-Started-Resource-Infos-Topologie", conf, builder.createTopology());
        Thread.sleep(1000000000);
        cluster.shutdown();
    }

}

storm部署執行

工程開發完成以後就需要將工程進行部署執行。我這裡以docker為例對開發的功能進行部署。
這裡基於storm官方的docker映象進行部署。
我們基於storm的1.2.2版本的映象(與本地開發的storm版本保持 一致就好),將工程依賴的第三方包加入到映象中storm元件的extlib目錄下。

FROM storm:1.2.2

ADD  extlib/dubbo-2.5.8.jar  extlib/dubbo-2.5.8.jar 
ADD  extlib/fastjson-1.2.29.jar  extlib/fastjson-1.2.29.jar 
ADD  extlib/going-storm-rocketmq-0.0.1-SNAPSHOT.jar  extlib/going-storm-rocketmq-0.0.1-SNAPSHOT.jar 
ADD  extlib/guava-13.0.1.jar  extlib/guava-13.0.1.jar 
ADD  extlib/jackson-annotations-2.9.0.jar  extlib/jackson-annotations-2.9.0.jar 
ADD  extlib/jackson-core-2.9.4.jar  extlib/jackson-core-2.9.4.jar 
ADD  extlib/jackson-databind-2.9.4.jar  extlib/jackson-databind-2.9.4.jar 
ADD  extlib/javassist-3.20.0-GA.jar  extlib/javassist-3.20.0-GA.jar 
ADD  extlib/jedis-2.9.0.jar  extlib/jedis-2.9.0.jar 
ADD  extlib/netty-3.2.5.Final.jar  extlib/netty-3.2.5.Final.jar 
ADD  extlib/netty-all-4.0.42.Final.jar  extlib/netty-all-4.0.42.Final.jar 
ADD  extlib/rocketmq-client-3.6.2.Final.jar  extlib/rocketmq-client-3.6.2.Final.jar 
ADD  extlib/rocketmq-client-4.2.0.jar  extlib/rocketmq-client-4.2.0.jar 
ADD  extlib/rocketmq-common-4.2.0.jar  extlib/rocketmq-common-4.2.0.jar 
ADD  extlib/rocketmq-remoting-4.2.0.jar  extlib/rocketmq-remoting-4.2.0.jar 
ADD  extlib/storm-redis-1.2.2.jar extlib/storm-redis-1.2.2.jar

然後通過生成好的映象ab/storm生成例項

docker run -d --name storm-resources -it -v ${LOCAL_PATH}/going-storm-0.0.1-SNAPSHOT.jar:/topology.jar ab/storm storm jar /topology.jar com.going.storm.topology.ResourceInfosTopology

到這一步日誌統計功能即開發完成了,對應的分析功能也可以通過Bolt來完成。

相關文章