akka系统是一个分布式的消息驱动系统。akka应用由一群负责不同运算工作的actor组成,每个actor都是被动等待外界的某种消息来驱动自己的作业。所以,通俗点描述:akka应用就是一群actor相互之间发送消息的系统,每个actor接收到消息后开始自己负责的工作。对于akka-typed来说,typed-actor只能接收指定类型的消息,所以actor之间的消息交流需要按照消息类型来进行,即需要协议来规范消息交流机制。想想看,如果用户需要一个actor做某件事,他必须用这个actor明白的消息类型来发送消息,这就是一种交流协议。

所谓消息交流方式包括单向和双向两类。如果涉及两个actor之间的消息交换,消息发送方式可以是单向和双向的。但如果是从外界向一个actor发送消息,那么肯定只能是单向的发送方式了,因为消息发送两端只有一端是actor。

典型的单向消息发送fire-and-forget如下:

import akka.actor.typed._
import scaladsl._ object Printer {
case class PrintMe(message: String)
// 只接收PrintMe类型message
def apply(): Behavior[PrintMe] =
Behaviors.receive {
case (context, PrintMe(message)) =>
context.log.info(message)
Behaviors.same
}
} object FireAndGo extends App {
// system就是一个root-actor
val system: ActorRef[Printer.PrintMe] = ActorSystem(Printer(), "fire-and-forget-sample")
val printer: ActorRef[Printer.PrintMe] = system
// 单向消息发送,printMe类型的消息
printer ! Printer.PrintMe("hello")
printer ! Printer.PrintMe("world!") system.asInstanceOf[ActorSystem[Printer.PrintMe]].terminate() }

当然,在现实中通常我们要求actor去进行某些运算然后返回运算结果。这就涉及到actor之间双向信息交换了。第一种情况:两个actor之间的消息是任意无序的,这是一种典型的无顺序request-response模式。就是说一个response不一定是按照request的接收顺序返回的,只是它们之间能够交流而已。不过,在akka-typed中这种模式最基本的要求就是发送的消息类型必须符合接收方actor的类型。

好了,我们先对这个模式做个示范。所有actor的定义可以先从它的消息类型开始。对每个参加双向交流的actor来说,可以从request和response两种消息来反映它的功能:

object FrontEnd {
sealed trait FrontMessages
case class SayHi(who: String) extends FrontMessages
}
object BackEnd {
//先从这个actor的回应消息开始
sealed trait Response
case class HowAreU(msg: String) extends Response
case object Unknown extends Response //可接收消息类型
sealed trait BackMessages
//这个replyTo应该是一个能处理Reponse类型消息的actor
case class MakeHello(who: String, replyTo: ActorRef[Response]) extends BackMessages }

这个FrontEnd接收SayHi消息后开始工作,不过目前还没有定义返回的消息类型。BackEnd接到MakeHello类型消息后返回response类型消息。从这个角度来讲,返回的对方actor必须能够处理Response类型的消息。

我们试试实现这个FrontEnd actor:

object FrontEnd {
sealed trait FrontMessages
case class SayHi(who: String) extends FrontMessages def apply(backEnd: ActorRef[BackEnd.BackMessages]): Behavior[FrontMessages] = {
Behaviors.receive { (ctx,msg) => msg match {
case SayHi(who) =>
ctx.log.info("requested to say hi to {}", who)
backEnd ! BackEnd.MakeHello(who, ???) }
}
}

MakeHello需要一个replyTo,应该是什么呢?不过它一定是可以处理Response类型消息的actor。但我们知道这个replyTo就是FrontEnd,不过FrontEnd只能处理FrontMessages类型消息,应该怎么办呢?可不可以把replyTo直接写成FrontEnd呢?虽然可以这么做,但这个MakeHello消息就只能跟FrontEnd绑死了。如果其它的actor也需要用到这个MakeHello的话就需要另外定义一个了。所以,最好的解决方案就是用某种类型转换方式来实现。如下:

import akka.actor.typed._
import scaladsl._ object FrontEnd {
sealed trait FrontMessages
case class SayHi(who: String) extends FrontMessages case class WrappedBackEndResonse(res: BackEnd.Response) extends FrontMessages def apply(backEnd: ActorRef[BackEnd.BackMessages]): Behavior[FrontMessages] = {
Behaviors.setup[FrontMessages] { ctx =>
//ctx.messageAdapter(ref => WrappedBackEndResonse(ref))
val backEndRef: ActorRef[BackEnd.Response] = ctx.messageAdapter(WrappedBackEndResonse)
Behaviors.receive { (ctx, msg) =>
msg match {
case SayHi(who) =>
ctx.log.info("requested to say hi to {}", who)
backEnd ! BackEnd.MakeHello(who, backEndRef)
Behaviors.same
//messageAdapter将BackEnd.Response转换成WrappedBackEndResponse
case WrappedBackEndResonse(msg) => msg match {
case BackEnd.HowAreU(msg) =>
ctx.log.info(msg)
Behaviors.same
case BackEnd.Unknown =>
ctx.log.info("Unable to say hello")
Behaviors.same
}
}
}
}
}
}

首先,我们用ctx.mesageAdapter产生了ActorRef[BackEnd.Response],正是我们需要提供给MakeHello消息的replyTo。看看这个messageAdapter函数:

def messageAdapter[U: ClassTag](f: U => T): ActorRef[U]

如果我们进行类型替换U -> BackEnd.Response, T -> FrontMessage 那么:

      val backEndRef: ActorRef[BackEnd.Response] =
ctx.messageAdapter((response: BackEnd.Response) => WrappedBackEndResonse(response))

实际上这个messageAdapter函数在本地ActorContext范围内登记了一个从BackEnd.Response类型到FrontMessages的转换。把接收到的BackEnd.Response立即转换成WrappedBackEndResponse(response)。

还有一种两个actor之间的双向交流模式是 1:1 request-response,即一对一模式。一对一的意思是发送方发送消息后等待回应消息。这就意味着收信方需要在完成运算任务后立即向发信方发送回应,否则造成发信方的超时异常。无法避免的是,这种模式依然会涉及消息类型的转换,如下:

object FrontEnd {
sealed trait FrontMessages
case class SayHi(who: String) extends FrontMessages case class WrappedBackEndResonse(res: BackEnd.Response) extends FrontMessages
case class ErrorResponse(errmsg: String) extends FrontMessages def apply(backEnd: ActorRef[BackEnd.BackMessages]): Behavior[FrontMessages] = {
Behaviors.setup[FrontMessages] { ctx =>
//ask需要超时上限
import scala.concurrent.duration._
import scala.util._
implicit val timeOut: Timeout = .seconds
Behaviors.receive[FrontMessages] { (ctx, msg) =>
msg match {
case SayHi(who) =>
ctx.log.info("requested to say hi to {}", who) ctx.ask(backEnd,(backEndRef: ActorRef[BackEnd.Response]) => BackEnd.MakeHello(who,backEndRef) ){
case Success(backResponse) => WrappedBackEndResonse(backResponse)
case Failure(err) =>ErrorResponse(err.getLocalizedMessage)
}
Behaviors.same case WrappedBackEndResonse(msg) => msg match {
case BackEnd.HowAreU(msg) =>
ctx.log.info(msg)
Behaviors.same
case BackEnd.Unknown =>
ctx.log.info("Unable to say hello")
Behaviors.same
}
case ErrorResponse(errmsg) =>
ctx.log.info("ask error: {}",errmsg)
Behaviors.same
}
}
}
}
}

似乎类型转换是在ask里实现的,看看这个函数:

  def ask[Req, Res](target: RecipientRef[Req], createRequest: ActorRef[Res] => Req)(
mapResponse: Try[Res] => T)(implicit responseTimeout: Timeout, classTag: ClassTag[Res]): Unit

req -> BackEnd.BackMessages, res -> BackEnd.Response, T -> FrontMessages。现在ask可以写成下面这样:

