Scala之并发编程模型Akka

Akka 介绍
1) Akka 是 JAVA 虚拟机 JVM 平台上构建高并发、分布式和容错应用的工具包和运行时,你可以
理解成 Akka  是编写并发程序的框架。
2) Akka 用 Scala 语言写成,同时提供了 Scala 和 JAVA 的开发接口。
3) Akka 主要解决的问题是:可以轻松的写出高效稳定的并发程序,程序员不再过多的考虑线程、
锁和资源竞争等细节。


Actor 模型用于解决什么问题
1) 处理并发问题关键是要保证共享数据的一致性和正确性,因为程序是多线程时,多个线程对同
一个数据进行修改,若不加同步条件,势必会造成数据污染。但是当我们对关键代码加入同步条件
synchronized 后,实际上大并发就会阻塞在这段代码,对程序效率有很大影响。
2) 若是用单线程处理,不会有数据一致性的问题,但是系统的性能又不能保证。
3) Actor 模型的出现解决了这个问题,简化并发编程,提升程序性能。 你可以这里理解:Actor 模
型是一种处理并发问题的解决方案,很牛!


Akka 中 Actor 模型
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),因此处理大并发性能高.

 

 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) AActor 要给 B Actor 发送消息,那么 A Actor 要先拿到(也称为持有) B Actor 的 代理对象
ActorRef 才能发送消息

 

