在上篇讨论里我们主要介绍了Akka-Cluster的基本原理。同时我们也确认了几个使用Akka-Cluster的重点:首先,Akka-Cluster集群构建与Actor编程没有直接的关联。集群构建是ActorSystem层面上的,可以是纯粹的配置和部署行为;分布式Actor程序编程实现了Actor消息地址的透明化,无须考虑目标运行环境是否分布式的,可以按正常的Actor编程模式进行。

既然分布式的Actor编程无须特别针对集群环境,那么摆在我们面前的就是多个可以直接使用的运算环境(集群节点)了,现在我们的分布式编程方式应该主要聚焦在如何充分使用这些分布的运算环境,即:如何把程序合理分配到各集群节点以达到最优的运算输出效率。我们可以通过人为方式有目的向集群节点分配负载,或者通过某种算法来自动分配就像前面讨论过的Routing那样。

我们首先示范如何手工进行集群的负载分配:目的很简单:把不同的功能分配给不同的集群节点去运算。先按运算功能把集群节点分类:我们可以通过设定节点角色role来实现。然后需要一类节点(可能是多个节点)作为前端负责承接任务,然后根据任务类型来分配。负责具体运算的节点统称后台节点(backend nodes),负责接收和分配任务的节点统称前端节点(frontend nodes)。在编程过程中唯一需要考虑集群环境的部分就是前端节点需要知道处在所有后台节点上运算Actor的具体地址,即ActorRef。这点需要后台节点Node在加人集群时向前端负责分配任务的Actor报备自己的ActorRef。前端Actor是通过一个后台报备的ActorRef清单来分配任务的。假如我们用加减乘除模拟四项不同的运算功能,用手工形式把每一项功能部署到一个集群节点上,然后用一个前端节点按功能分配运算任务。下面是所有节点共享的消息类型:

package clusterloadbalance.messages

object Messages {
sealed trait MathOps
case class Add(x: Int, y: Int) extends MathOps
case class Sub(x: Int, y: Int) extends MathOps
case class Mul(x: Int, y: Int) extends MathOps
case class Div(x: Int, y: Int) extends MathOps sealed trait ClusterMsg
case class RegisterBackendActor(role: String) extends ClusterMsg }

负责运算的后台定义如下:

package clusterloadbalance.backend

import akka.actor._
import clusterloadbalance.messages.Messages._ import scala.concurrent.duration._
import akka.cluster._
import akka.cluster.ClusterEvent._
import com.typesafe.config.ConfigFactory object CalcFuctions {
def propsFuncs = Props(new CalcFuctions)
def propsSuper(role: String) = Props(new CalculatorSupervisor(role))
} class CalcFuctions extends Actor {
override def receive: Receive = {
case Add(x,y) =>
println(s"$x + $y carried out by ${self} with result=${x+y}")
case Sub(x,y) =>
println(s"$x - $y carried out by ${self} with result=${x - y}")
case Mul(x,y) =>
println(s"$x * $y carried out by ${self} with result=${x * y}")
case Div(x,y) =>
println(s"$x / $y carried out by ${self} with result=${x / y}")
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
println(s"Restarting calculator: ${reason.getMessage}")
super.preRestart(reason, message)
}
} class CalculatorSupervisor(mathOps: String) extends Actor {
def decider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: ArithmeticException => SupervisorStrategy.Resume
} override def supervisorStrategy: SupervisorStrategy =
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds){
decider.orElse(SupervisorStrategy.defaultDecider)
} val calcActor = context.actorOf(CalcFuctions.propsFuncs,"calcFunction")
val cluster = Cluster(context.system)
override def preStart(): Unit = {
cluster.subscribe(self,classOf[MemberUp])
super.preStart()
} override def postStop(): Unit =
cluster.unsubscribe(self)
super.postStop() override def receive: Receive = {
case MemberUp(m) =>
if (m.hasRole("frontend")) {
context.actorSelection(RootActorPath(m.address)+"/user/frontend") !
RegisterBackendActor(mathOps)
}
case msg@ _ => calcActor.forward(msg)
} } object Calculator {
def create(role: String): Unit = { //create instances of backend Calculator
val config = ConfigFactory.parseString("Backend.akka.cluster.roles = [\""+role+"\"]")
.withFallback(ConfigFactory.load()).getConfig("Backend")
val calcSystem = ActorSystem("calcClusterSystem",config)
val calcRef = calcSystem.actorOf(CalcFuctions.propsSuper(role),"calculator")
}
}

