编辑:澄清意图:
我有一个(5-10秒)scala计算,它在给定的时间点聚合来自许多AWS S3对象的一些数据。我想通过REST API提供这些信息。我还希望每隔一分钟更新此信息,以便在此期间为此存储桶写入新对象。摘要本身将是一个大型JSON blob,如果我从先前的更新缓存我的S3 API调用的结果(因为这些对象是不可变的),可以节省大量的AWS调用。
我目前正在Scala中编写基于Spray.io的REST服务。 即使正在进行计算,我也希望REST服务器继续提供“陈旧”数据。然后,一旦计算完成,我想原子地开始提供新数据快照的请求。
我最初的想法是让两个参与者,一个执行Spray路由和服务,另一个处理长时间运行的计算并将最新的缓存结果提供给路由actor:
class MyCompute extends Actor {
var myvar = 1.0 // will eventually be several megabytes of state
import context.dispatcher
// [ALTERNATIVE A]:
// def compute() = this.synchronized { Thread.sleep(3000); myvar += 1.0 }
// [ALTERNATIVE B]:
// def compute() = { Thread.sleep(3000); this.synchronized { myvar += 1.0 }}
def compute() = { Thread.sleep(3000); myvar += 1.0 }
def receive = {
case "compute" => {
compute() // BAD: blocks this thread!
// [FUTURE]:
Future(compute()) // BAD: Not threadsafe
}
case "retrieve" => {
sender ! myvar
// [ALTERNATIVE C]:
// sender ! this.synchronized { myvar }
}
}
}
class MyHttpService(val dataService:ActorRef) extends HttpServiceActor {
implicit val timeout = Timeout(1 seconds)
import context.dispatcher
def receive = runRoute {
path("ping") {
get {
complete {
(dataService ? "retrieve").map(_.toString).mapTo[String]
}
}
} ~
path("compute") {
post {
complete {
dataService ! "compute"
"computing.."
}
}
}
}
}
object Boot extends App {
implicit val system = ActorSystem("spray-sample-system")
implicit val timeout = Timeout(1 seconds)
val dataService = system.actorOf(Props[MyCompute], name="MyCompute")
val httpService = system.actorOf(Props(classOf[MyHttpService], dataService), name="MyRouter")
val cancellable = system.scheduler.schedule(0 milliseconds, 5000 milliseconds, dataService, "compute")
IO(Http) ? Http.Bind(httpService, system.settings.config.getString("app.interface"), system.settings.config.getInt("app.port"))
}
正如编写的那样,一切都是安全的,但是当传递“计算”消息时,MyCompute actor将阻塞该线程,并且无法向MyHttpService actor提供请求。
一些替代方案:
akka.agent.Agent看起来好像是为了很好地处理这个问题(用代理替换MyCompute actor),除了它似乎是为更简单的状态更新而设计的::实际上,MyCompute将有多个状态位(其中一些是几兆字节的数据结构),并且使用sendOff
功能似乎每次都会重写所有状态,这似乎会不必要地施加大量的GC压力。
上面的[Future]代码解决了阻塞问题,但好像我正确地阅读了Akka文档,这不是线程安全的。在[ALTERNATIVE A]中添加同步块会解决这个问题吗?我还想象我只需要将实际更新同步到[ALTERNATIVE B]中的状态。我似乎也必须像在[ALTERNATIVE C]中那样阅读国家一样吗?
喷涂缓存模式似乎是考虑到Web服务用例(带密钥的小缓存对象),所以我不确定它是否适用于此。
我已经看到在Future中包装长时间运行的计算的示例,然后使用pipeTo将其传回给同一个actor以更新内部状态。
这个问题是:如果我想在长时间运行的计算期间更新我的actor的可变内部状态怎么办?
有没有人对此用例有任何想法或建议?
tl; dr:
我希望我的actor在长时间运行的计算过程中更新内部的可变状态而不会阻塞。想法?
答案 0 :(得分:2)
因此,让MyCompute actor为每个计算创建一个Worker actor:
每当你在Actor中阻塞时,你就会产生一个专门的actor来处理它。每当你需要在Actor中使用另一个线程或Future时,你就会产生一个专用的actor。每当你需要在Actor中抽象任何复杂性时,你就会产生另一个actor。