Spark中的RDD是什么?

一、RDD 是什么

官方定义:
在这里插入图片描述

  • 第一点:不可变的.
    RDD类似scala中不可变的集合,例如列表List,当集合中的元素进行转换的操作的时候,产生新的集合RDD
  • 第二点:分区的
    每个RDD集合有多个分区组成,分区就是很多部分
  • 第三点并行的操作
    对RDD集合中数据操作时,可以同时对所有分区并行操作

在这里插入图片描述
RDD的五个特点

  • 一个RDD由一系列分区Partition组成
  • RDD中每个分区数据可以被处理分析(计算)
  • 每个RDD依赖一系列的RDD
  • 可选的特性,针对KeyValue类型的RDD,可以设置分区器,将每个RDD中各个分区数据重新划分,类似Mapreduce中分区器partitioner
  • 可选的特性:针对RDD中每个分区数据处理的时候,得到最好的路径,类似Mapreduce中数据本地性

在这里插入图片描述

二、创建RDD

将要分析的数据封装到RDD中,主要有两种方式
在这里插入图片描述

  • 第一种: 读取外部数据源的数据
最常用函数就是SparkContext#textFile函数

def textFile(
      path: String, // 第一个参数表示:数据路径,可以是LocalFS、也可以是HDFS
      minPartitions: Int = defaultMinPartitions // 第二参数表示:RDD分区数目
): RDD[String] 
  • 第二种方式:并行化集合
将Scala或者Java或者Python中集合转换为RDD

  def parallelize[T: ClassTag](
      seq: Seq[T],  // 集合,针对Scala语言来说,就是序列Seq
      numSlices: Int = defaultParallelism  // 表示RDD分区数目
  ): RDD[T]

     从HDFS或者LocalFS读取数据文件数据时,RDD默认分区数目如何确定??

原则:
	RDD的分区数目通常是Spark Application中所有Executor CPU Core核数之和的2-3倍
	
比如:Spark Application,有5个Executor,每个Executor的CPU Core核数为8核,计算RDD分区数目最好为多少??
	all-executor-cpu-cores = 5 * 8 = 40 
	所以:
		rdd-partitions = 40 * 2  - 40 * 3  = 80 - 120 之间

三、RDD Operations

      将数据封装到RDD中以后,依据具体业务对RDD中数据处理分析,需要调用函数,RDD中的函数分为三类:
在这里插入图片描述

  • 第一类:转换函数Transformation\

    • 当一个RDD调用转换函数以后,产生一个新的RDD
    • 使用最多的函数,用于处理数据,比如使用过的map、flatMap、reduceByKey等等
    • 特点:当RDD调用此类函数以后,不会立即执行,属于Lazy(懒惰),需要等待Action函数触发执行
  • 第二类:Action函数(触发函数/Job函数)

    • 当一个RDD调用Action函数以后,触发一个Job的执行,返回值不是RDD,可以是Unit,可以是其他
    • 此类函数触发Job执行,比如count、first、take、top、foreach等
    • 特点:立即执行Job,属于Eager操作
  • 第三类:持久化函数

    • 将数据集RDD保存到内存或者磁盘中,方便再次使用时快速的读取
      在这里插入图片描述

四、RDD 重要函数

RDD中有很多函数,主要使用的函数如下五类
在这里插入图片描述
1、分区操作函数
RDD中映射函数map和输出函数foreach,都是针对RDD中每个元素操作,但是不建议使用,建议对RDD中的数据操作时,针对每个分区的。

// 1. 映射函数 map和mapPartitions
def map[U: ClassTag](f: T => U): RDD[U]
//	针对每个元素处理操作

def mapPartitions[U: ClassTag](
      // 将每个分区数据封装到迭代器Iterator中
      f: Iterator[T] => Iterator[U],
      preservesPartitioning: Boolean = false
): RDD[U]

// 2. 输出函数 foreach和foreachPartition
  /**
   * Applies a function f to all elements of this RDD.
   */
  def foreach(f: T => Unit): Unit 

  /**
   * Applies a function f to each partition of this RDD.
   */
  def foreachPartition(f: Iterator[T] => Unit): Unit 

为什么要对分区操作,而不是对每个数据操作,好处在哪里呢???

应用场景:
	处理网站日志数据,数据量为10GB,统计各个省份PV和UV。
假设10GB日志数据,从HDFS上读取的,此时RDD的分区数目:80 分区

