来自喷雾路线的催生演员感到困惑

时间:2014-03-12 08:10:58

标签: scala asynchronous akka actor spray

我正在使用Spray进行一些Http请求处理。对于一个请求,我启动一个actor并将有效负载发送给actor进行处理,并且在actor完成有效负载之后,我在actor上调用context.stop(self)来控制演员。这个想法是为了防止过度饱和物理机器上的演员。

这就是我设置的方式..

httphandler.scala中,我的路线设置如下:

path("users"){
   get{
      requestContext => {
         val userWorker = actorRefFactory.actorOf(Props(new UserWorker(userservice,requestContext)))
        userWorker ! getusers //get user is a case object 
      }
   }
} ~ path("users"){
     post{
       entity(as[UserInfo]){
          requestContext => {
          userInfo => {
             val userWorker = actorRefFactory.actorOf(Props(new UserWorker(userservice,requestContext)))
             userWorker ! userInfo
           }
         }
       }
     }
 }

我的UserWorker演员定义如下:

trait RouteServiceActor extends Actor{
  implicit val system = context.system
  import system.dispatcher
  def processRequest[responseModel:ToResponseMarshaller](requestContex:RequestContext)(processFunc: => responseModel):Unit = {
       Future{
         processFunc
       } onComplete {
          case Success(result) => {
            requestContext.complete(result)
          }
          case Failure(error) => requestContext.complete(error)
       }
  }
}

class UserWorker(userservice: UserServiceComponent#UserService,requestContext:RequestContext) extends RouteServiceActor{
 def receive = {
    case getusers => processRequest(requestContext){
         userservice.getAllUsers
    }
    context.stop(self)
  }
  case userInfo:UserInfo => {
    processRequest(requestContext){
         userservice.createUser(userInfo)
    }
    context.stop(self) 
 }  

}

我的第一个问题是,我是否以真正的异步方式处理请求?我的代码有哪些陷阱?

我的第二个问题是requestContext.complete是如何运作的?由于原始请求处理线程不再存在,requestContext如何将计算结果发送回客户端。

我的第三个问题是,由于我在每个部分方法之后调用context.stop(self),我是否有可能在处理不同消息时终止该工作。

我的意思是,当Actor接收到处理getusers的消息时,同一个actor处理UserInfo并终止Actor,然后才能进入" getusers&#34 ;信息。我正在根据每个请求创建新的演员,但是有可能在幕后,actorRefFactory提供对先前创建的演员的引用,而不是新演员吗?

我对所有的抽象感到非常困惑,如果有人能为我分解,那将会很棒。

由于

2 个答案:

答案 0 :(得分:12)

1)请求是异步处理的吗?是的。但是,如果您立即将实际处理委派给未来,那么您对每个请求的actor不会获得太多收益。在这个简单的例子中,一个更简洁的方法就是像

一样编写你的路线
path("users") {
   get {
      complete(getUsers())
   }
}

def getUsers(): Future[Users] = // ... invoke userservice

如果您还希望使路由处理逻辑并行运行,或者如果处理请求具有更复杂的要求,例如,请求演员更有意义。如果您需要并行查询多个服务中的内容,或者需要在某些后台服务处理请求时保持每个请求状态。有关此常规主题的一些信息,请参阅https://github.com/NET-A-PORTER/spray-actor-per-request

2)requestContext.complete如何运作?在幕后,它将HTTP响应作为普通演员消息“tell”发送到spray-can HTTP连接actor。所以,基本上RequestContext只是将ActorRef包装到HTTP连接,这是可以安全地同时使用的。

3)“工人”是否可能被context.stop(self)终止?我认为在幕后如何安排事情会有一些困惑。当然,您使用context.stop终止了actor,但这只是停止了actor而不是任何线程(因为线程完全独​​立于Akka中的actor实例进行管理)。由于你没有真正利用演员的优势,即封装和同步对可变状态的访问,一切都应该工作(但如1所述)对于这个用例是不必要的复杂)。 akka文档提供了大量有关actor,future,dispatchers和ExecutionContexts如何协同工作以使一切正常工作的信息。

答案 1 :(得分:2)

除了jrudolph之外,您的喷涂路由结构甚至不应该编译,因为在您的post分支中您没有明确指定requestContext。这个结构可以简化一点:

def spawnWorker(implicit ctx: RequestContext): ActorRef = {
  actorRefFactory actorOf Props(new UserWorker(userservice, ctx))
}

lazy val route: Route = {
  path("users") { implicit ctx =>
    get {
      spawnWorker ! getUsers
    } ~
    (post & entity(as[UserInfo])) { 
      info => spawnWorker ! info 
    }
  }
}

info => spawnWorker ! info也可以简化为spawnWorker ! _

还有一个关于显式ctx声明和complete指令的重点。如果您在路线中明确声明ctx您不能使用完整指令,则必须在此问题上明确写出ctx.complete(...)link