慕课网日志分析实战三:需求实现代码

本来打算这一系列一直更下去,但是后来由于实习和秋招的问题一直耽搁,本来打算一切结束再继续更新这一系列,感觉评论有点多,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

猜你喜欢

转载自blog.csdn.net/weixin_39216383/article/details/81272355