package report
import config.ConfigHelper
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import scalikejdbc.{DB, SQL}
import scalikejdbc.config.DBs
import utils.MakeATPKpi
//利用广播变量来进行数据的传输
object TrainTimeBroadcastAnalysis {
def main(args: Array[String]): Unit = {
//session
val session=SparkSession
.builder()
//设置进程的名称
.appName(this.getClass.getName)
//设置是否是在本地运行
//*代表几个线程
.master("local[*]")
//序列化
.config("spark.serializer",ConfigHelper.serializer)
.getOrCreate()
//导入隐式转换
import session.implicits._
//读取原始日志
val frame = session.read.parquet(args(0))
//读取列车出场时间字典表
val trainTimeSource = session.sparkContext.textFile(args(1))
//读取以后对数据以"|"为标准进行切分
//spilt中的regax属性里面的"\\"是转译符号,除非后面的符号是","或者";",不然类似于"|"这样的前面都要加上"\\"
//limit
//什么都不加:300T// 剪切完:300T
//加-1:300T// 剪切完:300T “” “” “” “” “” “”
val rddSpilt: RDD[Array[String]] = trainTimeSource.map(_.split("\\|",-1))
//清洗数据
val rddFilted: RDD[Array[String]] = rddSpilt.filter(_.length>=2)
//搞成元组rdd
val rddTuple: RDD[(String, String)] = rddFilted.map(arr=>(arr(0),arr(1)))
//先收集再转换成map
// val rddArr: Array[(String, String)] = rddTuple.collect()
// val rddMap: Map[String, String] = rddArr.toMap
//这个比上面的两行更高效
val rddMap: collection.Map[String, String] = rddTuple.collectAsMap()
//创建广播变量
val trainTimeBro: Broadcast[collection.Map[String, String]] = session.sparkContext.broadcast(rddMap)
//处理数据
val rddResult: RDD[(String, List[Int])] = frame.map(row => {
//获取车号
val trainId = row.getAs[String]("MPacketHead_TrainID")
//获取列车的出厂map
val trainMap: collection.Map[String, String] = trainTimeBro.value
//获取列车出厂的时间
//第一个参数是凭借trainId去找trainTime
//第二个参数是找不到trainTime就返回一个默认值
val trainTime = trainMap.getOrElse(trainId, ",,,,")
//获取kpi
//使用MakeATPKpi样例类中的makeKpi方法,参数为row
//这条语句在map中,map可以理解为有返回值的循环,因此参数为row,表示每一行都要获取数据
val list: List[Int] = MakeATPKpi.makeKpi(row)
//以列车的出厂时间为key,list为value,返回到map
(trainTime, list)
})
//转换成rdd,因为只有rdd中才有reduceByKey方法
.rdd
//进行数据聚合
//zip函数是把两个list聚合
//开发中,需要聚合的文段
//举个例子:
//RDD中有如下元素
//kv
//((a,b),List(7,8,9))
//((a,b),List(1,2,3))
//((a,b),List(4,5,6))
//((c,d),List(4,5,6))
//。。。。。。
//reducebykey后要对v进行操作
//原理将v中的list前后zip再map每个元素进行逐个元组元素(a,b)的累加。
//a代表v的前一个元素,b代表v的后一个元素
//如val a=List(7,8,9)
//val b=List (1,2,3)
//val k= a zip b =((7,1),(8,2)(9,3))
//k map (tp=>tp._1+tp._2) ----- > (8,10,12)
//如此循环 结果:
//新RDD中的元素:
//((a,b),List(12,15,18))
//((c,d),List(4,5,6))
//。。。。。。
.reduceByKey {
(list1, list2) => list1 zip list2 map (tp => tp._1 + tp._2)
}
//写入mysql中,用scalikejdbc写
DBs.setup()
//在driver端创建一个mysql链接,发送到从节点端
//一个链接被n个从节点公用,排队,十分影响效率
//在excutor端中每一条数据创建一个链接,每次都获取开线程,关线程,浪费时间
//每个excutor用一个线程
rddResult.foreachPartition(partition=>{
DB.localTx{implicit session=>
//遍历
partition.foreach(tp=>{
SQL("insert into TrainTimeBroadcastAnalysis values (?,?,?,?,?,?,?,?,?,?,?)")
//把rddResult中的几个值都加入到insert语句中
.bind(tp._1,tp._2(0),tp._2(1),tp._2(2),tp._2(3),tp._2(4),tp._2(5),tp._2(6),tp._2(7),tp._2(8),tp._2(9))
.update()
.apply()
})
}
})
//释放资源
session.stop()
}
}
样例类
MakeATPKpi.scala
package utils
import org.apache.commons.lang.StringUtils
import org.apache.spark.sql.Row
object MakeATPKpi {
def makeKpi(row:Row)={
//获取atperror
val atpError = row.getAs[String]("MATPBaseInfo_AtpError")
//判断指标
//如果atpError不为空,前面为1,如果atpError为空,那么前面为0
//先判断atpError不为空,如果为空,则执行下面的else语句,全部输出为0
val listAtpError: List[Int] = if (StringUtils.isNotEmpty(atpError)) {
val listError: List[Int] =
if (atpError.equals("车载主机")) {
List[Int](1, 0, 0, 0, 0, 0, 0, 0)
} else if (atpError.equals("无线传输单元")) {
List[Int](0, 1, 0, 0, 0, 0, 0, 0)
} else if (atpError.equals("应答器信息接收单元")) {
List[Int](0, 0, 1, 0, 0, 0, 0, 0)
} else if (atpError.equals("轨道电路信息读取器")) {
List[Int](0, 0, 0, 1, 0, 0, 0, 0)
} else if (atpError.equals("测速测距单元")) {
List[Int](0, 0, 0, 0, 1, 0, 0, 0)
} else if (atpError.equals("人机交互接口单元")) {
List[Int](0, 0, 0, 0, 0, 1, 0, 0)
} else if (atpError.equals("列车接口单元")) {
List[Int](0, 0, 0, 0, 0, 0, 1, 0)
} else if (atpError.equals("司法记录单元")) {
List[Int](0, 0, 0, 0, 0, 0, 0, 1)
} else {
//这是为了防止获取的atpError的值都不符合上述判断条件的情况
List[Int](0, 0, 0, 0, 0, 0, 0, 0)
}
//两个list的拼接要用++
List[Int](1) ++ listError
} else {
//如果atpError为空的情况,说明这行的这个属性没有值,那么全取为0即可
List[Int](0, 0, 0, 0, 0, 0, 0, 0, 0)
}
//创建一个容器用来存放标签
//两个list的拼接要用++
//这个是为了在整合以后统计总共多少条数据用,相当于数量
val list: List[Int] = List[Int](1) ++ listAtpError
list
}
}
相关配置文件
application.conf
#配置文件
#配置压缩格式
parquet.code="snappy"
#配置序列化方式
spark.serializer="org.apache.spark.serializer.KryoSerializer"
#配置jdbc链接
jdbc.url="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"
jdbc.driver="com.mysql.jdbc.Driver"
jdbc.user="root"
jdbc.password="000000"
#配置scalikejdbc链接
db.default.url="jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8"
db.default.driver="com.mysql.jdbc.Driver"
db.default.user="root"
db.default.password="000000"
ConfigHelper.scala
package config
import com.typesafe.config.{Config, ConfigFactory}
object ConfigHelper {
//加载配置文件
private lazy val load: Config = ConfigFactory.load()
//加载压缩格式
val parquetCode: String = load.getString("parquet.code")
//加载序列化方式
val serializer: String = load.getString("spark.serializer")
//加载jdbc
val url: String = load.getString("jdbc.url")
val driver: String = load.getString("jdbc.driver")
val user: String = load.getString("jdbc.user")
val password: String = load.getString("jdbc.password")
//加载scalikejdbc
val url2: String = load.getString("db.default.url")
val driver2: String = load.getString("db.default.driver")
val user2: String = load.getString("db.default.user")
val password2: String = load.getString("db.default.password")
}