Scala入门指南之Akka并发编程框架简介

Scala入门指南系列教程目录

Scala入门指南之配置环境

Scala入门指南之面向对象编程基础

Scala入门指南之面向对象编程进阶

Scala入门指南之集合(一)

Scala入门指南之集合(二)

Scala入门指南之函数式编程

Scala入门指南之Akka并发编程框架简介

前面介绍了那么多关于Scala的基础语法,终于迎来了了Scala入门指南的最后一篇——Akka简介。看标题读者就能知道,Akka是一个并发编程框架,也可以通俗点说,就是用来开发多线程的。Java中的多线程开发是使用加锁的机制来确保同一时间只有一个线程访问共享数据的传统方法,但是这种方式存在资源争夺、以及死锁问题,程序越大问题越麻烦。而Scala作为Java的升级版或者说是Java的现代版,自然要摒弃这种老套的理念。Scala使用Actor并发编程模型,是一种基于事件模型的并发机制,也是一种不共享数据,依赖消息船体的一种并发编程模式,可以有效避免资源争夺、死锁等情况。

Actor的并发编程模型很像消息队列,只不过消息队列分生产者、消费者和消息队列,而Actor的模型中只有多个Actor。Actor和Actor之间相互传递消息,并没有一个中间存放的地方,在Actor收到消息后执行用户定义的操作。

读者可能会有疑问,标题是介绍Akka,为什么在说Actor呢?他们之间的关系是什么呢?其实Akka和Actor的理念是一样的,只不过Actor是Scala自己的实现,并不能被Java所用。Akka虽然是使用Scala开发的库,但是他可以被Scala和Java使用。也就是说Akka是独立于Scala的库。同时Akka还有一些其他的优点,比如内容容错机制,允许Actor在出错时进行恢复和重置操作;超级轻量级的事件处理,每GB堆内存几百万Actor;更加适合高并发,可以构建分布式程序并且是可扩展的等等。

那么Akka是怎么改进的呢?Akka引入了ActorSystem、ActorRef(Actor引用)、Message Dispatcher(消息分发器)、MailBox。具体的基本流程为:

1.用户创建一个ActorSystem,由他来管理Actor,相当于是Actor的一个文件夹。

2.用户通过ActorSystem来创建一个ActorRef,并将消息发送给ActorRef,这里ActorRef扮演的是个Proxy(代理)的角色。

3.ActorRef把消息发送给Message Dispatcher,这个消息分发器用来把消息分发下去,相当于是个传达室的作用。

4.Message Dispatcher把消息按照顺序保存到目标Actor的MailBox中。可以理解为因为Actor很忙,所以传达室收到报纸后把报纸交给小助手。

5.Message Dispatcher将MailBox放到一个线程中。相当于传达室安排小助手去工作。

5.MailBox按照顺序取出消息,最终把它传递给目标Actor接收的方法中。这一步相当于小助手按照接收的顺序把报纸给到接收人。

可以看到Akka在Actor和Actor之间传递消息的工作机制里引入了更多的角色,这些角色的出现主要都是辅助的作用,让消息分发更加高效和准确。虽然系统变得复杂了,但是复杂带来的高效和准确却是值得的。工作流程如图:

Scala入门指南之Akka并发编程框架简介_第1张图片

下面我们就用Akka实现一个简单的两个进程之间的通信。

需求是这样的,现在有两个进程,一个是worker,工作进程,一个是master,主进程。工作进程接收到安装的命令后,往主进程发送连接消息,主进程收到消息后返回命令成功消息给工作进程表明安装成功。

既然是两个进程,我们就需要新建一个Maven项目akka-demo,然后在项目里新建两个maven模块,一个叫akka-worker,一个叫akka-master。

分别在akka-worker和akka-master模块的pom.xml中加入如下配置:


        1.8
        1.8
        UTF-8
        2.11.8
        2.11
    

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

        
            com.typesafe.akka
            akka-actor_2.11
            2.3.14
        

        
            com.typesafe.akka
            akka-remote_2.11
            2.3.14
        

    

    
        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
                                
                                
                                    
                                
                            
                        
                    
                
            
        
    

分别在akka-worker和akka-master的src/main/resources中新建application.conf,写下如下内容:

akka.actor.provider = "akka.remote.RemoteActorRefProvider"
akka.remote.netty.tcp.hostname = "127.0.0.1"
akka.remote.netty.tcp.port = "8888"

然后修改akka-master中的port参数为9999。

这个配置文件指明了akka的Actor Provider以及ip地址和端口。这样就可以通过url跨模块获取Actor了。

分别在akka-worker和akka-master中的src/main中新建scala文件夹,在src/test中新建scala文件夹,然后在maven标签页中点击刷新按钮,成功后会看到scala文件夹变色了。

在akka-worker和akka-master的src/main/scala中新建cn.csdn.demo包。

先写akka-worker的代码:

