使用Akka Actors

时间:2015-06-29 03:56:57

标签: scala concurrency akka actor mutable

编辑:澄清意图:

我有一个(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

akka.agent.Agent看起来好像是为了很好地处理这个问题(用代理替换MyCompute actor),除了它似乎是为更简单的状态更新而设计的::实际上,MyCompute将有多个状态位(其中一些是几兆字节的数据结构),并且使用sendOff功能似乎每次都会重写所有状态,这似乎会不必要地施加大量的GC压力。

同步

上面的[Future]代码解决了阻塞问题,但好像我正确地阅读了Akka文档,这不是线程安全的。在[ALTERNATIVE A]中添加同步块会解决这个问题吗?我还想象我只需要将实际更新同步到[ALTERNATIVE B]中的状态。我似乎也必须像在[ALTERNATIVE C]中那样阅读国家一样吗?

喷雾缓存

喷涂缓存模式似乎是考虑到Web服务用例(带密钥的小缓存对象),所以我不确定它是否适用于此。

期货与pipeTo

我已经看到在Future中包装长时间运行的计算的示例,然后使用pipeTo将其传回给同一个actor以更新内部状态。

这个问题是:如果我想在长时间运行的计算期间更新我的actor的可变内部状态怎么办?

有没有人对此用例有任何想法或建议?

tl; dr:

我希望我的actor在长时间运行的计算过程中更新内部的可变状态而不会阻塞。想法?

1 个答案:

答案 0 :(得分:2)

因此,让MyCompute actor为每个计算创建一个Worker actor:

  1. “计算”来到MyCompute
  2. 它会记住发件人并产生工人演员。它将Worker和Sender存储在Map [Worker,Sender]
  3. 工作人员进行计算。完成后,Worker将结果发送到MyCompute
  4. MyCompute更新结果,使用完成的Worker作为键从Map [Worker,Sender]中检索它的订货人。然后它将结果发送给订货人,然后终止工人。
  5. 每当你在Actor中阻塞时,你就会产生一个专门的actor来处理它。每当你需要在Actor中使用另一个线程或Future时,你就会产生一个专用的actor。每当你需要在Actor中抽象任何复杂性时,你就会产生另一个actor。