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