            ctx.ask[BackEnd.BackMessages,BackEnd.Response](backEnd,
(backEndRef: ActorRef[BackEnd.Response]) => BackEnd.MakeHello(who,backEndRef) ){
case Success(backResponse:BackEnd.Response) => WrappedBackEndResonse(backResponse)
case Failure(err) =>ErrorResponse(err.getLocalizedMessage)
}

这样看起来更明白点,也就是说ask把接收的BackEnd.Response转换成了FrontEnd处理的消息类型WrappedBackEndRespnse,也就是FrontMessages

还有一种ask模式是在actor之外进行的,如下:

object AskDemo extends App {
import akka.actor.typed.scaladsl.AskPattern._
import scala.concurrent._
import scala.concurrent.duration._
import akka.util._
import scala.util._ implicit val system: ActorSystem[BackEnd.BackMessages] = ActorSystem(BackEnd(), "front-app")
// asking someone requires a timeout if the timeout hits without response
// the ask is failed with a TimeoutException
implicit val timeout: Timeout = .seconds val result: Future[BackEnd.Response] =
system.asInstanceOf[ActorRef[BackEnd.BackMessages]]
.ask[BackEnd.Response]((ref: ActorRef[BackEnd.Response]) =>
BackEnd.MakeHello("John", ref)) // the response callback will be executed on this execution context
implicit val ec = system.executionContext result.onComplete {
case Success(res) => res match {
case BackEnd.HowAreU(msg) =>
println(msg)
case BackEnd.Unknown =>
println("Unable to say hello")
}
case Failure(ex) =>
println(s"error: ${ex.getMessage}")
} system.terminate() }

这个ask是在akka.actor.typed.scaladsl.AskPattern包里。函数款式如下:

   def ask[Res](replyTo: ActorRef[Res] => Req)(implicit timeout: Timeout, scheduler: Scheduler): Future[Res]

向ask传入一个函数ActorRef[BackEnd.Response] => BackEnd.BackMessages,然后返回Future[BackEnd.Response]。这个模式中接收回复方是在ActorContext之外,不存在消息截获机制,所以不涉及消息类型的转换。

另一种单actor双向消息交换模式,即自己ask自己。在ActorContext内向自己发送消息并提供回应消息的接收,如pipeToSelf:

object PipeFutureTo {
trait CustomerDataAccess {
def update(value: Customer): Future[Done]
} final case class Customer(id: String, version: Long, name: String, address: String) object CustomerRepository { sealed trait Command final case class Update(value: Customer, replyTo: ActorRef[UpdateResult]) extends Command sealed trait UpdateResult final case class UpdateSuccess(id: String) extends UpdateResult final case class UpdateFailure(id: String, reason: String) extends UpdateResult private final case class WrappedUpdateResult(result: UpdateResult, replyTo: ActorRef[UpdateResult])
extends Command private val MaxOperationsInProgress = def apply(dataAccess: CustomerDataAccess): Behavior[Command] = {
Behaviors.setup[Command] { ctx =>
implicit val dispatcher = ctx.system.dispatchers.lookup(DispatcherSelector.fromConfig("my-dispatcher"))
next(dataAccess, operationsInProgress = )
}
} private def next(dataAccess: CustomerDataAccess, operationsInProgress: Int)(implicit ec: ExecutionContextExecutor): Behavior[Command] = {
Behaviors.receive { (context, command) =>
command match {
case Update(value, replyTo) =>
if (operationsInProgress == MaxOperationsInProgress) {
replyTo ! UpdateFailure(value.id, s"Max $MaxOperationsInProgress concurrent operations supported")
Behaviors.same
} else {
val futureResult = dataAccess.update(value)
context.pipeToSelf(futureResult) {
// map the Future value to a message, handled by this actor
case Success(_) => WrappedUpdateResult(UpdateSuccess(value.id), replyTo)
case Failure(e) => WrappedUpdateResult(UpdateFailure(value.id, e.getMessage), replyTo)
}
// increase operationsInProgress counter
next(dataAccess, operationsInProgress + )
} case WrappedUpdateResult(result, replyTo) =>
// send result to original requestor
replyTo ! result
// decrease operationsInProgress counter
next(dataAccess, operationsInProgress - )
}
}
}
}
}

akka-typed(2) - typed-actor交流方式和交流协议的更多相关文章

  1. akka设计模式系列(Actor模型)

    谈到Akka就必须介绍Actor并发模型,而谈到Actor就必须看一篇叫做<A Universal Modular Actor Formalism for Artificial Intellig ...

  2. Akka系列---什么是Actor

    本文已.Net语法为主,同时写有Scala及Java实现代码 严肃的说,演员是一个广泛的概念,作为外行人我对Actor 模型的定义: Actor是一个系统中参与者的虚拟人物,Actor与Actor之间 ...

  3. Akka源码分析-Actor&ActorContext&ActorRef&ActorCell

