Scala语言学习四-akka actor

半城伤御伤魂 提交于 2020-03-10 18:22:32

并发编程模型 Akka -并发编程框架

(不需要关注并发情况底层的东西,易开发易维护)

Akka 介绍

写并发程序很难。 程序员不得不处理线程、 锁和竞态条件等等, 这个过程很容易出错, 而且会导致程序代码难以阅读、 测试和维护。

Akka 是 JVM 平台上构建高并发、 分布式和容错应用的工具包和运行时。 Akka 用 Scala 语言写成, 同时提供了 Scala 和 JAVA 的开发接口。

Akka 中 Actor 模型

Akka 处理并发的方法基于 Actor 模型。 在基于 Actor 的系统里, 所有的事物都是 Actor, 就好像在面向对象设计里面所有的事物都是对象一样。 但是有一个重要区别, 那就是 Actor 模型是作为一个并发模型设计和架构的, 而面向对象模式则不是。 Actor 与 Actor 之间只能通
过消息通信。

  • 对并发模型进行了更高的抽象
  • 异步、 非阻塞、 高性能的事件驱动编程模型(就是不会一个卡主另一个)
  • 轻量级事件处理(1GB 内存可容纳百万级别个 Actor)

为什么 Actor 模型是一种处理并发问题的解决方案?

处理并发问题就是如何保证共享数据的一致性和正确性,为什么会有保持共享数据正确性这个问题呢? 无非是我们的程序是多线程的, 多个线程对同一个数据进行修改, 若不加同步条件, 势必会造成数据污染。那么我们是不是可以转换一下思维, 用单线程去处理相应的请求, 但是又有人会问了, 若是用单线程处理, 那系统的性能又如何保证。 Actor 模型的出现解决了这个问题, 简化并发编程, 提升程序性能。

解决这个问题(单线程要排队,多线程引发数据污染)
在这里插入图片描述从图中可以看到, Actor 与 Actor 之前只能用消息进行通信, 当某一个 Actor 给另外一个 Actor
发消息, 消息是有顺序的, 只需要将消息投寄的相应的邮箱, 至于对方 Actor 怎么处理你的
消息你并不知道, 当然你也可等待它的回复。
Actor 是 ActorSystem 创建的, ActorSystem 的职责是负责创建并管理其创建的 Actor,
ActorSystem 的单例的, 一个 JVM 进程中有一个即可, 而 Acotr 是多例的。

akka actor创建过程
在这里插入图片描述
actor之间可以进行通信,通信方式是相互传递消息
首先ActorSystem创建Actor,通过ActorRef(可以理解为Actor的代理)发送消息,发送至Dispatcher Message分发器(线程池)进行转发或者分发,发送到自己的MailBox(消息队列)中,消息根据FIFO的规则(先进先出原则),然后调用recive方法接受并且进行处理。
案例1

自己给自己发消息的案例(单机进行)





package cn.sheep.actor

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

class HelloActor extends Actor{
    // 接受消息的
    override def receive: Receive = {
        // 接受消息并处理
        case "你好帅" => println("竟说实话,我喜欢你这种人!")
        case "丑" => println("滚犊子 !")
        case "stop" => {
            context.stop(self)  // 停止自己的actorRef
            context.system.terminate()  // 关闭ActorSystem
        }
    }
}


object HelloActor {

    private val nBFactory = ActorSystem("NBFactory")// actorSystem,工厂,参数为工厂的名字
    private val helloActorRef = nBFactory.actorOf(Props[HelloActor], "helloActor")    // 通过actorSystem创建ActorRef ,参数为actor的名字
    def main(args: Array[String]): Unit = {

        // 给自己发送消息
        helloActorRef ! "你好帅"
        helloActorRef ! "丑"

        helloActorRef ! "stop"  

    }

}

案例2:双人相互发消息的案例,模拟龙哥和峰哥打乒乓球
a要给b发消息,要拿到b的ActorRef引用,利用sender直接给a发送回消息
(单机进行)
代码:
龙哥actor

package cn.sheep.actor

import akka.actor.{Actor, ActorRef}

/**
  * 马龙,先发消息,所以需要引用到对方actor 的ActorRef(就是峰哥)
  */
class LongGeActor(val fg: ActorRef) extends Actor{
    // 接受消息的
    override def receive: Receive = {
        case "start" => {
            println("龙龙:I'm OK !")
            fg ! "啪"   //马龙中持有的峰哥的ActorRef,即给对方发消息
        }
   //接收对方发的 消息
        case "啪啪" => {
            println("你真猛!")
            Thread.sleep(1000)
            fg ! "啪"    //继续发给峰哥消息
        }
    }
}

峰哥actor

package cn.sheep.actor

import akka.actor.Actor

/**
  * 高峰
  */
class FengGeActor extends Actor{

    override def receive: Receive = {
        case "start" => println("峰峰说:I'm OK !")
        
    //接收到马龙给自己发送的消息
 case "啪" => {
            println("峰峰:那必须滴!")  
            Thread.sleep(1000)   //停止一秒
            sender() ! "啪啪"    //给消息发送方返回的消息
        }
    }
}

开始打

package cn.sheep.actor

import akka.actor.{ActorSystem, Props}

object PingPongApp extends App{

    // actorSystem
    private val pingPongActorSystem = ActorSystem("PingPongActorSystem")

    // 通过actorSystem创建ActorRef

    // 创建FengGeActor
    private val ffActorRef = pingPongActorSystem.actorOf(Props[FengGeActor], "ff")

    // 创建LongGeActorRef,new LongGeActor(ffActorRef))的意思是需要将接收方的ActorRef作为参数传入
    private val mmActorRef = pingPongActorSystem.actorOf(Props(new LongGeActor(ffActorRef)), "mm")
   //峰哥给自己发送消息
    ffActorRef ! "start"  
//马龙给自己发送消息
    mmActorRef ! "start"

}

案例三

在这里插入图片描述

需要知道serverActor的ip和端口
serverActor


class Edu360Server extends Actor{
    // 用来接受客户端发送过来的问题的
    override def receive: Receive = {
        case "start" => println("老娘已就绪 !")

        case ClientMessage(msg) => {
            println(s"收到客户端消息:$msg")
            msg match {
                case "你叫啥" => sender() ! ServerMessage("铁扇公主")
                case "你是男是女" => sender() ! ServerMessage("老娘是男的")
                case "你有男票吗" => sender() ! ServerMessage("没有")
                case _ => sender() ! ServerMessage("What you say ?") //sender()发送端的代理对象, 发送到客户端的mailbox中 -> 客户端的receive
            }
        }

    }
}

object Edu360Server {

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

        val host = "127.0.0.1"
        val port = 8088

        val config = ConfigFactory.parseString(
            s"""
               |akka.actor.provider="akka.remote.RemoteActorRefProvider"
               |akka.remote.netty.tcp.hostname=$host
               |akka.remote.netty.tcp.port=$port
        """.stripMargin)

        // 指定IP 和 端口
        val actorSystem = ActorSystem("Server", config)

          orRef = actorSystem.actorOf(Props[Edu360Server], "shanshan")
        serverActorRef ! "start" // 到自己的mailbox -》 receive方法 


    }
}

ClientActor


package cn.sheep.robot

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

import scala.io.StdIn

class ClientActor(host: String, port: Int) extends Actor{

    var serverActorRef: ActorSelection = _ // 服务端的代理对象

    // 在receive方法之前调用
    override def preStart(): Unit = {
        // akka.tcp://Server@127.0.0.1:8088
        serverActorRef = context.actorSelection(s"akka.tcp://Server@${host}:${port}/user/shanshan")
    }

    // mailbox ->receive
    override def receive: Receive = { // shit
        case "start" => println("牛魔王系列已启动...")
        case msg: String => { // shit
            serverActorRef ! ClientMessage(msg) // 把客户端输入的内容发送给 服务端(actorRef)--》服务端的mailbox中 -> 服务端的receive
        }
        case ServerMessage(msg) => println(s"收到服务端消息:$msg")
    }
}

object ClientActor  {


    def main(args: Array[String]): Unit = {
        val host = "127.0.0.1"
        val port  = 8089

        val serverHost = "127.0.0.1"
        val serverPort = 8088

        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 clientSystem = ActorSystem("client", config)

        // 创建dispatch | mailbox
        v al actorRef = clientSystem.actorOf(Props(new ClientActor(serverHost, serverPort.toInt)), "NMW-002")

        actorRef ! "start" // 自己给自己发送了一条消息 到自己的mailbox => receive

        while (true) {
            val question = StdIn.readLine() // 同步阻塞的, shit
            actorRef ! question // mailbox -> receive
        }
    }






}

样例类

package cn.sheep.robot



//    样例类,使用 case 关键字 修饰的类, 其重要的特征就是支持模式匹配

//    样例类默认是实现了序列化接口

// 服务端发送给客户端的消息格式
case class ServerMessage(msg: String)

// 客户端发送给服务器端的消息格式
case class ClientMessage(msg: String)

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!