文章目錄
本案例源碼下載
鏈接:https://pan.baidu.com/s/1x-68_9KYgA07Mb4QUsSseQ
提取碼:ddzc
項目背景
網絡發展迅速的時代,越來越多人通過網絡獲取跟多的信息或通過網絡作一番自己的事業,當投身於搭建屬於自己的網站、APP或小程序時會發現,經過一段時間經營和維護髮現瀏覽量和用戶數量的增長速度始終沒有提升。在對其進行設計改造時無從下手,當在不瞭解用戶的瀏覽喜歡和個用戶羣體的喜好。雖然服務器日誌中明確的記載了用戶訪瀏覽的喜好但是通過普通方式很難從大量的日誌中及時有效的篩選出優質信息。Spark Streaming是一個實時的流計算框架,該技術可以對數據進行實時快速的分析,通過與Flume、Kafka的結合能夠做到近乎零延遲的數據統計分析。
案例需求
要求:實時分析服務器日誌數據,並實時計算出某時間段內的瀏覽量等信息。
使用技術:Flume-》Kafka-》SparkStreaming-》MySql數據庫
#案例架構
架構中通過Flume實時監控日誌文件,當日志文件中出現新數據時將該條數據發送給Kafka,並有Spark Streaming接收進行實時的數據分析最後將分析結果保存到MySQL數據庫中。
一、分析
1、日誌分析
1.通過瀏覽器訪問服務器中的網頁,每訪問一次就會產生一條日誌信息。日誌中包含訪問者IP、訪問時間、訪問地址、狀態碼和耗時等信息,如下圖所示:
二、日誌採集
第一步、代碼編輯
通過使用Flume實時監控服務器日誌文件內容,每生成一條都會進行採集,並將採集的結構發送給Kafka,Flume代碼如下。
2、啓動採集代碼
代碼編輯完成後啓動Flume對服務器日誌信息進行監控,進入Flume安裝目錄執行如下代碼。
[root@master flume]# bin/flume-ng agent --name a1 --conf conf --conf-file conf/access_log-HDFS.properties -Dflume.root.logger=INFO,console
效果下圖所示。
三、編寫Spark Streaming的代碼
第一步 創建工程
第二步 選擇創建Scala工程
第三步 設置工程名與工程所在路徑和使用的Scala版本後完成創建
第四步 創建scala文件
項目目錄的”src”處單機鼠標右鍵依次選擇”New”->”Package”創建一個包名爲”com.wordcountdemo”,並在該包處單機右鍵依次選擇”New”->”scala class”創建文件命名爲wordcount
第五步:導入依賴包
在IDEA中導入Spark依賴包,在菜單中依次選擇”File”->”Project Structure”->”Libraries”後單擊”+”號按鈕選擇”Java”選項,在彈出的對話框中找到spark-assembly-1.6.1-hadoop2.6.0.jar依賴包點擊”OK”將所有依賴包加載到工程中,結果如圖X所示。
第六步:引入本程序所需要的全部方法
注意此處使用了三個spark2中沒有的jar包分別爲kafka_2.11-0.8.2.1.jar、
metrics-core-2.2.0.jar、spark-streaming-kafka_2.11-1.6.3.jar。
import java.sql.DriverManager //連接數據庫
import kafka.serializer.StringDecoder //序列化數據
import org.apache.spark.streaming.dstream.DStream //接收輸入數據流
import org.apache.spark.streaming.kafka.KafkaUtils //連接Kafka
import org.apache.spark.streaming.{Seconds, StreamingContext} //實時流處理
import org.apache.spark.SparkConf //spark程序的入口函數
結果如圖所示。
第七步:創建main函數與Spark程序入口。
def main(args: Array[String]): Unit = {
//創建sparksession
val conf = new SparkConf().setAppName("Consumer")
val ssc = new StreamingContext(conf,Seconds(20)) //設置每隔20秒接收並計算一次
}
結果如圖所示。
第八步:設置kafka服務的主機地址和端口號,並設置從哪個topic接收數據和設置消費者組
//kafka服務器地址
val kafkaParam = Map("metadata.broker.list" -> "192.168.10.10:9092")
//設置topic
val topic = "testSpark".split(",").toSet
//接收kafka數據
val logDStream: DStream[String] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParam,topic).map(_._2)
第九步:數分析
接收到數據後,對數據進行分析,將服務器日誌數據按照空格進行拆分,並分別統計出階段時間內的網站瀏覽量、用戶註冊數量和用戶的跳出率並將統計結果轉換爲鍵值對類型的RDD。
//拆分接收到的數據
val RDDIP =logDStream.transform(rdd=>rdd.map(x=>x.split(" ")))
//進行數據分析
val pv = RDDIP.map(x=>x(0)).count().map(x=>("pv",x)) //用戶瀏覽量
val jumper = RDDIP.map(x=>x(0)).map((_,1)).reduceByKey(_+_).filter(x=>x._2 == 1).map(x=>x._1).count.map(x=>("jumper",x)) //跳出率
val reguser =RDDIP.filter(_(8).replaceAll("\"","").toString == "/member.php?mod=register&inajax=1").count.map(x=>("reguser",x)) //註冊用戶數量
第十步:保存計算結果
遍歷統計結果RDD取出鍵值對中的值並分別分別將分析結果保存到pvtab、jumpertab和regusetab表中,最後啓動Spark Streaming程序。
pv.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("yyyy-MM-dd H:mm:ss")
val dateFf= format.format(new java.util.Date())
val sql = "insert into pvtab(time,pv) values("+"'"+dateFf+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
jumper.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("yyyy-MM-dd H:mm:ss")
val dateFf= format.format(new java.util.Date())
val sql = "insert into jumpertab(time,jumper) values("+"'"+dateFf+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
reguser.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("yyyy-MM-dd H:mm:ss")
val dateFf= format.format(new java.util.Date())
val sql = "insert into regusetab(time,reguse) values("+"'"+dateFf+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
ssc.start() //啓動Spark Streaming程序
結果如圖所示。
第十一步 數據庫設計
創建一個數據庫名爲“test”,並在該庫中創建三個表分別名爲"jumpertab"、“pvtab”、“regusetab”,數據庫結構如下圖所示
jumpertab表
pvtab表
regusetab表
四、編譯運行
將程序編輯爲jar包提交到集羣中運行。
第一步、將工程添加到jar文件並設置文件名稱
選擇“File”-“Project Structure”命令,在彈出的對話框中選擇“Artifacts”按鈕,選擇“+”下的“JAR”->“Empty”在隨後彈出的對話框中“NAME”處設置JAR文件的名字爲“WordCount”,並雙擊右側“firstSpark”下的“’firstSpark’compile output”將其加載到左側,表示已經將工程添加到JAR包中然後點擊“OK”按鈕,如下圖所示。
第二步、生成jar包
點擊菜單欄中的“Build”->“Build Artifacts…”按鈕在彈出的對話框中單擊“Build”按鈕,jar包生成後工程根目錄會自動創建一個out目錄在目錄中可以看到生成的jar包,結果如下圖所示。
第三步、提交運行Spark Streaming程序
[root@master bin]# ./spark-submit --master local[*] --class com.spark.streaming.sparkword /usr/local/Streaminglog.jar
結果如下圖所示
第四步:查看數據庫
完整代碼如下
package spark
import java.sql.DriverManager
import java.util.Calendar
import kafka.serializer.StringDecoder
import org.apache.spark.streaming.dstream.DStream
import org.apache.spark.streaming.kafka.KafkaUtils
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.SparkConf
object kafkaspark {
def main(args: Array[String]): Unit = {
// 創建sparksession
val conf = new SparkConf().setAppName("Consumer")
val ssc = new StreamingContext(conf,Seconds(1))
val kafkaParam = Map("metadata.broker.list" -> "192.168.10.10:9092")
val topic = "testSpark".split(",").toSet
//接收kafka數據
val logDStream: DStream[String] = KafkaUtils.createDirectStream[String,String,StringDecoder,StringDecoder](ssc,kafkaParam,topic).map(_._2)
//拆分接收到的數據
val RDDIP =logDStream.transform(rdd=>rdd.map(x=>x.split(" ")))
//進行數據分析
val pv = RDDIP.map(x=>x(0)).count().map(x=>("pv",x))
val jumper = RDDIP.map(x=>x(0)).map((_,1)).reduceByKey(_+_).filter(x=>x._2 == 1).map(x=>x._1).count.map(x=>("jumper",x))
val reguser =RDDIP.filter(_(8).replaceAll("\"","").toString == "/member.php?mod=register&inajax=1").count.map(x=>("reguser",x))
//將分析結果保存到MySQL數據庫
pv.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("H:mm:ss")
val dateFf= format.format(new java.util.Date())
var cal:Calendar=Calendar.getInstance()
cal.add(Calendar.SECOND,-1)
var Beforeasecond=format.format(cal.getTime())
val date = Beforeasecond.toString+"-"+dateFf.toString
val sql = "insert into pvtab(time,pv) values("+"'"+date+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
jumper.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("H:mm:ss")
val dateFf= format.format(new java.util.Date())
var cal:Calendar=Calendar.getInstance()
cal.add(Calendar.SECOND,-1)
var Beforeasecond=format.format(cal.getTime())
val date = Beforeasecond.toString+"-"+dateFf.toString
val sql = "insert into jumpertab(time,jumper) values("+"'"+date+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
reguser.foreachRDD(line =>line.foreachPartition(rdd=>{
rdd.foreach(word=>{
val conn = DriverManager.getConnection("jdbc:mysql://master:3306/test", "root", "123456")
val format = new java.text.SimpleDateFormat("H:mm:ss")
val dateFf= format.format(new java.util.Date())
var cal:Calendar=Calendar.getInstance()
cal.add(Calendar.SECOND,-1)
var Beforeasecond=format.format(cal.getTime())
val date = Beforeasecond.toString+"-"+dateFf.toString
val sql = "insert into regusetab(time,reguse) values("+"'"+date+"'," +"'"+word._2+"')"
conn.prepareStatement(sql).executeUpdate()
})
}))
val num = logDStream.map(x=>(x,1)).reduceByKey(_+_)
num.print()
//啓動Streaming
ssc.start()
ssc.awaitTermination()
ssc.stop()
}
}