在包下新建一个Scala的Object类Entrance,作为主方法的入口,写下如下代码:

package cn.csdn.demo

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

object Entrance {
  def main(args: Array[String]): Unit = {
    val actorSystem = ActorSystem("actorSystem",ConfigFactory.load())
    val workerActor = actorSystem.actorOf(Props(WorkerActor),"workerActor")
    workerActor!"setup"
  }
}

第8行创建了一个叫actorSystem的ActorSystem,采用默认配置。

第9行创建了一个叫workerActor的WorkerActor。

第10行向workerActor使用“Actor变量名! 内容”的格式发送了一个“setup”消息。这里要介绍几个不同发送消息的格式。!感叹号用来发送异步且没有返回值的消息,!?感叹号问号用来发送同步消息并等待返回值,!!双感叹号用来发送异步且有返回值消息,返回值为Future[Any]类型。Future类型有两个方法,isSet()和apply(),通过判断isSet()的返回bool值来判断是否收到具体的返回消息,为true就可以用apply()方法来获取具体返回值。为了简化,我们就发送一个异步且没有返回值的消息。

下面要定义WorkerActor类:

在包下新建一个Scala的Object类WorkerActor,写下如下代码:

package cn.csdn.demo

import akka.actor.Actor
object WorkerActor extends Actor {
  override def receive: Receive = {
    case "setup" =>{
      println("WorkerActor接收到:Entrance发送过来的指令 setup!.")
      val masterActor = context.system.actorSelection("akka.tcp://[email protected]:8888/user/masterActor")
      masterActor !"connect"
    }
    case "success" =>println("workerActor接收到,masterActor发送过来的消息:success!")
  }
}

在单例对象扩展aka.actor.Actor类时,需要重写receive方法,方法返回一个Receive对象,方法体内用守卫代码的方式判断收到的消息,并进行相应的操作。这里分为接收到“setup”字符串和“success”字符串两种情况。接收到“success”之后比较简单,打印一下workerActor收到了success就可以了。接收到“setup”之后,需要获取masterActor,然后向其发送消息“connect”。

第8行展示获取masterActor的方式,利用context.system.actorSelection方法来获取,传入的参数为目标Actor的URL。需要介绍一下,对于本地的Actor,只需要使用“akka://actorSystem名称/user/Actor名称”的格式就可以了,但是这里因为是不同进程,masterActor相对于workerActor是远程Actor,所以需要使用“akka.tcp://actorSystem名称@ip地址:port/user/Actor名称“的格式来获取。这里的actorSystem名称为masterActor的ActorSystem名称。为了简化,我们把两个进程的ActorSystem都命名为actorSystem。

第9行来向目标Acotr发送异步且无返回的“connect”消息。

akka-worker就写完了,下面写akka-master:

在cn.csdn.demo包下新建一个Entrance的Object类,写下如下代码:

package cn.csdn.demo

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

object Entrance {
  def main(args: Array[String]): Unit = {
    val actorSystem = ActorSystem("actorSystem",ConfigFactory.load())
    val masterActor = actorSystem.actorOf(Props(MasterActor),"masterActor")
  }
}

这里和akka-worker的Entrance差不多,区别在于新建了一个叫masterActor的MasterActor,并且没有发送消息。因为从工作流程来看,masterActor是消息接收方,他需要等待workerActor发送消息来之后再返回成功消息吗,所以并没有发送消息的代码。运行之后。主方法会一直保持运行,等待消息,并不会结束。

新建一个MasterActor的Object类。写下如下代码:

package cn.csdn.demo

import akka.actor.Actor
object MasterActor extends Actor {
  override def receive: Receive = {
    case "connect" =>{
      println("MasterActor接收到:connect!...")
      sender !"success"
    }
  }
}

Actor方法里有个final的sender对象,表示消息的发送方,他是一个ActorRef类型,所以可以直接用它来发送消息给发送方。

到这里代码就写完了,先运行akka-master的主方法,等运行成功后再运行akka-worker的主方法。可以看到在akka-worker的控制台打印了如下信息:

在akka-master的控制台打印了如下信息:

成功实现了两个进程之间的通信。

要注意的是,两个进程的启动顺序是一定的,不可以颠倒,不然会有Dead Letter(死信)报错,因为先启动Worker之后,这时候Master还没启动,WorkerActor发送消息给MasterActor,是不可达的。第二个是,这两个进程必须在同一个maven项目下的两个模块中,不可以分两个项目,不然也是会有Dead Letter报错,这个原因我还不清楚,如果有哪位大神了解的,不妨留下您的评论。

至此,Scala的入门指南就介绍完毕了。

感谢观看,如果您觉得文章写得还不错,不妨点个赞。如果您觉得有什么疑惑或者不对的地方,可以留下评论,看到我会及时回复的。如果您关注一下我,那么我会更高兴的,谢谢!

你可能感兴趣的:(Scala,scala)