Scala notes finishing (9): Actor and AKKA

[TOC]


Overview

Scala's Actor is somewhat similar to multi-threaded programming in Java. But the difference is that Scala's Actor provides a different model than multithreading. Scala's Actor avoids locks and shared state as much as possible, so as to avoid resource contention when multi-threaded concurrency occurs, thereby improving the performance of multi-threaded programming.

The distributed multi-threading framework used in Spark is Akka, a multi-threaded class library for Scala. Akka also implements a model similar to Scala Actor, whose core concept is also Actor. The Scala Actor model was still in use in 2.1.0, but was abandoned in 2.1.1. Spark began to switch to AKKA to replace Scala Actor, but the concept and principle of Scala Actor are still the same. So learning Scala Actor is helpful for us to learn AKKA, Spark

The reason for learning Scala Actor, AKKA is because when we learn Spark source code, we can understand Spark source code, because AKKA's transmission mechanism is widely used in the underlying message passing mechanism.

scale actor

Before using it, you need to introduce maven dependencies:

<!--scala actor-->
<dependency>
    <groupId>org.scala-lang</groupId>
    <artifactId>scala-actors</artifactId>
    <version>2.10.5</version>
</dependency>

Actor one-way communication

The test code is as follows:

package cn.xpleaf.bigdata.p5.myactor
import scala.actors.Actor
/**
  * 学习scala actor的基本操作
  * 和java中的Runnable Thread几乎一致
  *
  * 第一步:编写一个类,扩展特质trait Actor(scala 的actor)
  * 第二步:复写其中的act方法
  * 第三步:创建该actor的对象,调用该对象的start()方法,启动该线程
  * 第四步:通过scala的操作符"!",发送消息
  * 第五步:结束的话,调用close即可
  *
  * 模拟单向打招呼
  */
object ActorOps {
    def main(args: Array[String]): Unit = {
        val mFActor = new MyFirstActor()
        mFActor.start()
        // 发送消息
        mFActor ! "小美,睡了吗?"
        mFActor ! "我去洗澡了~"
        mFActor ! "呵呵"
    }
}

class MyFirstActor extends Actor {
    override def act(): Unit = {
        while(true) {
            receive {
                case str: String => println(str)
            }
        }
    }
}

The output is as follows:

小美,睡了吗?
我去洗澡了~
呵呵

Actor messaging using case classes

The test code is as follows:

package cn.xpleaf.bigdata.p5.myactor

import scala.actors.Actor

/**
  *
  */
object GreetingActor {
    def main(args: Array[String]): Unit = {
        val ga = new GreetingActor
        ga.start()

        ga ! Greeting("小美")
        ga ! WorkContent("装系统")
    }
}

case class Greeting(name:String)
case class WorkContent(content:String)

class GreetingActor extends Actor {
    override def act(): Unit = {
        while(true) {
            receive {
                case Greeting(name) => println(s"Hello, $name")
                case WorkContent(content) => println(s"Let's talk about sth. with $content")
            }
        }
    }
}

The output is as follows:

Hello, 小美
Let's talk about sth. with 装系统

actors communicate with each other

The test code is as follows:

package cn.xpleaf.bigdata.p5.myactor

import scala.actors.Actor

/**
  * actor之线程间,互相通信
  *
  * studentActor
  *     向老师问了一个问题
  *
  * teacherActor
  *     向学生做回应
  *
  * 通信的协议:
  * 请求,使用Request(内容)来表示
  * 响应,使用Response(内容)来表示
  */
object _03CommunicationActorOps {
    def main(args: Array[String]): Unit = {
        val teacherActor = new TeacherActor()
        teacherActor.start()
        val studentActor = new StudentActor(teacherActor)
        studentActor.start()

        studentActor ! Request("老李啊,scala学习为什么这么难啊")
    }
}

case class Request(req:String)
case class Response(resp:String)

class StudentActor(teacherActor: TeacherActor) extends Actor {
    override def act(): Unit = {
        while(true) {
            receive {
                case Request(req) => {
                    // 向老师请求相关的问题
                    println("学生向老师说:" + req)
                    teacherActor ! Request(req)
                }
                case Response(resp) => {
                    println(resp)
                    println("高!")
                }
            }
        }
    }
}

class TeacherActor() extends Actor {
    override def act(): Unit = {
        while (true) {
            receive {
                case Request(req) => {  // 接收到学生的请求
                    sender ! Response("这个问题,需要如此搞定~")
                }
            }
        }
    }
}

The output is as follows:

学生向老师说:老李啊,scala学习为什么这么难啊
这个问题,需要如此搞定~
高!

Synchronization and Future of Messages

1. By default in Scala, messages are sent asynchronously; but if the message sent is synchronous, that is, after the other party accepts it, it must return the result to itself, then you can use the !? method to send the message. which is:

val response= activeActor !? activeMessage

2. If you want to send a message asynchronously, but you want to get the return value of the message later, you can use Future. That is, the !! syntax, as follows:

val futureResponse = activeActor !! activeMessage
val activeReply = future()

AKKA actor

First you need to add akka's maven dependencies:

<!--akka actor-->
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor_2.10</artifactId>
    <version>2.3.16</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-remote_2.10</artifactId>
    <version>2.3.16</version>
</dependency>
<dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-slf4j_2.10</artifactId>
    <version>2.3.16</version>
</dependency>

AKKA Messaging - Local

The principle is as follows:

Scala notes finishing (9): Actor and AKKA

_01StudentActorOps

package cn.xpleaf.bigdata.p5.myakka.p1

import akka.actor.{Actor, ActorSystem, Props}
import cn.xpleaf.bigdata.p5.myakka.MessageProtocol.{QuoteRequest, QuoteResponse}

import scala.util.Random

/**
  * 基于AKKA Actor的单向通信案例
  * 学生向老师发送请求
  */
object _01StudentActorOps {
    def main(args: Array[String]): Unit = {
        // 第一步:构建Actor操作系统
        val actorSystem = ActorSystem("StudentActorSystem")
        // 第二步:actorSystem创建TeacherActor的代理对象ActorRef
        val teacherActorRef = actorSystem.actorOf(Props[TeacherActor])
        // 第三步:发送消息
        teacherActorRef ! QuoteRequest()

        Thread.sleep(2000)
        // 第四步:关闭
        actorSystem.shutdown()
    }
}

class TeacherActor extends Actor {
    val quotes = List(
        "Moderation is for cowards",
        "Anything worth doing is worth overdoing",
        "The trouble is you think you have time",
        "You never gonna know if you never even try")

    override def receive = {
        case QuoteRequest() => {
            val random = new Random()

            val randomIndex = random.nextInt(quotes.size)
            val randomQuote = quotes(randomIndex)

            val response = QuoteResponse(randomQuote)
            println(response)
        }
    }
}

MessageProtocol

Several test programs of akka communication will use this object, which is only given here, and will not be given later.

package cn.xpleaf.bigdata.p5.myakka

/**
  * akka actor通信协议
  */
object MessageProtocol {

    case class QuoteRequest()

    case class QuoteResponse(resp: String)

    case class InitSign()

}

object Start extends Serializable

object Stop extends Serializable

trait Message {
    val id: String
}

case class Shutdown(waitSecs: Int) extends Serializable

case class Heartbeat(id: String, magic: Int) extends Message with Serializable

case class Header(id: String, len: Int, encrypted: Boolean) extends Message with Serializable

case class Packet(id: String, seq: Long, content: String) extends Message with Serializable

test

The output is as follows:

QuoteResponse(Anything worth doing is worth overdoing)

AKKA request and response - local

The principle is as follows:

Scala notes finishing (9): Actor and AKKA

TeacherActor

package cn.xpleaf.bigdata.p5.myakka.p2

import akka.actor.Actor
import cn.xpleaf.bigdata.p5.myakka.MessageProtocol.{QuoteRequest, QuoteResponse}

import scala.util.Random

/**
  * Teacher Actor
  */
class TeacherActor extends Actor {
    val quotes = List(
        "Moderation is for cowards",
        "Anything worth doing is worth overdoing",
        "The trouble is you think you have time",
        "You never gonna know if you never even try")

    override def receive = {
        case QuoteRequest() => {
            val random = new Random()

            val randomIndex = random.nextInt(quotes.size)
            val randomQuote = quotes(randomIndex)

            val response = QuoteResponse(randomQuote)
//            println(response)
            sender ! response
        }
    }
}

StudentActor

package cn.xpleaf.bigdata.p5.myakka.p2

