akka 2.6.x正式发布以来已经有好一段时间了。核心变化是typed-actor的正式启用,当然persistence,cluster等模块也有较大变化。一开始从名称估摸就是把传统any类型的消息改成强类型消息,所以想拖一段时间看看到底能对我们现有基于akka-classic的应用软件有什么深层次的影响。不过最近考虑的一些系统架构逼的我不得不立即开始akka-typed的调研,也就是说akka-classic已经无法或者很困难去实现新的系统架构,且听我道来:最近在考虑一个微服务中台。作为后台数据服务调用的唯一入口,平台应该是个分布式软件,那么采用akka-cluster目前是唯一的选择,毕竟前期搞过很多基于akka-cluster的应用软件。但是,akka-cluster-sharding只能支持一种entity actor。毕竟,由于akka-classic的消息是没有类型的,只能在收到消息后再通过类型模式匹配的方式确定应该运行的代码。所以,这个actor必须包括所有的业务逻辑处理运算。也就是说对于一个大型应用来说这就是一块巨型代码。还有,如果涉及到维护actor状态的话,比如persistenceActor,或者综合类型业务运算,那么又需要多少种类的数据结构,又怎样去维护、管理这些结构呢?对我来说这基本上是mission-impossible。实际上logom应该正符合这个中台的要求:cluster-sharding, CQRS... 抱着一种好奇的心态了解了一下lagom源码,忽然恍然大悟:这个东西是基于akka-typed的!想想看也是:如果我们可以把actor和消息类型绑在一起,那么我们就可以通过消息类型对应到某种actor。也就是说基于akka-typed,我们可以把综合性的业务划分成多个actor模块,然后我们可以指定那种actor做那些事情。当然,经过了功能细分,actor的设计也简单了许多。现在这个新的中台可以实现前台应用直接调用对应的actor处理业务了。不用多想了,这注定就是akka应用的将来,还等什么呢?

先从一个最简单的hello程序开始吧:基本上是两个actor相互交换消息。先用第一个来示范标准的actor构建过程:

  object HelloActor {
sealed trait Request
case class Greeting(fromWhom: String, replyTo: ActorRef[Greeter.Greeted]) extends Request def apply(): Behavior[Greeting] = {
Behaviors.receive { (ctx, greeter) =>
ctx.log.info("receive greeting from {}", greeter.fromWhom)
greeter.replyTo ! Greeter.Greeted(s"hello ${greeter.fromWhom}!")
Behaviors.same
}
}
}

akka-typed的actor构建是通过定义它的Behavior行为实现的。特别的是类型参数Behavior[Greeting],代表这个actor只处理Greeting类型的消息,因而是个typed-actor。akka-typed已经不支持sender()了,在消息里自带,如Greeting.replyTo。Behavior定义是通过工厂模式Behaviors实现的,看看Behaviors的定义:

/**
* Factories for [[akka.actor.typed.Behavior]].
*/
object Behaviors {
def setup[T](factory: ActorContext[T] => Behavior[T]): Behavior[T] def withStash[T](capacity: Int)(factory: StashBuffer[T] => Behavior[T]): Behavior[T] def same[T]: Behavior[T] def unhandled[T]: Behavior[T] def stopped[T]: Behavior[T] def stopped[T](postStop: () => Unit): Behavior[T] def empty[T]: Behavior[T] def ignore[T]: Behavior[T] def receive[T](onMessage: (ActorContext[T], T) => Behavior[T]): Receive[T] def receiveMessage[T](onMessage: T => Behavior[T]): Receive[T] def receivePartial[T](onMessage: PartialFunction[(ActorContext[T], T), Behavior[T]]): Receive[T] def receiveMessagePartial[T](onMessage: PartialFunction[T, Behavior[T]]): Receive[T] def receiveSignal[T](handler: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Behavior[T] def supervise[T](wrapped: Behavior[T]): Supervise[T] def withTimers[T](factory: TimerScheduler[T] => Behavior[T]): Behavior[T] ... }

上面的构建函数除返回Behavior[T]外还有Receive[T]和Supervise[T],这两个类型是什么?它们还是Behavior[T]:

  trait Receive[T] extends Behavior[T] {
def receiveSignal(onSignal: PartialFunction[(ActorContext[T], Signal), Behavior[T]]): Behavior[T]
} def supervise[T](wrapped: Behavior[T]): Supervise[T] =
new Supervise[T](wrapped) private final val ThrowableClassTag = ClassTag(classOf[Throwable])
final class Supervise[T] private[akka] (val wrapped: Behavior[T]) extends AnyVal { /** Specify the [[SupervisorStrategy]] to be invoked when the wrapped behavior throws. */
def onFailure[Thr <: Throwable: ClassTag](strategy: SupervisorStrategy): Behavior[T] = {
val tag = classTag[Thr]
val effectiveTag = if (tag == ClassTag.Nothing) ThrowableClassTag else tag
Supervisor(Behavior.validateAsInitial(wrapped), strategy)(effectiveTag)
}
}

注意,Supervise.onFailure返回了Behavior[T]。

helloActor的Behavior是通过Behaviors.receive构建的。还可以用setup,receiveMessage来构建。注意:构建函数的入参数也是Behavior[T],所以这些构造器可以一层层嵌套着使用。setup,receive为函数内层提供了ActorContext, withTimers提供TimerScheduler[T]。那么我可以把HelloActor的功能再完善点,加个监管策略SupervisorStrategy:

  object HelloActor {
sealed trait Request
case class Greeting(fromWhom: String, replyTo: ActorRef[Greeter.Greeted]) extends Request def apply(): Behavior[Greeting] = {
Behaviors.supervise(
Behaviors.receive[Greeting] { (ctx, greeter) =>
ctx.log.info("receive greeting from {}", greeter.fromWhom)
greeter.replyTo ! Greeter.Greeted(s"hello ${greeter.fromWhom}!")
Behaviors.same
}
).onFailure(SupervisorStrategy.restartWithBackoff(.seconds, .minute, 0.20))
}
}

在akka-typed里,actor监管已经从父辈转到自身。再就是增加了BackOff-SupervisorStrategy,不需要独立的BackOffSupervisor actor了。

再看看另一个Greeter:

 object Greeter {

    sealed trait Response
case class Greeted(hello: String) extends Response def apply(): Behavior[Greeted] = {
Behaviors.setup ( ctx =>
Behaviors.receiveMessage { message =>
ctx.log.info(message.hello)
Behaviors.same
}
)
}
}

这个跟HelloActor没什么不同,不过用了setup,receiveMessage套装。值得注意的是Greeter负责处理Greeted消息,这是一个不带sender ActorRef的类型,意味着处理这类消息后不需要答复消息发送者。

然后还需要一个actor来构建上面两个actor实例,启动对话:

 object GreetStarter {
sealed trait Command
case class SayHiTo(whom: String) extends Command
case class RepeatedGreeting(whom: String, interval: FiniteDuration) extends Command def apply(): Behavior[Command] = {
Behaviors.setup[Command] { ctx =>
val helloActor = ctx.spawn(HelloActor(), "hello-actor")
val greeter = ctx.spawn(Greeter(), "greeter")
Behaviors.withTimers { timer =>
new GreetStarter(
helloActor,greeter,ctx,timer)
.repeatGreeting(,)
}
}
}
}
class GreetStarter private (
helloActor: ActorRef[HelloActor.Greeting],
greeter: ActorRef[Greeter.Greeted],
ctx: ActorContext[GreetStarter.Command],
timer: TimerScheduler[GreetStarter.Command]){
import GreetStarter._ private def repeatGreeting(count: Int, max: Int): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case RepeatedGreeting(whom, interval) =>
ctx.log.info2("start greeting to {} with interval {}", whom, interval)
timer.startSingleTimer(SayHiTo(whom), interval)
Behaviors.same
case SayHiTo(whom) =>
ctx.log.info2("{}th time greeting to {}",count,whom)
if (max == count)
Behaviors.stopped
else {
helloActor ! HelloActor.Greeting(whom, greeter)
repeatGreeting(count + , max)
}
}
}
}

上面这个例子有点复杂,逻辑也有些问题,主要是为了示范一种函数式actor构建模式及actor状态转换虚构出来的。akka-typed已经不再支持become方法了。

最后,需要一个相当于main这么一个顶层的程序:

  def main(args: Array[String]) {
val man: ActorSystem[GreetStarter.Command] = ActorSystem(GreetStarter(), "greetDemo")
man ! GreetStarter.RepeatedGreeting("Tiger",.seconds)
man ! GreetStarter.RepeatedGreeting("Peter",.seconds)
man ! GreetStarter.RepeatedGreeting("Susanna",.seconds)
}

