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中的数据