1.  需求分析

互联网程序开发中,有两个步骤不可或缺:

1、  code review

2、  试错

但是由于人员限制和功能抢占时间限制,做这两个工作的人和时间极其有限,那么如何能自动完成这两项任务呢?

我们需要一套监控系统,这套系统不和我们的业务关联,并且是容错性高、兼容性强、独立部署与运维的通用架构,用来监控我们的程序。达到两个目的:

1、  统计每个接口的平均调用时间。

2、  统计接口的调用次数。

如何做这套系统呢?

2.  技术调研

现在互联网技术层面上,做流式监控的技术组合很多,但有一套技术架构被很多公司应用,就是flume+kafka+storm。

flume:负责监控日志,无论日志的格式是什么,也无论是什么日志,与其他系统耦合性相当低。

kafka:分布式消息系统,通过分布式的方式将flume传进来的消息保存,供消费者消费,集群稳定性极高。所以即使生产者和消费者挂掉,只要重启,可以继续生产消息。

storm:流式计算框架,可以实时的从kafka中读取数据,实时统计结果存储到数据底层。

redis:高速缓存工具,并发性好,查询速度块。

3.  技术分析

这套监控系统,由于组件过多,容易导致数据丢失的情况,这种情况会不会影响我们的结果?

在海量数据的流式监控过程中,不会产生用户级别的交互行为,数据仅供我们分析使用,数据的精度和准度由海量数据分析得来,少量数据的丢失不会影响结果。

4.  技术点

1、  flume环境搭建

2、  kafka集群搭建

3、  storm集群搭建

4、  flume的agent配置

5、  storm相关api使用

6、  flume-kafka插件使用

7、  kafka-storm插件使用

8、  集群部署

5.  项目架构图

见架构文档

6.  开发流程

6.1.  搭建集群环境

1、  搭建flume环境

2、  搭建zookeeper环境

3、  搭建kafka环境

4、  搭建storm环境

6.2.  启动集群

依次启动:zookeeper、kafka、storm

6.3.  开发web应用,并在方法中添加日志

用monitor应用

6.4.  修改web项目中的log4j配置

# Rules reminder:
# DEBUG < INFO < WARN < ERROR < FATAL
# Global logging configuration
log4j.rootLogger=INFO,R,stdout
## File output...
log4j.appender.R=org.apache.log4j.RollingFileAppender
log4j.appender.R.File=/home/hadoop/flume/logs/test.log
log4j.appender.R.MaxFileSize=100MB
log4j.appender.R.MaxBackupIndex=7
log4j.appender.R.layout=org.apache.log4j.PatternLayout
log4j.appender.R.layout.ConversionPattern=%d [%t] %-5p%l method\:%M %m%n
#log4j.logger.org.apache.catalina=INFO,R,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%t] %-5p%l method\:%M %m%n
#%d:显示日志记录时间
#[%t]:输出产生该日志事件的线程名
#%-5p:显示该条日志的优先级
#%l:输出日志事件的发生位置,包括类目名、发生的线程,以及在代码中的行数
#%M:方法名
#%m:显示输出消息
#%n:当前平台下的换行符

6.5.  部署web服务

在client机器部署tomcat,将应用打成war包放到tomcat中,启动tomcat即可

6.6.  开发flume,监控tomcat日志

6.6.1.  配置

log4j_agent.conf:

a1.sources = r1
a1.sinks = k1 k2
a1.channels = c1 c2
 
# Describe/configure the source
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F /home/hadoop/flume/logs/test.log
 
# Describe the sink
a1.sinks.k1.type = logger
a1.sinks.k2.type = org.apache.flume.plugins.KafkaSink
a1.sinks.k2.metadata.broker.list=master:9092,slave1:9092,slave2:9092,slave3:9092
a1.sinks.k2.sink.directory = /home/hadoop/flume/logs
a1.sinks.k2.partitioner.class=org.apache.flume.plugins.SinglePartition
a1.sinks.k2.serializer.class=kafka.serializer.StringEncoder
a1.sinks.k2.request.required.acks=0
a1.sinks.k2.max.message.size=1000000
a1.sinks.k2.producer.type=sync
a1.sinks.k2.encoding=UTF-8
a1.sinks.k2.topic.name=testTopic
a1.sinks.k2.channel = c2
 
# Use a channel which buffers events in memory
a1.channels.c1.type = memory
a1.channels.c1.capacity = 10000
a1.channels.c1.transactionCapacity = 1000
a1.channels.c2.type = memory
a1.channels.c2.capacity = 1000
a1.channels.c2.transactionCapacity = 100
 
# Bind the source and sink to the channel
a1.sources.r1.channels = c1 c2
a1.sources.r1.selector.type = replicating
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2

6.6.2.  启动脚本

因为是监控tomcat,所以flume需要在tomcat的机器上部署一个,并在此机器启动flume的agent:
命令:

flume-ng agent -c /home/hadoop/flume/conf/-f /home/hadoop/flume/conf/log4j_agent.conf -n a1-Dflume.root.logger=INFO,console

6.7.  中间件kafka

flume读取的日志会被存储在kafka中,而这些数据又会被后面的storm用到,所以kafka作为消息中间件使用,应用到两个kafka的插件,不需要写代码。

6.8.  开发storm程序,读取kafka,写向redis

6.8.1.  开发main启动类