但是分析PV和UV有多少条数据:34,存储在80个分区中,实际中降低分区数目,比如设置为2个分区
	resultRDD:
		p0:  24 条数据
		p1:  10 条数据
	现在需要将结果RDD保存到MySQL数据库表中。
1、使用foreach函数
	34条数据,就需要创建34 数据库连接
2、使用foreachPartition函数
	针对每个分区数据操作,只需要创建2个数据库连接

2、重分区函数
在实际项目中,需要调整RDD中分区数目,要么就是增大分区数目,要么减少分区数目
1)、增加分区数目
repartition函数调整RDD中分区数目,既可以增加又可以减少,但是会产生Shuffle,性能降低。

def repartition(numPartitions: Int)(implicit ord: Ordering[T] = null): RDD[T]

实际中常常用于增加分区的数目。
应用场景:
	实际项目很多时候业务数据存储到HBase表中,数据对应到各个Region中,此时要分析的就需要从HBase中读取数据,假设HBase中表的Region数目为30个,那么SparkCore读取数据以后,封装的RDD的分区数目就是30个。
			默认:rdd-partitions = table-regions
	但是每个Region中的数据量大概时5GB数据,对于读取到RDD的每个分区中来说,数据量也是5GB,当一个Task处理一个分区的数据,显得很大,此时需要增加RDD的分区数目。
	val etlRDD = hbaseRDD.repartition(40 * 30)
	etlRDD-partitions = 1200

2)、减少分区数目
coalesce函数用于降低RDD分区数目,不会产生Shuffle,建议使用。

  def coalesce(
      numPartitions: Int, 
      shuffle: Boolean = false,
      partitionCoalescer: Option[PartitionCoalescer] = Option.empty
   )
    (implicit ord: Ordering[T] = null): RDD[T]

什么时候需要降低分区数目呢???

1、当对RDD数据使用filter函数过滤以后,需要考虑是否降低分区数目
	比如从ES中获取数据封装到RDD中, 分区数目为50个分区,数据量为20GB
		val etlRDD = esRDD.filter(.......)
	过滤以后数据量为12GB,此时考虑降低分区数目
		etlRDD.coalesce(35)
		
2、当将分析结果RDD(resultRDD)保存到外部存储系统时,需要考虑降低分区数目
	resultRDD.coalesce(1).foreachPartition()

3、数据缓存函数
可以将RDD数据缓存到内存中,如果内存不足的话,可以缓存到磁盘中,在实际项目中适当对数据缓存提升性能。
1)、缓存函数
首先查看RDD中函数,有两个函数可以直接使用,将RDD的数据缓存到内存中。

  /**
   * Persist this RDD with the default storage level (`MEMORY_ONLY`).
   */
  def persist(): this.type = persist(StorageLevel.MEMORY_ONLY)

  /**
   * Persist this RDD with the default storage level (`MEMORY_ONLY`).
   */
  def cache(): this.type = persist()

上述的两个函数慎用,由于直接将数据缓存到内存中,默认情况下内存就是Executor的内存,如果RDD的数据量很大,就会出现OOM内存溢出,程序GameOver。往往缓存数据时,使用如下函数,指定缓存级别:

def persist(newLevel: StorageLevel): this.type

2)、缓存级别
将RDD数据缓存时,可以设置级别,要么存储到内存,要么存储到磁盘,要么存储内存和磁盘(当内存不足存储磁盘),要么是系统内存中等等。

  // 不缓存
  val NONE = new StorageLevel(false, false, false, false)

  // 缓存数据到磁盘
  val DISK_ONLY = new StorageLevel(true, false, false, false)
  val DISK_ONLY_2 = new StorageLevel(true, false, false, false, 2) // 副本数

  //缓存数据到内存(Executor中内存)
  val MEMORY_ONLY = new StorageLevel(false, true, false, true)
  val MEMORY_ONLY_2 = new StorageLevel(false, true, false, true, 2)
  // 是否将数据序列化以后存储内存中
  val MEMORY_ONLY_SER = new StorageLevel(false, true, false, false)
  val MEMORY_ONLY_SER_2 = new StorageLevel(false, true, false, false, 2)

  // 缓存数据到内存和磁盘,当内存不足就缓存到磁盘,使用最多
  val MEMORY_AND_DISK = new StorageLevel(true, true, false, true)
  val MEMORY_AND_DISK_2 = new StorageLevel(true, true, false, true, 2)
  val MEMORY_AND_DISK_SER = new StorageLevel(true, true, false, false)
  val MEMORY_AND_DISK_SER_2 = new StorageLevel(true, true, false, false, 2)

  // 缓存数据到系统内存中
  val OFF_HEAP = new StorageLevel(true, true, true, false, 1)

