十六、并发编程模型 Akka
16.1 Akka 介绍
1) Akka
是
JAVA
虚拟机
JVM
平台上构建高并发、分布式和容错应用的工具包和运行时,你可以
理解成
Akka
是编写并发程序的框架
。
2) Akka
用
Scala
语言写成,同时提供了
Scala
和
JAVA
的开发接口。
3) Akka
主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、
锁和资源竞争等细节。
16.2 Actor 模型用于解决什么问题
1)
处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同
一个数据进行修改,若不加同步条件,势必会造成数据污染。但是当我们对关键代码加入同步条件
synchronized
后,实际上大并发就会阻塞在这段代码,对程序效率有很大影响。
2)
若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。
3) Actor
模型的出现解决了这个问题,简化并发编程,提升程序性能。 你可以这里理解:
Actor
模
型是一种处理并发问题的解决方案,很牛
!
16.3 Akka 中 Actor 模型
16.3.1 Actor 模型及其说明
1) Akka
处理并发的方法基于
Actor
模型。
(
示意图
)
2)
在基于
Actor
的系统里,所有的事物都是
Actor
,就好像在面向对象设计里面所有的事物都是
对象一样。
3) Actor
模型是作为一个并发模型设计和架构的。
Actor
与
Actor
之间只能通过消息通信,如图
的信封
4) Actor
与
Actor
之间只能用消息进行通信,当一个
Actor
给另外一个
Actor
发消息,消息是有
顺序的
(
消息队列
)
,只需要将消息投寄的相应的邮箱即可。
5)
怎么处理消息是由接收消息的
Actor
决定的,发送消息
Actor
可以等待回复,也可以异步处理
【
ajax
】
6) ActorSystem
的职责是负责创建并管理其创建的
Actor
,
ActorSystem
是单例的
(
可以
ActorSystem
是一个工厂,专门创建
Actor)
,一个
JVM
进程中有一个即可,而
Acotr
是可以有多个的。
7) Actor
模型是对并发模型进行了更高的抽象。
8) Actor
模型是
异步、非阻塞、高性能
的事件驱动编程模型。
[
案例
:
说明 什么是异步、非阻塞
,
最
经典的案例就是
ajax
异步请求处理
]
![](https://i-blog.csdnimg.cn/blog_migrate/344283b204bfc37dc85d964326c56bf4.jpeg)
9) Actor
模型是轻量级事件处理(
1GB
内存可容纳百万级别个
Actor
),因此处理大并发性能高
.
16.4 Actor 模型工作机制说明
说明了 Actor 模型的工作机制(对应上图)
1) ActorySystem
创建
Actor
2) ActorRef:
可以理解成是
Actor
的代理或者引用。消息是通过
ActorRef
来发送
,
而不能通过
Actor
发
送消息,通过哪个
ActorRef
发消息,就表示把该消息发给哪个
Actor
3)
消息发送到
Dispatcher Message (
消息分发器
)
,它得到消息后,会将消息进行分发到对应的
MailBox
。
(
注
: Dispatcher Message
可以理解成是一个线程池
, MailBox
可以理解成是消息队列,可以缓冲多个消息,遵守 FIFO)
4) Actor 可以通过 receive
方法来获取消息,然后进行处理。
Actor 模型的消息机制(对应上图)
1)
每一个消息就是一个
Message
对象。
Message
继承了
Runable
, 因为
Message
就是线程类。
2)
从
Actor
模型工作机制看上去很麻烦,但是程序员编程时只需要编写
Actor
就可以了,其它的交
给
Actor
模型完成即可。
3) A Actor
要给
B Actor
发送消息,那么
A Actor
要先拿到
(
也称为持有
) B Actor
的 代理对象
ActorRef
才能发送消息
16.5 Actor 模型快速入门
16.5.1 应用实例需求
1)
编写一个
Actor,
比如
SayHelloActor
2) SayHelloActor
可以给自己发送消息
,
如图
3)
要求使用
Maven
的方式来构建项目
,
这样可以很好的解决项目开发包的依赖关系。
[scala
和
akka]
//说明
//1. 当我们继承Actor后,就是一个Actor,核心方法receive 方法重写
class SayHelloActor extends Actor{
//说明
//1. receive方法,会被该Actor的MailBox(实现了Runnable接口)调用
//2. 当该Actor的MailBox 接收到消息,就会调用 receive
//3. type Receive = PartialFunction[Any, Unit]
override def receive:Receive = {
case "hello" => println("收到hello, 回应hello too:)")
case "ok" => println("收到ok, 回应ok too:)")
case "exit" => {
println("接收到exit指令,退出系统")
context.stop(self) //停止actoref
context.system.terminate()//退出actorsystem
}
case _ => println("匹配不到")
}
}
object SayHelloActorDemo {
//1. 先创建一个ActorSystem, 专门用于创建Actor
private val actoryFactory = ActorSystem("actoryFactory")
//2. 创建一个Actor的同时,返回Actor的ActorRef
// 说明
//(1) Props[SayHelloActor] 创建了一个 SayHelloActor实例,使用反射
//(2) "sayHelloActor" 给actor取名
//(3) sayHelloActorRef: ActorRef 就是 Props[SayHelloActor] 的ActorRef
//(4) 创建的SayHelloActor 实例被ActorSystme接管
private val sayHelloActorRef: ActorRef = actoryFactory.actorOf(Props[SayHelloActor],"sayHelloActor")
def main(args: Array[String]): Unit = {
//给SayHelloActor 发消息(邮箱)
sayHelloActorRef ! "hello"
sayHelloActorRef ! "ok"
sayHelloActorRef ! "ok~"
//研究异步如何退出ActorSystem
sayHelloActorRef ! "exit"
// 收到hello, 回应hello too:)
// 收到ok, 回应ok too:)
// 匹配不到
// 接收到exit指令,退出系统
}
}
16.5.3 代码的小结和示意图
小结和说明
当程序执行
aActorRef = actorFactory.actorOf(Props[AActor], "aActor")
,会完成如下任务
[
这是非常重要的方法]
1. actorFactory
是
ActorSystem("ActorFactory")
这样创建的。
2.
这 里 的 Props[AActor] 会 使 用 反 射 机 制 , 创 建 一 个
AActor
对 象 , 如 果 是
actorFactory.actorOf(Props(new AActor(bActorRef)), "aActorRef")
形式,就是使用
new
的方式创建一 个 AActor
对象
,
注意
Props()
是小括号。
3.
会创建一个
AActor
对象的代理对象
aActorRef ,
使用
aActorRef
才能发送消息
4.
会在底层创建
Dispather Message
,是一个线程池,用于分发消息, 消息是发送到对应的
Actor
的 MailBox
5.
会在底层创建
AActor
的
MailBox
对象,该对象是一个队列,可接收
Dispatcher Message
发送的消息
6. MailBox
实现了
Runnable
接口,是一个线程,一直运行并调用
Actor
的
receive
方法,因此当
Dispather
发送消息到
MailBox
时,
Actor
在
receive
方法就可以得到信息
.
7. aActorRef ! "hello",
表示把
hello
消息发送到
AActor
的
mailbox
(通过
Dispatcher Message
转发) 一个示意图的说明:
![](https://i-blog.csdnimg.cn/blog_migrate/3cf1050339fbeccca00161abd819dac0.jpeg)
16.6 Actor 模型应用实例-Actor 间通讯
16.6.1 应用实例需求
1)
编写
2
个
Actor ,
分别是
AActor
和
BActor
2) AActor
和
BActor
之间可以相互发送消息
.
3)
加强对
Actor
传递消息机制的理解
16.6.2 两个 Actor 的通讯机制原理图
16.6.3 代码如下
class AActor(actorRef : ActorRef) extends Actor{
val bActorRef:ActorRef =actorRef
override def receive: Receive = {
case "start" => {
println("AActor 出招了,start ok")
self ! "我打" //发给自己
}
case "我打" =>{
//给BActor 发出消息
//这里需要持有BActor的引用(BActorRef)
println("Actor(黄飞鸿) 厉害 看我佛山无影脚")
Thread.sleep(1000)
bActorRef ! "我打" //给BActor 发出消息
}
}
}
class BActor extends Actor{
override def receive: Receive = {
case "我打" =>{
println("BActor(乔峰) 挺猛 看我降龙十八掌")
Thread.sleep(1000)
//通过sender() 可以获取到发送消息的actor的ref
sender() ! "我打"
}
}
}
object ActorGame extends App{
//创建ActorSystem
val actorFactory: ActorSystem = ActorSystem("actorFactory")
//先创建BActor 引用/代理
val bActorRef: ActorRef = actorFactory.actorOf(Props[BActor], "bActor")
//创建AActor 引用/代理
val aActorRef: ActorRef = actorFactory.actorOf(Props(new AActor(bActorRef)), "aActor")
//aActor出招
aActorRef ! "start"
}
输出结果:
AActor 出招了,start ok
Actor(黄飞鸿) 厉害 看我佛山无影脚
BActor(乔峰) 挺猛 看我降龙十八掌
Actor(黄飞鸿) 厉害 看我佛山无影脚
BActor(乔峰) 挺猛 看我降龙十八掌
Actor(黄飞鸿) 厉害 看我佛山无影脚
BActor(乔峰) 挺猛 看我降龙十八掌
Actor(黄飞鸿) 厉害 看我佛山无影脚
BActor(乔峰) 挺猛 看我降龙十八掌
16.7 Akka 网络编程
16.7.1 看两个实际应用(socket/tcp/ip)
16.8 Akka 网络编程基本介绍
Akka
支持面向大并发后端服务程序,网络通信这块是服务端程序重要的一部分。
网络编程有两种
:
1) TCP socket
编程,是网络编程的主流。之所以叫
Tcp socket
编程,是因为底层是基于
Tcp/ip
协
议的
.
比如
: QQ
聊天
[
示意图
]
2) b/s
结构的
http
编程,我们使用浏览器去访问服务器时,使用的就是
http
协议,而
http
底层依
旧是用
tcp socket
实现的。 比如
:
京东商城 【属于
web
开发范畴 】
16.8.1 OSI 与 Tcp/ip 参考模型 (推荐 tcp/ip 协议 3 卷)
16.8.2 端口(port)-介绍
我们这里所指的端口不是指物理意义上的端口,而是特指
TCP/IP
协议中的端口,是
逻辑意义上的端口。
如果把
IP
地址比作一间房子,端口就是出入这间房子的门。真正的房子只有几个
门,但是一个
IP
地址的端口 可以有
65535
(即:
256
×
256-1
)个之多!端口是通过
端口号来标记的。(
端口号
0
:Reserved)
端口(port)-分类
1) 0
号是保留端口
.
2) 1-1024
是固定端口
又叫有名端口,
即被某些程序固定使用
,
一般程序员不使用
.
22: SSH 远程登录协议
23: telnet
使用
21: ftp
使用
25: smtp 服务使用
80: iis 使用
7: echo
服务
3) 1025-65535
是动态端口
这些端口,程序员可以使用
端口(port)-使用注意
1)
在计算机
(
尤其是做服务器
)
要尽可能的少开端口
[
2)
一个端口只能被一个程序监听
( )
3)
如果使用
netstat
–
an
可以查看本机有哪些端口在监听
4)
可以使用
netstat
–
anb
来查看监听端口的
pid,
在结合任务管理器关闭不安全的端口
5)
使用示意图
![](https://i-blog.csdnimg.cn/blog_migrate/bbbcfeed6221e9719bed97d91c8f794c.jpeg)
16.9 Akka 网络编程-小黄鸡客服
16.9.1 需求分析
16.9.2 程序网络拓扑图
16.9.3 程序框架图
16.9.4 功能实现(走代码)
服务端:
class YellowChickenServer extends Actor {
override def receive: Receive = {
case "start" => println("start 小黄鸡客服开始工作了......")
//如果接收到ClientMessage
case ClientMessage(mes) =>{
//使用match --case 匹配(模糊)
mes match {
case "大数据学费" => sender() ! ServerMessage("35000RMB")
case "学校地址" => sender() ! ServerMessage("中国大厦2栋201")
case "学习什么技术" => sender() ! ServerMessage("Java、C、前端")
case _ => sender() ! ServerMessage("不好意思,我还不太懂,正在努力学习中!")
}
}
}
}
//主程序-入口
object YellowChickenServer extends App {
//创建ActorSystem
//url (统一资源定位符)
//单机版的创建
//val serverActorSystem: ActorSystem = ActorSystem("Server")
//创建YellowChickenServer 的actor和返回actorRef
//val yellowChickenServerRef: ActorRef = serverActorSystem.actorOf(Props[YellowChickenServer], "YellowChickenServer")
//联网的创建,指定Ip和端口
val host = "127.0.0.1" //服务端ip地址
val port = 9999
//创建config对象,指定协议类型,监听的ip和端口
//.stripMargin 将前面空格和|拿掉
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$host
|akka.remote.netty.tcp.port=$port
""".stripMargin
)
//创建ActorSystem
val serverActorSystem: ActorSystem = ActorSystem("Server", config)
//创建YellowChickenServer 的actor和返回actorRef
val yellowChickenServerRef: ActorRef = serverActorSystem.actorOf(Props[YellowChickenServer], "YellowChickenServer")
//启动
yellowChickenServerRef ! "start"
}
客户端:
class CustomerActor(serverHost: String, serverPort: Int) extends Actor {
//定义一个YellowChickenServerRef
var serverActorRef: ActorSelection = _
//在Actor中有一个方法preStart方法,他会在actor运行前执行
//在akka的开发中,通常将初始化的工作,放在preStart方法中
//akka.tcp://Server@127.0.0.1:9999 这里的Server,就是 ActorSystem("Server", config) 中取的名字
override def preStart(): Unit = {
println("preStart() 执行")
serverActorRef = context.actorSelection(
//找到远程主机的哪一个Actor,就是serverActorSystem.actorOf(Props[YellowChickenServer], "YellowChickenServer")
s"akka.tcp://Server@${serverHost}:${serverPort}/user/YellowChickenServer")
println("serverActorRef=" + serverActorRef)
}
override def receive: Receive = {
case "start" => println("客户端运行,可以咨询问题!")
case mes: String => {
//发给小黄鸡客服
serverActorRef ! ClientMessage(mes) //使用了ClientMessage case class apply方法
}
//如果接收到服务器的恢复
case ServerMessage(mes) => {
println(s"接收到小黄鸡客服(Server):$mes")
}
}
}
//主程序-入口
object CustomerActor extends App {
val (clientHost, clientPort, serverHost, serverPort) = ("127.0.0.1", 9990, "127.0.0.1", 9999)
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$clientHost
|akka.remote.netty.tcp.port=$clientPort """.stripMargin
)
//创建ActorSystem
val clientSystem: ActorSystem = ActorSystem("client", config)
//创建CustomerActor的实例和引用
val customerActorRef: ActorRef = clientSystem.actorOf(Props(new CustomerActor(serverHost, serverPort)), "CustomerActor")
//启动customerRef ,也可以理解启动Actor
customerActorRef ! "start"
//客户端可以以发送消息给服务器
while (true) {
println("请输入你的问题")
val mes = StdIn.readLine()
customerActorRef ! mes
}
}
中间协议:
//使用样例类来构建协议
//客户端发给服务器协议(序列化的对象)
//因为样例类实现了序列化
case class ClientMessage(mes: String)
//服务端发给客户端的协议(样例类对象)
case class ServerMessage(mes: String)
启动服务端和客户端,结果展示:
16.10 Spark Master Worker 进程通讯项目
16.10.1 项目的意义
1)
深入理解
Spark
的
Master
和
Worker
的通讯机制
2)
为了方便同学们看
Spark
的底层源码,命名的方式和源码保持一致
.(
如: 通讯消息类命名就是
一样的
)
3)
加深对主从服务
心跳检测机制
(HeartBeat)
的理解,方便以后
spark
源码二次开发
16.10.2 项目需求分析
1) worker
注册到
Master, Master
完成注册,并回复
worker
注册成功
2) worker
定时发送心跳,并在
Master
接收到
3) Master
接收到
worker
心跳后,要更新该
worker
的最近一次发送心跳的时间
4)
给
Master
启动定时任务,定时检测注册的
worker
有哪些没有更新心跳
,
并将其从
hashmap
中删除
5) master worker
进行分布式部署
(Linux
系统
)
16.10.3 实现功能 1-Worker 完成注册
功能要求
: worker
注册到
Master, Master
完成注册,并回复
worker
注册成功
思路分析
(
程序框架图
)
![](https://i-blog.csdnimg.cn/blog_migrate/33b42c2c1bf2c45cae5c3244e15eb616.jpeg)
代码实现:
Master 服务端:
class SparkMaster extends Actor {
//定义个hm,管理workers
val workers=mutable.Map[String,WorkerInfo]()
override def receive: Receive = {
case "start" => println("master服务器启动了....")
case RegisterWorkerInfo(id,cpu,ram) =>{
//接收到worker注册的信息
if(!workers.contains(id)){
//创建WorkerInfo对象
val workerInfo = new WorkerInfo(id, cpu, ram)
//加入到workers
workers+=((id,workerInfo))
println("服务器的workers="+workers)
//回复一个消息
sender() ! RegisteredWorkerInfo
}
}
}
}
object SparkMaster {
def main(args: Array[String]): Unit = {
//联网的创建,指定Ip和端口
val masterHost = "127.0.0.1" //服务端ip地址
val masterPort = 10005
//创建config对象,指定协议类型,监听的ip和端口
//.stripMargin 将前面空格和|拿掉
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$masterHost
|akka.remote.netty.tcp.port=$masterPort
""".stripMargin
)
//先创建ActorSystem
val sparkMasterSystem = ActorSystem("SparkMaster", config)
//创建SparkMaster的引用
val sparkMasterRef = sparkMasterSystem.actorOf(Props[SparkMaster], "SparkMaster-01")
//启动SparkMaster
sparkMasterRef ! "start"
}
}
worker客户端:
class SparkWorker(masterHost: String, masterPort: Int) extends Actor {
//masterProxy是Master的代理/引用ref
var masterPorxy: ActorSelection = _
val id = java.util.UUID.randomUUID().toString
override def preStart(): Unit = {
println("preStart()调用了")
//初始化masterPorxy
masterPorxy = context.actorSelection(
s"akka.tcp://SparkMaster@${masterHost}:${masterPort}/user/SparkMaster-01")
println("masterProxy=" + masterPorxy)
}
override def receive: Receive = {
case "start" => {
println("worker启动了")
//发出一个注册消息
masterPorxy ! RegisterWorkerInfo(id, 16, 16 * 1024)
}
case RegisteredWorkerInfo =>{
println("workerId="+id +" 注册成功!")
}
}
}
object SparkWorker {
def main(args: Array[String]): Unit = {
//联网的创建,指定Ip和端口
val workHost = "127.0.0.1" //服务端ip地址
val workPort = 10001
val masterHost = "127.0.0.1"
val masterPort = 10005
//创建config对象,指定协议类型,监听的ip和端口
//.stripMargin 将前面空格和|拿掉
val config = ConfigFactory.parseString(
s"""
|akka.actor.provider="akka.remote.RemoteActorRefProvider"
|akka.remote.netty.tcp.hostname=$workHost
|akka.remote.netty.tcp.port=$workPort
""".stripMargin
)
//先创建ActorSystem
val sparkWorkerSystem = ActorSystem("SparkWorker", config)
val sparkWorkerRef = sparkWorkerSystem.actorOf(Props(
new SparkWorker(masterHost, masterPort)), "SparkWorker-01")
//启动actor
sparkWorkerRef ! "start"
}
}
中间协议:
// worker注册信息 //MessageProtocol.scala
case class RegisterWorkerInfo(id: String, cpu: Int, ram: Int)
// 这个是WorkerInfo, 这个信息将来是保存到master的 hm(该hashmap是用于管理worker)
// 将来这个WorkerInfo会扩展(比如增加worker上一次的心跳时间)
class WorkerInfo(val id: String, val cpu: Int, val ram: Int) {
var lastHeartBeat : Long = System.currentTimeMillis()
}
// 当worker注册成功,服务器返回一个RegisteredWorkerInfo 对象
case object RegisteredWorkerInfo
结果展示:
16.10.4 实现功能 2-Worker 定时发送心跳
功能要求
: worker
定时发送心跳给
Master
,
Master
能够接收到
,
并更新
worker
上一次心跳时间
思路分析
(
程序框架图
)
![](https://i-blog.csdnimg.cn/blog_migrate/6ef1e5542e3bb47886e35ad0b5e9f5ae.jpeg)
代码实现:在原有的代码上添加
master服务端:
class SparkMaster extends Actor {
//定义个hm,管理workers
val workers=mutable.Map[String,WorkerInfo]()
override def receive: Receive = {
case "start" => println("master服务器启动了....")
case RegisterWorkerInfo(id,cpu,ram) =>{
//接收到worker注册的信息
if(!workers.contains(id)){
//创建WorkerInfo对象
val workerInfo = new WorkerInfo(id, cpu, ram)
//加入到workers
workers+=((id,workerInfo))
println("服务器的workers="+workers)
//回复一个消息
sender() ! RegisteredWorkerInfo
}
}
case HeartBeat(id) => {
//更新对应的心跳时间
//1. 从workers取出WorkerInfo
val workerInfo = workers(id)
workerInfo.lastHeartBeat=System.currentTimeMillis()
println("master更新了="+id+" 心跳时间....")
}
}
}
worker客户端:
override def receive: Receive = {
case "start" => {
println("worker启动了")
//发出一个注册消息
masterPorxy ! RegisterWorkerInfo(id, 16, 16 * 1024)
}
case RegisteredWorkerInfo =>{
println("workerId="+id +" 注册成功!")
//当注册成功后,就定义一个定时器,每隔一定时间,发送SendHeartBeat给自己
import context.dispatcher
//说明
//1. 0 millis 不延时,立即执行定时器
//2. 3000 millis 表示每个3秒执行一次
//3. self: 表示发给自己
//4. SendHearBeat 发送的内容
context.system.scheduler.schedule(0 millis,3000 millis,self,SendHeartBeat)
}
case SendHeartBeat =>{
println("worker="+id+" 给master发送心跳")
masterPorxy ! HeartBeat(id)
}
}
中间协议:
//worker每隔一定时间由定时器发给自己的一个消息
case object SendHeartBeat
//worker每隔一定时间由定时器触发,而向master发现的协议消息
case class HeartBeat(id: String)
结果展示:
16.10.5 实现功能 3-Master 启动定时任务,定时检测注册的 worker
功能要求:
Master
启动定时任务,定时检测注册的
worker
有哪些没有更新心跳,已经超时的
worker
, 将其从 hashmap
中删除掉
思路分析
(
程序框架图
)
![](https://i-blog.csdnimg.cn/blog_migrate/33254e1e094d14e5fdea54a734fdb239.jpeg)
代码实现
master服务端:
case StartTimeOutWorker => {
println("开始了定时检测worker心跳的任务")
import context.dispatcher
context.system.scheduler.schedule(0 millis, 9000 millis, self, RemoveTimeOutWorker)
}
//对RemoveTimeOutWorker 消息处理
//这里需求检测哪些worker心跳超时(now - lastHearBeat),并从map中删除
case RemoveTimeOutWorker => {
//首先将所有的 workers 的所有 WorkerInfo
val workerInfos = workers.values
val nowTime = System.currentTimeMillis()
//先把所有超时的workerInfo取出,删除即可
workerInfos.filter(workerInfo =>nowTime-workerInfo.lastHeartBeat>6000)
.foreach(workerInfo => workers.remove(workerInfo.id))
println("当前有"+workers.size+" 个worker存活的")
}
中间协议:
//master给自己发送一个触发检查超时worker的信息
case object StartTimeOutWorker
// master给自己发消息,检测worker,对于心跳超时的.
case object RemoveTimeOutWorker
结果展示:
![](https://i-blog.csdnimg.cn/blog_migrate/9f0c36918d97744b0d7df7a6c03f1da4.jpeg)
16.10.6 实现功能 4-Master,Worker 的启动参数运行时指定
功能要求:
Master,Worker
的启动参数运行时指定,而不是固定写在程序中的。
代码实现:
master服务端:
//这里我们分析出来有6个参数worker和master的host,port,sparkMasterActor,sparkWorkerActor
if(args.length!=6){
println("请输入参数 workHost workPort workName masterHost masterPort masterName")
sys.exit()
}
val workerHost = args(0) //服务端ip地址
val workerPort = args(1)
val workerName = args(2)
val masterHost = args(3)
val masterPort = args(4)
val masterName = args(5)
worker客户端:
class SparkWorker(masterHost: String, masterPort: Int,masterName:String) extends Actor {
//这里我们分析出来有6个参数worker和master的host,port,sparkMasterActor,sparkWorkerActor
if(args.length!=6){
println("请输入参数 workHost workPort workName masterHost masterPort masterName")
sys.exit()
}
val workerHost = args(0) //服务端ip地址
val workerPort = args(1)
val workerName = args(2)
val masterHost = args(3)
val masterPort = args(4)
val masterName = args(5)
在控制台输入参数:
16.11 经典的Wordcount的统计
object WordCount {
def main(args: Array[String]): Unit = {
/*
val lists=List("atgugu han hello","atguigu han aaa aaa aaa ccc ddd uuu")
使用映射集合,list中,各个单词出现的次数,并接出现次数排序(从大到小)
* */
//看看scala可以怎么完成
val lines=List("atguigu han hello","atguigu han aaa aaa aaa ccc ddd uuu")
//先分布完成,在组合
//1.步骤
//使用扁平化
val res1 = lines.flatMap((s: String) => s.split(" "))
println("扁平化和切分后的样子="+res1) //扁平化和切分后的样子=List(atgugu, han, hello, atguigu, han, aaa, aaa, aaa, ccc, ddd, uuu)
//使用简写
val res2 = lines.flatMap(_.split(" "))
println("使用简写的样子="+res2) //使用简写的样子=List(atgugu, han, hello, atguigu, han, aaa, aaa, aaa, ccc, ddd, uuu)
//2.步骤 =》做成一个对偶List => 才能分组并统计
val res11 = res1.map((s: String) => (s, 1))
println("进行对偶="+res11) //进行对偶=List((atgugu,1), (han,1), (hello,1), (atguigu,1), (han,1), (aaa,1), (aaa,1), (aaa,1), (ccc,1), (ddd,1), (uuu,1))
//使用简化
val res22 = res2.map((_, 1))
println("使用简化的对偶="+res22) //使用简化的对偶=List((atgugu,1), (han,1), (hello,1), (atguigu,1), (han,1), (aaa,1), (aaa,1), (aaa,1), (ccc,1), (ddd,1), (uuu,1))
//3.步骤 =》 分组,把不同的单词归属到不同的组
val res3 = res11.groupBy((x: (String, Int)) => x._1)
println("按第一个元素进行分组="+res3)
//按第一个元素进行分组=HashMap(atgugu -> List((atgugu,1)), han -> List((han,1), (han,1)), ddd -> List((ddd,1)), ccc -> List((ccc,1)), uuu -> List((uuu,1)), atguigu -> List((atguigu,1)), hello -> List((hello,1)), aaa -> List((aaa,1), (aaa,1), (aaa,1)))
//使用简化
val res33= res22.groupBy(_._1)
println("使用简化的进行分组="+res33)
//使用简化的进行分组=HashMap(atgugu -> List((atgugu,1)), han -> List((han,1), (han,1)), ddd -> List((ddd,1)), ccc -> List((ccc,1)), uuu -> List((uuu,1)), atguigu -> List((atguigu,1)), hello -> List((hello,1)), aaa -> List((aaa,1), (aaa,1), (aaa,1)))
//4.步骤 ,对上面的各个元组,进行统计大小
val res4 = res3.map((x: (String, List[(String, Int)])) => ((x._1), x._2.size))
println("进行统计大小="+res4) //进行统计大小=HashMap(han -> 2, ddd -> 1, ccc -> 1, uuu -> 1, atguigu -> 2, hello -> 1, aaa -> 3)
//使用简化
val res44 = res33.map(x => (x._1, x._2.size))
println("使用简化进行统计大小="+res44) //使用简化进行统计大小=HashMap(han -> 2, ddd -> 1, ccc -> 1, uuu -> 1, atguigu -> 2, hello -> 1, aaa -> 3)
//5.步骤,进行排序(默认从小到大,现在要从大到小)
val res5 = res4.toList.sortBy((x: (String, Int)) => x._2).reverse
println("按照数字排序后="+res5) //按照数字排序后=List((aaa,3), (atguigu,2), (han,2), (hello,1), (uuu,1), (ccc,1), (ddd,1))
//使用简写
val res55 = res44.toList.sortBy(_._2).reverse
println("使用简写排序后="+res55) //使用简写排序后=List((aaa,3), (atguigu,2), (han,2), (hello,1), (uuu,1), (ccc,1), (ddd,1))
//合并写
println("+++++++++++++++++++++++++++++++++++")
val totil = lines.flatMap(_.split(" ")).map((_, 1)).groupBy(_._1).map(x => (x._1, x._2.size)).toList.sortBy(_._2).reverse
println("合并的写法="+totil) //合并的写法=List((aaa,3), (atguigu,2), (han,2), (hello,1), (uuu,1), (ccc,1), (ddd,1))
}
}