上一篇讨论里我们介绍了几种任务分配(Routing)模式。Akka提供的几种现成智能化Routing模式大多数是通过对用户屏蔽具体的运算Routee选择方式来简化Router使用,提高智能程度,所以我们提到Router的运算是一种无序的运算,消息之间绝对不容许任何形式的依赖,因为向Router发送的消息可能在任何Routee上运算。但是,如果我们能够把运算任务按照任务的类型分配给专门负责处理此等类型任务的Routee,那么我们就可以充分利用Routing模式所带来的运算拓展能力来提高整体运算效率。Akka的ConsistentHashingRouter就是为了满足这样的需求而提供的。ConsistentHashingRouter是通过消息的特征来分辨消息类型,然后自动构建和管理处理各种类型消息的Routees。当然,这就要求系统的消息必须具备预先设定的特征,使ConsistentHashingRouter可以正确分辨并分配给指定的Routee去运算。如果我们确定只有一个Routee负责处理一种类型消息的话,甚至可以在这个Routee中维护某种状态。我们可以设计一个场景来示范ConsistentHashingRouter的应用:模拟一个多货币的存钱盒,分n次随意从盒里取出钱币然后统计各种货币的总额。这个场景中的特征很明显:就是货币种类了,我们把抽出的货币按币种、金额合成消息发给ConsistentHashingRouter。例子里的Routee应该是按照币种由Router自动构建的,维护各种货币当前总额作为内部状态。向ConsistentHashingRouter发送的消息被分配给相应币种的Routee去登记更新货币当前总额。这个统计金额的Routee可以如下定义:
import akka.actor._
val currencies = List("RMB","USD","EUR","JPY","GBP","DEM","HKD","FRF","CHF")
object MoneyCounter {
sealed trait Counting
case class OneHand(cur: String, amt: Double) extends Counting
case class ReportTotal(cur: String) extends Counting
}
class MoneyCounter extends Actor with ActorLogging {
import MoneyCounter._
var currency: String = "RMB"
var amount: Double = 0
override def receive: Receive = {
case OneHand(cur,amt) =>
currency = cur
amount += amt
log.info(s"${self.path.name} received one hand of $amt$cur")
case ReportTotal(_) =>
log.info(s"${self.path.name} has a total of $amount$currency")
}
}
1、定义ConsistentHashingRouter的hashMapping函数:这是个PartialFunction[Any,Any],如下:
object HashingRouter extends App {
import MoneyCounter._
val currencies = List("RMB","USD","EUR","JPY","GBP","DEM","HKD","FRF","CHF")
val routerSystem = ActorSystem("routerSystem")
def mcHashMapping: PartialFunction[Any,Any] = {
case OneHand(cur,_) => cur
case ReportTotal(cur) => cur
}
val router = routerSystem.actorOf(ConsistentHashingPool(
nrOfInstances = 5,hashMapping = mcHashMapping,virtualNodesFactor = 2)
.props(MoneyCounter.props),name = "moneyCounter" )
router ! OneHand("RMB",10.00)
router ! OneHand("USD",10.00)
router ! OneHand("HKD",10.00)
router ! OneHand("RMB",10.00)
router ! OneHand("CHF",10.00)
router ! ReportTotal("RMB")
router ! ReportTotal("USD")
scala.io.StdIn.readLine()
routerSystem.terminate()
}
INFO] [06/05/2017 15:20:09.334] [routerSystem-akka.actor.default-dispatcher-6] [akka://routerSystem/user/moneyCounter/$e] $e received one hand of 10.0RMB
[INFO] [06/05/2017 15:20:09.334] [routerSystem-akka.actor.default-dispatcher-3] [akka://routerSystem/user/moneyCounter/$b] $b received one hand of 10.0USD
[INFO] [06/05/2017 15:20:09.334] [routerSystem-akka.actor.default-dispatcher-7] [akka://routerSystem/user/moneyCounter/$d] $d received one hand of 10.0CHF
[INFO] [06/05/2017 15:20:09.334] [routerSystem-akka.actor.default-dispatcher-2] [akka://routerSystem/user/moneyCounter/$a] $a received one hand of 10.0HKD
[INFO] [06/05/2017 15:20:09.334] [routerSystem-akka.actor.default-dispatcher-6] [akka://routerSystem/user/moneyCounter/$e] $e received one hand of 10.0RMB
[INFO] [06/05/2017 15:20:09.337] [routerSystem-akka.actor.default-dispatcher-2] [akka://routerSystem/user/moneyCounter/$b] $b has a total of 10.0USD
[INFO] [06/05/2017 15:20:09.337] [routerSystem-akka.actor.default-dispatcher-6] [akka://routerSystem/user/moneyCounter/$e] $e has a total of 20.0RMB
2、可以让消息继承ConsistentHashable,如此我们要在消息里实现函数constentHashKey, 如下:
object MoneyCounter {
sealed class Counting(cur: String) extends ConsistentHashable {
override def consistentHashKey: Any = cur
}
case class OneHand(cur: String, amt: Double) extends Counting(cur)
case class ReportTotal(cur: String) extends Counting(cur)
def props = Props(new MoneyCounter)
}
val router = routerSystem.actorOf(ConsistentHashingPool(
nrOfInstances = 5, virtualNodesFactor = 2).props(
MoneyCounter.props),name = "moneyCounter")
router ! OneHand("RMB",10.00)
router ! OneHand("USD",10.00)
router ! OneHand("HKD",10.00)
router ! OneHand("RMB",10.00)
router ! OneHand("CHF",10.00)
router ! ReportTotal("RMB")
router ! ReportTotal("USD")
[INFO] [06/05/2017 15:36:29.746] [routerSystem-akka.actor.default-dispatcher-7] [akka://routerSystem/user/moneyCounter/$e] $e received one hand of 10.0RMB
[INFO] [06/05/2017 15:36:29.746] [routerSystem-akka.actor.default-dispatcher-5] [akka://routerSystem/user/moneyCounter/$b] $b received one hand of 10.0USD
[INFO] [06/05/2017 15:36:29.746] [routerSystem-akka.actor.default-dispatcher-6] [akka://routerSystem/user/moneyCounter/$a] $a received one hand of 10.0HKD
[INFO] [06/05/2017 15:36:29.746] [routerSystem-akka.actor.default-dispatcher-4] [akka://routerSystem/user/moneyCounter/$d] $d received one hand of 10.0CHF
[INFO] [06/05/2017 15:36:29.746] [routerSystem-akka.actor.default-dispatcher-7] [akka://routerSystem/user/moneyCounter/$e] $e received one hand of 10.0RMB
[INFO] [06/05/2017 15:36:29.749] [routerSystem-akka.actor.default-dispatcher-7] [akka://routerSystem/user/moneyCounter/$e] $e has a total of 20.0RMB
[INFO] [06/05/2017 15:36:29.749] [routerSystem-akka.actor.default-dispatcher-4] [akka://routerSystem/user/moneyCounter/$b] $b has a total of 10.0USD
router ! ConsistentHashableEnvelope(message = OneHand("RMB",23.00),hashKey = "RMB")
看来还是第二种方法比较合适。因为比起第一种方法多了类型安全和与Router的松散耦合。下面就是一个用第二种方法的完整示范源代码:
import akka.actor._
import akka.routing.ConsistentHashingRouter.{ConsistentHashMapping, ConsistentHashable, ConsistentHashableEnvelope}
import akka.routing._
object MoneyCounter {
sealed class Counting(cur: String) extends ConsistentHashable {
override def consistentHashKey: Any = cur
}
case class OneHand(cur: String, amt: Double) extends Counting(cur)
case class ReportTotal(cur: String) extends Counting(cur)
def props = Props(new MoneyCounter)
}
class MoneyCounter extends Actor with ActorLogging {
import MoneyCounter._
var currency: String = "RMB"
var amount: Double = 0
override def receive: Receive = {
case OneHand(cur,amt) =>
currency = cur
amount += amt
log.info(s"${self.path.name} received one hand of $amt$cur")
case ReportTotal(_) =>
log.info(s"${self.path.name} has a total of $amount$currency")
}
}
object HashingRouter extends App {
import MoneyCounter._
import scala.util.Random
val currencies = List("RMB","USD","EUR","JPY","GBP","DEM","HKD","FRF","CHF")
val routerSystem = ActorSystem("routerSystem")
val router = routerSystem.actorOf(ConsistentHashingPool(
nrOfInstances = currencies.size+1, virtualNodesFactor = 2).props(
MoneyCounter.props),name = "moneyCounter")
(1 to 20).toList foreach (_ => router ! OneHand(
currencies(Random.nextInt(currencies.size-1))
,Random.nextInt(100) * 1.00))
currencies foreach (c => router ! ReportTotal(c))
scala.io.StdIn.readLine()
routerSystem.terminate()
}