3)、何时缓存数据
什么时候将RDD数据缓存呢????

1、当某个RDD被使用多次,大于等于2次以上

2、当某个RDD来之不易,使用不止一次,建议缓存

在后续Spark高级课程中(机器学习和图形计算),常常将RDD数据缓存。

4)、释放数据
当某个缓存的RDD数据不再被使用的时候,需要释放资源。

def unpersist(blocking: Boolean = true): this.type

4、聚合函数
在大数据分析中,聚合函数非常重要,在实际项目业务需求中,往往需要按照某些条件分组”聚合“。

RDD中聚合函数
在RDD中提供类似列表List中聚合函数reduce和fold,查看如下:

def reduce(f: (T, T) => T): T 

// 可以初始化聚合中间临时变量的值
def fold(zeroValue: T)(op: (T, T) => T): T

在这里插入图片描述
查看RDD中高级聚合函数aggregate,函数声明如下:

def aggregate[U: ClassTag]
(zeroValue: U)  // 聚合中间临时变量初始值,先确定聚合中间临时变量的类型
(
    // 分区内聚合函数,每个分区内数据如何聚合
    seqOp: (U, T) => U, 
    // 分区间聚合函数,每个分区聚合的结果如何聚合
    combOp: (U, U) => U
): U 

5、关联函数
类似SQL中两张表的Join操作,需要每张表中有一个字段时关联的。

1、emp,雇员表
	empno int,
    ename string,
    job string,
    mgr int,
    hiredate string,
    sal double,
    comm double,
    deptno int    部门编号
2、dpet,部门表
	deptno int,   部门编号
    dname string,
    loc string
    
如果关联上述两张表的话,使用deptno部门编号关联,对应到RDD中,要求RDD的数据类型必须是KeyValue,按照Key作为关联字段,进行关联分析。

两张表的关联:
	等值jion(内连接)
	左连接leftJoin
	右连接rightJoin
	全连接fullJoin
在SparkCore中提供针对RDD[(Key, Value)]数据提供工具类==PairRDDFunctions==,里面很多函数,方便操作数据。

RDD中提供关联函数:

class PairRDDFunctions[K, V](self: RDD[(K, V)])

def join[W](other: RDD[(K, W)]): RDD[(K, (V, W))]

参考代码:

import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * Spark中RDD的关联操作
  */
object SparkJoinFunc {

	def main(args: Array[String]): Unit = {

		// TODO: 构建SparkContext上下文实例对象
		val sc: SparkContext = {
			// a. 创建SparkConf对象,设置应用配置信息
			val sparkConf = new SparkConf()
				.setMaster("local[2]")
				.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
			// b. 创建SparkContext, 有就获取,没有就创建,建议使用
			val context = SparkContext.getOrCreate(sparkConf)
			// c. 返回对象
			context
		}
		sc.setLogLevel("WARN")

		// 模拟数据集
		val empRDD: RDD[(Int, String)] = sc.parallelize(
			Seq((1001, "zhangsan"), (1001, "lisi"), (1002, "wangwu"), (1002, "zhangliu"))
		)
		val deptRDD: RDD[(Int, String)] = sc.parallelize(
			Seq((1001, "sales"), (1002, "tech"))
		)

		val joinRDD: RDD[(Int, (String, String))] = empRDD.join(deptRDD)

		joinRDD.foreach{case (deptno, (ename, dname)) =>
			println(s"deptno = $deptno, ename = $ename, dname = $dname")
		}


		// 应用结束,关闭资源
		sc.stop()
	}

}

五、Spark 案例

网站点击流日志分析,统计PV和UV

样本数据:
60.208.6.156 - - [18/Sep/2013:06:49:48 +0000]   - 5    
 "GET /wp-content/uploads/2013/07/rcassandra.png HTTP/1.0" 200 185524  - 5 
 "http://cos.name/category/software/packages/"  - 1 
 "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36"

如何获取字段的值:
	怎么从日志文件中获取呢???
	1)、分割获取
		企业不会这么干
	2)、编写正则匹配获取对应字段的值
		Regex

