文章目录
Spark案例实操
1. 案例描述
在之前的学习中,我们已经学习了 Spark 的基础编程方式,接下来,我们看看在实际的工作中如何使用这些 API 实现具体的需求。这些需求是电商网站的真实需求,所以在实现功能前,咱们必须先将数据准备好。
上面的数据图是从数据文件中截取的一部分内容,表示为电商网站的用户行为数据,主要包含用户的 4 种行为:搜索,点击,下单,支付。数据规则如下:
- 数据文件中每行数据采用
下划线
分隔数据; - 每一行数据表示用户的一次行为,这个行为只能是 4 种行为的一种;
- 如果搜索关键字为 null,表示数据不是搜索数据;
- 如果点击的品类 ID 和产品 ID 为-1,表示数据不是点击数据;
- 针对于下单行为,一次可以下单多个商品,所以品类 ID 和产品 ID 可以是多个,id 之间采用逗号分隔,如果本次不是下单行为,则数据采用 null 表示;
- 支付行为和下单行为类似。
详细字段说明:
编号 | 字段名称 | 字段类型 | 字段含义 |
---|---|---|---|
1 | date | String | 用户点击行为的日期 |
2 | user_id | Long | 用户的 ID |
3 | session_id | String | Session 的 ID |
4 | page_id | Long | 某个页面的 ID |
5 | action_time | String | 动作的时间点 |
6 | search_keyword | String | 用户搜索的关键词 |
7 | click_category_id | Long | 某一个商品品类的 ID |
8 | click_product_id | Long | 某一个商品的 ID |
9 | order_category_ids | String | 一次订单中所有品类的 ID 集合 |
10 | order_product_ids | String | 一次订单中所有商品的 ID 集合 |
11 | pay_category_ids | String | 一次支付中所有品类的 ID 集合 |
12 | pay_product_ids | String | 一次支付中所有商品的 ID 集合 |
13 | city_id | Long | 城市 id |
样例类:
//用户访问动作表
case class UserVisitAction(
date: String,//用户点击行为的日期
user_id: Long,// 用 户 的 ID
session_id: String,//Session 的 ID
page_id: Long,// 某 个 页 面 的 ID
action_time: String,//动作的时间点
search_keyword: String,//用户搜索的关键词
click_category_id: Long,// 某 一 个 商 品 品 类 的 ID
click_product_id: Long,// 某 一 个 商 品 的 ID
order_category_ids: String,//一次订单中所有品类的 ID 集合
order_product_ids: String,//一次订单中所有商品的 ID 集合
pay_category_ids: String,//一次支付中所有品类的 ID 集合
pay_product_ids: String,//一次支付中所有商品的 ID 集合
city_id: Long//城市 id
)
2. 需求一
品类是指产品的分类,大型电商网站品类分多级,咱们的项目中品类只有一级,不同的公司可能对热门的定义不一样。我们按照每个品类的点击、下单、支付的量来统计热门品类。
鞋 | 点击数 | 下单数 | 支付数 |
---|---|---|---|
衣服 | 点击数 | 下单数 | 支付数 |
电脑 | 点击数 | 下单数 | 支付数 |
例如,综合排名 = 点击数*20%+下单数*30%+支付数*50%
本项目需求优化为:先按照点击数排名,靠前的就排名高;如果点击数相同,再比较下单数;下单数再相同,就比较支付数。
2.1 实现方案一
- 首先
textFile()
读取数据源。- 使用filter过滤出,搜索/点击/订单/支付的数据
- 再使用map()和reduceByKey()得到每种数据的(品类id,搜索次数/点击次数/订单次数/支付次数)RDD
- 使用cogroup()把上面输出的RDD联系在一起,得到(品类id,(Interable点击,Interable订单,Interable支付))RDD
- 使用mapValues()将上述结果RDD,得到(品类id,(点击次数,订单次数,支付次数))
- 最后通过sortBy()和take()拿到最终我们需要的数据。
- 完整代码如下:
import org.apache.spark.{SparkConf, SparkContext}
object Req01HotCategoryTop10_One {
def main(args: Array[String]): Unit = {
// 1、创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("req1")
// 2、创建Spark上下文环境对象(连接对象)
val sc = new SparkContext(sparkConf)
// 3、读取文件数据,RDD[String]是什么?弹性分布式数据集
val rdd = sc.textFile("data/user_visit_action.txt")
// 首先得到搜索的数据,搜索的数据datas(5)不为null
val searchRdd = rdd.filter(line => {
val datas = line.split("_")
datas(5) != "null"
})
val searchCountRdd = searchRdd.map(action => {
val datas = action.split("_")
(datas(5), 1)
}).reduceByKey(_+_)
// 得到点击的数据,点击数据datas(6)不为-1
// datas(6)和datas(7)分别为点击品类ID和产品ID
val clickRdd = rdd.filter(line => {
val datas = line.split("_")
datas(6) != "-1"
})
// 获取各个品类点击的次数(电脑,999)
val clickCountRdd = clickRdd.map(action => {
val datas = action.split("_")
(datas(6), 1)
}).reduceByKey(_+_)
// 得到下单的数据,下单数据datas(8)和datas(9)不为null
val orderRdd = rdd.filter(line => {
val datas = line.split("_")
datas(8) != "null"
})
// 注意这里使用flatMap,这里就体现出map和flatMap的区别了
val orderCountRdd = orderRdd.flatMap(action => {
val datas = action.split("_")
// 获取下单品类ID
val cids = datas(8).split(",")
cids.map(cid => (cid, 1))
}).reduceByKey(_ + _)
// 得到下单的数据,下单数据datas(8)和datas(9)不为null
val payRdd = rdd.filter(line => {
val datas = line.split("_")
datas(10) != "null"
})
val payCountRdd = payRdd.flatMap(action => {
val datas = action.split("_")
// 获取下单品类ID
val cids = datas(10).split(",")
cids.map(cid => (cid, 1))
}).reduceByKey(_ + _)
// 将品类进行排序,并且取前10名
// 点击数量排序,下单数量排序,支付数量排序
// 元组排序:先比较第一个,再比较第二个,再比较第三个,依此类推
// ( 品类ID, ( 点击数量, 下单数量, 支付数量 ) )
//
// cogroup = connect + group
val cogroupRdd = clickCountRdd.cogroup(orderCountRdd, payCountRdd)
val mapValuesRdd = cogroupRdd.mapValues({
case (clickCountIter, orderCountIter, payCountIter) => {
var clickCount = 0
val clickIter = clickCountIter.iterator
if (clickIter.hasNext) {
clickCount = clickIter.next()
}
var orderCount = 0
val orderkIter = orderCountIter.iterator
if (orderkIter.hasNext) {
orderCount = orderkIter.next()
}
var payCount = 0
val payIter = payCountIter.iterator
if (payIter.hasNext) {
payCount = payIter.next()
}
(clickCount,orderCount,payCount)
}
})
val resRdd = mapValuesRdd.sortBy(_._2, false).take(10)
// 7、打印结果
println(s"品类id_点击数_订单数_支付数")
resRdd.foreach(println)
// 8、关闭spark连接
sc.stop()
}
}
2.2 实现方式二
第一张实现方式存在的问题:
- rdd重复使用
- cogroup性能可能较低
准对以上两个问题我们做出以下优化:
- 通过使用
rdd.cache()
来优化rdd
重复使用的问题。- 使用union替换cogroup,然后再使用reduceByKey()进行聚合。
- 其他同方式一,完整代码如下:
object Req02HotCategoryTop10_Two {
def main(args: Array[String]): Unit = {
// 1、创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("req1")
// 2、创建Spark上下文环境对象(连接对象)
val sc = new SparkContext(sparkConf)
// 3、读取文件数据,RDD[String]是什么?弹性分布式数据集
val rdd = sc.textFile("data/user_visit_action.txt")
rdd.cache()
// 得到点击的数据,点击数据datas(6)不为-1
// datas(6)和datas(7)分别为点击品类ID和产品ID
val clickRdd = rdd.filter(line => {
val datas = line.split("_")
datas(6) != "-1"
})
// 获取各个品类点击的次数(电脑,999)
val clickCountRdd = clickRdd.map(action => {
val datas = action.split("_")
(datas(6), 1)
}).reduceByKey(_ + _)
// 得到下单的数据,下单数据datas(8)和datas(9)不为null
val orderRdd = rdd.filter(line => {
val datas = line.split("_")
datas(8) != "null"
})
// 注意这里使用flatMap,这里就体现出map和flatMap的区别了
val orderCountRdd = orderRdd.flatMap(action => {
val datas = action.split("_")
// 获取下单品类ID
val cids = datas(8).split(",")
cids.map(cid => (cid, 1))
}).reduceByKey(_ + _)
// 得到下单的数据,下单数据datas(8)和datas(9)不为null
val payRdd = rdd.filter(line => {
val datas = line.split("_")
datas(10) != "null"
})
val payCountRdd = payRdd.flatMap(action => {
val datas = action.split("_")
// 获取下单品类ID
val cids = datas(10).split(",")
cids.map(cid => (cid, 1))
}).reduceByKey(_ + _)
// TODO 在这里改进
val clickResRdd = clickCountRdd.map {
case (id, clickCount) => {
(id, (clickCount, 0, 0))
}
}
val orderResRdd = orderCountRdd.map {
case (id, orderCount) => {
(id, (0, orderCount, 0))
}
}
val payResRdd = payCountRdd.map {
case (id, payCount) => {
(id, (0, 0, payCount))
}
}
// 使用union并集将上面三个RDD并在一起
val sourceRdd = clickResRdd.union(orderResRdd).union(payResRdd)
val resRdd = sourceRdd.reduceByKey(
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
}
)
val top10Rdd = resRdd.sortBy(_._2, false).take(10)
// 7、打印结果
println(s"品类id_点击数_订单数_支付数")
top10Rdd.foreach(println)
// 8、关闭spark连接
sc.stop()
}
}
2.3 实现方式三
第二种方式存在的问题:
- 存在大量的shuffle操作(reduceByKey)
- reduceByKey 聚合算子,spark会提供优化,缓存
准对上述问题做出以下优化:
- 直接通过flatMap把源数据转成(品类id,(点击次数,订单次数,支付次数)),有零。
- 然后其他步骤同方式二,这样减少了三次
reduceByKey()
- 全部代码如下:
import org.apache.spark.{SparkConf, SparkContext}
object Req03HotCategoryTop10_Three {
def main(args: Array[String]): Unit = {
// 1、创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("req1")
// 2、创建Spark上下文环境对象(连接对象)
val sc = new SparkContext(sparkConf)
// 3、读取文件数据,RDD[String]是什么?弹性分布式数据集
val rdd = sc.textFile("data/user_visit_action.txt")
val sourceRdd = rdd.flatMap(action => {
val datas = action.split("_")
if (datas(6) != "-1") {
List((datas(6), (1, 0, 0)))
} else if (datas(8) != "null") {
val cids = datas(8).split(",")
cids.map(cid => {
(cid, (0, 1, 0))
})
} else if (datas(10) != "null") {
val cids = datas(10).split(",")
cids.map(cid => {
(cid, (0, 0, 1))
})
} else {
Nil
}
})
val resRdd = sourceRdd.reduceByKey(
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
}
)
val top10Rdd = resRdd.sortBy(_._2, false).take(10)
// 7、打印结果
println(s"品类id_点击数_订单数_支付数")
top10Rdd.foreach(println)
// 8、关闭spark连接
sc.stop()
}
}
2.4 实现方式四
方法三的问题:
- 仍然存在shuffle操作
解决方案:
- 使用自定义累加器去解决
- 通过自定义累加器,我们得到(cid,(cid,点击数,下单数,支付数))
- 然后通过
acc.value.map(_._2)
得到(cid,点击数,下单数,支付数)- 然后进行排序,就可以得到我们需要的数据了。
package com.atguigu.sparkstudy.core.req
import org.apache.spark.util.AccumulatorV2
import org.apache.spark.{SparkConf, SparkContext}
import scala.collection.mutable
object Req04HotCategoryTop10_Four {
def main(args: Array[String]): Unit = {
// 1、创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("req1")
// 2、创建Spark上下文环境对象(连接对象)
val sc = new SparkContext(sparkConf)
// 3、读取文件数据,RDD[String]是什么?弹性分布式数据集
val rdd = sc.textFile("data/user_visit_action.txt")
// 4、注册累加器
val acc = new HotCategoryAccumulator
sc.register(acc, "hotCategory")
// 5、将数据转换结构
rdd.foreach {
action => {
val datas = action.split("_")
if (datas(6) != "-1") {
// 点击的场合
acc.add((datas(6), "click"))
} else if (datas(8) != "null") {
// 下单的场合
val ids = datas(8).split(",")
ids.foreach(
id => {
acc.add((id, "order"))
}
)
} else if (datas(10) != "null") {
// 支付的场合
val ids = datas(10).split(",")
ids.foreach(
id => {
acc.add((id, "pay"))
}
)
}
}
}
val accVal = acc.value
val categories = accVal.map(_._2)
// 进行排序
val sort = categories.toList.sortWith(
(left, right) => {
if (left.clickCnt > right.clickCnt) {
true
} else if (left.clickCnt == right.clickCnt) {
if (left.orderCnt > right.orderCnt) {
true
} else if (left.orderCnt == right.orderCnt) {
left.payCnt > right.payCnt
} else {
false
}
} else {
false
}
}
)
sort.take(10).foreach(println)
// 8、关闭spark连接
sc.stop()
}
/**
* 样例类:为模式匹配优化的类,使用case声明
* 1. 构造器中的每一个参数都成为val——除非它被显式地声明为var(不建议这样做)
* 2. 在样例类对应的伴生对象中提供apply方法让你不用new关键字就能构造出相应的对象提供unapply方法让模式匹配可以工作
* 3. 将自动生成toString、equals、hashCode和copy方法(有点类似模板类,直接给生成,供程序员使用)
* 这里我们使用HotCategory样例类来表示(cid,(点击数,下单数,支付数))
*
* @param cid
* @param clickCnt
* @param orderCnt
* @param payCnt
*/
case class HotCategory(cid: String, var clickCnt: Int, var orderCnt: Int, var payCnt: Int)
/**
* 自定义累加器
* 1. 继承AccumulatorV2,定义泛型
* IN : ( 品类ID, 行为类型 )
* OUT : mutable.Map[String, HotCategory]
* 2. 重写方法(6)
*/
class HotCategoryAccumulator extends AccumulatorV2[(String, String), mutable.Map[String, HotCategory]] {
private val hcMap = mutable.Map[String, HotCategory]()
override def isZero: Boolean = {
hcMap.isEmpty
}
override def copy(): AccumulatorV2[(String, String), mutable.Map[String, HotCategory]] = {
new HotCategoryAccumulator()
}
override def reset(): Unit = {
hcMap.clear()
}
/**
* _1 品类id
* _2 操作类型
*
* @param v
*/
override def add(v: (String, String)): Unit = {
val cid = v._1
val actionType = v._2
// 根据品类id从集合中获取category,如果有则返回,如果没有则返回HotCategory(cid, 0, 0, 0)
val category = hcMap.getOrElse(cid, HotCategory(cid, 0, 0, 0))
if (actionType == "click") {
category.clickCnt += 1
} else if (actionType == "order") {
category.orderCnt += 1
} else if (actionType == "pay") {
category.payCnt += 1
}
// 更新map
hcMap.update(cid, category)
}
override def merge(other: AccumulatorV2[(String, String), mutable.Map[String, HotCategory]]): Unit = {
val map1 = this.hcMap
val map2 = other.value
map2.foreach {
case (cid, hc) => {
val category: HotCategory = map1.getOrElse(cid, HotCategory(cid, 0, 0, 0))
category.clickCnt += hc.clickCnt
category.orderCnt += hc.orderCnt
category.payCnt += hc.payCnt
map1.update(cid, category)
}
}
}
override def value: mutable.Map[String, HotCategory] = hcMap
}
3. 需求二
Top10 热门品类中每个品类的 Top10 活跃 Session 统计
在需求一的基础上,增加每个品类用户 session 的点击统计
解决思路:
- 根据需求一的结果获取Top10Cids
- 根据Top10Cids去过滤原始数据(只要点击数据并cid在Top10Cids中)
- 先使用map和reduceByKey得到((cid,sessionId),count)
- 再使用map转换结构(cid,(sessionId,count))
- 通过groupByKey进行分组
- 最后通过mapValues根据点击量排序取前十
- 完整代码如下:
package com.atguigu.sparkstudy.core.req
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Req05HotCategoryTop10Session {
def main(args: Array[String]): Unit = {
// 1、创建Spark运行配置对象
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("req1")
// 2、创建Spark上下文环境对象(连接对象)
val sc = new SparkContext(sparkConf)
// 3、读取文件数据,RDD[String]是什么?弹性分布式数据集
val rdd = sc.textFile("data/user_visit_action.txt")
// 根据需求一获得的Top10的cids
val top10Cids = top10Category(rdd)
// 根据Top10的cids过滤,点击类数据且是Top10
val filterRdd = rdd.filter {
action => {
val datas = action.split("_")
if (datas(6) != "-1") {
top10Cids.contains(datas(6))
} else {
false
}
}
}
// 根据(品类id,session)进行聚合
val reduceByKeyRdd = filterRdd.map {
action => {
val datas = action.split("_")
// ((品类id,session),1)
((datas(6), datas(2)), 1)
}
}.reduceByKey(_ + _)
// 将统计的结果进行结构的转换
// (( 品类ID,sessionId ),sum) => ( 品类ID,(sessionId, sum) )
val mapRdd = reduceByKeyRdd.map {
// 改变数据的结构
case ((cid, sessionId), count) => {
(cid, (sessionId, count))
}
}
// 根据cid进行分组
val groupByKeyRdd = mapRdd.groupByKey()
// 将分组后的数据进行点击量的排序,取前10名
val resRdd = groupByKeyRdd.mapValues {
iter => {
iter.toList.sortBy(_._2)(Ordering.Int.reverse).take(10)
}
}
// 7、打印结果
resRdd.foreach(println)
// 8、关闭spark连接
sc.stop()
}
/**
* 获取top10的cid
*
* @param rdd
*/
def top10Category(rdd: RDD[String]): Array[String] = {
val sourceRdd = rdd.flatMap(action => {
val datas = action.split("_")
if (datas(6) != "-1") {
List((datas(6), (1, 0, 0)))
} else if (datas(8) != "null") {
val cids = datas(8).split(",")
cids.map(cid => {
(cid, (0, 1, 0))
})
} else if (datas(10) != "null") {
val cids = datas(10).split(",")
cids.map(cid => {
(cid, (0, 0, 1))
})
} else {
Nil
}
})
val resRdd = sourceRdd.reduceByKey(
(t1, t2) => {
(t1._1 + t2._1, t1._2 + t2._2, t1._3 + t2._3)
}
)
val top10Rdd = resRdd.sortBy(_._2, false).take(10)
top10Rdd.map(_._1)
}
}
4. 需求三
3.1 需求说明
页面单跳转化率
- 计算页面单跳转化率,什么是页面单跳转换率,比如一个用户在一次 Session 过程中访问的页面路径 3,5,7,9,10,21,那么页面 3 跳到页面 5 叫一次单跳,7-9 也叫一次单跳, 那么单跳转化率就是要统计页面点击的概率。
- 比如:计算 3-5 的单跳转化率,先获取符合条件的 Session 对于页面 3 的访问次数(PV) 为 A,然后获取符合条件的 Session 中访问了页面 3 又紧接着访问了页面 5 的次数为 B,那么 B/A 就是 3-5 的页面单跳转化率。
-
统计页面单跳转化率意义
- 产品经理和运营总监,可以根据这个指标,去尝试分析,整个网站,产品,各个页面的表现怎么样,是不是需要去优化产品的布局;吸引用户最终可以进入最后的支付页面。
- 数据分析师,可以此数据做更深一步的计算和分析。
- 企业管理层,可以看到整个公司的网站,各个页面的之间的跳转的表现如何,可以适当调整公司的经营战略或策略。
3.2 图解分析
3.3 实现方式
实现思路:根据图解我们需要分别计算分子和分母
package com.atguigu.sparkstudy.core.req
import org.apache.spark.rdd.RDD
import org.apache.spark.{SparkConf, SparkContext}
object Req06PageflowAnalysis {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setMaster("local[*]").setAppName("Pageflow")
val sc = new SparkContext(sparkConf)
// 读取文件
val rdd = sc.textFile("data/user_visit_action.txt")
val actionRDD = rdd.map(
action => {
val datas = action.split("_")
UserVisitAction(
datas(0),
datas(1).toLong,
datas(2),
datas(3).toLong,
datas(4),
datas(5),
datas(6).toLong,
datas(7).toLong,
datas(8),
datas(9),
datas(10),
datas(11),
datas(12).toLong
)
}
)
// 根据图解我们需要分别计算分子和分母
// TODO 计算分母
val ids = List[Long](1, 2, 3, 4, 5, 6, 7)
// List((1,2), (2,3), (3,4), (4,5), (5,6), (6,7))
val okflowIds = ids.zip(ids.tail)
val tempRdd = actionRDD.filter(
action => {
ids.contains(action.page_id)
}
).map(
action => {
// 先获取想获取页面的(page_id,1)
(action.page_id, 1L)
}
// 通过reduceByKey进行聚合(page_id,sum)
).reduceByKey(_ + _)
val denominatorRDD = tempRdd.collect().toMap
// TODO 计算分子
// 根据sessionId进行分组
val groupRDD = actionRDD.groupBy(_.session_id)
/**
* (1b338bbe-2ed4-43da-984f-1160ac311e8d,
* CompactBuffer(
* UserVisitAction(2019-07-23,99,1b338bbe-2ed4-43da-984f-1160ac311e8d,3,2019-07-23 03:09:03,null,2,59,null,null,null,null,24),
* UserVisitAction(2019-07-23,99,1b338bbe-2ed4-43da-984f-1160ac311e8d,16,2019-07-23 03:09:11,苹果,-1,-1,null,null,null,null,14)
* )
* )
*/
val mvRDD = groupRDD.mapValues(
iter => {
// 分组后,根据访问时间进行排序(升序)
val sortList = iter.toList.sortBy(_.action_time)
val flowIds = sortList.map(_.page_id)
val pageflowIds = flowIds.zip(flowIds.tail)
// 将不合法的页面跳转进行过滤
pageflowIds.filter(
t => {
okflowIds.contains(t)
}
).map(
t => {
(t, 1)
}
)
}
)
val flatRDD: RDD[((Long, Long), Int)] = mvRDD.map(_._2).flatMap(list=>list)
val dataRDD = flatRDD.reduceByKey(_ + _)
// TODO 计算单跳转换率 分子除以分母
dataRDD.foreach{
case ( (pageid1, pageid2), sum ) => {
val lon: Long = denominatorRDD.getOrElse(pageid1, 0L)
println(s"页面${pageid1}跳转到页面${pageid2}单跳转换率为:" + ( sum.toDouble/lon ))
}
}
}
// TODO 用户访问动作表
case class UserVisitAction(
date: String, //用户点击行为的日期
user_id: Long, // 用 户 的 ID
session_id: String, //Session 的 ID
page_id: Long, // 某 个 页 面 的 ID
action_time: String, //动作的时间点
search_keyword: String, //用户搜索的关键词
click_category_id: Long, // 某 一 个 商 品 品 类 的 ID
click_product_id: Long, // 某 一 个 商 品 的 ID
order_category_ids: String, //一次订单中所有品类的 ID 集合
order_product_ids: String, //一次订单中所有商品的 ID 集合
pay_category_ids: String, //一次支付中所有品类的 ID 集合
pay_product_ids: String, //一次支付中所有商品的 ID 集合
city_id: Long //城市 id
)
}
5. 工程化
MVC三层架构
三层架构
WordCountApplication
object WordCountApplication extends App with TApplication{
start(){
val controller = new WordCountController()
controller.dispatch()
}
}
WordCountController
/**
* 控制层
*/
class WordCountController extends TController{
private val wordCountService = new WordCountService()
def dispatch(): Unit ={
val wordCountArray = wordCountService.dataAnalysis()
wordCountArray.foreach(println)
}
}
WordCountService
/**
* 服务层
*/
class WordCountService extends TService{
private val wordCountDao = new WordCountDao()
def dataAnalysis() = {
val fileRDD: RDD[String] = wordCountDao.readFile("data/word.txt")
val words: RDD[String] = fileRDD.flatMap(_.split(" "))
val wordGroup: RDD[(String, Iterable[String])] = words.groupBy(word => word)
val wordToCount = wordGroup.map {
case (word, list) => {
(word, list.size)
}
}
val resArray: Array[(String, Int)] = wordToCount.collect()
resArray
}
}
WordCountDao
/**
* 持久层
*/
class WordCountDao extends TDao{
}
common
TApplication
trait TApplication {
def start(master:String ="local[*]", app:String = "Application")( op : => Unit ): Unit = {
val sparkConf = new SparkConf().setMaster(master).setAppName(app)
val sc = new SparkContext(sparkConf)
EnvUtil.put(sc)
try {
op
} catch {
case ex => println(ex.getMessage)
}
sc.stop()
EnvUtil.clear()
}
}
TController
trait TController {
def dispatch(): Unit
}
TService
trait TService {
def dataAnalysis():Any
}
TDao
trait TDao {
def readFile(path:String) = {
EnvUtil.take().textFile(path)
}
}
ThreadLocal的妙用-EnvUtil
package com.atguigu.sparkstudy.core.framework.utils
import org.apache.spark.SparkContext
object EnvUtil {
private val scLocal = new ThreadLocal[SparkContext]()
def put( sc : SparkContext ): Unit = {
scLocal.set(sc)
}
def take(): SparkContext = {
scLocal.get()
}
def clear(): Unit = {
scLocal.remove()
}
}