尚硅谷Scala (16)

文章详细介绍了Akka作为并发编程框架的核心特性,包括基于Actor模型的并发处理、消息传递机制、ActorSystem的管理以及如何创建和管理Actor。此外,还探讨了Akka在网络编程中的应用,如Socket和TCP/IP通信,并通过实例展示了Actor间的通信和心跳检测机制。最后,文章以一个简单的WordCount统计为例,展示了如何使用Akka进行数据处理。
摘要由CSDN通过智能技术生成

十六、并发编程模型 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 异步请求处理 ]

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 转发) 一个示意图的说明:

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) 使用示意图

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 注册成功
思路分析 ( 程序框架图 )

代码实现:

 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 上一次心跳时间
思路分析 ( 程序框架图 )

 代码实现:在原有的代码上添加

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 中删除掉
思路分析 ( 程序框架图 )

代码实现
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

结果展示:

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))
  }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值