使用Maven构建简单案例

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.buba.akka</groupId>
    <artifactId>Akka1712</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!-- 定义一下常量 -->
    <properties>
        <encoding>UTF-8</encoding>
        <scala.version>2.11.8</scala.version>
        <scala.compat.version>2.11</scala.compat.version>
        <akka.version>2.4.17</akka.version>
    </properties>

    <dependencies>
        <!-- 添加scala的依赖 -->
        <dependency>
            <groupId>org.scala-lang</groupId>
            <artifactId>scala-library</artifactId>
            <version>${scala.version}</version>
        </dependency>

        <!-- 添加akka的actor依赖 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-actor_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>

        <!-- 多进程之间的Actor通信 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>
    </dependencies>

    <!-- 指定插件-->
    <build>
        <!-- 指定源码包和测试包的位置 -->
        <sourceDirectory>src/main/scala</sourceDirectory>
        <testSourceDirectory>src/test/scala</testSourceDirectory>
        <plugins>
            <!-- 指定编译scala的插件 -->
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <version>3.2.2</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>compile</goal>
                            <goal>testCompile</goal>
                        </goals>
                        <configuration>
                            <args>
                                <arg>-dependencyfile</arg>
                                <arg>${project.build.directory}/.scala_dependencies</arg>
                            </args>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <!-- maven打包的插件 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <filters>
                                <filter>
                                    <artifact>*:*</artifact>
                                    <excludes>
                                        <exclude>META-INF/*.SF</exclude>
                                        <exclude>META-INF/*.DSA</exclude>
                                        <exclude>META-INF/*.RSA</exclude>
                                    </excludes>
                                </filter>
                            </filters>
                            <transformers>
                                <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>reference.conf</resource>
                                </transformer>
                                <!-- 指定main方法 -->
                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <mainClass>xxx</mainClass>
                                </transformer>
                            </transformers>
                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

案例一   单个actor给自己发送消息

import akka.actor.{Actor, ActorRef, ActorSystem, Props}

//说明
//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 发消息(邮箱mailbox)
    sayHelloActorRef ! "hello"
    sayHelloActorRef ! "ok"
    sayHelloActorRef ! "ok~"
    //研究异步如何退出 ActorSystem
    sayHelloActorRef ! "exit"
  }
}

1. 当程序执行 aActorRef = actorFactory.actorOf(Props[AActor], "aActor") ,会完成如下任务 [这是非常
重要的方法]
2. actorFactory 是 ActorSystem("ActorFactory") 这样创建的。
3. 这 里 的 Props[AActor] 会 使 用 反 射 机 制 , 创 建 一 个 AActor 对 象 , 如 果 是
actorFactory.actorOf(Props(new AActor(bActorRef)), "aActorRef") 形式,就是使用 new 的方式创建一
个 AActor 对象, 注意 Props() 是小括号。
4. 会创建一个 AActor 对象的代理对象 aActorRef , 使用 aActorRef 才能发送消息
5. 会在底层创建 Dispather Message ,是一个线程池,用于分发消息, 消息是发送到对应的 Actor 的
MailBox
6. 会在底层创建 AActor 的 MailBox 对象,该对象是一个队列,可接收 Dispatcher Message 发送的消

7. MailBox 实现了 Runnable 接口,是一个线程,一直运行并调用 Actor 的 receive 方法,因此当
Dispather 发送消息到 MailBox 时,Actor 在 receive 方法就可以得到信息.
8. aActorRef ! "hello", 表示把 hello 消息发送到 AActor 的 mailbox(通过 Dispatcher Message 转发)


案例二   Actor 模型应用实例-Actor 间通讯

AActor

import akka.actor.{Actor, ActorRef}

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("AAcotr(黄飞鸿) 厉害 看我佛山无影脚")
      Thread.sleep(2000)
      println()
      bActorRef ! "我打"
      println()
    }

  }
}

BActor 

import akka.actor.Actor

class BActor extends Actor {
  override def receive: Receive = {
    case "我打" => {
      println("我是Bactor(乔峰) 看我降龙十八掌")
      //通过sender() 可以获取到谁发送来消息的actor的ref的引用
      println("BActor发了我打")
      Thread.sleep(2000)
      sender() ! "我打"
      println()
    }
  }
}

 启动类

import akka.actor.{ActorRef, ActorSystem, Props}

object ActorGame extends App{

  //创建ActorSystem
  private val actorFactory = ActorSystem("actorFactory");

  //先创建BActor引用代理
  val bActorRef : ActorRef = actorFactory.actorOf(Props[BActor],"bActor")

  //创建AActor的引用
  private val aActorRef: ActorRef = actorFactory.actorOf(Props(new AActor(bActorRef)),"aActor")

  aActorRef ! "start"
  println()
  
}

 

上面的都是在对象之间进行互动,没有经过网络的 

Akka 网络编程案例  底层基于TCP/IP协议

样例类

package com.buba.akka.yellochicken.common

//使用样例类来构建协议
//客户端发给服务器协议(序列化的对象)
case class ClientMessage(mes: String)

//服务端发给客户端的协议(样例类对象)
case class ServerMessage(mes: String)

服务端代码 

import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.buba.akka.yellochicken.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory

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("北京昌平xx路xx大楼")
        case "学习什么技术" => sender() ! ServerMessage("大数据 前端 python")
        case _ => sender() ! ServerMessage("你说的啥子~")
      }
    }
  }
}

//主程序-入口

object YellowChickenServer extends App {


  val host = "127.0.0.1" //服务端ip地址
  val port = 9999
  //创建config对象,指定协议类型,监听的ip和端口
  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
  //url (统一资源定位)
  val serverActorSystem = ActorSystem("Server",config)
  //创建YellowChickenServer 的actor和返回actorRef
  val yellowChickenServerRef: ActorRef = serverActorSystem.actorOf(Props[YellowChickenServer],"YellowChickenServer")

  //启动
  yellowChickenServerRef ! "start"


}

客户端代码 

import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.buba.akka.yellochicken.common.{ClientMessage, ServerMessage}
import com.typesafe.config.ConfigFactory

import scala.io.StdIn

class CustomerActor(serverHost: String, serverPort: Int) extends Actor {
  //定义一个YellowChickenServerRef
  var serverActorRef: ActorSelection = _

  //在Actor中有一个方法PreStart方法,他会在actor运行前执行
  //在akka的开发中,通常将初始化的工作,放在preStart方法
  override def preStart(): Unit = {
    println("preStart() 执行")
    serverActorRef = context.actorSelection(s"akka.tcp://Server@${serverHost}:${serverPort}/user/YellowChickenServer")//这个路径Server是服务端System起的名字,必须一致  YellowChickenServer是服务端的Actor其余的就是固定写法

    println("serverActorRef=" + serverActorRef)
  }

  override def receive: Receive = {
    case "start" => println("start,客户端运行,可以咨询问题")
    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 clientActorSystem = ActorSystem("client", config)

  //创建CustomerActor的实例和引用
  val customerActorRef: ActorRef = clientActorSystem.actorOf(Props(new CustomerActor(serverHost, serverPort)), "CustomerActor")

  //启动customerRef/也可以理解启动Actor
  customerActorRef ! "start"

  //客户端可以发送消息给服务器
  while (true) {
    println("请输入要咨询的问题")
    val mes = StdIn.readLine()
    customerActorRef ! mes
  }


}

TCP/IP案例二  模拟Master Worder工作机制

// 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

//worker每隔一定时间由定时器发给自己的一个消息
case object SendHeartBeat
//worker每隔一定时间由定时器触发,而向master发现的协议消息
case class HeartBeat(id: String)

//master给自己发送一个触发检查超时worker的信息
case object StartTimeOutWorker
// master给自己发消息,检测worker,对于心跳超时的.
case object RemoveTimeOutWorker
import akka.actor.{Actor, ActorSystem, Props}
import com.atguigu.akka.sparkmasterworker.common._
import com.typesafe.config.ConfigFactory

import scala.collection.mutable
import scala.concurrent.duration._

class SparkMaster extends Actor {
  //定义个hm,管理workers
  val workers = mutable.Map[String, WorkerInfo]()

  override def receive: Receive = {

    case "start" => {
      println("master服务器启动了...")
      //这里开始。。
      self ! StartTimeOutWorker
    }
    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) => {
      //更新对应的worker的心跳时间
      //1.从workers取出WorkerInfo
      val workerInfo = workers(id)
      workerInfo.lastHeartBeat = System.currentTimeMillis()
      println("master更新了 " + id + " 心跳时间...")
    }
    case StartTimeOutWorker => {
      println("开始了定时检测worker心跳的任务")
      import context.dispatcher
      //说明
      //1. 0 millis 不延时,立即执行定时器
      //2. 9000 millis 表示每隔3秒执行一次
      //3. self:表示发给自己
      //4. RemoveTimeOutWorker 发送的内容
      context.system.scheduler.schedule(0 millis, 9000 millis, self, RemoveTimeOutWorker)
    }
    //对RemoveTimeOutWorker消息处理
    //这里需求检测哪些worker心跳超时(now - lastHeartBeat > 6000),并从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存活的")
    }
  }
}

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

    //这里我们分析出有3个host,port,sparkMasterActor
    if (args.length != 3) {
      println("请输入参数 host port sparkMasterActor名字")
      sys.exit()
    }

    val host = args(0)
    val port = args(1)
    val name = args(2)

    //先创建ActorSystem
    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=${host}
         |akka.remote.netty.tcp.port=${port}
            """.stripMargin)
    val sparkMasterSystem = ActorSystem("SparkMaster", config)
    //创建SparkMaster -actor
    val sparkMasterRef = sparkMasterSystem.actorOf(Props[SparkMaster], s"${name}")
    //启动SparkMaster
    sparkMasterRef ! "start"
  }
}
import akka.actor.{Actor, ActorSelection, ActorSystem, Props}
import com.atguigu.akka.sparkmasterworker.common.{HeartBeat, RegisterWorkerInfo, RegisteredWorkerInfo, SendHeartBeat}
import com.typesafe.config.ConfigFactory

import scala.concurrent.duration._

class SparkWorker(masterHost:String,masterPort:Int,masterName:String) 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/${masterName}")
    println("masterProxy=" + masterPorxy)
  }
  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. SendHeartBeat 发送的内容
      context.system.scheduler.schedule(0 millis, 3000 millis, self, SendHeartBeat)

    }
    case SendHeartBeat =>{
      println("worker = " + id + "给master发送心跳")
      masterPorxy ! HeartBeat(id)
    }
  }
}

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

    if (args.length != 6) {
      println("请输入参数 workerHost workerPort workerName masterHost masterPort masterName")
      sys.exit()
    }

    val workerHost = args(0)
    val workerPort = args(1)
    val workerName = args(2)
    val masterHost = args(3)
    val masterPort = args(4)
    val masterName = args(5)
    val config = ConfigFactory.parseString(
      s"""
         |akka.actor.provider="akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname=${workerHost}
         |akka.remote.netty.tcp.port=${workerPort}
            """.stripMargin)

    //创建ActorSystem
    val sparkWorkerSystem = ActorSystem("SparkWorker",config)

    //创建SparkWorker 的引用/代理
    val sparkWorkerRef = sparkWorkerSystem.actorOf(Props(new SparkWorker(masterHost, masterPort.toInt,masterName)), s"${workerName}")

    //启动actor
    sparkWorkerRef ! "start"
  }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值