Actor模式最大的优点就是每个Actor都是一个独立的任务运算器。这种模式让我们很方便地把一项大型的任务分割成若干细小任务然后分配给不同的Actor去完成。优点是在设计时可以专注实现每个Actor的功能,在实际运算时由于每个Actor都在独立的线程里运行,能充分利用多核CPU的优势实现相似并行运算的效率。我们同样可以把一个独立的功能按不同的输入分配给多个Actor去完成以达到同样的效率提高目的,这就是Akka的Routing模式了。Routing模式的特点是所有运算Actor的运算逻辑都是相同的,分别对不同的输入进行相同的运算。不过我们应该知道运算结果的顺序是无法预计的,毕竟Actor模式是典型的无序运算。Routing模式由Router和Routee组成:Routee是负责具体运算的Actor(因为运算逻辑必须在Actor的receive里实现),Router的主要功能是把外界发来的运算指令按照某种指定的方式分配给Routee去运算。可以说Router不是标准的Actor,因为它不需要实现任何其它的功能,基本功能是预设嵌入的。Router的信箱直接代表了任务分配逻辑,与标准Actor逐个运算信箱中消息相比,能大大提高任务分配效率。Akka自带许多现成的任务分配模式,以不同的算法来满足不同的任务分配要求。这些算法的配置可以在配置文件或者代码中定义。Router又可分Pool和Group两种模式:在Router-Pool模式中Router负责构建所有的Routee。如此所有Routee都是Router的直属子级Actor,可以实现Router对Routees的直接监管。由于这种直接的监管关系,Router-Pool又可以按运算负载自动增减Routee,能更有效地分配利用计算资源。Router-Group模式中的Routees由外界其它Actor产生,特点是能实现灵活的Routee构建和监控,可以用不同的监管策略来管理一个Router下的Routees,比如可以使用BackoffSupervisor。从另一方面来讲,Router-Group的缺点是Routees的构建和管理复杂化了,而且往往需要人为干预。

下面我们先做个示范:

import akka.actor._
import akka.routing._
import scala.annotation.tailrec object FibonacciRoutee {
case class FibonacciNumber(nbr: Int)
def props = Props[FibonacciRoutee]
}
class FibonacciRoutee extends Actor with ActorLogging {
import FibonacciRoutee._ override def receive: Receive = {
case FibonacciNumber(nbr) =>
val answer = fibonacci(nbr)
log.info(s"${self.path.name}'s answer: Fibonacci($nbr)=$answer")
}
private def fibonacci(n: Int): Int = {
@tailrec
def fib(n: Int, b: Int, a: Int): Int = n match {
case => a
case _ =>
fib(n - , a + b, b)
}
fib(n, , )
}
}
object RouterDemo extends App {
import FibonacciRoutee._
val routingSystem = ActorSystem("routingSystem")
val router = routingSystem.actorOf(
FromConfig.props(FibonacciRoutee.props)
,"balance-pool-router") router ! FibonacciNumber()
router ! FibonacciNumber()
router ! FibonacciNumber()
router ! FibonacciNumber() scala.io.StdIn.readLine() routingSystem.terminate() }

在这个例子里我们用3个Routees来根据指示计算Fibonacci。FibonacciRoutee只有一项功能:就是按输入计算Fibonacci数。我们看到,Router构建过程十分简单。在我们的例子里只需要读出配置文件内容就可以了。balance-pool-router是配置文件里的一个定义项:

akka {
prio-dispatcher {
mailbox-type = "PriorityMailbox"
}
actor {
deployment {
/balance-pool-router {
router = balancing-pool
nr-of-instances =
pool-dispatcher {
executor = "fork-join-executor"
# Configuration for the fork join pool
fork-join-executor {
# Min number of threads to cap factor-based parallelism number to
parallelism-min =
# Parallelism (threads) ... ceil(available processors * factor)
parallelism-factor = 2.0
# Max number of threads to cap factor-based parallelism number to
parallelism-max =
}
# Throughput defines the maximum number of messages to be
# processed per actor before the thread jumps to the next actor.
# Set to for as fair as possible.
throughput =
}
}
}
} }

Routing模式设置的完整标识是akka.actor.deployment{/balance-pool-router}。完成构建router后我们直接向router发送计算指令,运算结果如下:

[INFO] [// ::52.323] [routingSystem-BalancingPool-/balance-pool-router-] [akka://routingSystem/user/balance-pool-router/$b] $b's answer: Fibonacci(13)=233
[INFO] [// ::52.323] [routingSystem-BalancingPool-/balance-pool-router-] [akka://routingSystem/user/balance-pool-router/$a] $a's answer: Fibonacci(10)=55
[INFO] [// ::52.323] [routingSystem-BalancingPool-/balance-pool-router-] [akka://routingSystem/user/balance-pool-router/$c] $c's answer: Fibonacci(15)=610
[INFO] [// ::52.323] [routingSystem-BalancingPool-/balance-pool-router-] [akka://routingSystem/user/balance-pool-router/$c] $c's answer: Fibonacci(17)=1597

我们看到,router按配置自动构建了3个FibonacciRoutee。Routee的构建过程是无法人工干预的。向router发送的计算指令被分配给b,a,c,c去运算了。从显示顺序可以证明每个参与的Actor占用运算时间不同,产生了无序的运算结果。

下面我们在Routee里加一个延迟效应。这样运算结果显示会更自然些:

object FibonacciRoutee {
case class FibonacciNumber(nbr: Int, msDelay: Int) //增加延迟参数
case class GetAnswer(nbr: Int) class RouteeException extends Exception def props = Props[FibonacciRoutee]
}
class FibonacciRoutee extends Actor with ActorLogging {
import FibonacciRoutee._
import context.dispatcher override def receive: Receive = {
case FibonacciNumber(nbr,ms) =>
context.system.scheduler.scheduleOnce(ms second,self,GetAnswer(nbr))
case GetAnswer(nbr) =>
if (Random.nextBoolean())
throw new RouteeException
else {
val answer = fibonacci(nbr)
log.info(s"${self.path.name}'s answer: Fibonacci($nbr)=$answer")
}
}
private def fibonacci(n: Int): Int = {
@tailrec
def fib(n: Int, b: Int, a: Int): Int = n match {
case => a
case _ =>
fib(n - , a + b, b)
}
fib(n, , )
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
log.info(s"Restarting ${self.path.name} on ${reason.getMessage}")
message foreach {m => self ! m}
super.preRestart(reason, message)
} override def postRestart(reason: Throwable): Unit = {
log.info(s"Restarted ${self.path.name} on ${reason.getMessage}")
super.postRestart(reason)
} override def postStop(): Unit = {
log.info(s"Stopped ${self.path.name}!")
super.postStop()
} }

因为在Actor内部不能使用Thread.sleep,所以我们用了个scheduleOnce在延迟时间后向自己发送一个唤醒消息。注意,scheduleOnce是无阻塞non-blocking代码,调用后程序不会停留等待计划动作。在上面修改后的代码里增加了监管策略SupervisorStrategy的使用测试。Router的默认监管策略是Esculate,即把某个Routee发生的异常提交给Router的直属父级处理。如果Router直属父级对Routee异常的处理方式是重启的话,那么首先重启Router,然后是作为直属子级的所有Routees都会被重启,结果并不是我们想要的。所以必须人为的设定Router监管策略。由于Router的SupervisorStrategy无法在设置文件中定义,所以这次我们只有用代码方式来设置routing模式了:

object RouterDemo extends App {
import FibonacciRoutee._
import scala.concurrent.ExecutionContext.Implicits.global
val routingSystem = ActorSystem("routingSystem")
/* cannot set SupervisorStrategy in config file
val router = routingSystem.actorOf(
FromConfig.props(FibonacciRoutee.props)
,"balance-pool-router")
*/
val routingDecider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: RouteeException => SupervisorStrategy.Restart
}
val routerSupervisorStrategy = OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
routingDecider.orElse(SupervisorStrategy.defaultDecider)
)
val router = routingSystem.actorOf(
BalancingPool(nrOfInstances =
,supervisorStrategy=routerSupervisorStrategy //set SupervisorStrategy here
).withDispatcher("akka.pool-dispatcher")
.props(FibonacciRoutee.props)
,"balance-pool-router"
) router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,) scala.io.StdIn.readLine() routingSystem.terminate() }

注意:我们在FibonacciRoutee的preRestart接口中增加了向自己补发产生异常消息的过程。运算结果显示:虽然出现了多次异常,router重启了f发生异常的Routee,所有消息都得到了处理。

Akka中有些routing模式支持Router-Pool Routee的自动增减。由于BalancingPool不支持此项功能,下面我们就用RoundRobinPool来做个示范。由于需要定义监管策略,只有在代码中设置Resizer了:

 val resizer = DefaultResizer(
lowerBound = , upperBound = , pressureThreshold =
,rampupRate = , backoffRate = 0.25
,backoffThreshold = 0.25, messagesPerResize =
)
val router = routingSystem.actorOf(
RoundRobinPool(nrOfInstances =
,resizer = Some(resizer)
,supervisorStrategy = routerSupervisorStrategy)
.props(FibonacciRoutee.props)
,"roundrobin-pool-router"
)

以上resizer设置为:Routee最少2个,可以自动增加到5个。运行后routingSystem自动增加了两个Routee: c,d。

下面是本次示范的完整源代码:

import akka.actor._
import akka.routing._
import scala.annotation.tailrec
import scala.concurrent.duration._
import scala.util.Random object FibonacciRoutee {
case class FibonacciNumber(nbr: Int, msDelay: Int) //增加延迟参数
case class GetAnswer(nbr: Int) class RouteeException extends Exception def props = Props[FibonacciRoutee]
}
class FibonacciRoutee extends Actor with ActorLogging {
import FibonacciRoutee._
import context.dispatcher override def receive: Receive = {
case FibonacciNumber(nbr,ms) =>
context.system.scheduler.scheduleOnce(ms second,self,GetAnswer(nbr))
case GetAnswer(nbr) =>
if (Random.nextBoolean())
throw new RouteeException
else {
val answer = fibonacci(nbr)
log.info(s"${self.path.name}'s answer: Fibonacci($nbr)=$answer")
}
}
private def fibonacci(n: Int): Int = {
@tailrec
def fib(n: Int, b: Int, a: Int): Int = n match {
case => a
case _ =>
fib(n - , a + b, b)
}
fib(n, , )
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
log.info(s"Restarting ${self.path.name} on ${reason.getMessage}")
message foreach {m => self ! m}
super.preRestart(reason, message)
} override def postRestart(reason: Throwable): Unit = {
log.info(s"Restarted ${self.path.name} on ${reason.getMessage}")
super.postRestart(reason)
} override def postStop(): Unit = {
log.info(s"Stopped ${self.path.name}!")
super.postStop()
} }
object RouterDemo extends App {
import FibonacciRoutee._
import scala.concurrent.ExecutionContext.Implicits.global
val routingSystem = ActorSystem("routingSystem")
/* cannot set SupervisorStrategy in config file
val router = routingSystem.actorOf(
FromConfig.props(FibonacciRoutee.props)
,"balance-pool-router")
*/
val routingDecider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: RouteeException => SupervisorStrategy.Restart
}
val routerSupervisorStrategy = OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds)(
routingDecider.orElse(SupervisorStrategy.defaultDecider)
)
/* does not support resizing routees
val router = routingSystem.actorOf(
BalancingPool(nrOfInstances = 3
,supervisorStrategy=routerSupervisorStrategy //set SupervisorStrategy here
).withDispatcher("akka.pool-dispatcher")
.props(FibonacciRoutee.props)
,"balance-pool-router"
) */ val resizer = DefaultResizer(
lowerBound = , upperBound = , pressureThreshold =
,rampupRate = , backoffRate = 0.25
,backoffThreshold = 0.25, messagesPerResize =
)
val router = routingSystem.actorOf(
RoundRobinPool(nrOfInstances =
,resizer = Some(resizer)
,supervisorStrategy = routerSupervisorStrategy)
.props(FibonacciRoutee.props)
,"roundrobin-pool-router"
) router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,)
router ! FibonacciNumber(,) scala.io.StdIn.readLine() routingSystem.terminate() }