package com.itcast.monitor.main;
import java.util.ArrayList;
 
import storm.kafka.BrokerHosts;
import storm.kafka.KafkaSpout;
import storm.kafka.SpoutConfig;
import storm.kafka.StringScheme;
import storm.kafka.ZkHosts;
import backtype.storm.Config;
import backtype.storm.StormSubmitter;
import backtype.storm.generated.AlreadyAliveException;
import backtype.storm.generated.InvalidTopologyException;
import backtype.storm.spout.SchemeAsMultiScheme;
import backtype.storm.topology.TopologyBuilder;
 
import com.itcast.monitor.bolts.HandlerBolt;
 
 
public class KafkaStormMain {
         
         public static void main(String[] args) throws InterruptedException, AlreadyAliveException, InvalidTopologyException {
                   //设置所有的broker
                   BrokerHosts brokerHosts = new ZkHosts("master,slave1,slave2,slave3");
                   //创建SpoutConfig,用来封装brokerHosts、topic、zookeeper节点、id
                   //topic是当前向消费的topic
                   ///storm-test随便写
                   //monitor_local:随便写
                   SpoutConfig kafkaConfig = new SpoutConfig(brokerHosts, "monitorTopic", "/storm-monitor", "monitor_local5");
                   //序列化
                   kafkaConfig.scheme = new SchemeAsMultiScheme(new StringScheme());//序列化工具
                   //配置zookeeper地址和port
                   kafkaConfig.zkServers = new ArrayList<String>() {
                            {
                                     add("slave1");
                                     add("slave2");
                                     add("slave3");
                            }
                   };
                   kafkaConfig.zkPort = 2181;//zookeeper端口
                   /**
         * 创建topologyBuilder
         */
                   TopologyBuilder builder = new TopologyBuilder();
        builder.setSpout("readlog", new KafkaSpout(kafkaConfig));
        builder.setBolt("handlerbolt", new HandlerBolt()).shuffleGrouping("readlog");
        Config config = new Config();
        config.setDebug(false);
        /**
         * 本地模式
         */
        //LocalCluster cluster = new LocalCluster();  
        //cluster.submitTopology("DubboService-Test", config, builder.createTopology());  
       // Thread.sleep(2000);//睡一会 
        /**
                    * 线上运行
                    * 命令:storm jar Getting-Started-0.0.1-SNAPSHOT.jar countword.WordMain arg0
                    */
                   StormSubmitter.submitTopology("flume-kafka-storm", config, builder.createTopology());
         }
}

6.8.2.  开发bolt类

package com.itcast.monitor.bolts;
 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
 
import redis.clients.jedis.Jedis;
import backtype.storm.task.TopologyContext;
import backtype.storm.topology.BasicOutputCollector;
import backtype.storm.topology.OutputFieldsDeclarer;
import backtype.storm.topology.base.BaseBasicBolt;
import backtype.storm.tuple.Tuple;
 
public class HandlerBolt extends BaseBasicBolt {
   public Jedis jedis = null;
   
   @Override
   public void prepare(Map stormConf, TopologyContext context) {
      jedis = new Jedis("192.168.56.204",6379);
   }
   public void execute(Tuple tuple, BasicOutputCollector collector) {
      System.out.println(tuple.getString(0)+"============================================");
      String value = tuple.getString(0);
      if (value.contains("method")&&value.contains("com.itcast.tsc")&&!value.contains("springframework")) {
         String[] split = value.split(" ");
         String methodName = split[5].split(":")[1];
         String time = split[6];
         List<String> lrange = jedis.lrange("monitor_"+methodName, 0, -1);
         if (lrange==null||lrange.size()==0) {
            jedis.rpush("monitor_"+methodName, time);
            jedis.rpush("monitor_"+methodName, "1");
            jedis.rpush("monitor_"+methodName, time);
         }else{
            int lastTotleTime = Integer.parseInt(lrange.get(0));
            int thisTime = Integer.parseInt(time);
            int lastTotleCount = Integer.parseInt(lrange.get(1));
            int TotleCount = Integer.parseInt(lrange.get(1))+1;
            int avgTime = (lastTotleTime+thisTime)/TotleCount;
            jedis.del("monitor_"+methodName);
            jedis.rpush("monitor_"+methodName, lastTotleTime+thisTime+"");
            jedis.rpush("monitor_"+methodName, TotleCount+"");
            jedis.rpush("monitor_"+methodName, avgTime+"");
         }
      }
   }
 
   public void declareOutputFields(OutputFieldsDeclarer declarer) {
      
   }
 
}

6.9.  开发监控项目

演示使用,无需掌握。

6.10.    测试程序

6.10.1.          测试flume-kafka

1、  启动tomcat

2、  启动flume的agent

3、  此套应用会向kafka中写数据。用kafka的consumer例子读取数据,并验证。

6.10.2.          测试kafka-storm

1、  用kafka的producer例子向kafka中写数据

2、  用storm的程序做本地测试启动,打印日志

6.10.3.          测试flume+kafka+storm线上版

1、  启动tomcat

2、  启动flume的agent,向kafka写数据

3、  启动storm集群模式,读取数据,写入redis

a)      将storm程序改成集群模式,打成jar包

b)      在storm集群机器上运行,

c)      命令:storm jar Storm-Test_jiqun.jarcom.itcast.monitor.main.KafkaStormMain

4、  用测试应用查看redis中的数据