推迟消息的最佳方式是什么?

时间:2014-10-31 08:30:55

标签: java scala akka

我有一个演员" ItemProvider"它可以接收" getItems"信息。 ItemProvider管理项目的项目。所以我可以有几个" getItems"消息请求项目A和其他" getItems"请求项目B项目的消息。

第一次" itemProvider"得到这样一条消息,它需要调用服务来实际获取项目 (这可能需要一分钟,服务返回一个未来,所以它不会阻止演员)。在此等待期间,其他" getItems"消息可以到达。

项目" ItemProvider"缓存"项目"它从服务中收到。 因此,在1分钟的加载时间后,它可以立即提供物品。

我很确定" ItemProvider"应该使用Akka成为特色。但它应该如何处理它无法立即服务的客户?

我可以考虑以下选项:

  1. ItemProvider包含List pendingMessages。并且无法提供的消息将添加到此列表中。当ItemProvider准备就绪时#34;它将处理待处理的客户

  2. ItemProvider将消息发送回其父级。父母将重新发出消息

  3. ItemProvider使用调度程序。并在将来再次收到消息。

  4. 也许不使用成为但使用AbstractFSM类?

    有人知道实施ItemProvider的最佳Akka方式吗?

3 个答案:

答案 0 :(得分:1)

看看Akka的Stash featureusage example)。 下面是(未经测试的)代码,用于在从服务器请求实际项目时隐藏getItems消息,然后在服务器请求完成后处理所有getItems消息

import akka.actor.{Actor, Stash}

class ItemProviderActor extends Actor with Stash {
  private[this] itemsOpt : Option[Items] = None

  def receive = processing

  def processing: Receive = {
    case m:GetItems => {
      if(itemsOpt.nonEmpty) {
        // respond immediately
        itemsOpt.foreach(sender() ! _)
      }
      else {
        // Stash current request and initiate cache update
        context.become(retrivingData)
        stash()

        // Will send future results of item retrieval as a message to self
        retrieveItems().pipeTo(self)
      }
    }
  }

  def retrivingData: Receive = {
    case m: Items => 

      // items are retrieved, update cache
      itemsOpt = Option(m)

      // resume normal processing
      context.become(processing)

      // put all pending item requests back to actor's message queue
      unstashAll()


    case m:GetItems => 
      // busy retrieving items, store request to serve later
      stash()
  }

  def retrieveItems() : Future[Items] = {
    ???
  }

}

答案 1 :(得分:0)

客户应按计划重新发送幂等请求,直到收到满意的答案或超时。

是否需要更多ItemProviders,或者ItemProvider批处理请求取决于所查询资源的性质。如果您每分钟只能发出1个请求,那么请求应该在ItemProvider中进行批处理。但是,客户有责任确保在满意之前继续请求答案。它不应该依赖ItemProvider来可靠地记住请求。

答案 2 :(得分:0)

下面您将找到一种可能的方法来构建演员以满足您的要求。在此解决方案中,我将使用每个项目的actor实例来缓存特定于该项目的项目。然后,我将使用一个路由actor,它将接收获取项目项目的请求,并委托给处理该项目缓存的正确子actor。在实际的缓存actor中,您将看到我使用stash / unstash来处理延迟请求,直到加载了要缓存的项目(我在代码中模拟)。代码如下:

import akka.actor._
import scala.concurrent.Future
import akka.pattern._
import concurrent.duration._ 
import akka.util.Timeout

class ItemProviderRouter extends Actor{
  import ItemProvider._

  def receive = {
    case get @ GetItems(project) =>

      //Lookup the child for the supplied project.  If one does not
      //exist, create it
      val child = context.child(project).getOrElse(newChild(project))
      child.forward(get)
  }

  def newChild(project:String) = {
    println(s"creating a new child ItemProvider for project $project")
    context.actorOf(Props[ItemProvider], project)
  }

}

object ItemProvider{
  case class GetItems(project:String)
  case class Item(foo:String)
  case class LoadedItems(items:List[Item])
  case object ClearCachedItems
  case class ItemResults(items:List[Item])
}

class ItemProvider extends Actor with Stash{
  import ItemProvider._  

  //Scheduled job to drop the cached items and force a reload on subsequent request
  import context.dispatcher
  context.system.scheduler.schedule(5 minutes, 5 minutes, self, ClearCachedItems)

  def receive = noCachedItems

  def noCachedItems:Receive = {
    case GetItems(project) =>
      stash()      
      fetchItems(project)
      context.become(loadingItems)


    case ClearCachedItems =>
      //Noop
  }

  def loadingItems:Receive = {
    case get:GetItems => stash

    case LoadedItems(items) =>
      println(s"Actor ${self.path.name} got items to cache, changing state to cachedItems")
      context.become(cachedItems(items))
      unstashAll()    

    case ClearCachedItems => //Noop      
  }

  def cachedItems(items:List[Item]):Receive = {
    case GetItems(project) =>
      sender ! ItemResults(items)

    case ClearCachedItems =>
      println("Clearing out cached items")
      context.become(noCachedItems)       

    case other =>
      println(s"Received unexpected request $other when in state cachedItems")          
  }

  def fetchItems(project:String){
    println(s"Actor ${self.path.name} is fetching items to cache")

    //Simulating doing something that results in a Future
    //representing the items to cache    

    val fut = Future{
      Thread.sleep(5000)
      List(Item(s"hello $project"), Item(s"world $project"))
    }

    fut.map(LoadedItems(_)).pipeTo(self)
  }
}

然后测试它:

object ItemProviderTest extends App{
  import ItemProvider._
  val system = ActorSystem("test")
  import system.dispatcher
  val provider = system.actorOf(Props[ItemProviderRouter])

  implicit val timeout = Timeout(10 seconds)
  for(i <- 1 until 20){
    val afut = provider ? GetItems("a")
    val bfut = provider ? GetItems("b")

    afut onSuccess{
      case ItemResults(items) => println(s"got items list of $items for project a")
    }

    bfut onSuccess{
      case ItemResults(items) => println(s"got items list of $items for project b")
    }    
  }
} 

为了简单起见,我使用实际的actor来进行路由而不是自定义路由器,但是如果性能(即邮箱命中)对您很重要,您也可以在这里实现自定义路由器。 / p>