22

在过去的几个月里,我和我的同事成功地构建了一个服务器端系统,用于向 iPhone 设备发送推送通知。基本上,用户通过 RESTful Web 服务( Spray-Server,最近更新为使用Spray-can作为 HTTP 层)注册这些通知,并且逻辑使用 Akka 的调度程序调度一个或多个消息以供将来调度。

我们构建的这个系统很简单:它每秒可以处理数百甚至数千个 HTTP 请求,并且可以以每秒 23,000 个的速度发送通知——如果我们减少日志输出、添加多个通知,可能会更多发送方参与者(因此与 Apple 有更多联系),并且我们使用的 Java 库 ( java-apns )中可能需要进行一些优化。

这个问题是关于如何做到这一点的权利(tm)。我的同事对 Scala 和基于 actor 的系统有更多的了解,他指出该应用程序不是一个“纯粹的”基于 actor 的系统——他是对的。我现在想知道的是如何做到这一点。

目前,我们有一个单独的 Spray HttpServiceActor,没有子类化,它使用一组指令进行初始化,这些指令概述了我们的 HTTP 服务逻辑。目前,非常简化,我们有这样的指令:

post {
  content(as[SomeBusinessObject]) { businessObject => request =>
    // store the business object in a MongoDB back-end and wait for the ID to be
    // returned; we want to send this back to the user.
    val businessObjectId = persister !! new PersistSchedule(businessObject)
    request.complete("/businessObject/%s".format(businessObjectId))
  }
}

现在,如果我做对了,“等待演员的回应”在基于演员的编程中是一个禁忌(加上 !! 已弃用)。我认为“正确”的做法是在消息中将request对象传递给参与者,并在从后端接收到生成的 ID 后立即persister调用它。request.complete

我已经重写了我的应用程序中的一条路线来做到这一点;在发送给参与者的消息中,请求对象/引用也被发送。这似乎像它应该的那样工作:

  content(as[SomeBusinessObject]) { businessObject => request =>
    persister ! new PersistSchedule(request, businessObject)
  }

我主要关心的是我们似乎将request对象传递给“业务逻辑”,在这种情况下是持久化。持久化者现在获得了额外的责任,即调用request.complete,并了解它在什么系统中运行,即它是 Web 服务的一部分。

处理这种情况的正确方法是什么,以便持久化参与者不知道它是 http 服务的一部分,并且不需要知道如何输出生成的 ID?

我认为请求仍应传递给持久化参与者,但不是持久化参与者调用 request.complete,而是将消息发送回 HttpService 参与者(一条SchedulePersisted(request, businessObjectId)消息),后者只需调用request.complete("/businessObject/%s".format(businessObjectId)). 基本上:

def receive = {
  case SchedulePersisted(request, businessObjectId) =>
    request.complete("/businessObject/%s".format(businessObjectId))
}

val directives = post {
  content(as[SomeBusinessObject]) { businessObject => request =>
    persister ! new PersistSchedule(request, businessObject)
  }
}

我用这种方法走在正确的轨道上吗?

一个较小的次要spray-server特定问题,子类HttpService化和覆盖接收方法是否可以,或者我会以这种方式破坏事情吗?(我不知道演员的子类化,或者如何将无法识别的消息传递给“父”演员)

最后一个问题,request在可能通过整个应用程序的参与者消息中传递对象/引用是一种好的方法,还是有更好的方法来“记住”在请求通过应用程序后应该发送响应的请求?

4

2 回答 2

3

关于你的第一个问题,是的,你走在正确的轨道上。(尽管我也希望看到一些替代方法来处理此类问题)。

我的一个建议是让persister演员完全不知道请求。您可以将请求作为Any类型传递。您的服务代码中的匹配器可以自动将 cookie 转换回Request.

case class SchedulePersisted(businessObjectId: String, cookie: Any)

// in your actor
override def receive = super.receive orElse {
  case SchedulePersisted(businessObjectId, request: Request) =>
    request.complete("/businessObject/%s".format(businessObjectId))
}

关于你的第二个问题,演员班真的和普通班没有什么不同。但是您确实需要确保调用超类的receive方法,以便它可以处理自己的消息。我在原始答案中有一些其他方法可以做到这一点,但我认为我更喜欢像这样链接部分函数

class SpecialHttpService extends HttpService {
  override def receive = super.receive orElse {
    case SpecialMessage(x) =>
      // handle special message
  }
}
于 2012-02-10T14:41:19.503 回答
0

您也可以使用生产指令。它允许您将实际编组与请求完成分离:

get {
  produce(instanceOf[Person]) { personCompleter =>
    databaseActor ! ShowPersonJob(personCompleter)
  }
}

此示例中的produce指令提取了一个函数Person => Unit,您可以使用该函数在业务逻辑层深处透明地完成请求,这不应该知道spray。

https://github.com/spray/spray/wiki/Marshalling-Unmarshalling

于 2012-03-14T17:07:22.423 回答