注意,这个运算Actor是带监管的,可以自动处理异常。

为了方便表达,我把所有功能都集中在CalcFunctions一个Actor里。在实际情况下应该分成四个不同的Actor,因为它们会被部署到不同的集群节点上。另外,增加了一个supervisor来监管CalcFunctions。这个supervisor也是分布在四个节点上分别监管本节点上的子级Actor。现在所有集群节点之间的功能交流都会通过这个supervisor来进行了,所以需要向前端登记ActorRef和具体负责的功能role。下面是前台程序定义:

package clusterloadbalance.frontend

import akka.actor._
import clusterloadbalance.messages.Messages._
import com.typesafe.config.ConfigFactory object CalcRouter {
def props = Props(new CalcRouter)
} class CalcRouter extends Actor {
var nodes: Map[String,ActorRef] = Map()
override def receive: Receive = {
case RegisterBackendActor(role) =>
nodes += (role -> sender())
context.watch(sender())
case add: Add => routeCommand("adder", add)
case sub: Sub => routeCommand("substractor",sub)
case mul: Mul => routeCommand("multiplier",mul)
case div: Div => routeCommand("divider",div) case Terminated(ref) => //remove from register
nodes = nodes.filter { case (_,r) => r != ref} }
def routeCommand(role: String, ops: MathOps): Unit = {
nodes.get(role) match {
case Some(ref) => ref ! ops
case None =>
println(s"$role not registered!")
}
}
} object FrontEnd {
private var router: ActorRef = _
def create = { //must load this seed-node before any backends
val calcSystem = ActorSystem("calcClusterSystem",ConfigFactory.load().getConfig("Frontend"))
router = calcSystem.actorOf(CalcRouter.props,"frontend")
}
def getRouter = router
}

只有一个前台负责功能分配。这是个seed-node,必须先启动,然后其它后台节点启动时才能进行后台登记。

我们用下面的代码来测试这个程序。由于Akka的集群节点是由不同的ActorSystem实例代表的,所以在这个例子里没有必要使用多层项目结构:

package clusterLoadBalance.demo
import clusterloadbalance.messages.Messages._
import clusterloadbalance.backend.Calculator
import clusterloadbalance.frontend.FrontEnd object LoadBalancerDemo extends App {
FrontEnd.create Calculator.create("adder") Calculator.create("substractor") Calculator.create("multiplier") Calculator.create("divider") Thread.sleep() val router = FrontEnd.getRouter router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) }

然后,resources/application.conf是这样定义的:

Frontend {
akka {
actor {
provider = "cluster"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
roles = [frontend]
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s
}
}
} Backend {
akka{
actor {
provider = "cluster"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
roles = [backend]
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s
}
}
}

运算结果:

...
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1800460396] with result=21
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#1046049287] with result=13
/ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-2008880936] with result=4
- carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#367851617] with result=42
[WARN] [// ::39.465] [calcClusterSystem-akka.actor.default-dispatcher-] [akka://calcClusterSystem/user/calculator/calcFunction] / by zero

结果显示不同的功能在不同的节点运算,而且ArithmeticException并没有造成Restart,说明SupervisorStrategy发挥了作用。

我们也可以用类似前面介绍的Routing方式来分配负载,原理还是通过前端的Router把任务分配到后台多个Routee上去运算。不同的是这些Routee可以在不同的集群节点上。原则上讲,具体任务分配是通过某种Routing算法自动安排到Routees上去的。但是,我们还是可以用ConsistentHashing-Router模式实现目标Routees的配对。上面例子的加减乘除操作类型可以成为这种Router模式的分类键(Key)。Router还是分Router-Group和Router-Pool两种:Router-Pool更自动化些,它的Routees是由Router构建及部署的,Router-Group的Router则是通过路径查找方式连接已经构建和部署好了的Routees的。如果我们希望有针对性的把任务分配到指定集群节点上的Routee,必须首先构建和部署Routees后再用Router-Group方式连接Routees。还是用上面的例子,使用ConsistentHashing-Router模式。首先调整一下消息类型:

package clusterrouter.messages
import akka.routing.ConsistentHashingRouter._
object Messages {
class MathOps(hashKey: String) extends Serializable with ConsistentHashable {
override def consistentHashKey: Any = hashKey
}
case class Add(x: Int, y: Int) extends MathOps("adder")
case class Sub(x: Int, y: Int) extends MathOps("substractor")
case class Mul(x: Int, y: Int) extends MathOps("multiplier")
case class Div(x: Int, y: Int) extends MathOps("divider") }

注意现在Router是在跨系统的集群环境下,消息类型必须实现Serializable。构建Calculator并且完成部署:

package clusterrouter.backend

import akka.actor._
import clusterrouter.messages.Messages._ import scala.concurrent.duration._ import com.typesafe.config.ConfigFactory object CalcFuctions {
def propsFuncs = Props(new CalcFuctions)
def propsSuper = Props(new CalculatorSupervisor)
} class CalcFuctions extends Actor {
override def receive: Receive = {
case Add(x,y) =>
println(s"$x + $y carried out by ${self} with result=${x+y}")
case Sub(x,y) =>
println(s"$x - $y carried out by ${self} with result=${x - y}")
case Mul(x,y) =>
println(s"$x * $y carried out by ${self} with result=${x * y}")
case Div(x,y) =>
println(s"$x / $y carried out by ${self} with result=${x / y}")
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
println(s"Restarting calculator: ${reason.getMessage}")
super.preRestart(reason, message)
}
} class CalculatorSupervisor extends Actor {
def decider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: ArithmeticException => SupervisorStrategy.Resume
} override def supervisorStrategy: SupervisorStrategy =
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds){
decider.orElse(SupervisorStrategy.defaultDecider)
} val calcActor = context.actorOf(CalcFuctions.propsFuncs,"calcFunction") override def receive: Receive = {
case msg@ _ => calcActor.forward(msg)
} } object Calculator {
def create(port: Int): Unit = { //create instances of backend Calculator
val config = ConfigFactory.parseString("akka.cluster.roles = [backend]")
.withFallback(ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"))
.withFallback(ConfigFactory.load())
val calcSystem = ActorSystem("calcClusterSystem",config)
val calcRef = calcSystem.actorOf(CalcFuctions.propsSuper,"calculator")
}
}

用create函数构建后台节点后再在之上构建部署一个CalculatorSupervisor。

frontend定义如下:

package clusterrouter.frontend

import akka.actor._
import akka.routing.FromConfig
import com.typesafe.config.ConfigFactory object CalcRouter {
def props = Props(new CalcRouter)
} class CalcRouter extends Actor { // This router is used both with lookup and deploy of routees. If you
// have a router with only lookup of routees you can use Props.empty
// instead of Props[CalculatorSupervisor].
val calcRouter = context.actorOf(FromConfig.props(Props.empty),
name = "calcRouter") override def receive: Receive = {
case msg@ _ => calcRouter forward msg
}
} object FrontEnd {
private var router: ActorRef = _
def create = { //must load this seed-node before any backends
val calcSystem = ActorSystem("calcClusterSystem",ConfigFactory.load("hashing"))
router = calcSystem.actorOf(CalcRouter.props,"frontend")
}
def getRouter = router
}

注意calcRouter构建,用了FromConfig.props(Props.empty),代表只进行Routees查找,无需部署。hashing.conf文件如下:

include "application"
akka.cluster.roles = [frontend]
akka.actor.deployment {
/frontend/calcRouter {
router = consistent-hashing-group
routees.paths = ["/user/calculator"]
cluster {
enabled = on
allow-local-routees = on
use-role = backend
}
}
}

application.conf:

akka {
actor {
provider = cluster
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] # auto downing is NOT safe for production deployments.
# you may want to use it during development, read more about it in the docs.
auto-down-unreachable-after = 10s
}
}

用下面的代码进行测试:

package clusterLoadBalance.demo
import clusterrouter.messages.Messages._
import clusterrouter.backend.Calculator
import clusterrouter.frontend.FrontEnd object LoadBalancerDemo extends App { Calculator.create() //seed-node
Calculator.create() //backend node
Calculator.create()
Calculator.create()
Calculator.create()
Calculator.create() Thread.sleep() FrontEnd.create Thread.sleep() val router = FrontEnd.getRouter router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) Thread.sleep() router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) }

重复两批次运算后显示:

 /  carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#1669565820] with result=4
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1532934391] with result=21
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#904088130] with result=13
- carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1532934391] with result=42
[WARN] [// ::03.381] [calcClusterSystem-akka.actor.default-dispatcher-] [akka://calcClusterSystem/user/calculator/calcFunction] / by zero
[INFO] [// ::05.076] [calcClusterSystem-akka.actor.default-dispatcher-] [akka.cluster.Cluster(akka://calcClusterSystem)] Cluster Node [akka.tcp://calcClusterSystem@127.0.0.1:2551] - Leader is moving node [akka.tcp://calcClusterSystem@127.0.0.1:51304] to [Up]
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#904088130] with result=13
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1532934391] with result=21
/ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#1669565820] with result=4
- carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1532934391] with result=42
[WARN] [// ::05.374] [calcClusterSystem-akka.actor.default-dispatcher-] [akka://calcClusterSystem/user/calculator/calcFunction] / by zero

相同功能的运算被分配到了相同的节点上,这点可以用Actor的地址证明。

Akka-Cluster提供的Adaptive-Group是一种比较智能化的自动Routing模式,它是通过对各集群节点的具体负载情况来分配任务的。用户只需要定义adaptive-group的配置,按情况增减集群节点以及在不同的集群节点上构建部署Routee都是自动的。Adaptive-Group-Router可以在配置文件中设置:

include "application"

akka.cluster.min-nr-of-members = 

akka.cluster.role {
frontend.min-nr-of-members =
backend.min-nr-of-members =
} akka.actor.deployment {
/frontend/adaptive/calcRouter {
# Router type provided by metrics extension.
router = cluster-metrics-adaptive-group
# Router parameter specific for metrics extension.
# metrics-selector = heap
# metrics-selector = load
# metrics-selector = cpu
metrics-selector = mix
#
nr-of-instances =
routees.paths = ["/user/calculator"]
cluster {
enabled = on
use-role = backend
allow-local-routees = off
}
}
}

后台的运算Actor与上面人为分配方式一样不变。前端Router的构建也没变,只不过在构建时目标是cluster-metrics-adaptive-group。这个在测试代码中可以看到:

package clusterLoadBalance.demo
import clusterrouter.messages.Messages._
import clusterrouter.backend.Calculator
import clusterrouter.frontend.CalcRouter
import com.typesafe.config.ConfigFactory
import scala.util.Random
import scala.concurrent.duration._
import akka.actor._
import akka.cluster._ class RouterRunner extends Actor {
val jobs = List(Add,Sub,Mul,Div)
import context.dispatcher val calcRouter = context.actorOf(CalcRouter.props,"adaptive")
context.system.scheduler.schedule(.seconds, .seconds, self, "DoRandomMath") override def receive: Receive = {
case _ => calcRouter ! anyMathJob
}
def anyMathJob: MathOps =
jobs(Random.nextInt())(Random.nextInt(), Random.nextInt())
} object AdaptiveRouterDemo extends App { Calculator.create() //seed-node
Calculator.create() //backend node Thread.sleep() val config = ConfigFactory.parseString("akka.cluster.roles = [frontend]").
withFallback(ConfigFactory.load("adaptive")) val calcSystem = ActorSystem("calcClusterSystem",config) //#registerOnUp
Cluster(calcSystem) registerOnMemberUp {
val _ = calcSystem.actorOf(Props[RouterRunner],"frontend")
}
//#registerOnUp //val _ = calcSystem.actorOf(Props[RouterRunner],"frontend") }

测试结果显示如下:

...
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1771471734] with result=108
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1512057504] with result=101
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1512057504] with result=115
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1771471734] with result=2541
- carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1512057504] with result=13
* carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1771471734] with result=2990
- carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1771471734] with result=-31
+ carried out by Actor[akka://calcClusterSystem/user/calculator/calcFunction#-1771471734] with result=115
...

运算已经被部署到不同节点去进行了。

下面是这次讨论示范的源代码:

例1:

build.sbt:

name := "cluster-load-balance"

version := "1.0"

scalaVersion := "2.11.8"

libraryDependencies ++= {
val akkaVersion = "2.5.3"
Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-cluster" % akkaVersion
) }

application.conf:

Frontend {
akka {
actor {
provider = "cluster"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
roles = [frontend]
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s
}
}
} Backend {
akka{
actor {
provider = "cluster"
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
roles = [backend]
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] auto-down-unreachable-after = 10s
}
}
}

