简体   繁体   English

控制Akka actor中的消息流

[英]Control flow of messages in Akka actor

I have an actor using Akka which performs an action that takes some time to complete, because it has to download a file from the network. 我有一个使用Akka的演员,该演员执行的操作需要一些时间才能完成,因为它必须从网络上下载文件。

  def receive = {
    case songId: String => {
      Future {
        val futureFile = downloadFile(songId)

        for (file <- futureFile) {
          val fileName = doSomenthingWith(file)
          otherActor ! fileName
        }
      }
    }
  }

I would like to control the flow of messages to this actor. 我想控制发送给该参与者的消息流。 If I try to download too many files simultaneously, I have a network bottleneck. 如果我尝试同时下载太多文件,则会遇到网络瓶颈。 The problem is that I am using a Future inside the actor receive, so, the methods exits and the actor is ready to process a new message. 问题是我在actor的接收器内部使用了Future,所以方法退出了,actor准备处理新消息。 If I remove the Future, I will download only one file per time. 如果删除“未来”,则每次只能下载一个文件。

What is the best way to limit the number of messages being processed per unit of time? 限制每单位时间处理的消息数的最佳方法是什么? Is there a better way to design this code? 有没有更好的方法来设计此代码?

There is a contrib project for Akka that provides a throttle implementation ( http://letitcrash.com/post/28901663062/throttling-messages-in-akka-2 ). 有一个针对Akka的contrib项目,它提供了一个节流实现( http://letitcrash.com/post/28901663062/throttling-messages-in-akka-2 )。 If you sit this in front of the actual download actor then you can effectively throttle the rate of messages going into that actor. 如果您将它放在实际的下载角色前面,则可以有效地限制进入该角色的消息速率。 It's not 100% perfect in that if the download times are taking longer than expected you could still end up with more downloads then might be desired, but it's a pretty simple implementation and we use it quite a bit to great effect. 这不是100%完美的,因为如果下载时间花费的时间比预期的要长,那么您仍然可以得到更多的下载,那么可能就需要这样做,但这是一个非常简单的实现,我们使用它会产生很大的效果。

Another option could be to use a pool of download actors and remove the future and allow the actors to perform this blocking so that they are truly handling only one message at a time. 另一种选择是使用下载参与者的池并删除将来的对象,并允许参与者执行此阻止,以便他们一次只真正处理一条消息。 Because you are going to let them block, I would suggest giving them their own Dispatcher ( ExecutionContext ) so that this blocking does not negatively effect the main Akka Dispatcher . 因为您要让他们阻止,所以我建议给他们他们自己的DispatcherExecutionContext ),以便这种阻止不会对主要的Akka Dispatcher产生负面影响。 If you do this, then the pool size itself represents your max allowed number of simultaneous downloads. 如果执行此操作,则池大小本身代表您允许的同时下载的最大数量。

Both of these solutions are pretty much "out-of-the-box" solutions that don't require much custom logic to support your use case. 这两种解决方案都是非常“开箱即用”的解决方案,不需要太多自定义逻辑即可支持您的用例。

Edit 编辑

I also thought it would be good to mention the Work Pulling Pattern as well. 我还认为最好同时提一下“ 工作拉动模式 ”。 With this approach you could still use a pool and then a single work distributer in front. 通过这种方法,您仍然可以使用池,然后在前面使用单个工作分配器。 Each worker (download actor) could perform the download (still using a Future ) and only request new work (pull) from the work distributer when that Future has fully completed meaning the download is done. 每个工作人员(下载演员)都可以执行下载(仍使用Future ),并且仅在该Future完全完成(即下载完成)时才向工作发布者请求新工作(拉动)。

If you have an upper bound on the amount of simultanious downloads you want to happen you can 'ack' back to the actor saying that a download completed and to free up a spot to download another file: 如果您想同时进行大量下载,则可以“确认”演员,说下载已完成,并释放了一个位置来下载其他文件:

case object AckFileRequest

class ActorExample(otherActor:ActorRef, maxFileRequests:Int = 1) extends Actor {

  var fileRequests = 0

  def receive = {
    case songId: String if fileRequests < maxFileRequests =>
      fileRequests += 1
      val thisActor = self
      Future {
        val futureFile = downloadFile(songId)
        //not sure if you're returning the downloaded file or a future here, 
        //but you can move this to wherever the downloaded file is and ack
        thisActor ! AckFileRequest

        for (file <- futureFile) {
          val fileName = doSomenthingWith(file)
          otherActor ! fileName
        }
      }
    case songId: String =>
      //Do some throttling here
      val thisActor = self
      context.system.scheduler.scheduleOnce(1 second, thisActor, songId)
    case AckFileRequest => fileRequests -= 1
  }
}

In this example, if there are too many file requests then we put this songId request on hold and queue it back up for processing 1 second later. 在此示例中,如果文件请求太多,则将这个songId请求置于保留状态,并将其排队等待1秒后再处理。 You can obviously change this however you see fit, maybe you can just send the message straight back to the actor in a tight loop or do some other throttling, depends on your use case. 您显然可以更改此设置,但是您认为合适,也许您可​​以将消息直接发送给actor紧密循环,也可以进行其他限制,具体取决于您的用例。

There is a contrib implementation of message Throttling, as described here . 有消息节流的的contrib实施, 这里描述

The code is very simple: 代码很简单:

// A simple actor that prints whatever it receives
class Printer extends Actor {
  def receive = {
    case x => println(x)
  }
}

val printer = system.actorOf(Props[Printer], "printer")

// The throttler for this example, setting the rate
val throttler = system.actorOf(Props(classOf[TimerBasedThrottler], 3 msgsPer 1.second))

// Set the target
throttler ! SetTarget(Some(printer))
// These three messages will be sent to the printer immediately
throttler ! "1"
throttler ! "2"
throttler ! "3"
// These two will wait at least until 1 second has passed
throttler ! "4"
throttler ! "5"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM