我正在使用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
提供对先前创建的演员的引用,而不是新演员吗?
我对所有的抽象感到非常困惑,如果有人能为我分解,那将会很棒。
由于
答案 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