import akka.actor.{Actor, ActorLogging, ActorRef}
import cn.xpleaf.bigdata.p5.myakka.MessageProtocol.{InitSign, QuoteRequest, QuoteResponse}

/**
  * Student Actor
  * 当学生接收到InitSign信号之后,便向老师发送一条Request请求的消息
  */
class StudentActor(teacherActorRef:ActorRef) extends Actor with ActorLogging {
    override def receive = {
        case InitSign => {
            teacherActorRef ! QuoteRequest()
//            println("student send request")
        }
        case QuoteResponse(resp) => {
            log.info(s"$resp")
        }
    }
}

DriverApp

package cn.xpleaf.bigdata.p5.myakka.p2

import akka.actor.{ActorSystem, Props}
import cn.xpleaf.bigdata.p5.myakka.MessageProtocol.InitSign

object DriverApp {
    def main(args: Array[String]): Unit = {
        val actorSystem = ActorSystem("teacherStudentSystem")
        // 老师的代理对象
        val teacherActorRef = actorSystem.actorOf(Props[TeacherActor], "teacherActor")
        // 学生的代理对象
        val studentActorRef = actorSystem.actorOf(Props[StudentActor](new StudentActor(teacherActorRef)), "studentActor")

        studentActorRef ! InitSign

        Thread.sleep(2000)

        actorSystem.shutdown()
    }
}

test

The output is as follows:

[INFO] [04/24/2018 10:02:19.932] [teacherStudentSystem-akka.actor.default-dispatcher-2] [akka://teacherStudentSystem/user/studentActor] Anything worth doing is worth overdoing

AKKA Request and Response - Remote

application.conf

MyRemoteServerSideActor {
  akka {
    actor {
      provider = "akka.remote.RemoteActorRefProvider"
    }
    remote {
      enabled-transports = ["akka.remote.netty.tcp"]
      netty.tcp {
        hostname = "127.0.0.1"
        port = 2552
      }
    }
  }
}
MyRemoteClientSideActor {
  akka {
    actor {
      provider = "akka.remote.RemoteActorRefProvider"
    }
  }
}

RemoteActor

package cn.xpleaf.bigdata.p5.myakka.p3

import akka.actor.{Actor, ActorLogging}
import cn.xpleaf.bigdata.p5.myakka.{Header, Shutdown, Start, Stop}

class RemoteActor extends Actor with ActorLogging {
    def receive = {
        case Start => { // 处理Start消息
            log.info("Remote Server Start ==>RECV Start event : " + Start)
        }
        case Stop => { // 处理Stop消息
            log.info("Remote Server Stop ==>RECV Stop event: " + Stop)
        }
        case Shutdown(waitSecs) => { // 处理Shutdown消息
            log.info("Remote Server Shutdown ==>Wait to shutdown: waitSecs=" + waitSecs)
            Thread.sleep(waitSecs)
            log.info("Remote Server Shutdown ==>Shutdown this system.")
            context.system.shutdown // 停止当前ActorSystem系统
        }
        case Header(id, len, encrypted) => log.info("Remote Server => RECV header: " + (id, len, encrypted)) // 处理Header消息
        case _ =>
    }
}

AkkaServerApplication

package cn.xpleaf.bigdata.p5.myakka.p3

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

object AkkaServerApplication extends App {
    // 创建名称为remote-system的ActorSystem:从配置文件application.conf中获取该Actor的配置内容
    val system = ActorSystem("remote-system",
        ConfigFactory.load().getConfig("MyRemoteServerSideActor"))

    val log = system.log
    log.info("===>Remote server actor started: " + system)
    // 创建一个名称为remoteActor的Actor,返回一个ActorRef,这里我们不需要使用这个返回值
    system.actorOf(Props[RemoteActor], "remoteActor")

}

ClientActor

package cn.xpleaf.bigdata.p5.myakka.p3

import akka.actor.SupervisorStrategy.Stop
import akka.actor.{Actor, ActorLogging}
import cn.xpleaf.bigdata.p5.myakka.{Header, Start}

class ClientActor extends Actor with ActorLogging {

    // akka.<protocol>://<actor system>@<hostname>:<port>/<actor path>
    val path = "akka.tcp://[email protected]:2552/user/remoteActor" // 远程Actor的路径,通过该路径能够获取到远程Actor的一个引用
    val remoteServerRef = context.actorSelection(path) // 获取到远程Actor的一个引用,通过该引用可以向远程Actor发送消息
    @volatile var connected = false
    @volatile var stop = false

    def receive = {
        case Start => { // 发送Start消息表示要与远程Actor进行后续业务逻辑处理的通信,可以指示远程Actor初始化一些满足业务处理的操作或数据
            send(Start)
            if (!connected) {
                connected = true
                log.info("ClientActor==> Actor connected: " + this)
            }
        }
        case Stop => {
            send(Stop)
            stop = true
            connected = false
            log.info("ClientActor=> Stopped")
        }
        case header: Header => {
            log.info("ClientActor=> Header")
            send(header)
        }
        case (seq, result) => log.info("RESULT: seq=" + seq + ", result=" + result) // 用于接收远程Actor处理一个Packet消息的结果
        case m => log.info("Unknown message: " + m)
    }

    private def send(cmd: Serializable): Unit = {
        log.info("Send command to server: " + cmd)
        try {
            remoteServerRef ! cmd // 发送一个消息到远程Actor,消息必须是可序列化的,因为消息对象要经过网络传输
        } catch {
            case e: Exception => {
                connected = false
                log.info("Try to connect by sending Start command...")
                send(Start)
            }
        }
    }

}

AkkaClientApplication

package cn.xpleaf.bigdata.p5.myakka.p3

import akka.actor.{ActorSystem, Props}
import cn.xpleaf.bigdata.p5.myakka.{Header, Start}
import com.typesafe.config.ConfigFactory

object AkkaClientApplication extends App {
    // 通过配置文件application.conf配置创建ActorSystem系统
    val system = ActorSystem("client-system",
        ConfigFactory.load().getConfig("MyRemoteClientSideActor"))
    val log = system.log
    val clientActor = system.actorOf(Props[ClientActor], "clientActor") // 获取到ClientActor的一个引用
    clientActor ! Start // 发送一个Start消息,第一次与远程Actor握手(通过本地ClientActor进行转发)
    Thread.sleep(2000)
    clientActor ! Header("What's your name: Can you tell me ", 20, encrypted = false) // 发送一个Header消息到远程Actor(通过本地ClientActor进行转发)
    Thread.sleep(2000)

}

test

The output of the server is as follows:

[INFO] [04/24/2018 09:39:49.271] [main] [Remoting] Starting remoting
[INFO] [04/24/2018 09:39:49.508] [main] [Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:2552]
[INFO] [04/24/2018 09:39:49.509] [main] [Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:2552]
[INFO] [04/24/2018 09:39:49.517] [main] [ActorSystem(remote-system)] ===>Remote server actor started: akka://remote-system
[INFO] [04/24/2018 09:46:01.872] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://[email protected]:2552/user/remoteActor] Remote Server Start ==>RECV Start event : cn.xpleaf.bigdata.p5.myakka.Start$@325737b3
[INFO] [04/24/2018 09:46:03.501] [remote-system-akka.actor.default-dispatcher-3] [akka.tcp://[email protected]:2552/user/remoteActor] Remote Server => RECV header: (What's your name: Can you tell me ,20,false)

The client output is as follows:

[INFO] [04/24/2018 09:46:01.274] [main] [Remoting] Starting remoting
[INFO] [04/24/2018 09:46:01.479] [main] [Remoting] Remoting started; listening on addresses :[akka.tcp://[email protected]:2552]
[INFO] [04/24/2018 09:46:01.480] [main] [Remoting] Remoting now listens on addresses: [akka.tcp://[email protected]:2552]
[INFO] [04/24/2018 09:46:01.493] [client-system-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:2552/user/clientActor] Send command to server: cn.xpleaf.bigdata.p5.myakka.Start$@4f00805d
[INFO] [04/24/2018 09:46:01.496] [client-system-akka.actor.default-dispatcher-4] [akka.tcp://[email protected]:2552/user/clientActor] ClientActor==> Actor connected: cn.xpleaf.bigdata.p5.myakka.p3.ClientActor@5a85b576
[INFO] [04/24/2018 09:46:03.490] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://[email protected]:2552/user/clientActor] ClientActor=> Header
[INFO] [04/24/2018 09:46:03.491] [client-system-akka.actor.default-dispatcher-2] [akka.tcp://[email protected]:2552/user/clientActor] Send command to server: Header(What's your name: Can you tell me ,20,false)

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324763784&siteId=291194637
Recommended