我有一个演员" ItemProvider"它可以接收" getItems"信息。 ItemProvider管理项目的项目。所以我可以有几个" getItems"消息请求项目A和其他" getItems"请求项目B项目的消息。
第一次" itemProvider"得到这样一条消息,它需要调用服务来实际获取项目 (这可能需要一分钟,服务返回一个未来,所以它不会阻止演员)。在此等待期间,其他" getItems"消息可以到达。
项目" ItemProvider"缓存"项目"它从服务中收到。 因此,在1分钟的加载时间后,它可以立即提供物品。
我很确定" ItemProvider"应该使用Akka成为特色。但它应该如何处理它无法立即服务的客户?
我可以考虑以下选项:
ItemProvider包含List pendingMessages。并且无法提供的消息将添加到此列表中。当ItemProvider准备就绪时#34;它将处理待处理的客户
ItemProvider将消息发送回其父级。父母将重新发出消息
ItemProvider使用调度程序。并在将来再次收到消息。
也许不使用成为但使用AbstractFSM类?
有人知道实施ItemProvider的最佳Akka方式吗?
答案 0 :(得分:1)
看看Akka的Stash feature(usage 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>