基于Scala的并发编程模型Akka

一、Akka概念

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

 

 

二、Akka 中 Actor 模型

2.1  Actor模型介绍

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

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

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

基于Scala的并发编程模型Akka_第1张图片

        从图中可以看到,Actor 与 Actor 之前只能用消息进行通信,当某一个 Actor 给另外一个 Actor发消息,消息是有顺序的,只需要将消息投寄到相应的邮箱,至于对方 Actor 怎么处理你的消息你并不知道,当然你也可等待它的回复。

 

2.2  Actor工作机制

        Actor 是 ActorSystem 创建的,ActorSystem 的职责是负责创建并管理其创建的 Actor,ActorSystem 的单例的,一个 JVM 进程中有一个即可,而 Acotr 是多例的。

基于Scala的并发编程模型Akka_第2张图片

如果Actor A需要给Actor B发送消息,必须获得ActorRef B,然后调用ActorRef B的recive方法,在该方法内有相关的API(sender方法)可以获得是由Actor A作为发送方发送给B的消息。

 

2.3  HelloActor示例

导入pom 依赖:



    4.0.0

    cn.itcats.akka
    itcats-akka
    1.0-SNAPSHOT

    
    
        UTF-8
        2.11.8
        2.11
        2.4.17
    

    
        
        
            org.scala-lang
            scala-library
            ${scala.version}
        

        
        
            com.typesafe.akka
            akka-actor_${scala.compat.version}
            ${akka.version}
        

        
        
            com.typesafe.akka
            akka-remote_${scala.compat.version}
            ${akka.version}
        
    

    
    
        
        src/main/scala
        src/test/scala
        
            
            
                net.alchim31.maven
                scala-maven-plugin
                3.2.2
                
                    
                        
                            compile
                            testCompile
                        
                        
                            
                                -dependencyfile
                                ${project.build.directory}/.scala_dependencies
                            
                        
                    
                
            

            
            
                org.apache.maven.plugins
                maven-shade-plugin
                2.4.3
                
                    
                        package
                        
                            shade
                        
                        
                            
                                
                                    *:*
                                    
                                        META-INF/*.SF
                                        META-INF/*.DSA
                                        META-INF/*.RSA
                                    
                                
                            
                            
                                
                                    reference.conf
                                
                                
                                
                                    cn.sheep.robot.ClientActor
                                
                            
                        
                    
                
            
        
    

创建一个最简单的Actor模型,实现自己给自己发消息的环回实验

//继承akka.actor.Actor
class HelloActor extends Actor{
  //用于接收Receive
  //type Receive = PartialFunction[Any, Unit]
  override def receive: Receive = {
    //接收消息并处理
    case "itcats" => println("This is itcats")
    case "itcats_cn" => println("This is itcats_cn")
    case "stop" => {
        context.stop(self)           //self: ActorRef引用自己,将Actor关闭
        context.system.terminate()   //关闭ActorSystem
    }
    case _ => println("What is is?")
  }
}

object HelloActor{
  //1.获取ActorSystem工厂对象,并命名
  private val helloActorFactory = ActorSystem("HelloActorFactory")
  //2.从ActorSystem获取HelloActor的引用对象ActorRef,并命名
  private val helloActorRef: ActorRef = helloActorFactory.actorOf(Props[HelloActor],"helloActor")


  def main(args: Array[String]): Unit = {
    //给自己(HelloActor)发送消息  方法名称就是!
    helloActorRef ! "itcats"      //给自己发送了消息itcats
    helloActorRef ! "itcats_cn"   //给自己发送了消息itcats_cn
    helloActorRef ! "stop"        //给自己发送消息stop
  }
}

打印结果:

This is itcats
This is itcats_cn

打印完运行结果后,发现Actor工程一直在运行。实际上说明了Dispatcher Message内部是一个线程池,receive()方法实际上是从自己的Mail Box中取出消息,内部类似于调用Runnable的run方法。

 

三、"乒乓球"模型的Akka通信

import akka.actor.{Actor, ActorRef}

class AActor(val bActor: ActorRef) extends Actor{
  override def receive: Receive = {
    case "a is ready" => {
      println("a is ready")
      bActor ! "啪"
    }
    case "啪啪" => {
      println("接到了BActor的球")
      Thread.sleep(1000)
      bActor ! "啪"
    }
  }
}
import akka.actor.Actor

class BActor extends Actor {
  override def receive: Receive = {
    case "b is ready" => println("b is ready")
    case "啪" => {
      println("接到了AActor的球")
      Thread.sleep(1000)
      sender() ! "啪啪"
    }
  }
}

执行类:

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

//  A --> B
//  B --> A
object PingPongMain {
  def main(args: Array[String]): Unit = {
    //创建ActorSystem
    val pingpongActorSystem = ActorSystem("pingpongActorSystem")
    //创建bActor的引用对象
    val bActor: ActorRef = pingpongActorSystem.actorOf(Props[BActor],"bActor")
    //创建aActor的引用对象
    val aActor: ActorRef = pingpongActorSystem.actorOf(Props(new AActor(bActor)),"aActor")
    bActor ! "b is ready"
    aActor ! "a is ready"
  }
}

 

四、基于Akka实现智能聊天客服

假设智能聊天客服能回答如下问题

1、在吗

2、你叫什么名字

3、你是男的还是女的

4、你在哪

对于其他问题直接返回:我听不懂你的问题!

基于Scala的并发编程模型Akka_第3张图片

客户端程序:

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

class ChatServer extends Actor {
  override def receive: Receive = {
    case "start" => println("服务器已就绪")
    case ClientToServerMessage(msg) => {
      println(s"收到客户端消息: $msg")
      msg match {
        case "在吗" => sender() ! ServerToClientMessage("我在")
        case "你叫什么名字" => sender() ! ServerToClientMessage("我是中国电信语音客服")
        case "你是男的还是女的" => sender() ! ServerToClientMessage("我是小姐姐哟~")
        case "你在哪" => sender() ! ServerToClientMessage("我在广东省广州市")
        case _ => sender() ! ServerToClientMessage("我听不懂你亲的意思,客服还在不断学习中哦~")
      }
    }
  }
}

//为了服务端能够独立运行,定义一个伴生对象
object ChatServer extends App {
  val host: String = "127.0.0.1"
  val port: Int = 8878

  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和端口号
  private val server = ActorSystem("Server", config)
  private val serverActorRef: ActorRef = server.actorOf(Props[ChatServer], "chat")
  serverActorRef ! "start"
}

客户端程序:

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

class ClientActor extends Actor{
  var serverActorRef: ActorSelection = _
  //根据服务端的协议地址
  override def preStart(): Unit = {
    //获得服务端的引用对象
    serverActorRef = context.actorSelection("akka.tcp://[email protected]:8878/user/chat")
  }

  override def receive = {
    case "start" => println("聊天客户端已就绪")
    case msg: String => {
      serverActorRef ! ClientToServerMessage(msg)
    }
    case ServerToClientMessage(msg) => {
      println(s"收到服务端消息: $msg")
    }
  }
}

//创建伴生类对象,可执行
object ClientActor extends App {
  val host: String = "127.0.0.1"
  //再启动只需修改端口号即可
  val port: Int = 8879

  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和端口号
  private val clientSystem = ActorSystem("client", config)
  private val actorRef: ActorRef = clientSystem.actorOf(Props[ClientActor], "chatClient1")
  //再启动一个客户端修改名称和端口号即可
  //private val actorRef: ActorRef = clientSystem.actorOf(Props[ClientActor], "chatClient2")
  actorRef ! "start"

  //接收客户端的参数
  while(true){
    //获取用户输入的内容
    val question = StdIn.readLine()
    actorRef ! question
  }
}

协议格式样例类:

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

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

 

你可能感兴趣的:(Scala)