本来打算这一系列一直更下去,但是后来由于实习和秋招的问题一直耽搁,本来打算一切结束再继续更新这一系列,感觉评论有点多,CSND小透明受宠若惊。决定继续更新下去。在慕课日志分析这个项目我觉得说简单其实也并不简单,蕴含着很多日志处理的坑。说简单是因为大部分繁重的步骤,包括业务梳理,字段整合之类的,已经在前面帮咱们解决了,不需要学习者做什么,学习者只需要将数据进行简单分词,转化DF或DS写代码或者SQL进行简单分析,并且入库。这是它的简单之处。但难在于在解析日志时,如果转化时间转化不到位,很可能会导致线程不安全,产生一些莫名其妙的时间分区。难在于在数据量达到一定程度如何对其进行shuffle调优、数据倾斜调优(有groupby 代表必然有数据倾斜)。这些课程里面也没有详细讲解,如何后期我面试顺利的话,也会分享一下我的浅薄见解。
首先我这篇博文想解决对于实战分析二中的三个需求进行实现,日志的解析我们已经解决,下面我们应该做的就是分析和入库。
需求一:统计imooc主站最受欢迎课程/手记的topn访问次数
def videoAccessTopNStat(spark:SparkSession,accessDF:DataFrame,day:String)={
import spark.implicits._
//代码版本分析
// val videotopn=accessDF.filter($"day"==="20170511"&&$"cmsType"==="video")
// .groupBy("day","cmsId").agg(count("cmsId").as("times")).orderBy($"times")
//videotopn.show(false)
//创建临时视图的方式
accessDF.createOrReplaceTempView("access_log")
val videotopn=spark.sql(s"select day,cmsId,count(1) as times from access_log where day=$day and cmsType='video' group by day,cmsId order by times desc")
videotopn.show(false)
//将统计结果写数据库
try {
videotopn.foreachPartition(p => {
var list = new ListBuffer[DayVideoAccessStat]
p.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val times = info.getAs[Long]("times")
list.append(DayVideoAccessStat(day, cmsId, times))
})
StatDAO.insertDayVideotopn(list)
})
}catch {
case e:Exception=>e.printStackTrace()
}
}
//入库函数我采用分批写入的方式
/*
* 统计imooc主站最受欢迎课程/手记的topn访问次数
* */
def insertDayVideotopn(list:ListBuffer[DayVideoAccessStat]):Unit={
var connect:Connection=null
var pstmt:PreparedStatement=null
try{
connect=MySQLUtils.getConnection()
connect.setAutoCommit(false)//设置手动提交
val sql="insert into day_video_access_topn_stat(day,cmsId,times) values(?,?,?)"
pstmt=connect.prepareStatement(sql)
for (ele<-list)
{
pstmt.setString(1,ele.day)
pstmt.setLong(2,ele.cmsId)
pstmt.setLong(3,ele.time)
pstmt.addBatch()
}
pstmt.executeBatch()
connect.commit()
}
}
需求二:按照地市统计topn课程
/*
* 按照地市统计topn课程,分析
* */
def cityAccessTopNStat(spark:SparkSession,accessDF:DataFrame,day:String): Unit =
{
//accessDF.createOrReplaceTempView("access_log")
import spark.implicits._
val citytopn=accessDF.filter($"day"===day)
.groupBy("day","city","cmsId").agg(count("cmsId").as("times")).orderBy($"times")
citytopn.show(false)
val top3DF=citytopn.select(citytopn("day"),
citytopn("city"),
citytopn("cmsId"),
citytopn("times"),
row_number().over(Window.partitionBy(citytopn("city")).orderBy(citytopn("times").desc)).as("times_rank")
).filter("times_rank<=3")
try {
top3DF.foreachPartition(p => {
var list = new ListBuffer[DayCityVideoAccessStat]
p.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val city=info.getAs[String]("city")
val times = info.getAs[Long]("times")
val times_rank=info.getAs[Int]("times_rank")
list.append(DayCityVideoAccessStat(day,cmsId,city,times,times_rank))
})
StatDAO.insertDayCityVideotopn(list)
})
}catch {
case e:Exception=>e.printStackTrace()
}
}
/*
* 按地市统计imooc主站最受欢迎topn课程,入库函数
* */
def insertDayCityVideotopn(list:ListBuffer[DayCityVideoAccessStat]):Unit={
var connect:Connection=null
var pstmt:PreparedStatement=null
try{
connect=MySQLUtils.getConnection()
connect.setAutoCommit(false)//设置手动提交
val sql="insert into day_video_city_access_topn_stat (day,cmsId,city,times,times_rank) values(?,?,?,?,?)"
pstmt=connect.prepareStatement(sql)
for (ele<-list)
{
pstmt.setString(1,ele.day)
pstmt.setLong(2,ele.cmsId)
pstmt.setString(3,ele.city)
pstmt.setLong(4,ele.times)
pstmt.setInt(5,ele.times_rank)
pstmt.addBatch()
}
pstmt.executeBatch()
connect.commit()
}
}
需求三:按流量统计imooc主站最受欢迎的topn课程
//分析
def videoTrafficAccessTopNStat(spark:SparkSession,accessDF:DataFrame,day:String): Unit =
{
accessDF.createOrReplaceTempView("access_log")
import spark.implicits._
val traffics=accessDF.filter($"day"===day&&$"cmsType"==="video")
.groupBy("day","cmsId").agg(sum("traffic")as("traffics")).orderBy($"traffics".desc)//.show(false)
try {
traffics.foreachPartition(p => {
val list = new ListBuffer[DayVideoTrafficsStat]
p.foreach(info => {
val day = info.getAs[String]("day")
val cmsId = info.getAs[Long]("cmsId")
val traffics = info.getAs[Long]("traffics")
list.append(DayVideoTrafficsStat(day, cmsId, traffics))
})
StatDAO.insertDayVideoTrafficstopn(list)
})
}catch {
case e:Exception=>e.printStackTrace()
}
}
//入库函数
def insertDayVideoTrafficstopn(list:ListBuffer[DayVideoTrafficsStat]):Unit={
var connect:Connection=null
var pstmt:PreparedStatement=null
try{
connect=MySQLUtils.getConnection()
connect.setAutoCommit(false)//设置手动提交
val sql="insert into day_video_traffics_topn_stat(day,cmsId,traffics) values(?,?,?)"
pstmt=connect.prepareStatement(sql)
for (ele<-list)
{
pstmt.setString(1,ele.day)
pstmt.setLong(2,ele.cmsId)
pstmt.setLong(3,ele.traffics)
pstmt.addBatch()
}
pstmt.executeBatch()
connect.commit()
}
}
如果对Scala和JDBC有一定了解,相信这些代码很简单。我的心得就是
- 在需求二和三时,使用groupby函数,势必会产生数量分布不均的key,导致数据倾斜,应注意。
- 在清洗日志时,会产生因线程安全而导致的问题,后续我会更新,我在github上并没有写这一部分
- 在如何处理IP与城市关系问题我打算后面继续写,哎,边实习边准备秋招累成狗。
最后附上github地址,如果觉得满意的话,给个star
https://github.com/doudianer/ImoocLoganalysis