messages/Messages.scala

package clusterloadbalance.messages

object Messages {
sealed trait MathOps
case class Add(x: Int, y: Int) extends MathOps
case class Sub(x: Int, y: Int) extends MathOps
case class Mul(x: Int, y: Int) extends MathOps
case class Div(x: Int, y: Int) extends MathOps sealed trait ClusterMsg
case class RegisterBackendActor(role: String) extends ClusterMsg }

backend/Calculator.scala:

package clusterloadbalance.backend

import akka.actor._
import clusterloadbalance.messages.Messages._ import scala.concurrent.duration._
import akka.cluster._
import akka.cluster.ClusterEvent._
import com.typesafe.config.ConfigFactory object CalcFuctions {
def propsFuncs = Props(new CalcFuctions)
def propsSuper(role: String) = Props(new CalculatorSupervisor(role))
} class CalcFuctions extends Actor {
override def receive: Receive = {
case Add(x,y) =>
println(s"$x + $y carried out by ${self} with result=${x+y}")
case Sub(x,y) =>
println(s"$x - $y carried out by ${self} with result=${x - y}")
case Mul(x,y) =>
println(s"$x * $y carried out by ${self} with result=${x * y}")
case Div(x,y) =>
println(s"$x / $y carried out by ${self} with result=${x / y}")
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
println(s"Restarting calculator: ${reason.getMessage}")
super.preRestart(reason, message)
}
} class CalculatorSupervisor(mathOps: String) extends Actor {
def decider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: ArithmeticException => SupervisorStrategy.Resume
} override def supervisorStrategy: SupervisorStrategy =
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds){
decider.orElse(SupervisorStrategy.defaultDecider)
} val calcActor = context.actorOf(CalcFuctions.propsFuncs,"calcFunction")
val cluster = Cluster(context.system)
override def preStart(): Unit = {
cluster.subscribe(self,classOf[MemberUp])
super.preStart()
} override def postStop(): Unit =
cluster.unsubscribe(self)
super.postStop() override def receive: Receive = {
case MemberUp(m) =>
if (m.hasRole("frontend")) {
context.actorSelection(RootActorPath(m.address)+"/user/frontend") !
RegisterBackendActor(mathOps)
}
case msg@ _ => calcActor.forward(msg)
} } object Calculator {
def create(role: String): Unit = { //create instances of backend Calculator
val config = ConfigFactory.parseString("Backend.akka.cluster.roles = ["+role+"]")
.withFallback(ConfigFactory.load()).getConfig("Backend")
val calcSystem = ActorSystem("calcClusterSystem",config)
val calcRef = calcSystem.actorOf(CalcFuctions.propsSuper(role),"calculator")
}
}

frontend/FrontEnd.scala:

package clusterloadbalance.frontend

import akka.actor._
import clusterloadbalance.messages.Messages._
import com.typesafe.config.ConfigFactory object CalcRouter {
def props = Props(new CalcRouter)
} class CalcRouter extends Actor {
var nodes: Map[String,ActorRef] = Map()
override def receive: Receive = {
case RegisterBackendActor(role) =>
nodes += (role -> sender())
context.watch(sender())
case add: Add => routeCommand("adder", add)
case sub: Sub => routeCommand("substractor",sub)
case mul: Mul => routeCommand("multiplier",mul)
case div: Div => routeCommand("divider",div) case Terminated(ref) => //remove from register
nodes = nodes.filter { case (_,r) => r != ref} }
def routeCommand(role: String, ops: MathOps): Unit = {
nodes.get(role) match {
case Some(ref) => ref ! ops
case None =>
println(s"$role not registered!")
}
}
} object FrontEnd {
private var router: ActorRef = _
def create = { //must load this seed-node before any backends
val calcSystem = ActorSystem("calcClusterSystem",ConfigFactory.load().getConfig("Frontend"))
router = calcSystem.actorOf(CalcRouter.props,"frontend")
}
def getRouter = router
}

loadBalanceDemo.scala

package clusterLoadBalance.demo
import clusterloadbalance.messages.Messages._
import clusterloadbalance.backend.Calculator
import clusterloadbalance.frontend.FrontEnd object LoadBalancerDemo extends App {
FrontEnd.create Calculator.create("adder") Calculator.create("substractor") Calculator.create("multiplier") Calculator.create("divider") Thread.sleep() val router = FrontEnd.getRouter router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) }