需求:PV、UV、TopK Refer(外链)

分析:
	1. pv   - url 第7个字段
		网站页面的访问量,url 不能为null
	2. uv   - ip  第1个字段
		独立访客数,在网站收集数据的时候,通常特定字段,标识用户的唯一性,此处使用ip地址
	3. TopKey Refer  - referUrl  第11个字段
		统计最多的外链Refer,前20个

参考代码如下:

import org.apache.spark.rdd.RDD
import org.apache.spark.storage.StorageLevel
import org.apache.spark.{SparkConf, SparkContext}

/** 分析:
  *1. pv   - url
  * 网站页面的访问量,url 不能为null
  *2. uv   - ip
  * 独立访客数,在网站收集数据的时候,通常特定字段,标识用户的唯一性,此处使用ip地址
  *3. TopKey Refer  - referUrl
  * 统计最多的外链Refer,前20个
  */
object SparkAccessAnalysis {

	def main(args: Array[String]): Unit = {

		// 构建SparkContext上下文实例对象
		val sc: SparkContext = {
			// a. 创建SparkConf对象,设置应用配置信息
			val sparkConf = new SparkConf()
				.setMaster("local[2]")
				.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
			// b. 创建SparkContext, 有就获取,没有就创建,建议使用
			val context = SparkContext.getOrCreate(sparkConf)
			// c. 返回对象
			context
		}
		sc.setLogLevel("WARN")


		// TODO: 从本地文件系统读取日志数据
		val accessLogsRDD: RDD[String] = sc.textFile("datas/logs/access.log", minPartitions = 2)


		// TODO: 过滤、提取字段(数据ETL)
		val etlRDD: RDD[(String, String, String)] = accessLogsRDD
			// 过滤不合格的数据
	    	.filter(log => null != log && log.trim.split("\\s").length >= 11)
	    	// 对每条日志数据提取ip、url和referUrl
	    	.mapPartitions{iter =>
				iter.map{log =>
					// 按照空格分割单词
					val arr: Array[String] = log.trim.split("\\s")
					// 使用三元组返回(ip, url, refer)
					(arr(0), arr(6), arr(10))
				}
			}

		// 由于后续使用ETL的RDD多次,将数据缓存
		etlRDD.persist(StorageLevel.MEMORY_AND_DISK)


		// TODO: 业务一、pv统计
		val pvTotal: Long = etlRDD
			// 提取出url字段
			.map(tuple => tuple._2)
			// 过滤url为空
			.filter(url => null != url && url.trim.length > 0)
	    	.count()
		println(s"pv = $pvTotal")



		// TODO: 业务二、uv统计
		val uvTotal: Long = etlRDD
	    	.map(tuple => tuple._1) // 提取字段
	    	.filter(ip => null != ip && ip.trim.length > 0) // 过滤
	    	// 去重函数
	    	.distinct()
	    	.count()
		println(s"uv = $uvTotal")


		// TODO: 业务三、TopKey Refer  -> 词频统计WordCount变形
		val topReferUrl: Array[(String, Int)] = etlRDD
	    	.map(tuple => (tuple._3, 1))  // 提取字段,表示一次
	    	.filter(pair => null != pair._1 && pair._1.trim.length > 0) // 过滤数据
	    	// 按照referUrl分组聚合
	    	.reduceByKey((tmp, item) => tmp + item)
			// 按照次数降序排序,使用sortBy
	    	.sortBy(tuple => tuple._2, ascending = false)
			// 获取前20个Refer Url
	    	.take(20)
		topReferUrl.foreach(println)


		// 数据不再使用,释放资源
		etlRDD.unpersist()

		// 应用结束,关闭资源
		sc.stop()
	}

}

六、数据源

1)、保存数据至MySQL

将前面词频统计WordCount 结果保存到MySQL表中,表的创建语句如下:

CREATE TABLE `tb_wordcount` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `word` varchar(255) NOT NULL,
  `count` bigint(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM AUTO_INCREMENT=7 DEFAULT CHARSET=latin1;

参考代码如下

import java.sql.{Connection, DriverManager, PreparedStatement}

import org.apache.spark.{SparkConf, SparkContext}
import org.apache.spark.rdd.RDD

/**
  * 将词频统计结果保存到MySQL表中
  */
object SparkWriteMySQL {

	def main(args: Array[String]): Unit = {

		// TODO: 构建SparkContext上下文实例对象
		val sc: SparkContext = {
			// a. 创建SparkConf对象,设置应用配置信息
			val sparkConf = new SparkConf()
				.setMaster("local[2]")
				.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
			// b. 创建SparkContext, 有就获取,没有就创建,建议使用
			val context = SparkContext.getOrCreate(sparkConf)
			// c. 返回对象
			context
		}
		sc.setLogLevel("WARN")


		// TODO: 读取本地文件系统文本文件数据
		val datasRDD: RDD[String] = sc.textFile("datas/wordcount/input/wordcount.data", minPartitions = 2)

		// 词频统计
		val resultRDD: RDD[(String, Int)] = datasRDD
			// 数据分析,考虑过滤脏数据
			.filter(line => null != line && line.trim.length > 0)
			// TODO: 分割单词,注意去除左右空格
			.flatMap(line => line.trim.split("\\s+"))
			// 转换为二元组,表示单词出现一次
			.mapPartitions{iter =>
				iter.map(word => (word, 1))
			}
			// 分组聚合,按照Key单词
			.reduceByKey((tmp, item) => tmp + item)


		// 输出结果RDD
		resultRDD
			// 对结果RDD保存到外部存储系统时,考虑降低RDD分区数目
	    	.coalesce(1)
			// 对分区数据操作
	    	.foreachPartition{iter =>
				// val xx: Iterator[(String, Int)] = iter
				saveToMySQL(iter)
			}

		// 应用结束,关闭资源
		sc.stop()


	}


	/**
	  * 将每个分区中的数据保存到MySQL表中
	  *
	  * @param datas 迭代器,封装RDD中每个分区的数据
	  */
	def saveToMySQL(datas: Iterator[(String, Int)]): Unit = {

		// a. 加载驱动类
		Class.forName("com.mysql.jdbc.Driver")

		// 声明变量
		var conn: Connection = null
		var pstmt: PreparedStatement = null

		try{
			// b. 获取连接
			conn = DriverManager.getConnection(
				"jdbc:mysql://bigdata-cdh01.itcast.cn:3306/", "root", "123456"
			)

			// c. 获取PreparedStatement对象
			val insertSql = "INSERT INTO test.tb_wordcount (word, count) VALUES(?, ?)"
			pstmt = conn.prepareStatement(insertSql)

			// d. 将分区中数据插入到表中,批量插入
			datas.foreach{case (word, count) =>
				pstmt.setString(1, word)
				pstmt.setLong(2, count.toLong)

				// 加入批次
				pstmt.addBatch()
			}

			// TODO: 批量插入
			pstmt.executeBatch()
		}catch {
			case e: Exception => e.printStackTrace()
		}finally {
			if(null != pstmt) pstmt.close()
			if(null != conn) conn.close()
		}
	}

}

2)、从MySQL表读取数据
使用SparkCore中提供的JdbcRDD

class JdbcRDD[T: ClassTag](
    // 表示SparkContext实例对象
    sc: SparkContext,
    // 连接数据库Connection
    getConnection: () => Connection,
    // 查询SQL语句
    sql: String,
    // 下限
    lowerBound: Long,
    // 上限
    upperBound: Long,
    // 封装数据RDD的分区数目
    numPartitions: Int,
    // 表示读取出MySQL数据库表中每条数据如何处理,数据封装在ResultSet
    mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _
) extends RDD[T](sc, Nil)

参考代码如下

import java.sql.{DriverManager, ResultSet}

import org.apache.spark.rdd.JdbcRDD
import org.apache.spark.{SparkConf, SparkContext}

/**
  * 从MySQL数据库表中读取数据
  */
object SparkReadMySQL {

