实战概览
一、实战内容
- 编写python脚本,源源不断产生学习网站的用户行为日志。
- 启动 Flume 收集产生的日志。
- 启动 Kfka 接收 Flume 接收的日志。
- 使用 Spark Streaming 消费 Kafka 的用户日志。
- Spark Streaming将数据清洗过滤非法数据,然后分析日志中用户的访问课程,统计课程数,得到最受欢迎课程TOP5,第二个业务是统计各个搜索引擎的搜索量。(通过分析日志还可以实现很多其他的业务,基于篇幅,本项目只实现其中两个)。
- 将 Spark Streaming 处理的结果写入 HBase 中。
- 前端使用 Spring MVC、 Spring、 MyBatis 整合作为数据展示平台。
- 使用Ajax异步传输数据到jsp页面,并使用 Echarts 框架展示数据。
- 本实战使用IDEA2018作为开发工具,JDK版本为1.8,Scala版本为2.11。
二、大数据实时流处理分析系统简介
-
1.需求
如今大数据技术已经遍布生产的各个角落,其中又主要分为离线处理和实时流处理。本实战项目则是使用了实时流处理,而大数据的实时流式处理的特点:
1.数据会不断的产生,且数量巨大。
2.需要对产生额数据实时进行处理。
3.处理完的结果需要实时读写进数据库或用作其他分析。
针对以上的特点,传统的数据处理结构已经无力胜任,因而产生的大数据实时流处理的架构思想。 -
2.背景及架构
数据的处理一般涉及数据的聚合,数据的处理和展现能够在秒级或者毫秒级得到响应。针对这些问题目前形成了Flume + kafka + Storm / Spark /Flink + Hbase / Redis 的技架构。本实战采用Flume + kafka + Spark + Hbase的架构。
三、实战所用到的架构和涉及的知识
-
1.后端架构
1.Hadoop-2.6.0-cdh5.7.0
2.Spark-2.2.0-bin-2.6.0-cdh5.7.0
3.Apache-flume-1.9.0-bin
4.Kafka_2.11-1.0.0
5.Hbase-1.2.0-cdh5.7.0 -
2.前端框架
1.Spring MVC-4.3.3.RELEASE
2.Spring-4.3.3.RELEASE
3.MyBatis-3.2.1
4.Echarts
四、项目实战
-
1.后端开发实战
1.构建项目
构建Scala工程项目,并添加Maven支持。
2.引入依赖
<properties> <scala.version>2.11.8</scala.version> <hadoop.version>2.6.0-cdh5.7.0</hadoop.version> <spark.version>2.2.0</spark.version> <hbase.version>1.2.0-cdh5.7.0</hbase.version> </properties> <repositories> <repository> <id>cloudera</id> <url>https://repository.cloudera.com/artifactory/cloudera-repos</url> </repository> </repositories> <dependencies> <dependency> <groupId>org.scala-lang</groupId> <artifactId>scala-library</artifactId> <version>${ scala.version}</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>${ hadoop.version}</version> </dependency> <dependency> <groupId>org.apache.hbase</groupId> <artifactId>hbase-client</artifactId> <version>${ hbase.version}</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId> spark-streaming-kafka-0-8_2.11</artifactId> <version>${ spark.version}</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.module</groupId> <artifactId>jackson-module-scala_2.11</artifactId> <version>2.6.5</version> </dependency> <dependency> <groupId>net.jpountz.lz4</groupId> <artifactId>lz4</artifactId> <version>1.3.0</version> </dependency> <dependency> <groupId>org.apache.spark</groupId> <artifactId>spark-streaming_2.11</artifactId> <version>${ spark.version}</version> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.5</version> </dependency> <dependency> <groupId>com.ggstar</groupId> <artifactId>ipdatabase</artifactId> <version>1.0-SNAPSHOT</version> </dependency> </dependencies>
3.创建工程包结构
在scala目录下创建如下包结构:
4.编写代码
(1). 在util包下创健Java类HBaseUtils,用于连接HBase,存储处理的结果:
类HBaseUtils完整代码如下:/** * Hbase工具类,用于: * 连接HBase,获得表实例 */ public class HBaseUtils { private Configuration configuration = null; private Connection connection = null; private static HBaseUtils instance = null; /** * 在私有构造方法中初始化属性 */ private HBaseUtils(){ try { configuration = new Configuration(); //指定要访问的zk服务器 configuration.set("hbase.zookeeper.quorum", "hadoop01:2181"); //得到Hbase连接 connection = ConnectionFactory.createConnection(configuration); }catch(Exception e){ e.printStackTrace(); } } /** * 获得HBase连接实例 */ public static synchronized HBaseUtils getInstance(){ if(instance == null){ instance = new HBaseUtils(); } return instance; } /** *由表名得到一个表的实例 * @param tableName * @return */ public HTable getTable(String tableName) { HTable hTable = null; try { hTable = (HTable)connection.getTable(TableName.valueOf(tableName)); }catch (Exception e){ e.printStackTrace(); } return hTable; } }
在util包下创Scala object类DateUtils,用于格式化日志的日期,代码如下:
/** * 格式化日期工具类 */ object DateUtils { //指定输入的日期格式 val YYYYMMDDHMMSS_FORMAT= FastDateFormat.getInstance("yyyy-MM-dd hh:mm:ss") //指定输出格式 val TARGET_FORMAT = FastDateFormat.getInstance("yyyyMMddhhmmss") //输入String返回该格式转为log的结果 def getTime(time:String) = { YYYYMMDDHMMSS_FORMAT.parse(time).getTime } def parseToMinute(time:String) = { //调用getTime TARGET_FORMAT.format(getTime(time)) } }
(2). 在domain包下创建以下几个Scala样例类:
ClickLog:用于封装清洗后的日志信息:
然后将该类声明为样例类,在class关键字前增加case关键字:
ClickLog 类完整代码如下:/** * 封装清洗后的数据 * @param ip 日志访问的ip地址 * @param time 日志访问的时间 * @param courseId 日志访问的实战课程编号 * @param statusCode 日志访问的状态码 * @param referer 日志访问的referer信息 */ case class ClickLog (ip:String,time:String,courseId:Int,statusCode:Int,referer:String)
再创建样例类 CourseClickCount 用于封装课程统计的结果,样例类 CourseSearchClickCount 用于封装搜索引擎的统计结果,因为创建过程与上面的ClickLog 类一样,这里不再展示,直接给出完整代码:
CourseClickCount 类完整代码如下:/** * 封装实战课程的总点击数结果 * @param day_course 对应于Hbase中的RowKey * @param click_count 总点击数 */ case class CourseClickCount(day_course:String,click_count:Int)
CourseSearchClickCount 类完整代码如下:
/** * 封装统计通过搜索引擎多来的实战课程的点击量 * @param day_serach_course 当天通过某搜索引擎过来的实战课程 * @param click_count 点击数 */ case class CourseSearchClickCount(day_serach_course:String,click_count:Int)
(3). 在dao包下创建以下Scala的object类:
CourseClickCountDao :用于交互HBase,把课程点击数的统计结果写入HBase:
CourseClickCountDao 类的完整代码如下:/** * 实战课程点击数统计访问层 */ object CourseClickCountDao { val tableName = "ns1:courses_clickcount" //表名 val cf = "info" //列族 val qualifer = "click_count" //列 /** * 保存数据到Hbase * @param list (day_course:String,click_count:Int) //统计后当天每门课程的总点击数 */ def save(list:ListBuffer[CourseClickCount]): Unit = { //调用HBaseUtils的方法,获得HBase表实例 val table = HBaseUtils.getInstance().getTable(tableName) for(item <- list){ //调用Hbase的一个自增加方法 table.incrementColumnValue(Bytes.toBytes(item.day_course), Bytes.toBytes(cf), Bytes.toBytes(qualifer), item.click_count) //赋值为Long,自动转换 } } }
CourseClickCountDao :用于交互HBase,把搜引擎搜索数量的统计结果写入HBase,创建过程与CourseClickCountDao 类一致故不再展示,完整代码如下:
object CourseSearchClickCountDao { val tableName = "ns1:courses_search_clickcount" val cf = "info" val qualifer = "click_count" /** * 保存数据到Hbase * @param list (day_course:String,click_count:Int) //统计后当天每门课程的总点击数 */ def save(list:ListBuffer[CourseSearchClickCount]): Unit = { val table = HBaseUtils.getInstance().getTable(tableName) for(item <- list){ table.incrementColumnValue(Bytes.toBytes(item.day_serach_course), Bytes.toBytes(cf), Bytes.toBytes(qualifer), item.click_count) //赋值为Long,自动转换 } } }
注意: 代码中的HBase表需要提前创建好,详情请看本节的 8.在HBase中创建项目需要的表。
(4). 在application包下创建Scala的object类 CountByStreaming,用于处理数据,是本项目的程序入口,最为核心的类:
CountByStreaming 类的完整代码如下:object CountByStreaming { def main(args: Array[String]): Unit = { /** * 最终该程序将打包在集群上运行, * 需要接收几个参数:zookeeper服务器的ip,kafka消费组, * 主题,以及线程数 */ if(args.length != 4){ System.err.println("Error:you need to input:<zookeeper> <group> <toplics> <threadNum>") System.exit(1) } //接收main函数的参数,外面的传参 val Array(zkAdderss,group,toplics,threadNum) = args /** * 创建Spark上下文,下本地运行需要设置AppName * Master等属性,打包上集群前需要删除 */ val sparkConf = new SparkConf() .setAppName("CountByStreaming") .setMaster("local[4]") //创建Spark离散流,每隔60秒接收数据 val ssc = new StreamingContext(sparkConf,Seconds(60)) //使用kafka作为数据源 val topicsMap = toplics.split(",").map((_,threadNum.toInt)).toMap //创建kafka离散流,每隔60秒消费一次kafka集群的数据 val kafkaInputDS = KafkaUtils.createStream(ssc,zkAdderss,group,topicsMap) //得到原始的日志数据 val logResourcesDS = kafkaInputDS.map(_._2) /** * (1)清洗数据,把它封装到ClickLog中 * (2)过滤掉非法的数据 */ val cleanDataRDD = logResourcesDS.map(line => { val splits = line.split("\t") if(splits.length != 5) { //不合法的数据直接封装默认赋予错误值,filter会将其过滤 ClickLog("", "", 0, 0, "") } else { val ip = splits(0) //获得日志中用户的ip val time = DateUtils.parseToMinute(splits(1)) //获得日志中用户的访问时间,并调用DateUtils格式化时间 val status = splits(3).toInt //获得访问状态码 val referer = splits(4) val url = splits(2).split(" ")(1) //获得搜索url var courseId = 0 if(url.startsWith("/class")){ val courseIdHtml = url.split("/")(2) courseId = courseIdHtml.substring(0,courseIdHtml.lastIndexOf(".")).toInt } ClickLog(ip,time,courseId,status,referer) //将清洗后的日志封装到ClickLog中 } }).filter(x => x.courseId != 0 ) //过滤掉非实战课程 /** * (1)统计数据 * (2)把计算结果写进HBase */ cleanDataRDD .map(line => { //这里相当于定义HBase表"ns1:courses_clickcount"的RowKey, // 将‘日期_课程’作为RowKey,意义为某天某门课的访问数 (line.time.substring(0,8) + "_" + line.courseId,1) //映射为元组 }).reduceByKey(_ + _) //聚合 .foreachRDD(rdd =>{ //一个DStream里有多个RDD rdd.foreachPartition(partition => { //一个RDD里有多个Partition val list = new ListBuffer[CourseClickCount] partition.foreach(item =>