例2:

build.sbt:

name := "cluster-router-demo"

version := "1.0"

scalaVersion := "2.11.9"

sbtVersion := "0.13.7"

resolvers += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/"

val akkaVersion = "2.5.3"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% "akka-actor" % akkaVersion,
"com.typesafe.akka" %% "akka-remote" % akkaVersion,
"com.typesafe.akka" %% "akka-cluster" % akkaVersion,
"com.typesafe.akka" %% "akka-cluster-metrics" % akkaVersion,
"com.typesafe.akka" %% "akka-cluster-tools" % akkaVersion,
"com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion)

application.conf+hashing.conf+adaptive.conf:

akka {
actor {
provider = cluster
}
remote {
log-remote-lifecycle-events = off
netty.tcp {
hostname = "127.0.0.1"
port =
}
} cluster {
seed-nodes = [
"akka.tcp://calcClusterSystem@127.0.0.1:2551"] # auto downing is NOT safe for production deployments.
# you may want to use it during development, read more about it in the docs.
auto-down-unreachable-after = 10s
}
} include "application"
akka.cluster.roles = [frontend]
akka.actor.deployment {
/frontend/calcRouter {
router = consistent-hashing-group
routees.paths = ["/user/calculator"]
cluster {
enabled = on
allow-local-routees = on
use-role = backend
}
}
} include "application" akka.cluster.min-nr-of-members = akka.cluster.role {
frontend.min-nr-of-members =
backend.min-nr-of-members =
} akka.actor.warn-about-java-serializer-usage = off
akka.log-dead-letters-during-shutdown = off
akka.log-dead-letters = off akka.actor.deployment {
/frontend/adaptive/calcRouter {
# Router type provided by metrics extension.
router = cluster-metrics-adaptive-group
# Router parameter specific for metrics extension.
# metrics-selector = heap
# metrics-selector = load
# metrics-selector = cpu
metrics-selector = mix
#
nr-of-instances =
routees.paths = ["/user/calculator"]
cluster {
enabled = on
use-role = backend
allow-local-routees = off
}
}
}

messages/Messages.scala:

package clusterrouter.messages
import akka.routing.ConsistentHashingRouter._
object Messages {
class MathOps(hashKey: String) extends Serializable with ConsistentHashable {
override def consistentHashKey: Any = hashKey
}
case class Add(x: Int, y: Int) extends MathOps("adder")
case class Sub(x: Int, y: Int) extends MathOps("substractor")
case class Mul(x: Int, y: Int) extends MathOps("multiplier")
case class Div(x: Int, y: Int) extends MathOps("divider") }

backend/Calculator.scala:

package clusterrouter.backend