akka-classic的顶级actor,即: /users是由系统默认创建的。akka-typed需要用户提供这个顶层actor。这个是在ActorSystem的第一个参数指定的。我们再看看akka-typed的ActorSystem的构建函数:

object ActorSystem {

  /**
* Scala API: Create an ActorSystem
*/
def apply[T](guardianBehavior: Behavior[T], name: String): ActorSystem[T] =
createInternal(name, guardianBehavior, Props.empty, ActorSystemSetup.create(BootstrapSetup())) /**
* Scala API: Create an ActorSystem
*/
def apply[T](guardianBehavior: Behavior[T], name: String, config: Config): ActorSystem[T] =
createInternal(name, guardianBehavior, Props.empty, ActorSystemSetup.create(BootstrapSetup(config))) /**
* Scala API: Create an ActorSystem
*/
def apply[T](guardianBehavior: Behavior[T], name: String, config: Config, guardianProps: Props): ActorSystem[T] =
createInternal(name, guardianBehavior, guardianProps, ActorSystemSetup.create(BootstrapSetup(config)))
...
}

其中一个apply与akka-classic的ActorSystem构建方式很相似:

  def main(args: Array[String]) {
val config = ConfigFactory.load("application.conf")
val man: ActorSystem[GreetStarter.Command] = ActorSystem(GreetStarter(), "greetDemo",config)
man ! GreetStarter.RepeatedGreeting("Tiger",.seconds)
man ! GreetStarter.RepeatedGreeting("Peter",.seconds)
man ! GreetStarter.RepeatedGreeting("Susanna",.seconds)
}

下面是本次讨论的完整源代码:

build.sbt

name := "learn-akka-typed"

version := "0.1"

scalaVersion := "2.13.2"

lazy val akkaVersion = "2.6.5"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor-typed" % akkaVersion,
"ch.qos.logback" % "logback-classic" % "1.2.3"
) fork in Test := true

Lesson01.scala

import akka.actor.typed._
import scaladsl._
import scala.concurrent.duration._
import com.typesafe.config._
object Lesson01 { object HelloActor {
sealed trait Request
case class Greeting(fromWhom: String, replyTo: ActorRef[Greeter.Greeted]) extends Request def apply(): Behavior[Greeting] = {
Behaviors.supervise(
Behaviors.receive[Greeting] { (ctx, greeter) =>
ctx.log.info("receive greeting from {}", greeter.fromWhom)
greeter.replyTo ! Greeter.Greeted(s"hello ${greeter.fromWhom}!")
Behaviors.same
}
).onFailure(SupervisorStrategy.restartWithBackoff(.seconds, .minute, 0.20))
}
} object Greeter { sealed trait Response
case class Greeted(hello: String) extends Response def apply(): Behavior[Greeted] = {
Behaviors.setup ( ctx =>
Behaviors.receiveMessage { message =>
ctx.log.info(message.hello)
Behaviors.same
}
)
}
} object GreetStarter {
sealed trait Command
case class SayHiTo(whom: String) extends Command
case class RepeatedGreeting(whom: String, interval: FiniteDuration) extends Command def apply(): Behavior[Command] = {
Behaviors.setup[Command] { ctx =>
val helloActor = ctx.spawn(HelloActor(), "hello-actor")
val greeter = ctx.spawn(Greeter(), "greeter")
Behaviors.withTimers { timer =>
new GreetStarter(
helloActor,greeter,ctx,timer)
.repeatGreeting(,)
}
}
}
}
class GreetStarter private (
helloActor: ActorRef[HelloActor.Greeting],
greeter: ActorRef[Greeter.Greeted],
ctx: ActorContext[GreetStarter.Command],
timer: TimerScheduler[GreetStarter.Command]){
import GreetStarter._ private def repeatGreeting(count: Int, max: Int): Behavior[Command] =
Behaviors.receiveMessage { msg =>
msg match {
case RepeatedGreeting(whom, interval) =>
ctx.log.info2("start greeting to {} with interval {}", whom, interval)
timer.startSingleTimer(SayHiTo(whom), interval)
Behaviors.same
case SayHiTo(whom) =>
ctx.log.info2("{}th time greeting to {}",count,whom)
if (max == count)
Behaviors.stopped
else {
helloActor ! HelloActor.Greeting(whom, greeter)
repeatGreeting(count + , max)
}
}
}
} def main(args: Array[String]) {
val config = ConfigFactory.load("application.conf")
val man: ActorSystem[GreetStarter.Command] = ActorSystem(GreetStarter(), "greetDemo",config)
man ! GreetStarter.RepeatedGreeting("Tiger",.seconds)
man ! GreetStarter.RepeatedGreeting("Peter",.seconds)
man ! GreetStarter.RepeatedGreeting("Susanna",.seconds)
}
}

akka-typed(0) - typed-actor, typed messages的更多相关文章

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

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

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

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

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

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

  4. Akka(0):聊聊对Akka的初步了解和想法

    前一段时间一直沉浸在函数式编程模式里,主要目的之一是掌握一套安全可靠的并发程序编程方法(concurrent programming),最终通过开源项目FunDA实现了单机多核CPU上程序的并行运算. ...

  5. Akka 编程: 什么是Actor

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

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

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

  7. 一图看懂Actor Typed

    引言 朋友看罢我之前整理的<Akka Typed 官方文档之随手记>,一人用了诗歌<长城长>作为回赠,另一人则要求推出简化版本.于是抽空整理了几张思维导图,并且用了一些不太恰当 ...

  8. Akka源码分析-Actor发消息

    前面两篇文章简单介绍了ActorSystem.actor以及dispatcher和mailbox的创建,下面我们就来看一下actor发消息的内部机制. val system = ActorSystem ...

  9. Akka源码分析-Actor创建

    上一篇博客我们介绍了ActorSystem的创建过程,下面我们就研究一下actor的创建过程. val system = ActorSystem("firstActorSystem" ...

  10. Akka源码分析-Actor创建(续)

    在上一遍博客中,我们已经分析了actor创建的大致过程,但只是涉及到了Dipatcher/Mailbox/ActorCell/InternalActorRef等对象的创建,并没有介绍我们自定义的继承A ...

随机推荐

  1. Linux shell基础(五)sed命令

    一.sed命令 sed是一种强大的流式编辑器 (stream editor for filtering and transforming text),它能够完美的使用正则表达式,逐行处理文本并把结果显 ...

  2. A Tile Painting(循环节)

    Ujan has been lazy lately, but now has decided to bring his yard to good shape. First, he decided to ...

  3. docker overlay network

    下载binary consul wget https://releases.hashicorp.com/consul/1.2.2/consul_1.2.2_linux_amd64.zip unzip ...

  4. 面试中的volatile关键字

    在Java的面试当中,面试官最爱问的就是volatile关键字相关的内容.经过多次面试之后,你是否思考过,为什么他们那么爱问volatile关键字相关的问题?而对于你,如果作为面试官,是否也会考虑采用 ...

  5. ASP.NET Core Blazor 初探之 Blazor WebAssembly

    最近Blazor热度很高,传说马上就要发布正式版了,做为微软脑残粉,赶紧也来凑个热闹,学习一下. Blazor Blazor是微软在ASP.NET Core框架下开发的一种全新的Web开发框架.Bla ...

  6. sshd: no hostkeys available — exiting

    在开启SSHD服务时报错.sshd re-exec requires execution with an absolute path用绝对路径启动,也报错如下:Could not load host ...

  7. LeetCode二分专题

    二分 二分模板 两个模板:1.最大值最小模板一,2.最小值最大用模板二 单调性.两段性的性质 版本1:二分绿色端点是答案,最大值最小 int bsearch_1(int l, int r){ whil ...

  8. 在微服务框架Demo.MicroServer中添加SkyWalking+SkyApm-dotnet分布式链路追踪系统

    1.APM工具的选取 Apm监测工具很多,这里选用网上比较火的一款Skywalking. Skywalking是一个应用性能监控(APM)系统,Skywalking分为服务端Oap.管理界面UI.以及 ...

  9. Struts2 为什么被淘汰?自己作死!

    Struts2 那些年可谓是风光无限啊,Struts2 + Spring + Hibernate 三大框架一起组成了 "SSH"----牛逼哄哄的 Java Web 框架三剑客. ...

  10. Mysql 常用函数(9)- reverse 函数

    Mysql常用函数的汇总,可看下面系列文章 https://www.cnblogs.com/poloyy/category/1765164.html reverse 的作用 将字符串反转,即顺序取反 ...