akka之分发和路由

一 Dispatchers

1.1 理解分发器

分发器是一个通信协调员的角色,主要负责消息的接收和传递。主要依据一定的分发策略,用于控制执行流程,然后将到来的消息或者请求路由给相关的业务进程。


提供飞机的航空公司类似于Akka的MailBox,机场跑道类似于Akka的线程资源,航空交通管制塔类似于Dispatcher

 

1.2 分发器的类型

1.2.1 Dispatcher


这是默认的分发器,它是一个基于事件的,绑定了一套Actors到一个线程池中。

特点如下:

# 每一个actor都依赖他们自己的邮箱

# 可以被任何数量的actors共享

# 是对非阻塞的优化

 

1.2.2 PinnedDispatcher


它对于每一个Actor提供了单独的或者专用的线程,在做一些I/O操作或者执行一些长时间计算任务的task.

# 每一个actor都依赖他们自己的邮箱

# 对于每一个actor的专用线程不能和其他actor共享

# 分发器依赖于线程池的Execcutor

# 是对阻塞操作的优化

1.2.3 BalancingDispatcher


他可以重新分配工作从比较忙的actor到比较空闲的actor,任务的重新分配是有前提的,那就是所有的actor的类型是必须一致的

特点:

# 只有一个邮箱

# 分发器被所有相同类型的actors共享

# 分发器依赖于一个线程池或者fork join pool

 

1.2.4 CallingThreadDispatcher

他只是在相同的线程上运行任务,不能创建任何新的线程,并且提供了一个执行顺序

特点:

# 每一个actor都依赖于自己的邮箱

# 所有actors共享分发器

# 分发器依赖于当前的调用线程

 

1.3 Dispatcher的使用

Akka支持的Executor Context:

第一种: Thread PoolExecutor

创建工作者线程池,任务队列分配到池子,如果task数量超过了线程数,那么就会排队等候,直到池子有空闲线程的时候

 

第二种:Fork JoinExecutor

这是基于分儿治之的前提,他的核心思想就是把一个大的任务划分成多个小任务,最后在进行结果的联合,这些任务是能够独立运行的

 

对于每一个ExecutorContext, Akka都可以指定一个配置文件参数,这些参数主要定义:

# 分配的最小数量的线程数

# 分配的最大数量的线程数

# 所使用的系数(基于可用的CPU核数)

比如最小线程数为3,系数为2,那么dispatcher总共可用开始6个线程

 

1.4 如何配置参数

它会默认去读取classpath下的application.conf文件,所以你可以定义一个该文件,然后在里面配置

akka {
  loglevel = INFO
}

# 你的dispatcher名字
my-dispatcher {
  # 指定Dispatcher类型[Dispatcher/PinnedDispatcher/BalancingDispatcher/CallingThreadDispatcher]
  type = Dispatcher
  # 指定使用哪一种 Executor Context [thread-pool-executor/fork-join-executor]
  executor = "fork-join-executor"
  # 针对executor context 类型配置参数
  fork-join-executor {
    # 最小的并发数
    parallelism-min = 2
    # 并发因子可以计算并发总并发数 ceil(available processors * factor)
    parallelism-factor = 2.0
    # 最大并发数
    parallelism-max = 10
  }
  thread-pool-executor {
    # 线程池最小线程数
    core-pool-size-min = 2
    # 因子 (有效核数)*因子
    core-pool-size-factor = 2.0
    # 线程池最大线程数
    core-pool-size-max = 10
  }
  # 每一个Actor在跳到另外一个Actor之前能处理的最大消息的吞吐量
  # 设成1,对于每一个Actor都是公平的
  throughput = 100
  # 指定mailbox的容量大小,如果设置成0 表示没有边界
  mailbox-capacity = 100
  # 指定mailbox的类型,这个应该配合mailbox-capacity
  # mailbox-type = ""
}

 

1.5 选择哪一种Dispatcher的策略

# 分发器的选择取决于你的应用程序是阻塞还是非阻塞

# executor的选择取决于你的应用程序逻辑,是不是需要将一个任务分成多个小任务,然后每一个小任务可以并行运行

# 依据CPU核数确定你的线程数的最大值和最小值

# 吞吐量的设置

 

简单的例子:

class DispatcherActor extends UntypedActor{
    val x:Int = 2
    var child:ActorRef = _
    @throws[Exception](classOf[Exception]) override
    def preStart(): Unit = {
        child = context.actorOf(Props[MsgEchoActor],name="MsgEchoActor")
    }

    @throws[Throwable](classOf[Throwable])
    override def onReceive(message: Any): Unit = {
        println("[onReceive] => "+message)
        var result = 0
        try {
            result = x + message.toString.toInt
        } catch {
            case t:Throwable => t.printStackTrace()
        }
        child.tell(result,sender)
    }
}

