大数据之scala_scala之Akka通信

什么是ACTORS

Akka Actors遵循Actor模型(废话!)
  我们这把Actor当作是一个人,这个人不会自己和其他的人直接说话,他们只通过mail来进行交流。
  现在来探讨Actors的一些特性:

一、消息传递

假设有两个人:学生和聪明的老师。学生每天早上都会给老师发送邮件,而聪明的老师都会回复一句名言。这里需要解释:
  1、学生发送邮件。一旦发送成功,邮件不能再修改。这天然就具备了不可变性;
  2、老师会自己决定何时检查邮箱;
  3、老师还会回复一封邮件(也是不可变的);
  4、学生会自己决定何时检查邮箱;
  5、学生不会一直等待回信(非阻塞的)
  这就可以总结出Actor模型的一个基本特征——消息传递
在这里插入图片描述

二、并发

现在,假设有三个聪明的老师和三个学生。每个学生都会给每个老师发送邮件。这会发生什么事?其实什么都没改变!每个人都有他自己的邮箱。这里需要注意的一点:默认情况下,邮箱里面的邮件是按照他们先后达到的次序进行阅读和处理的。
  本质上,这很像是ConcurrentLinkedQueue。没有人去等待邮件被阅读,简单来说这就是一个非阻塞的消息(在Akka中内置了许多的mailboxes,这里http://doc.akka.io/docs/akka/snapshot/scala/mailboxes.html,包括了有界和基于优先级的。其实,我们自己也可以去实现)。
在这里插入图片描述

三、错误恢复

假如这三个老师分别来自不同的院系:历史系、地理系和哲学系。
  历史系的老师用过去的某个事件笔记进行回复;而地理系的老师回复了一个有趣的地点;哲学系的老师回复了一个引用。每个学生分别给每个老师发送消息并分别得到回复。学生并不关心邮件到底是系里的哪个老师回复的。如果有一天有个老师生病了呢?系里至少得有一个老师在处理邮件才行。这样的话,系里的另一位老师就会顶上这项工作。
在这里插入图片描述

这里需要注意的地方:
  1、会有一个Actor池,每个Actor会处理不同的事件。
  2、Actor做的事情可能会抛出异常,而它自己无法从中恢复。在这种情况下,需要再生成(created )一个新的Actor来顶替它。换句话说,这个新的Actor会忽略刚才那条消息,继续处理剩余的消息。这些也被称为指令(Directive),后面我们会再讲到它们。

四、多任务

假设学生需要考试成绩,每个老师是通过邮件来发送的。也就是说,Actor可以处理多种类型的消息。

五、消息链

假如学生只想收到一封邮件而不是三件呢?
  我们也可用用Actor来实现!我们可以通过分层来把老师连在一起。这个我们将在后面讲到Supervisor和Future的时候再回来讲。
  应Mohan的要求,我们把类比的实体和Actor模型中的组件做一下映射。
在这里插入图片描述

1.学生和老师都是Actor,Actor是用来收发消息,处理逻辑的
2.教学总监和班主任:ActorSystem,它是用来创建Actor的,并且监控和管理他创建的Actor
3.ActorSystem是单例的,一个进程中只要有一个即可
4.Actor是多例的,可以创建多个实例
5.Actor编程模式就通过送消息来实现并发的。

Akka就是基于Actor编程模型实现的,可以是单机,也可以是分布式的

总结:

1.Actor就是通过发生消息实现并发
2.一个人就相当于一个Actor
3.Actor和Actor直接是可以发送消息的,自己也可以给自己发生消息
4.消息是有类型的,里面封装的数据(case class)
5.Actor是有生命周期的,是由ActorSystem创建的,ActorSystem负责管理和监控Actors,ActorSystem是单例的(object),Actor是多例的
6.消息的接收者可以将消息返回给消息的发送者

RPC通信流程图

在这里插入图片描述

代码实现:(简单发送信息)

首先创建一个maven项目,导入如下依赖:

 <!-- 常量 -->
    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <encoding>UTF-8</encoding>
        <scala.version>2.12.12</scala.version>
        <scala.compat.version>2.12</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>

        <!-- akka远程通信依赖 -->
        <dependency>
            <groupId>com.typesafe.akka</groupId>
            <artifactId>akka-remote_${scala.compat.version}</artifactId>
            <version>${akka.version}</version>
        </dependency>

    </dependencies>

    <build>
        <pluginManagement>
            <plugins>
                <!-- 编译scala的插件 -->
                <plugin>
                    <groupId>net.alchim31.maven</groupId>
                    <artifactId>scala-maven-plugin</artifactId>
                    <version>3.2.2</version>
                </plugin>
                <!-- 编译java的插件 -->
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.5.1</version>
                </plugin>
            </plugins>
        </pluginManagement>
        <plugins>
            <plugin>
                <groupId>net.alchim31.maven</groupId>
                <artifactId>scala-maven-plugin</artifactId>
                <executions>
                    <execution>
                        <id>scala-compile-first</id>
                        <phase>process-resources</phase>
                        <goals>
                            <goal>add-source</goal>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>scala-test-compile</id>
                        <phase>process-test-resources</phase>
                        <goals>
                            <goal>testCompile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <executions>
                    <execution>
                        <phase>compile</phase>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>


            <!-- 打jar插件 -->
            <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>

                                <!-- 指定maven方法 -->
                                <!--                                <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">-->
                                <!--                                    <mainClass>cn._51doit.rpc.Master</mainClass>-->
                                <!--                                </transformer>-->
                            </transformers>

                        </configuration>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

class Master extends Actor{
//用来接收消息的
  override def receive: Receive = {
    //采用匹配模式进行判断接收
    case "hello" => println("你好")

    case "hello world" => println("你好世界")
    //当接收到Worker发送的消息后,说明连接已经打通,此时可以使用sender()方法直接返回一个消息
      sender()  ! "hello china"
  }
}


object Master{
  def main(args: Array[String]): Unit = {
    //设置配置信息
    val masterHost = "localhost"
    val masterPort = 8888
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $masterHost
         |akka.remote.netty.tcp.port = $masterPort
         |""".stripMargin
    //获取配置信息
    val config: Config = ConfigFactory.parseString(configStr)

    //创建ActorSystem(Master的管理者),要求是单例的(object)
    //参数一:ActorSystem的名字,参数二:配置信息
    val system: ActorSystem = ActorSystem("Master-Actor-System", config)

    //获取actor对象(Master的执行者),
    //参数一:Props[Master],类似于java中的Master.class , 参数二:actoer的名字
    val actorRef: ActorRef = system.actorOf(Props[Master], "Master-Actor")

    // ! 表示发送单向异步消息。异步:“即发即弃,发送后就不管了,
    //可以去做其他的事”,同步:"发送后会一直等待接收"。
    //如果从actor内部调用,那么actor引用将作为隐式的“sender”参数隐式传递。
    //如果从一个actor内部调用,那么这个actor 'sender'引用在接收actor的'sender()'
   	//成员变量中可用。如果不是,则没有发送方可用。
    actorRef ! "hello" //没有指定发送对象,默认发送给自己

  }
}
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}

class Worker extends Actor{

//preStart()方法在构造方法之后,receive方法之前执行一次
  override def preStart(): Unit = {
    //跟Master建立连接(context是Actor提供的一个隐式参数,可以直接使用)
    //akka.tcp: ak通信协议
    //Master-Actor-System  我们自定义的MasterActorSystem的名字
    //Master-Actor  我们自定义的MasterActor的名字
    //user不可省略
    val masterRef: ActorSelection = context.actorSelection("akka.tcp://Master-Actor-System@localhost:8888/user/Master-Actor")
    //使用连接对象发送异步信息,到Master
    masterRef ! "hello world"
  }
  
  override def receive: Receive = {
    case "hello china" => println("你好中国")
  }
}
object Worker{
  def main(args: Array[String]): Unit = {
    //设置配置信息
    val workerHost = "localhost"
    val workerPort = 8889
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $workerHost
         |akka.remote.netty.tcp.port = $workerPort
         |""".stripMargin
    //获取配置信息
    val config: Config = ConfigFactory.parseString(configStr)
    //创建ActorSystem(worker的管理者)
    val actorSystem: ActorSystem = ActorSystem("Worker-Actor-System", config)
    //创建actor对象(worker的执行者)
    val actorRef: ActorRef = actorSystem.actorOf(Props[Worker], "Worker-Actor")
  }
}

代码实现:(完整实现)

package com.doit.akka
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//导入时间参数
import scala.concurrent.duration._
import scala.collection.mutable
class Master extends Actor{
  //new一个装注册信息的Map集合
  private val hashMap = new mutable.HashMap[String, WorkerInfo]()
  //preStart在构造方法之后,receive方法之前执行一次
  override def preStart(): Unit = {
    //导入隐式参数
    import context.dispatcher
    //每隔15秒检测是否有超时节点发送一个指令(CheckTimeOutWorker)给master自己
    context.system.scheduler.schedule(0 milliseconds,15000 milliseconds,self,CheckTimeOutWorker)
  }
  //用来接收消息的
  override def receive: Receive = {
    //采用匹配模式进行判断接收
    //case "hello" => println("你好")
    //接收注册信息,存入Map集合中
    case RegisterWorker(workerId,memory,cores) => {
    if( ! hashMap.contains(workerId)) {
      hashMap(workerId)=WorkerInfo(workerId,memory,cores)
    }
      //println("注册成功")
      //当接收到Worker发送的消息后,说明连接已经打通,此时可以使用sender()方法直接返回一个消息
    sender() ! RegisteredWorker
    }
    case HearBeat(workerId) => {
      //报告心跳信息时,更新worker的注册时间
      val workerInfo: WorkerInfo = hashMap(workerId)
      //获取当前时间,赋给workerInfo
      workerInfo.lastHeartBeatTime=System.currentTimeMillis()

    }
      //接收到检测超时节点的指令
    case CheckTimeOutWorker => {
      //筛选出心跳信息和当前时间超过10s每跳的节点
      val deadWorkers: Iterable[WorkerInfo] = hashMap.values.filter(w => (System.currentTimeMillis() - w.lastHeartBeatTime) > 10000)
      //移除该节点
      deadWorkers.foreach(w => hashMap -= w.workerId)
      //打印现有存活的节点数
      println(s"The number of remaining workers in master :${hashMap.size}")
    }
  }
}


object Master{
  val MASTER_ACTOR_SYSTEM = "MASTER_ACTOR_SYSTEM"
  val MASTER_ACTOR = "MASTER_ACTOR"
  def main(args: Array[String]): Unit = {
    //设置配置信息
    val masterHost = args(0)
    val masterPort = args(1).toInt
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $masterHost
         |akka.remote.netty.tcp.port = $masterPort
         |""".stripMargin
    //获取配置信息
    val config: Config = ConfigFactory.parseString(configStr)
    //创建ActorSystem(Master的管理者),要求是单例的(object)
    //参数一:ActorSystem的名字,参数二:配置信息
    val system: ActorSystem = ActorSystem(MASTER_ACTOR_SYSTEM, config)
    //获取actor对象(Master的执行者),参数一:Props[Master],类似于java中的Master.class , 参数二:actoer的名字
    val actorRef: ActorRef = system.actorOf(Props[Master], MASTER_ACTOR)
    // ! 表示发送单向异步消息。异步:“即发即弃,发送后就不管了,可以去做其他的事”,同步:"发送后会一直等待接收"。
    //如果从actor内部调用,那么actor引用将作为隐式的“sender”参数隐式传递。
    //如果从一个actor内部调用,那么这个actor 'sender'引用在接收actor的'sender()'成员变量中可用。如果不是,则没有发送方可用。
    //actorRef ! "hello" //没有指定发送对象,默认发送给自己
  }
}
package com.doit.akka
import java.util.UUID
import akka.actor.{Actor, ActorRef, ActorSelection, ActorSystem, Props}
import com.typesafe.config.{Config, ConfigFactory}
//导入时间参数
import scala.concurrent.duration._
//将这四个参数放在主构造器中
class Worker(val masterHost: String, val masterPort: Int, var workerMemory: Int, var workerCores: Int) extends Actor{
  //使用随机数,获取workerId
  val workerId = UUID.randomUUID().toString
  var masterRef: ActorSelection = null
//preStart在构造方法之后,receive方法之前执行一次
  override def preStart(): Unit = {
    //跟Master建立连接(context是Actor提供的一个隐式参数,可以直接使用)
    //akka.tcp: ak通信协议
    //Master-Actor-System  我们自定义的MasterActorSystem的名字
    //Master-Actor  我们自定义的MasterActor的名字
    //user不可省略
    masterRef = context.actorSelection(s"akka.tcp://${Master.MASTER_ACTOR_SYSTEM}@$masterHost:$masterPort/user/${Master.MASTER_ACTOR}")
    //使用连接对象发送异步信息,到Master
    masterRef ! RegisterWorker(workerId,workerMemory,workerCores)
  }
  override def receive: Receive = {
    //接收到注册成功的回复
    case RegisteredWorker => {

      //导入隐式参数
      import context.dispatcher
      //向自己发送心跳信息(SendHeartBeat),设置从0秒开始发送,间隔10秒发送一次,然后再转发给master
      context.system.scheduler.schedule(0 milliseconds,10000 milliseconds,self,SendHeartBeat)
    }
      //接收到自己发送过来的心跳信息
    case SendHeartBeat => {
      //转发给master,并附带上workerId
      masterRef ! HearBeat(workerId)

    }
  }
}
object Worker{
  //WORKER_ACTOR_SYSTEM和WORKER_ACTOR定义为常量
  val WORKER_ACTOR_SYSTEM = "WORKER_ACTOR_SYSTEM"
  val WORKER_ACTOR = "WORKER_ACTOR"
  def main(args: Array[String]): Unit = {
    //设置配置信息通过运行jar包调用mian方法时传入
    val masterHost = args(0)
    val masterPort = args(1).toInt
    val workerHost = args(2)
    val workerPort = args(3).toInt
    val workerMemory = args(4).toInt
    val workerCores = args(5).toInt
    val configStr =
      s"""
         |akka.actor.provider = "akka.remote.RemoteActorRefProvider"
         |akka.remote.netty.tcp.hostname = $workerHost
         |akka.remote.netty.tcp.port = $workerPort
         |""".stripMargin
    //获取配置信息
    val config: Config = ConfigFactory.parseString(configStr)
    //创建ActorSystem(worker的管理者)
    val actorSystem: ActorSystem = ActorSystem(WORKER_ACTOR_SYSTEM, config)
    //创建actor对象(worker的执行者)
    //new Worker 时可以将配置信息传入到class Worker类中
    actorSystem.actorOf(Props(new Worker(masterHost, masterPort, workerMemory, workerCores)), WORKER_ACTOR)
  }
}
package com.doit.akka

case class WorkerInfo(val workerId : String,var memory : Int,var cores :Int){

  var lastHeartBeatTime: Long = _
}

package com.doit.akka

//Master发送给自己的检测消息
case object CheckTimeOutWorker
//Worker -> Master的心跳消息
case class HearBeat(workerId: String)
//Master -> Worker注册成功的消息
case object RegisteredWorker
//Worker发送给Master的注册消息
case class RegisterWorker(workerId : String,memory : Int,cores :Int)
//Worker -> 自己的内部消息
case object SendHeartBeat

运行时可以打成jar包运行将master和worker运行在不同的机器上

jar包运行命令:
Master:

java -cp akka.jar(jar包路径) com.doit.akka.Master(main方法所在类的全类名) 192.168.128.2(本机ip) 
8888(自定义的端口号)

Worker:

java -cp akka.jar(jar包路径) com.doit.akka.Master(main方法所在类的全类名) 192.168.128.2(Master机器ip) 
8888(Master机器端口号) 192.168.128.201(本机ip) 8888(自定义的端口号,不同机器上运行,端口号可以相同) 
2048(worker分配的内存) 2(worker分配的核数)
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值