	def main(args: Array[String]): Unit = {

		// TODO: 构建SparkContext上下文实例对象
		val sc: SparkContext = {
			// a. 创建SparkConf对象,设置应用配置信息
			val sparkConf = new SparkConf()
				.setMaster("local[2]")
				.setAppName(this.getClass.getSimpleName.stripSuffix("$"))
			// b. 创建SparkContext, 有就获取,没有就创建,建议使用
			val context = SparkContext.getOrCreate(sparkConf)
			// c. 返回对象
			context
		}
		sc.setLogLevel("WARN")

		// TODO: 读取MySQL数据库中db_orders.so表的数据
		/*
			class JdbcRDD[T: ClassTag](
				// 表示SparkContext实例对象
				sc: SparkContext,
				// 连接数据库Connection
				getConnection: () => Connection,
				// 查询SQL语句
				sql: String,
				// 下限
				lowerBound: Long,
				// 上限
				upperBound: Long,
				// 封装数据RDD的分区数目
				numPartitions: Int,
				// 表示读取出MySQL数据库表中每条数据如何处理,数据封装在ResultSet
				mapRow: (ResultSet) => T = JdbcRDD.resultSetToObjectArray _
			)
					 */
		val sosRDD: JdbcRDD[(Long, Double)] = new JdbcRDD[(Long, Double)](
			sc, //
			() => {
				// a. 加载驱动类
				Class.forName("com.mysql.jdbc.Driver")
				// b. 获取连接
				val conn = DriverManager.getConnection(
					"jdbc:mysql://bigdata-cdh01.itcast.cn:3306/", "root", "123456"
				)
				// c. 返回连接
				conn
			}, //
			"select user_id, order_amt from db_orders.so where ? <= order_id and order_id <= ?", //
			314296308301917L, //
			314296313681142L, //
			2, //
			(rs: ResultSet) => {
				// 获取user_id
				val userId = rs.getLong("user_id")
			    // 获取order_amt
				val orderAmt = rs.getDouble("order_amt")
				// 返回二元组
				(userId, orderAmt)
			}
		)

		println(s"count = ${sosRDD.count()}")
		sosRDD.foreach(println)

		// 应用结束,关闭资源
		sc.stop()
	}

}

附录一、创建Maven模块

1)、Maven 工程结构

spark
+---src
|   +---main
|   |   +---java
|   |   +---resources
|   |   +---scala
|   +---test
|   |   +---java
|   |    +---resources
|   |    +---scala
|---core/pom.xml

2)、POM 文件内容
Maven 工程POM文件中内容(依赖包)

<!-- 指定仓库位置,依次为aliyun、cloudera和jboss仓库 -->
<repositories>
    <repository>
        <id>aliyun</id>
        <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
    </repository>
    <repository>
        <id>cloudera</id>
        <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
    </repository>
    <repository>
        <id>jboss</id>
        <url>http://repository.jboss.com/nexus/content/groups/public</url>
    </repository>
</repositories>

<properties>
    <scala.version>2.11.8</scala.version>
    <scala.binary.version>2.11</scala.binary.version>
    <spark.version>2.2.0</spark.version>
    <hadoop.version>2.6.0-cdh5.14.0</hadoop.version>
    <hbase.version>1.2.0-cdh5.14.0</hbase.version>
    <mysql.version>5.1.38</mysql.version>
</properties>

<dependencies>

    <!-- 依赖Scala语言 -->
    <dependency>
        <groupId>org.scala-lang</groupId>
        <artifactId>scala-library</artifactId>
        <version>${scala.version}</version>
    </dependency>

    <!-- Spark Core 依赖 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-core_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
    </dependency>

    <!-- Spark SQL 依赖 -->
    <dependency>
        <groupId>org.apache.spark</groupId>
        <artifactId>spark-sql_${scala.binary.version}</artifactId>
        <version>${spark.version}</version>
    </dependency>

    <!-- Hadoop Client 依赖 -->
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>${hadoop.version}</version>
    </dependency>

    <!-- HBase Client 依赖 -->
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-server</artifactId>
        <version>${hbase.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-hadoop2-compat</artifactId>
        <version>${hbase.version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hbase</groupId>
        <artifactId>hbase-client</artifactId>
        <version>${hbase.version}</version>
    </dependency>

    <!-- MySQL Client 依赖 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>${mysql.version}</version>
    </dependency>

</dependencies>

<build>
    <outputDirectory>target/classes</outputDirectory>
    <testOutputDirectory>target/test-classes</testOutputDirectory>
    <resources>
        <resource>
            <directory>${project.basedir}/src/main/resources</directory>
        </resource>
    </resources>
    <!-- Maven 编译的插件 -->
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.0</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
                <encoding>UTF-8</encoding>
            </configuration>
        </plugin>
        <plugin>
            <groupId>net.alchim31.maven</groupId>
            <artifactId>scala-maven-plugin</artifactId>
            <version>3.2.0</version>
            <executions>
                <execution>
                    <goals>
                        <goal>compile</goal>
                        <goal>testCompile</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值