Akka(4): Routers - 智能任务分配的更多相关文章

  1. Akka(25): Stream:对接外部系统-Integration

    在现实应用中akka-stream往往需要集成其它的外部系统形成完整的应用.这些外部系统可能是akka系列系统或者其它类型的系统.所以,akka-stream必须提供一些函数和方法来实现与各种不同类型 ...

  2. Akka(5): ConsistentHashing Router - 可选定Routee的任务分配模式

    上一篇讨论里我们介绍了几种任务分配(Routing)模式.Akka提供的几种现成智能化Routing模式大多数是通过对用户屏蔽具体的运算Routee选择方式来简化Router使用,提高智能程度,所以我 ...

  3. Akka Typed 官方文档之随手记

    ️ 引言 近两年,一直在折腾用FP与OO共存的编程语言Scala,采取以函数式编程为主的方式,结合TDD和BDD的手段,采用Domain Driven Design的方法学,去构造DDDD应用(Dom ...

  4. [翻译]AKKA笔记 - ACTOR MESSAGING - REQUEST AND RESPONSE -3

    上次我们看Actor消息机制,我们看到开火-忘记型消息发出(意思是我们只要发个消息给Actor但是不期望有响应). 技术上来讲, 我们发消息给Actors就是要它的副作用. 这就是这么设计的.除了不响 ...

  5. beego 0.9.0 中智能路由AutoRouter的使用方法及源码解读

    了解beego的开发者肯定知道,beego的路由设计来源于sinatra,原来是不支持自动路由的,每一个路由都要自己配置的,如: type MainController struct { beego. ...

  6. Akka边学边写(2)-- Echo Server

    EchoServer 上篇文章里,我们用Akka写了一个简单的HelloWorld样例,对Akka(以及Actor模式)有了初步的认识.本文将用Akka写一个EchoServer,看看在Actor的世 ...

  7. Akka(8): 分布式运算:Remoting-远程查找式

    Akka是一种消息驱动运算模式,它实现跨JVM程序运算的方式是通过能跨JVM的消息系统来调动分布在不同JVM上ActorSystem中的Actor进行运算,前题是Akka的地址系统可以支持跨JVM定位 ...

  8. Akka(11): 分布式运算:集群-均衡负载

    在上篇讨论里我们主要介绍了Akka-Cluster的基本原理.同时我们也确认了几个使用Akka-Cluster的重点:首先,Akka-Cluster集群构建与Actor编程没有直接的关联.集群构建是A ...

  9. Akka Essentials - 2

    Actors Defining an actor class MyActor extends Actor { def receive = { } } In Scala, the receive blo ...

随机推荐

  1. Azure IoT 技术研究系列1-入门篇

    物联网技术已经火了很多年了,业界各大厂商都有各自成熟的解决方案.我们公司主要搞新能源汽车充电,充电桩就是我们物联网技术的最大应用,车联网.物联网. 互联网三网合一.作为Azure重要的Partner和 ...

  2. YARN学习总结

    YARN学习总结 前言 YARN(Yet Another Resource Manage,另一种资源协调者)是hadoop-0.23版本引入的的一个新的特性,可以说它是对原有Hadoop Mapred ...

  3. 【树莓派】修改树莓派盒子MAC地址

    用树莓派盒子,在某些客户方实施过程中,不同客户的网络环境对树莓派盒子的要求不同,网络管理配置要求MAC地址和IP绑定. 一种情况下,查询盒子的MAC地址,添加到网络管理的路由规则中即可: 另一种情况下 ...

  4. 【Spark2.0源码学习】-4.Master启动

         Master作为Endpoint的具体实例,下面我们介绍一下Master启动以及OnStart指令后的相关工作   一.脚本概览      下面是一个举例: /opt/jdk1..0_79/ ...

  5. spine动画融合与动画叠加

    spine动画融合与动画叠加 一.动画融合setMix 1.概述:两个动作之间的平滑过渡 参数duration为需要多少时间从fromAnimation过渡到toAnimation,过渡时间为动画重叠 ...

  6. Less和Sass的使用

    [Less中的变量] 1.声明变量:@变量名:变量值;  使用变量:@变量名 @length:100px; @color:yellow; @opa:0.5; >>>Less中变量的类 ...

  7. bootstrap的常用组件和栅格式布局

    Bootstrap 是最受欢迎的 HTML.CSS 和 JS 框架,用于开发响应式布局.移动设备优先的 WEB 项目. 因为Bootstrap需要jquery的支持,所以在使用Bootstrap之前要 ...

  8. 第一篇 Rewrite规则简介

    1.Rewirte主要的功能就是实现URL的跳转,它的正则表达式是基于Perl语言.可基于服务器级的(httpd.conf)和目录级的(.htaccess)两种方式.如果要想用到rewrite模块,必 ...

  9. 蓝桥杯-组素数-java

    /* (程序头部注释开始) * 程序的版权和版本声明部分 * Copyright (c) 2016, 广州科技贸易职业学院信息工程系学生 * All rights reserved. * 文件名称: ...

  10. 随机Prim法创建随机迷宫(C#实现)

    因为这两天想参加一个比赛,所以就在上网找素材,刚好看到了迷宫生成,就决定拿这个开刀了. 参考的原文地址为(来源页面) 源地址中是使用AS实现的,没学过AS,所以直接不会运行,于是就自己根据原文的概念进 ...