Spark
Apache Spark is a fast and general engine for large-scale data processing
数据结构:RDD,用于存储和管理数据
词频统计演示
val inputRDD = sc.textFile("/datas/wordcount.data")
val wordCountRDD = inputRDD.flatMap(_.split("\\s+")
.map(_.trim))
.map(word => (word, 1)).reduceByKey(_ + _)
wordCountRDD.saveAsTextFile("/datas/spark-wc-output")
wordCountRDD.foreach(println)
RDD = List
split = partition
将要处理的数据转换为RDD
seq: Seq[T],
numSlices: Int = defaultParallelism
): RDD[T] ```
示例(Spark开发经典案例WordCount):
package com.erongda.bigdata.spark.core
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Spark 开发大数据经典案例,词频统计Wordcount
*/
object SparkWordCount {
def main(args: Array[String]): Unit = {
// Spark Application运行时的相关配置信息,比如AppName,Master
val sparkConf = new SparkConf()
.setAppName("Spark Application")
//设置应用运行在哪里,是本地模式还是集群(具体制定的地址)
.setMaster("local[2]")
// 创建SparkContext对象,主要用于读取处理的数据,封装在集合RDD中,调度Job
val sc = new SparkContext(sparkConf)
sc.setLogLevel("WARN")
/**
* 第一步 数据的读取(输入input)
*/
val inputRDD: RDD[String] = sc.textFile("/datas/wordcount.data")
//查看样本数据
println(s"count = ${inputRDD.count()}")
println(s"first = \n\t ${inputRDD.first()}")
/**
* 第二步 数据的处理
*/
// 内功
val wordCountRDD: RDD[(String, Int)] = inputRDD.flatMap(_.split("\\s+")).map((_, 1)).reduceByKey(_ + _)
// 基本
inputRDD
.flatMap(line => line.split("\\s+"))
.map(word => (word, 1))
.reduceByKey((tmp, item) => tmp + item)
// 按照统计词频count进行降序排序
/**
* def sortByKey(ascending: Boolean = true, numPartitions: Int = self.partitions.length)
* 从函数的名称sortByKey:
* 按照Key进行排序的
* 第一个参数“
* ascending = true 默认值,表示暗战升序排序
*/
println("======== sortByKey ========")
val sortedWordCountRDD = wordCountRDD.map(_.swap).sortByKey(ascending = false)
sortedWordCountRDD.take(3).foreach(println)
println("========= top ============")
/**
* def top(num: Int)(implicit ord: Ordering[T]: Array[T])
* takeOrdered(num)(ord.reverse)
*/
wordCountRDD.top(3)(OrderUtils.SecondValueOrdering).foreach(println)
/**
* TODO:
* 在企业中使用SparkCore RDD 来分析数据
* -a. 如果获取最大的前几个TopKey
* rdd#top
* -b. 如果获取最小的前几个BottomKey
* rdd#takeOrdered()
*/
/**
* 第三步 数据的保存(输出output)
*/
// 查看处理后的数据
println("=========== 原始数据 ============")
wordCountRDD.foreach(println)
Thread.sleep(1000000)
// 关闭资源
sc.stop()
}
}
/**
* 自定义的排序规则,依据实际需求定义
*/
object OrderUtils{
object SecondValueOrdering extends scala.math.Ordering[(String, Int)]{
override def compare(x: (String, Int), y: (String, Int)): Int = {
x._2 - y._2
}
}
}
特点
特点
如:
count -> Long, first -> 集合中第一条数据
take -> 获取集合中前N条数据
foreach -> 对每条进行操作,比如答应数据,无返回值
持久化函数:将RDD集合中的数据缓存到内存或磁盘中
如:
def cache(): this.type = persist()
def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)
StorageLevel:
表示的将RDD数据存储的地方(存储级别)
class StorageLevel private(
// 将数据存储到磁盘中
private var _useDisk: Boolean,
// 将数据存储到内存中
private var _useMemory: Boolean,
// JVM 内存中,Alluxio
private var _useOffHeap: Boolean,
// 数据存储的时候是否反序列化
private var _deserialized: Boolean,
// 持久化数据的副本数,默认值为1
private var _replication: Int = 1
)
Alluxio:分布式内存文件系统
类似于HDFS分布式文件系统,不过Ta将数据存储在内存中
在什么情况 ,将RDD数据持久化呢???
释放存储数据
def unpersist(blocking: Boolean = true): this.type
RDD 中Aciton函数
在RDD中调整分区数的大小函数:
numPartitions: Int,
shuffle: Boolean = false
): RDD[T] ```
- repartition:进行Shuffle操作,性能所有消耗,不建议使用
```def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]```
- 底层:````coalesce(numPartitions, shuffle = true)```
示例(SparkSessionWordCount):
```spark
package com.erongda.bigdata.spark.core
import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
/**
* Created by Administrator on 2018/7/16.
*/
object SparkSessionWordCount {
def main(args: Array[String]): Unit = {
// 创建SparkSession实例对象
val spark: SparkSession = SparkSession.builder()
.appName("SparkSessionWordCount")
.master("local[2]")
.getOrCreate()
// 设置日志级别
spark.sparkContext.setLogLevel("WARN")
/**
* 使用SparkSession读取数据
*/
val inputRDD: RDD[String] = spark.read.textFile("/datas/wordcount.data").rdd
// inputRDD.cache() 数据缓存
// inputRDD.unpersist() 释放缓存
// 数据处理
val wordCount: RDD[(String, Int)] = inputRDD.flatMap(_.split("\\s+").map((_, 1))) // 性能高
.reduceByKey(_ + _)
wordCount.foreach(println)
// 为了4040
Thread.sleep(1000000)
// 关闭资源
spark.close()
}
}
示例(SessionTimeCountSpark):
package com.erongda.bigdata.spark.core
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Created by Administrator on 2018/7/16.
* 分析用户 Session会话时长, 各个时段占比
*/
object SessionTimeCountSpark {
// 会话时长:0 - 10 秒
val TIME_LENGTH_0010: String = "0-10"
// 会话时长: 11 - 20 秒
val TIME_LENGTH_1120: String = "11-20"
// 会话时长: 21 - 30 秒
val TIME_LENGTH_2130: String = "21-30"
// 会话时长: 30+ 秒
val TIME_LENGTH_3000: String = "30+"
def main(args: Array[String]): Unit = {
// Spark Application运行时的相关配置信息,比如AppName, Master
val sparkConf = new SparkConf()
.setAppName("SessionTimeCountSpark")
.setMaster("local[2]")
// 创建SparkContext实例对象,主要用于读取处理的数据,封装集合到RDD中,调度Job
val sc = new SparkContext(sparkConf)
sc.setLogLevel("WARN")
// TODO: 1. 读取数据,从本地系统LocalFileSystem读取数据
val pageViewRDD = sc.textFile("file:///C:/spark-learning/datas/page_views.data")
// 将RDD集合的数据存储到内存中,属于lazy方法,需要一个Action函数触发,才会将数据真正的缓存
pageViewRDD.cache()
// 采用数据,获取第一条数据和总的条目数
println(s"count = ${pageViewRDD.count()} \nFirst: \n\t${pageViewRDD.first()} ")
/**
* 2.需求分析:
* 统计 各个会话时长段 0-10,11-20,21-30,30+
* 关键点:
* 1. 统计各个会话时长
* 按照session_id进行分组,得到每个会话中的所有的track_time, 使用最后一个track_time - 第一个track_time获取时长
* 2. 判断会话时长属于哪个 时间段
*/
val timeLengthCountRDD: RDD[(String, Int)] = pageViewRDD
.map(line => {
//分割单词
val arr = line.split("\t")
// 将track_time日期类型的数据(2013-05-19 13:00:00)转换为Long类型,以便后续操作
import java.text.SimpleDateFormat
val time = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(arr(0)).getTime
//返回
(arr(2), time)
})
// TODO: b. 按照session_id进行分组,得到每个session会话中的所有页面的访问时间
.groupByKey() // RDD[(String, Iterable[Long])]
// TODO: c.计算每个会话的时长
.map{case (session_id: String, iter: Iterable[Long]) =>
// 最大的和最小的track_time
val maxTrackTime = iter.max
val minTrackTime = iter.min
// 计算会话时长
val timeLength = (maxTrackTime - minTrackTime) / 1000.0
// 判断时长属于哪个时长段
if (30 < timeLength){
(TIME_LENGTH_3000, 1)
}else if(20 < timeLength){
(TIME_LENGTH_2130, 1)
}else if(10 < timeLength){
(TIME_LENGTH_1120, 1)
}else{
(TIME_LENGTH_0010, 1)
}
}
// TODO: d. 聚合统计,计算出各个时长段的会话个数
.reduceByKey(_ + _)
// 显示结果
timeLengthCountRDD.foreach(println)
// 将RDD的数据从缓存中释放出来
pageViewRDD.unpersist()
// 为了开发测试 ,对每Application运行左监控,所以当前线程休眠
Thread.sleep(100000)
// 关闭资源
sc.stop()
}
}
示例(SparkWordCountToMySQL):
package com.erongda.bigdata.spark.core
import java.sql.{Connection, DriverManager}
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Spark 开发大数据经典案例,词频统计Wordcount
*/
object SparkWordCountToMySQL {
def main(args: Array[String]): Unit = {
// Spark Application运行时的相关配置信息,比如AppName,Master
val sparkConf = new SparkConf()
.setAppName("Spark Application")
//设置应用运行在哪里,是本地模式还是集群(具体制定的地址)
.setMaster("local[2]")
// 创建SparkContext对象,主要用于读取处理的数据,封装在集合RDD中,调度Job
val sc = new SparkContext(sparkConf)
sc.setLogLevel("WARN")
/**
* 第一步 数据的读取(输入input)
*/
val inputRDD: RDD[String] = sc.textFile("/datas/wordcount.data")
val wordCount: RDD[(String, Int)] = inputRDD.flatMap(_.split("\\s+").map((_, 1))) // 性能高
.reduceByKey(_ + _)
wordCount.coalesce(1)
.foreachPartition(iter => {
Class.forName("com.mysql.jdbc.Driver")
val url ="jdbc:mysql://bigdata-training01.erongda.com:3306/test"
val username = "root"
val password = "123456"
var conn: Connection = null
try{
conn = DriverManager.getConnection(url, username, password)
val sql = "INSERT INTO rdd_word_count (word, count) values(?,?)"
val pst = conn.prepareStatement(sql)
iter.foreach{
case (word: String, count: Int) =>
println(s"word = $word, count = $count")
pst.setString(1, word)
pst.setInt(2, count)
pst.executeUpdate()
}
}catch{
case e: Exception => e.printStackTrace()
}finally {
if(conn != null) conn.close()
}
})
Thread.sleep(1000000)
// 关闭资源
sc.stop()
}
}
Spark框架自身带的分布式集群资源管理和任务调度的框架,类似Hadoop YARN框架
SPARK_MASTER_HOST=bigdata-training01.erongda.com
SPARK_MASTER_PORT=7077
SPARK_MASTER_WEBUI_PORT=8080
SPARK_WORKER_CORES=2
SPARK_WORKER_MEMORY=2g
SPARK_WORKER_PORT=7078
SPARK_WORKER_WEBUI_PORT=8081
bigdata-training01.erongda.com
$ sbin/start-master.sh
$ start-slaves.sh
$ ssh-keygen -r rsa
$ ssh-copy-id bigdata-training01.erongda.com
bin/spark-shell --master spark://bigdata-training01.erongda.com:7077
数据量:每天增量
设计集群规模
每天增量3653*3 / 12 = 从节点个数
中小型规模:20台
中型规模:50台
大型规模:100台以上
硬件选型
写数据时,先写第一块硬盘,直到第一块满了,才写第二块
手动分发
同步服务:rsync
CM:cloudera Manager
技术选型
相当于AppMaster,应用管理者,主要调度Job执行
就是每个程序的main方法,必须创建SparkContext实例对象
端口号:4040 ,提供应用的监控
每个Executor是一个JVM Process(JVM 进程,相当于线程池),包含Memory和CPU Core,运行Tasks。
Spark中Task是以线程Thread方式运行的,每个Task执行需要1 Core CPU。
每个Job(作业)都有很多Task(任务)进行计算
注意:
Action函数必须对RDD中所有分区的数据进行操作,才能将RDD中所有分区的数据进行缓存。
-a. first()函数仅仅获取RDD中某个分区的一条数据,所以仅仅 将一个分区中的数据进行缓存
-b. 可以使用count()函数对RDD中所有的分区数据进行统计个数,来进行缓存数据(触发缓存)
def map[U: ClassTag](f: T => U): RDD[U] f -> 针对RDD中每个元素进行操作 def mapPartitions[U: ClassTag](f: Iterator[T] => Iterator[U]): RDD[U] f -> 针对RDD中每个分区中的元素进行操作的
第一个"聚合"函数:
def groupByKey(): RDD[(K, Iterable[V])]
- 643 行代码
def combineByKeyWithClassTag[C]
- 74行第二个"聚合"函数:
def reduceByKey(func: (V, V) => V): RDD[(K, V)]
- 328
def combineByKeyWithClassTag[C]
- 74"聚合"函数 , 类似Scala中Listfold聚合函数 -243
def foldByKey(zeroValue: V)(func: (V, V) => V): RDD[(K, V)]
def combineByKeyWithClassTag[C]
- 74第三个"聚合"函数:
def aggregateByKey[U: ClassTag](zeroValue: U) - 204 (zeroValue: U) // 聚合中间临时变量初始化 ( // 每个分区的聚合操作 seqOp: (U, V) => U, // 合并所有分区的聚合结果 combOp: (U, U) => U ): RDD[(K, U)]
def combineByKeyWithClassTag[C]
- 74第四个"聚合"函数:
def combineByKey[C] - 613 行 ( createCombiner: V => C, mergeValue: (C, V) => C, mergeCombiners: (C, C) => C ): RDD[(K, C)]
def combineByKeyWithClassTag[C]
- 74def combineByKeyWithClassTag[C] ( // 创建 合并器:确定如何聚合合并Value的值, 初始化操作,聚合中间临时变量的数据类型 createCombiner: V => C, // 针对每个分区中V的值进行聚合操作 mergeValue: (C, V) => C, // 合并 各个分区聚合的结果 mergeCombiners: (C, C) => C, partitioner: Partitioner, // 指定分区器 mapSideCombine: Boolean = true, // 默认不问 serializer: Serializer = null // 默认值,不问 ) (implicit ct: ClassTag[C]): RDD[(K, C)]
${SPARK_HOME}/bin/spark-submit 脚本运行提交所有的应用
spark-submit [options] [app arguments]
--master MASTER_URL
--conf spark.eventLog.enabled=true \ --conf spark.eventLog.compress=true \ --conf spark.eventLog.dir=hdfs://bigdata-training01.erongda.com:8020/datas/spark/eventLogs/ \
sparkConf.set("spark.eventLog.enabled", "true") sparkConf.set("spark.eventLog.compress", "true") sparkConf.set("spark.eventLog.dir", "hdfs://bigdata-training01.erongda.com:8020/datas/spark/eventLogs/")
${SPARK_HOME}/spark-default.conf
${SPARK_HOME}/conf/spark-env.sh
中配置:SPARK_HISTORY_OPTS="-Dspark.history.fs.logDirectory=hdfs://bigdata-training01.erongda.com:8020/datas/spark/eventLogs/ -Dspark.history.fs.cleaner.enabled=true"
http://spark.apache.org/docs/2.2.0/running-on-yarn.html
conf/spark-env.sh
hdfs-site.xml core-site.xml
yarn-site.xml
${SPARK_HOME}/bin/spark-submit
SPARK_HOME=/opt/cdh-5.7.6/spark-2.2.0-bin-2.6.0-cdh5.7.6 ${SPARK_HOME}/bin/spark-shell \ --master yarn \ --driver-memory 512M \ --executor-memory 1g \ --executor-cores 2 \ --num-executors 3
对于YARN集群来说,以下两点清楚
yarn-site.xml
yarn.nodemanager.resource.memory-mb
yarn.nodemanager.resource.cpu-vcores
表示的是Driver Program(JVM Process)运行在地方,如果运行在本地Localy称为client,也可以运行在集群的从节点(Worker节点或者NodeManager节点)上的某台机器上。
注意两点
CDH 版本的HADOOP 框架使用的JDK 1.7,将Spark 2.x开发应用运行在YARN上,如何处理解决JDK 版本问题呢???
解决方案:
spark.yarn.appMasterEnv.JAVA_HOME=/opt/modules/jdk1.8.0_91
spark.executorEnv.JAVA_HOME=/opt/modules/jdk1.8.0_91
RDD <-> HFile(StoreFile)
返璞归真:
sc.textFile("/datas/wordcount")
-> hadoopFile
rdd.saveAsTextFile("/datas/xxx")
-> saveAsHadoopFile
org.apache.hbase hbase-server 1.2.0-cdh5.7.6 org.apache.hbase hbase-hadoop2-compat 1.2.0-cdh5.7.6 org.apache.hbase hbase-client 1.2.0-cdh5.7.6
def newAPIHadoopRDD[K, V, F <: NewInputFormat[K, V]]( conf: Configuration = hadoopConfiguration, fClass: Class[F], kClass: Class[K], vClass: Class[V]): RDD[(K, V)]
示例:
package com.erongda.bigdata.spark.hbase
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{CellUtil, HBaseConfiguration}
import org.apache.hadoop.hbase.client.Result
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableInputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* Spark Core从HBase表中读取数据:
* 表的名称: ns1:sale_orders
*/
object ReadSaleOrdersSpark {
def main(args: Array[String]): Unit = {
// Spark Application运行时的相关配置信息,比如AppName, Master
val sparkConf = new SparkConf()
.setAppName("ReadSaleOrdersSpark")
// 设置应用运行在哪里,是本地模式还是集群(具体指定的地址)
.setMaster("local[5]")
/**
* 设置Spark Application 序列化方式使用Kryo
* 默认情况下,对simple types, arrays of simple types, or string type 使用Kryo方式序列化
*/
sparkConf.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 告知哪些类型进行序列化
sparkConf.registerKryoClasses(Array(classOf[ImmutableBytesWritable], classOf[Result]))
// 创建SparkContext实例对象, 主要用于读取处理的数据,封装集合RDD中,调度Job
// val sc = new SparkContext(sparkConf)
val sc: SparkContext = SparkContext.getOrCreate(sparkConf)
/**
* def newAPIHadoopRDD[K, V, F <: NewInputFormat[K, V]](
conf: Configuration = hadoopConfiguration,
fClass: Class[F],
kClass: Class[K],
vClass: Class[V]
): RDD[(K, V)]
*/
// a. 读取配置信息
val conf: Configuration = HBaseConfiguration.create()
// b. 设置从HBase哪张表读取数据
conf.set(TableInputFormat.INPUT_TABLE, "ns1:sale_orders")
// c. 调用SparkContext中newAPIHadoopRDD读取HBase表中的数据
val resultRDD: RDD[(ImmutableBytesWritable, Result)] = sc.newAPIHadoopRDD(
conf, // Configuration
classOf[TableInputFormat], // storage format of the data to be read
classOf[ImmutableBytesWritable], //
classOf[Result]
)
// 测试获取的数据
println(s"Count = ${resultRDD.count()}")
/**
* 当使用RDD.take(3).foreach() 报如下异常:ImmutableBytesWritable不能进行序列化
* java.io.NotSerializableException: org.apache.hadoop.hbase.io.ImmutableBytesWritable
* Serialization stack:
* 原因在于:
* RDD.take(N) 将数据从Executor中返回给Driver端,需要经过网络传输,所以需要对数据进行序列化,然而
* ImmutableBytesWritable 和 Result 类型都没有实现Java中序列化接口Serializable,所以出错。
* 如何解决问题呢????
* Spark 大数据分析计算框架,默认情况下使用Java Serializable对数据进行序列化,设置其他序列化方式。
*/
//
resultRDD.take(3).foreach{ case (key, result) =>
// 获取RowKey
val rowKey = Bytes.toString(key.get)
println(s"RowKey = $rowKey")
// 获取每条数据
for(cell <- result.rawCells()){
// 获取列簇
val cf = Bytes.toString(CellUtil.cloneFamily(cell))
// 获取列
val column = Bytes.toString(CellUtil.cloneQualifier(cell))
// 获取值
val value = Bytes.toString(CellUtil.cloneValue(cell))
// 打印
println(s"\t $cf:$column = $value -> ${cell.getTimestamp}")
}
}
// 为了开发测试,对每个Application运行做监控,所以当前线程休眠
Thread.sleep(10000000)
// 关闭资源
sc.stop()
}
}
def saveAsNewAPIHadoopDataset(conf: Configuration): Unit
示例:
package com.erongda.bigdata.spark.hbase
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.HBaseConfiguration
import org.apache.hadoop.hbase.client.Put
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 将RDD数据存储到HBase表中,使用SparkCore中API完成
*/
object WriteDataToHBaseSpark {
def main(args: Array[String]): Unit = {
// Spark Application运行时的相关配置信息,比如AppName, Master
val sparkConf = new SparkConf()
.setAppName("SparkAppModule")
// 设置应用运行在哪里,是本地模式还是集群(具体指定的地址)
.setMaster("local[2]")
// 创建SparkContext实例对象, 主要用于读取处理的数据,封装集合RDD中,调度Job
val sc = new SparkContext(sparkConf)
/**
* 模拟数据
* 将词频统计的结果RDD存储到HBase表中
* 设计表:
* 表的名称:ht_wordcount
* RowKey: word
* 列簇:info
* 列名:count
*/
// 创建Scala中集合类列表 List
val list = List(("hadoop", 234), ("spark", 4356), ("ml", 456), ("SQL", 1235))
// 通过并行化集合创建RDD
val wordCountRDD: RDD[(String, Int)] = sc.parallelize(list, numSlices = 1)
/**
* TableOutputFormat 向HBase表中写入数据,要求(Key, Value), 所以需要将RDD中的数据数据类型转换为
* RDD[(ImmutableBytesWritable, Put)]
*/
val putsRDD: RDD[(ImmutableBytesWritable, Put)] = wordCountRDD
// 数据类型转换
.map{ case(word, count) =>
// RowKey
val rowKey: ImmutableBytesWritable = new ImmutableBytesWritable(Bytes.toBytes(word))
// 创建Put对象
val put: Put = new Put(rowKey.get())
// 增加列
put.addColumn(
Bytes.toBytes("info"), // cf
Bytes.toBytes("count"), // column
Bytes.toBytes(count.toString)
)
// 返回二元组
(rowKey, put)
}
// TODO: 读取配置信息
val conf: Configuration = HBaseConfiguration.create()
// a. 设置数据保存表的名称
conf.set(TableOutputFormat.OUTPUT_TABLE, "ht_wordcount")
// b. 设置OutputFormat
conf.set("mapreduce.job.outputformat.class",
"org.apache.hadoop.hbase.mapreduce.TableOutputFormat")
// c. 设置输出目录
conf.set("mapreduce.output.fileoutputformat.outputdir",
"/datas/spark/hbase/htwc-" + System.currentTimeMillis())
// TODO: 调用RDD中方法,将数据保存到HBase表中
putsRDD.saveAsNewAPIHadoopDataset(conf)
// 为了开发测试,对每个Application运行做监控,所以当前线程休眠
Thread.sleep(10000000)
// 关闭资源
sc.stop()
}
}
org.apache.hadoop.mapred
org.apache.hadoop.mapreduce
http://spark.apache.org/docs/2.2.0/tuning.html#memory-management-overview
比例的划分,在不同的应用运行的时候,适当考虑分配。
比如:
(默认情况下: 属于值的比例限定)
代码(ETL):
package com.erongda.bigdata.project.etl
import java.util
import java.util.zip.CRC32
import com.erongda.bigdata.project.common.EventLogConstants
import com.erongda.bigdata.project.common.EventLogConstants.EventEnum
import com.erongda.bigdata.project.util.{LogParser, TimeUtil}
import org.apache.commons.lang.StringUtils
import org.apache.hadoop.conf.Configuration
import org.apache.hadoop.hbase.{HBaseConfiguration, HColumnDescriptor, HTableDescriptor, TableName}
import org.apache.hadoop.hbase.client.{Connection, ConnectionFactory, HBaseAdmin, Put}
import org.apache.hadoop.hbase.io.ImmutableBytesWritable
import org.apache.hadoop.hbase.io.compress.Compression
import org.apache.hadoop.hbase.mapreduce.TableOutputFormat
import org.apache.hadoop.hbase.util.Bytes
import org.apache.spark.broadcast.Broadcast
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
/**
* 基于Spark框架读取HDFS上日志文件数据,进行ETL操作,最终将数据插入到HBase表中
* -1. 为什么选择ETL数据到HBase表中??
* 采集的数据包含很多Event类型的数据,不同Event时间类型的数据字段不一样,数据量相当比较大
* -2. HBase 表的设计???
* -a. 每天的日志数据,ETL到一张表中
* 本系统,主要针对日志数据进行分析的,基本上每天的数据分析一次,为了分析更快,加载更少的数据
* -b. 每次ETL数据的时候,创建一张表
* -i. 创建表的预分区,使用的数据在不同Region中,减少些热点,避免Region Split
* -ii. 可以考虑表中数据的压缩,使用snappy压缩或lz4压缩
* -v. RowKey设计原则:
* - 唯一性(不重复)
* - 结合业务考虑
* 某个EventType数据的分析,某个时间段数据的分析
* RowKey = 采用服务器时间戳 + crc32(用户ID、会员ID、事件名称)
*/
object EtlToHBaseSpark {
/**
* RowKey 创建
* @param time
* 服务器时间
* @param uUID
* 用户ID
* @param uMD
* 用户会员ID
* @param eventAlias
* 事件Event名称
* @return
*/
def createRowKey(time: Long, uUID: String, uMD: String, eventAlias: String): String = {
// 创建StringBuilder实例对象,用于拼接字符串
val sBuilder = new StringBuilder()
sBuilder.append(time + "_")
// 创建CRC32实例对象,进行字符串编码,将字符串转换为Logn类型数字
val crc32 = new CRC32()
// 重置
crc32.reset()
if(StringUtils.isNotBlank(uUID)){
crc32.update(Bytes.toBytes(uUID))
}
if(StringUtils.isNotBlank(uMD)){
crc32.update(Bytes.toBytes(uMD))
}
if(StringUtils.isNotBlank(eventAlias)){
crc32.update(Bytes.toBytes(eventAlias))
}
sBuilder.append(crc32.getValue % 100000000L)
// return
sBuilder.toString()
}
/**
* 创建HBase表,创建的时候判断是否已经存在,存在的话先删除后创建
* @param processDate
* 要处理哪天数据的日期,格式:2018-07-22
* @param conf
* HBase Client 要读取的配置信息
* @return
* 表的名称
*/
def createHBaseTable(processDate: String, conf: Configuration): String = {
// create 'event_logs20151220', 'info'
val time = TimeUtil.parseString2Long(processDate)
val date = TimeUtil.parseLong2String(time, "yyyyMMdd")
// table name
val tableName = EventLogConstants.HBASE_NAME_EVENT_LOGS + date
// 创建表,先判断是否存在
var conn: Connection = null
var admin: HBaseAdmin = null
try{
// 获取连接
conn = ConnectionFactory.createConnection(conf)
// 获取HBaseAdmin实例对象
admin = conn.getAdmin.asInstanceOf[HBaseAdmin]
// 判断表是否存在
if(admin.tableExists(tableName)){
// 表存在,先禁用,后删除
admin.disableTable(tableName)
admin.deleteTable(tableName)
}
// 创建TableDesc描述符
val desc = new HTableDescriptor(TableName.valueOf(tableName))
// 创建表的列簇描述符
val familyDesc = new HColumnDescriptor(EventLogConstants.BYTES_EVENT_LOGS_FAMILY_NAME)
/**
* 针对列簇设置属性值
*/
// 设置数据压缩
familyDesc.setCompressionType(Compression.Algorithm.SNAPPY)
// 设置读取数据不缓存
familyDesc.setBlockCacheEnabled(false)
// 向表中添加列簇
desc.addFamily(familyDesc)
// 设置表的预分区,针对整个表来说的,不是针对某个列簇
// TODO: createTable(desc: HTableDescriptor, splitKeys: Array[Array[Byte]])
admin.createTable(desc,
Array(
Bytes.toBytes("1450570500000_"), Bytes.toBytes("1450571100000_"),
Bytes.toBytes("1450571700000_")
)
)
}catch {
case e: Exception => e.printStackTrace()
}finally {
if(null != admin) admin.close()
if(null != conn) conn.close()
}
// 返回表的名称
tableName
}
/**
* Spark Application 运行的入口,就是Driver Program
* @param args
* 程序的参数,实际业务需要传递 处理哪天你的数据(processDate)
*/
def main(args: Array[String]): Unit = {
// 需求传递一个参数,表明ETL处理的数据时哪一天的
if(args.length < 1){
println("Usage: EtlToHBaseSpark process_date")
System.exit(1)
}
/**
* 1. 创建SparkContext实例对象,读取数据,调度Job
*/
val sparkConf = new SparkConf()
.setMaster("local[3]").setAppName("EtlToHBaseSpark Application")
// 设置使用kryo序列化
.set("spark.serializer", "org.apache.spark.serializer.KryoSerializer")
// 告知序列化数据类型,看RDD中数据类型,除了simple types, arrays of simple types, or string type
.registerKryoClasses(Array(classOf[ImmutableBytesWritable], classOf[Put]))
// 创建SparkContext
val sc = SparkContext.getOrCreate(sparkConf)
sc.setLogLevel("WARN")
/**
* TODO: a. 读取日志数据,从本地读取
*/
val eventLogsRDD: RDD[String] = sc
.textFile("file:///C:/spark-learning/datas/20151220.log", minPartitions = 3)
// println(s"Count = ${eventLogsRDD.count()}")
// println(eventLogsRDD.first())
/**
* TODO:b. 解析每条日志数据
*/
val parseEventLogsRDD: RDD[(String, util.Map[String, String])] = eventLogsRDD
// 通过解析工具类解析每条数据
.map(line => {
// 调用工具类进行解析得到Map集合
val logInfo: util.Map[String, String] = new LogParser().handleLogParser(line)
// 获取事件的类型
val eventAlias = logInfo.get(EventLogConstants.LOG_COLUMN_NAME_EVENT_NAME)
// 以二元组的形式返回
(eventAlias, logInfo)
})
// println(parseEventLogsRDD.first())
// 存储事件EventType类型
val eventTypeList = List(EventEnum.LAUNCH, EventEnum.PAGEVIEW, EventEnum.CHARGEREQUEST,
EventEnum.CHARGESUCCESS, EventEnum.CHARGEREFUND, EventEnum.EVENT)
// TODO: 定义广播变量,将事件类型列表广播出去,广播给所有Executor
val eventTypeListBroadcast: Broadcast[List[EventEnum]] = sc.broadcast(eventTypeList)
/**
* TODO:c. 过滤数据
*/
val eventPutsRDD: RDD[(ImmutableBytesWritable, Put)] = parseEventLogsRDD
// 过滤条件:解析Map集合不能为空; 事件类型EventType必须存在
// TODO: 性能优化点:将集合列表 拷贝到每个Executor中一份数据,而不是每个Task中一份数据
.filter{ case(eventAlias, logInfo) =>
// logInfo.size() != 0 && eventTypeList.contains(EventEnum.valueOfAlias(eventAlias))
logInfo.size() != 0 && eventTypeListBroadcast.value.contains(EventEnum.valueOfAlias(eventAlias))
}
// 数据转换,准备RDD数据库,将数据保存到HBASE表中RDD[(ImmtableBytesWritable, Put)]
.map{ case(eventAlias, logInfo) =>
// -i. RowKey 表的主键
val rowKey = createRowKey(
TimeUtil.parseNginxServerTime2Long(logInfo.get(EventLogConstants.LOG_COLUMN_NAME_SERVER_TIME)),
logInfo.get(EventLogConstants.LOG_COLUMN_NAME_UUID), // 用户ID
logInfo.get(EventLogConstants.LOG_COLUMN_NAME_MEMBER_ID), // 会员ID
eventAlias // 事件类型别名
)
// -ii. 创建Put对象
val put = new Put(Bytes.toBytes(rowKey))
// add columns
// TODO: 注意此处需要将Java中Map集合转换为Scala中Map集合,方能进行操作
import scala.collection.JavaConverters._
for((key, value) <- logInfo.asScala){
put.addColumn(
EventLogConstants.BYTES_EVENT_LOGS_FAMILY_NAME , // cf
Bytes.toBytes(key), // column
Bytes.toBytes(value)
)
}
// iii. 返回二元组
(new ImmutableBytesWritable(put.getRow), put)
}
/**
* TODO: d. 将RDD保存到HBase表中
*/
// d.1 获取配置信息, 需要将hbase-site.xml放入CLASSPATH下面
val conf = HBaseConfiguration.create()
/**
* 由于ETL每天执行一次(ETL失败,再次执行),对原始的数据进行处理,将每天的数据存储HBase表中
* 表的名称:
* create 'event_logs20151220', 'info'
*/
val tableName = createHBaseTable(args(0), conf)
// d.2 设置表的名称
conf.set(TableOutputFormat.OUTPUT_TABLE, tableName)
// d.3 设置OutputFort
conf.set("mapreduce.job.outputformat.class",
"org.apache.hadoop.hbase.mapreduce.TableOutputFormat")
// d.4. 设置输出目录
conf.set("mapreduce.output.fileoutputformat.outputdir",
"/datas/spark/hbase/etl-" + System.currentTimeMillis())
/**
* TODO: 真正的保存数据到HBase表中
*/
eventPutsRDD.saveAsNewAPIHadoopDataset(conf)
// println(eventPutsRDD.count())
// println(eventPutsRDD.first())
// 为了开发测试,线程休眠, WEB UI监控查看
Thread.sleep(1000000)
// 关闭资源
sc.stop()
}
}
familyDesc.setBlockCacheEnabled(false)
val eventTypeListBroadcast: Broadcast[List[EventEnum]] = sc.broadcast(eventTypeList)
eventTypeListBroadcast.value
新增用户 统计分析
分析指标:需要结合维度来分析
累加器示例:
val accum = sc.longAccumulator("Input Lines Accumulator")
val inputRDD = sc.textFile("/datas/wordcount.data")
val filterRDD = inputRDD.filter(line => {
accum.add(1L)
line.trim.length > 0
})
filterRDD.count()