class MsgEchoActor extends Actor {
    def receive:Receive = {
        case a if 1 to 10 contains(a) =>
            println(s"$a belongs to [1-10]")
        case b if 11 to 50 contains(b) =>
            println(s"$b belongs to [11-50]")
        case c if 51 to 100 contains(c) =>
            println(s"$c belongs to [51-100]")
        case _ =>
            println("Unknown Range Value")
    }
}
object DispatcherActor extends App {
    val _system = ActorSystem("DispatcherActorSystem")
    val dispatcherActor = _system.actorOf(Props[DispatcherActor]
        .withDispatcher("my-dispatcher"),name="DispatcherActor")
    dispatcherActor ! 88
    dispatcherActor ! 20
    dispatcherActor ! 1
    Thread.sleep(50)
    _system.terminate()
}

 

二 MailBox

邮箱,临时存储消息的地方,然后这些消息会派到Actor中,一般情况下,每一个Actor都会依赖一个Mailbox,但是BalancingPool允许所有actor共用一个MailBox

 

2.1Mailbox类型

以下是一些默认的邮箱实现

他们依赖于Java的并发库里的队列:

Blocking Queue: 阻塞队列意味着一个队列如果满了,那么该队列就会阻塞,直到有空的空间才可以存放数据

Bounded Queue: 边界队列意味着这个队列有大小限制,如果达到指定的数量,那么就不能继续存数据

 

Unbounded MailBox: 如果不修改akka.actor.default-mailbox配置,那么它是默认的mailbox type.

配置名称:unbounded 或者 akka.dispatch.UnboundedMainbox

SingleConsumerOnlyUnboundedMailBox:

依赖于多个生产者和单个消费者队列,不能被BalancingDispatcher使用,不是阻塞队列,也没有边界

配置名称:akka.dispatch.SingleConsumerOnlyUnbounded

 

UnboundedControlAwareMailbox: 非阻塞,无边界,高优先级

配置名字:akka.dispatch.UnboundedControlAwareMailbox

 

UnboundedPriorityMailbox: 消息投递的顺序等价于优先级,非阻塞,无边界,和UnboundedStablePriorityMaibox相反

配置名称:akka.dispatch.UnboundedPriorityMailbox

 

UnboundedStablePriorityMaibox:不阻塞,无边界,FIFO投递消息

和UnboundedPriorityMailbox相反。

配置名称:akka.dispatch.UnboundedStablePriorityMaibox

 

BoundedMailBox:只要达到了边界限制,则不能继续push数据

配置名称:bounded或者akka.dispatch.BoundedMailbox

 

BoundedPriorityMailbox: 消息投递的顺序等价于优先级,非阻塞,有边界

配置名称:akka.dispatch.BoundedPriorityMailbox

 

BoundedStablePriorityMailbox: 先进先出的顺序投递消息,非阻塞,有边界

配置名字:akka.dispatch.BoundedStablePriorityMailbox

 

BoundedControlAwareMailbox: 非阻塞,有边界,高优先级

配置名称:akka.dispatch.BoundedControlAwareMailbox

 

 

 

2.2 如何选择Mailbox类型

# 默认情况是默认的mailbox => akka.actor.default-mailbox被使用

怎么设置默认的mailboxtype呢?

akka.actor.default-mailbox {

      mailbox-type= "akka.dispatch.SingleConsumerOnlyUnbounded

Mailbox"

}

# 如果actor的部署配置段包含一个mailbox 的配置段,然后在里面描述的mailbox 的类型,那么就会使用这个类型

# 如果actor的Props#withMailbox也可以设置mailbox类型

# 如果分发器的配置段描述了mailbox-type,那么将会使用这个类型

 

2.3 定制Mailbox

import java.util.concurrent.ConcurrentLinkedQueue
import akka.actor.{ActorRef, PoisonPill, ActorSystem}
import akka.dispatch._
import com.typesafe.config.Config

trait MyUnboundedMessageQueueSemantics
class MyUnboundedMailBox extends MailboxType with ProducesMessageQueue[MyUnboundedMailBox.MyMessageQueue]{
    import MyUnboundedMailBox._
    def this(settings:ActorSystem.Settings,config:Config){
        this()
    }
    override def create(owner: Option[ActorRef], system: Option[ActorSystem]): MessageQueue = {
        new MyMessageQueue()
    }
}

object MyUnboundedMailBox {
    class MyMessageQueue extends MessageQueue with MyUnboundedMessageQueueSemantics {
        private finalval queue = new ConcurrentLinkedQueue[Envelope]()
        override def enqueue(receiver: ActorRef, handle: Envelope): Unit = {
            queue.offer(handle)
        }
        override def numberOfMessages: Int = queue.size
       
override def dequeue(): Envelope = queue.poll()
        override def cleanUp(owner: ActorRef, deadLetters: MessageQueue): Unit = {
            while (hasMessages){
                deadLetters.enqueue(owner,dequeue())
            }
        }
        override def hasMessages: Boolean = !queue.isEmpty
   
}
}

 

 

custom-dispatcher {
  mailbox-requirement = "com.concurrent.akka.mailbox.MyUnboundedMessageQueueSemantics"
}
akka.actor.mailbox.requirements {
  "com.concurrent.akka.mailbox.MyUnboundedMessageQueueSemantics" = custom-dispatcher-mainbox
}
custom-dispatcher-mainbox {
  mailbox-type = "com.concurrent.akka.mailbox.MyUnboundedMailBox"
}

 

三 路由 Routing

我们知道当大量的actor在并行工作的时候,处理到来的消息流,这时候就需要一个组件或者东西来引导消息从源到目的地Actor,这个组件或者东西就是Router

在Akka中,router也是一种actor 类型,它路由到来的消息到其他的actors,其他那些actors就叫做routees(被路由对象)


3.1Akka router支持以下路由策略

2.11以前的方式=>

# RoundRobinRouter: 轮询

# RadnomRouter: 随机

# SmallestMailboxRouter: 空闲

# BroadcastRouter: 广播,将转发相同消息到每一个routees

 

2.11之后的提供的路由实现:

# RoundRobinRouteLogic

# RandomRouteLogic

# SmallestMailboxRoutingLogic

# BroadcastRoutingLogic

# TailChoppingRoutingLogic

# ConsistentHashingRoutingLogic

 

3.2 创建router的2种方法

我们创建router,router可以包含需要路由到的Actor即routees

和路由到这些routees的路由策略

 

3.2.1 程序动态创建

class Master extends Actor{
    var router:Router = _
    @throws[Exception](classOf[Exception]) override
    def
preStart(): Unit = {
        router = {
            /*相当于创建了5actors作为routees*/
           
var routees = Vector.fill(5){
                val r = context.actorOf(Props[Worker],"WorkActor---"+math.round(math.random * 100) + 1)
                println(r.path)
                context watch r
               
ActorRefRoutee(r)
            }
            Router(RoundRobinRoutingLogic(),routees)
        }
    }

    def receive: Receive = {
        case a:Int => router.route(a,sender)
        case _ => println("参数异常")
    }
}

结果如下:

3.2.2 根据application.conf配置文件针对actor的配置

###############   2种配置方式   ###############
###############   单层结构      ###############
# akka.actor.deployment {
#  /MasterActor2/router1{
#    router = round-robin-pool
#    nr-of-instances = 5
#  }
#}

###############   多层结构      ###############
akka {
  actor{
    deployment {
      # 注意这里不需要加/user
      /MasterActor2/router1 {
        # 路由策略
        router = round-robin-pool
        # routees的数量,默认是按照$+字母的方式
        nr-of-instances = 5
      }

      # 远程调用
      # '/user/actorA/actorB' is a remote deployed actor
      # /actorA/actorB {
      #  remote = "akka.tcp://sampleActorSystem@127.0.0.1:2553"
      # }

      # 匹配所有子actor
      # all direct children of '/user/actorC' have a dedicated dispatcher
      # "/actorC/*" {
      # dispatcher = my-dispatcher
      # }

      # 为某一个actor指定一个邮箱
      # '/user/actorD/actorE' has a special priority mailbox
      # /actorD/actorE {
      #  mailbox = prio-mailbox
      # }
    }
  }
}

# my-dispatcher {
#  fork-join-executor.parallelism-min = 10
#  fork-join-executor.parallelism-max = 10
#}
# prio-mailbox {
#   mailbox-type = "a.b.MyPrioMailbox"
# }

 

class Master2 extends Actor{
    var router:ActorRef = _
    @throws[Exception](classOf[Exception]) override
    def preStart(): Unit = {
        /*根据配置文件Application.conf创建router*/
        router = context.actorOf(FromConfig.props(Props[Worker]),"router1")
    }

    def receive: Receive = {
        case a:Long => router ! a
        case _ => println("参数异常")
    }
}
object Master extends App {
    val system = ActorSystem("MasterActorSystem")
    val x = system.actorOf(Props[Master2],name="MasterActor2")
    for (i <- 0 until 10) {
        x ! math.round(math.random * 10) + 1
    }

    Thread.sleep(20)
    system.terminate()
}

 

结果如下:

3.3Router Actor的分类

Pool: Router actors创建routees作为子的actors,然后如果他们结束了,就从router中删除他们

 

Group: Routee Actor被Router创建,且使用actor selection,router发送消息到指定的路径,不需要监控终止情况

 

至于router在配置时候的选择:

router.type-mapping {
  from-code = "akka.routing.NoRouter"
  round-robin-pool = "akka.routing.RoundRobinPool"
  round-robin-group = "akka.routing.RoundRobinGroup"
  random-pool = "akka.routing.RandomPool"
  random-group = "akka.routing.RandomGroup"
  balancing-pool = "akka.routing.BalancingPool"
  smallest-mailbox-pool = "akka.routing.SmallestMailboxPool"
  broadcast-pool = "akka.routing.BroadcastPool"
  broadcast-group = "akka.routing.BroadcastGroup"
  scatter-gather-pool = "akka.routing.ScatterGatherFirstCompletedPool"
  scatter-gather-group = "akka.routing.ScatterGatherFirstCompletedGroup"
  tail-chopping-pool = "akka.routing.TailChoppingPool"
  tail-chopping-group = "akka.routing.TailChoppingGroup"
  consistent-hashing-pool = "akka.routing.ConsistentHashingPool"
  consistent-hashing-group = "akka.routing.ConsistentHashingGroup"
}

 

3.4 pool相关的只是

3.4.1 远程部署Routees

 

首先需要配置akka.remotesection在配置文件:

akka.actor.provider = "akka.remote.RemoteActorRefProvider"
akka.remote {
  enabled-transports = ["akka.remote.netty.tcp"]
  netty.tcp {
    hostname = "127.0.0.1"
    port = 10000  # 0表示自动选择一个可用的
  }
}

 

object Master extends App {
    val system = ActorSystem("MasterActorSystem")
    val address = Seq(Address("akka.tcp","RemoteActorSystem","localhost",10000),
        AddressFromURIString("akka.tcp://MasterActorSystem@localhost:10000"))
    val remoteRouter = system.actorOf(RemoteRouterConfig(RoundRobinPool(3),address).props(Props[Worker]))
    remoteRouter ! "xxx"
}

 

3.4.2Supervision 监管

我们可以通过pool router创建routees,然后作为routers的孩子,因此这个router可以监管这些孩子

 

监管策略 我们可以通过pool的supervisorStrategy属性设置,如果没有显示提供那么,默认就是always escalate策略,这就意味着错误是传递给routers的监管者来处理

 

3.5Group相关知识

有时候,我们希望通过传递path参数创建routees,消息最后通过ActorSelection发送

3.5.1application.conf配置

akka.actor.deployment {
  /master/router {
    router = broadcast-group
    routees.paths = ["/user/master/worker1","/user/master/worker2","/user/master/worker3"]
  }
}

 

3.5.2手动提供path创建routees

 

class Master3 extends Actor{
    var router:ActorRef = _
    @throws[Exception](classOf[Exception]) override
    def preStart(): Unit = {
        /*自己提供path路径*/
        val paths = List("/user/master/worker1","/user/master/worker2","/user/master/worker3")
        router = context.actorOf(BroadcastGroup(paths).props(),"router")
        context.actorOf(Props[Worker],name="worker1")
        context.actorOf(Props[Worker],name="worker2")
        context.actorOf(Props[Worker],name="worker3")
    }

    def receive: Receive = {
        case a:Long => router ! a
        case _ => println("参数异常")
    }
}
class Worker extends Actor with ActorLogging{
    def receive:Receive = {
        case a if 1 to 10 contains(a) =>
            log.info(s"$a belongs to [1-10]")
        case b if 11 to 50 contains(b) =>
            log.info(s"$b belongs to [11-50]")
        case c if 51 to 100 contains(c) =>
            log.info(s"$c belongs to [51-100]")
        case _ =>
            log.info("Unknown Range Value")
    }
}
object Master extends App {
    val _system = ActorSystem("MasterActorSystem",config = ConfigFactory.load("group"))
    val master = _system.actorOf(Props[Master3],"master")
    for (x <- 0 until 2 ) {
        master ! math.round(math.random * 20) + 1
    }

    Thread.sleep(100)
    _system.terminate()

}
 
结果:每一个routees都给广播一条消息
 
 

3.5.3 加载配置文件创建routees

class Master3 extends Actor{
    var router:ActorRef = _
    @throws[Exception](classOf[Exception]) override
    def preStart(): Unit = {
        /*自己提供path路径
        val paths = List("/user/master/worker1","/user/master/worker2","/user/master/worker3")
        router = context.actorOf(BroadcastGroup(paths).props(),"router")
        */
        /*读取配置文件参数,创建router*/
        router = context.actorOf(FromConfig.props(),"router")
        context.actorOf(Props[Worker],name="worker1")
        context.actorOf(Props[Worker],name="worker2")
        context.actorOf(Props[Worker],name="worker3")
    }

    def receive: Receive = {
        case a:Long => router ! a
        case _ => println("参数异常")
    }
}

其余步骤一样

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

莫言静好、

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值