    分析源码的过程中我们发现,Akka出现了Actor.ActorRef.ActorCell.ActorContext等几个相似的概念,它们之间究竟有什么区别和联系呢? /** * Actor base ...

  4. Expo大作战(二)--expo的生命周期,expo社区交流方式,expo学习必备资源,开发使用expo时关注的一些问题

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,讲全部来与官网 我猜去全部机翻+个人 ...

  5. Akka 编程: 什么是Actor

    上一篇我们简介了Actor系统.说明了Actor之间存在着层次关系,它也是构成Actor应用的最主要的单位. 本篇介绍Actor本身的一些基本概念.一个Actor包括了State(状态),Behavi ...

  6. Akka源码分析-Actor发消息(续)

    上一篇博客我们分析道mailbox同时也是一个forkjointask,run方法中,调用了processMailbox处理一定数量的消息,然后最终调用dispatcher的registerForEx ...

  7. PHP:CURL分别以GET、POST方式请求HTTPS协议接口api

    1.curl以GET方式请求https协议接口 //注意:这里的$url已经包含参数了,不带参数你自己处理哦GET很简单 function curl_get_https($url){ $curl = ...

  8. PHP函数CURL分别以GET、POST方式请求HTTPS协议接口api

    1.curl以GET方式请求https协议接口 function curl_get_https($url){ $curl = curl_init(); // 启动一个CURL会话 curl_setop ...

  9. PHP:CURL分别以GET、POST方式请求HTTPS协议接口api【转】

    1.curl以GET方式请求https协议接口 //注意:这里的$url已经包含参数了,不带参数你自己处理哦GET很简单 function curl_get_https($url){ $curl = ...

随机推荐

  1. [USACO1.5]回文质数 Prime Palindromes

    题目描述 因为151既是一个质数又是一个回文数(从左到右和从右到左是看一样的),所以 151 是回文质数. 写一个程序来找出范围[a,b](5 <= a < b <= 100,000 ...

  2. 数据结构--栈(附上STL栈)

    定义: 栈是一种只能在某一端插入和删除数据的特殊线性表.他按照先进先出的原则存储数据,先进的数据被压入栈底,最后进入的数据在栈顶,需要读数据的时候从栈顶开始弹出数据(最后被压入栈的,最先弹出).因此栈 ...

  3. Java 经典面试题:聊一聊 JUC 下的 LinkedBlockingQueue

    本文聊一下 JUC 下的 LinkedBlockingQueue 队列,先说说 LinkedBlockingQueue 队列的特点,然后再从源码的角度聊一聊 LinkedBlockingQueue 的 ...

  4. JavaWebCase

    目录 案例:用户登录 用户登录案例需求 分析 开发步骤 创建项目 创建数据库环境 创建包 com.my.domain,创建类User 创建包 com.my.dao,创建类UsesrDao,提供logi ...

  5. 动态代理学习(二)JDK动态代理源码分析

    上篇文章我们学习了如何自己实现一个动态代理,这篇文章我们从源码角度来分析下JDK的动态代理 先看一个Demo: public class MyInvocationHandler implements ...

  6. Spring cloud系列教程第二篇:支付项目父工程图文搭建

    Spring cloud系列教程第二篇:支付项目父工程图文搭建 在讲解spring cloud相关的技术的时候,咱们就模拟订单支付这个流程来讲讲 在这个支付模块微服务搭建过程中,上面的这些技术,都会融 ...

  7. 【Spark】通过Spark实现点击流日志分析

    文章目录 数据大致内容及格式 统计PV(PageViews) 统计UV(Unique Visitor) 求取TopN 数据大致内容及格式 194.237.142.21 - - [18/Sep/2013 ...

  8. Jmeter5.2.1 三种参数化

    Jmeter的相关操作,一定要会参数化,常见的参数化方法有如下几种: 一.用户定义的变量 常应用于设置一些全局变量,包括url.host.port等等 线程组--添加--配置元件--用户定义的变量,添 ...

  9. ssh暴力破解解决方案(Centos7更改端口)

    服务器默认ssh远程连接端口为22端口,通常通过22远程连接的话,容易有ssh暴力破解的风险,给我们造成一定的损失.下面是更换ssh端口过程: 1.添加ssh端口 vim /etc/ssh/sshd_ ...

  10. 第九章(二)DAG上的动态规划

    DAG上的动态规划: 有向无环图上的动态规划是学习DP的基础,很多问题都可以转化为DAG上的最长路.最短路或路径计数问题. 1.没有明确固定起点重点的DAG模型: 嵌套矩形问题:有n个矩形,每个矩形可 ...