import akka.actor._
import clusterrouter.messages.Messages._ import scala.concurrent.duration._ import com.typesafe.config.ConfigFactory object CalcFuctions {
def propsFuncs = Props(new CalcFuctions)
def propsSuper = Props(new CalculatorSupervisor)
} class CalcFuctions extends Actor {
override def receive: Receive = {
case Add(x,y) =>
println(s"$x + $y carried out by ${self} with result=${x+y}")
case Sub(x,y) =>
println(s"$x - $y carried out by ${self} with result=${x - y}")
case Mul(x,y) =>
println(s"$x * $y carried out by ${self} with result=${x * y}")
case Div(x,y) =>
println(s"$x / $y carried out by ${self} with result=${x / y}")
} override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
println(s"Restarting calculator: ${reason.getMessage}")
super.preRestart(reason, message)
}
} class CalculatorSupervisor extends Actor {
def decider: PartialFunction[Throwable,SupervisorStrategy.Directive] = {
case _: ArithmeticException => SupervisorStrategy.Resume
} override def supervisorStrategy: SupervisorStrategy =
OneForOneStrategy(maxNrOfRetries = , withinTimeRange = seconds){
decider.orElse(SupervisorStrategy.defaultDecider)
} val calcActor = context.actorOf(CalcFuctions.propsFuncs,"calcFunction") override def receive: Receive = {
case msg@ _ => calcActor.forward(msg)
} } object Calculator {
def create(port: Int): Unit = { //create instances of backend Calculator
val config = ConfigFactory.parseString("akka.cluster.roles = [backend]")
.withFallback(ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"))
.withFallback(ConfigFactory.load("adaptive"))
val calcSystem = ActorSystem("calcClusterSystem",config)
val calcRef = calcSystem.actorOf(CalcFuctions.propsSuper,"calculator")
}
}

frontend/FrontEnd.scala:

package clusterrouter.frontend

import akka.actor._
import akka.routing.FromConfig
import com.typesafe.config.ConfigFactory object CalcRouter {
def props = Props(new CalcRouter)
} class CalcRouter extends Actor { // This router is used both with lookup and deploy of routees. If you
// have a router with only lookup of routees you can use Props.empty
// instead of Props[CalculatorSupervisor].
val calcRouter = context.actorOf(FromConfig.props(Props.empty),
name = "calcRouter") override def receive: Receive = {
case msg@ _ => calcRouter forward msg
}
} object FrontEnd {
private var router: ActorRef = _
def create = { //must load this seed-node before any backends
val calcSystem = ActorSystem("calcClusterSystem",ConfigFactory.load("hashing"))
router = calcSystem.actorOf(CalcRouter.props,"frontend")
}
def getRouter = router
}

loadBalancerDemo.scala:

package clusterrouter.demo
import clusterrouter.messages.Messages._
import clusterrouter.backend.Calculator
import clusterrouter.frontend.FrontEnd object LoadBalancerDemo extends App { Calculator.create() //seed-node
Calculator.create() //backend node
Calculator.create()
Calculator.create()
Calculator.create()
Calculator.create() Thread.sleep() FrontEnd.create Thread.sleep() val router = FrontEnd.getRouter router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) Thread.sleep() router ! Add(,)
router ! Mul(,)
router ! Div(,)
router ! Sub(, )
router ! Div(,) }

adaptiveRouterDemo.scala:

package clusterrouter.demo
import clusterrouter.messages.Messages._
import clusterrouter.backend.Calculator
import clusterrouter.frontend.CalcRouter
import com.typesafe.config.ConfigFactory
import scala.util.Random
import scala.concurrent.duration._
import akka.actor._
import akka.cluster._ class RouterRunner extends Actor {
val jobs = List(Add,Sub,Mul,Div)
import context.dispatcher val calcRouter = context.actorOf(CalcRouter.props,"adaptive")
context.system.scheduler.schedule(.seconds, .seconds, self, "DoRandomMath") override def receive: Receive = {
case _ => calcRouter ! anyMathJob
}
def anyMathJob: MathOps =
jobs(Random.nextInt())(Random.nextInt(), Random.nextInt())
} object AdaptiveRouterDemo extends App { Calculator.create() //seed-node
Calculator.create() //backend node Thread.sleep() val config = ConfigFactory.parseString("akka.cluster.roles = [frontend]").
withFallback(ConfigFactory.load("adaptive")) val calcSystem = ActorSystem("calcClusterSystem",config) //#registerOnUp
Cluster(calcSystem) registerOnMemberUp {
val _ = calcSystem.actorOf(Props[RouterRunner],"frontend")
}
//#registerOnUp //val _ = calcSystem.actorOf(Props[RouterRunner],"frontend") }

Akka(11): 分布式运算:集群-均衡负载的更多相关文章

  1. 如何通俗理解——>集群、负载均衡、分布式

    转自:周洲 (Julie) 在“高并发,海量数据,分布式,NoSql,云计算......”概念满天飞的年代,相信不少朋友都听说过甚至常与人提起“集群,负载均衡”等,但不是所有人都有机会真正接触到这些技 ...

  2. 菜鸟教你如何通俗理解——>集群、负载均衡、分布式

    在“高并发,海量数据,分布式,NoSql,云计算......”概念满天飞的年代,相信不少朋友都听说过甚至常与人提起“集群,负载均衡”等,但不是所有人都有机会真正接触到这些技术,也不是所有人都真正理解了 ...

  3. Web服务器Tomcat集群与负载均衡技术

    我们曾经介绍过三种Tomcat集群方式的优缺点分析.本文将介绍Tomcat集群与负载均衡技术具体实施过程. 在进入集群系统架构探讨之前,先定义一些专门术语: 1. 集群(Cluster):是一组独立的 ...

  4. Tomcat服务器集群与负载均衡实现

    一.前言 在单一的服务器上执行WEB应用程序有一些重大的问题,当网站成功建成并开始接受大量请求时,单一服务器终究无法满足需要处理的负荷量,所以就有点显得有 点力不从心了.另外一个常见的问题是会产生单点 ...

  5. 结合Apache和Tomcat实现集群和负载均衡 JK 方式 2 (转)

    本文Apache+Tomcat集群配置       基于最新的Apache和Tomcat,具体是2011年4月20日最新的Tomcat和Apache集群和负载均衡配置. 准备环境 Apache Apa ...

  6. motan负载均衡/zookeeper集群/zookeeper负载均衡的关系

    motan/dubbo支持负载均衡.zookeeper有集群的概念.zookeeper似乎也能做负载均衡,这3者是什么关系呢? 3个概念:motan/dubbo负载均衡.zookeeper集群.zoo ...

  7. WEB 集群与负载均衡(一)基本概念-上

    Web集群是由多个同时运行同一个web应用的服务器组成,在外界看来就像一个服务器一样,这多台服务器共同来为客户提供更高性能的服务.集群更标准的定义是:一组相互独立的服务器在网络中表现为单一的系统,并以 ...

  8. 10分钟搭建服务器集群——Windows7系统中nginx与IIS服务器搭建集群实现负载均衡

    分布式,集群,云计算机.大数据.负载均衡.高并发······当耳边响起这些词时,做为一个菜鸟程序猿无疑心中会激动一番(或许这是判断是否是一个标准阿猿的标准吧)! 首先自己从宏观把控一下,通过上网科普自 ...

  9. 用apache和tomcat搭建集群,实现负载均衡

    型的企业应用每天都需要承受巨大的访问量,在着巨大访问量的背后有数台服务器支撑着,如果一台服务器崩溃了,那么其他服务器可以使企业应用继续运行,用户对服务器的运作是透明化的,如何实现这种透明化呢?由如下问 ...

随机推荐

  1. js实用方法记录-js动态加载css、js脚本文件

    js实用方法记录-动态加载css/js 附送一个加载iframe,h5打开app代码 1. 动态加载js文件到head标签并执行回调 方法调用:dynamicLoadJs('http://www.yi ...

  2. chroot jail

    注意,原标题是:Linux Virtualization using Chroot Jail,我实在不知道怎么翻译,所以,自作主张,选了chroot jail作为标题.原文地址 chroot jail ...

  3. 关于Python编码,超诡异的,我也是醉了

    Python的编码问题,真是让人醉了.最近碰到的问题还真不少.比如中文文件名.csv .python对外呈现不一致啊,感觉好不公平. 没图说个JB,下面立马上图.   我早些时候的其他脚本,csv都是 ...

  4. python的列表(二)

    1.遍历整个列表  #for 循环 # >>> name_list['faker', 'dopa', 'gogoing', 'uzi']  >>> for LOL_ ...

  5. 给sftp创建新用户、默认打开和限制在某个目录

    一.环境: CentOS 6.8 使用 FileZilla 进行 sftp 连接 二.背景 给外包的工作人员提供我司服务器的某一目录的访问(包括读写)权限,方便他们部署代码文件. 之所以是某一目录的访 ...

  6. malloc与new相关

    前言: 通常我们使用数组的时候:必须提前用一个常量来指定数组的长度,同时它的内存空间在编译的时候就已经被分配了.但是有时候数组的长度只有在运行的时候才能知道.因此,一种简单的解决方案就是提前申请一块较 ...

  7. java7增强的try语句关闭资源

    java7增强的try语句关闭资源 传统的关闭资源方式 import java.io.FileInputStream; import java.io.FileOutputStream; import ...

  8. OVS + kernel datapath 的安装

    ***kernel datapath的OVS编译安装 下载源代码 $ git clone https://github.com/openvswitch/ovs.git 准备工具:生成configure ...

  9. 前端单元测试框架-Mocha

    引言 随着前端工程化这一概念的产生,项目开发中前端的代码量可谓是'急剧上升',所以在这种情况下,我们如何才能保证代码的质量呢,对于框架,比如React.Vue,因为有自己的语法规则,及时每个开发人员的 ...

  10. 第一次使用idea从SVN什么checkout项目,一直都是用的eclipse

    IntelliJ IDEA 14 拉取SVN maven 多模块项目 部署tomcat 详细图解!   二话不说 进入主题 我们创建空项目实际上是项目空间 进入主界面 想用svn必须先启用它 选择Su ...