使用Scala Actors创建像管道一样的东西

hot*_*zen 6 concurrency pipeline scala actor

我现在正在努力解决以下问题一周,需要一些建议.

def query(title: String): List[Search]   // query("Terminator") => ["Terminator I", "Terminator II", "Terminator 1984", etc...]

def searchIMDB(s: Search): List[SearchResult]
def searchTMDB(s: Search): List[SearchResult]

def filterRedundantSearchResults(sr: SearchResult): Option[SearchResult]

def fetchIMDB(sr: SearchResult): List[MetaInfo]
def fetchTMDB(sr: SearchResult): List[MetaInfo]

def consolidate(infos: List[MetaInfo]): List[List[MetaInfo]]
Run Code Online (Sandbox Code Playgroud)

我想构建一个像以下一样的管道:

query("Terminator")
-> [askIMDB, askTMDB, ...]
-> filterRedundantSearchResults (already-searched-state per query)
-> [fetchIMDB, fetchTMDB, ...]
-> consolidate                  (collected-meta-infos-state per query)
   => List[  TerminatorI-List[MetaInfo],  TerminatorII-List[MetaInfo],  ...]
Run Code Online (Sandbox Code Playgroud)

到目前为止,我已经将每个Pipeline-Segment都实现为Actor.我需要为每个Query创建专用的actor实例,因为像filterXXX和mergeate这样的一些actor需要维护每个查询的状态.

像askIMDB这样的函数会产生多个我希望同时处理的结果(每个结果都是一个单独的actor).所以我没有找到任何方法执行query()之前预构建actor的整个图形,也没有在运行时修改它的优雅方法.

我的第一次尝试是一系列演员,并在消息中传递像Transaction-ID,所以每个Actor都有一个Map [TransactionID-> State],但这感觉相当难看.第二个尝试是创建一个排序管道,将演员的有向图抽象为一个流,但到目前为止我失败了.

这是我的第一篇文章,对不起,如果我忘记了某些内容或问题是一般/伪编码.任何建议非常感谢.谢谢!

Dan*_*ral 4

我建议您看一下ScalaQuery,它做同样的事情。它可以这样做,因为这是一个单子问题。事实上,一些 Haskell 解决方案(例如由Scalaz 库实现的 Arrows)似乎非常接近。

这将是最好的解决方案,因为适当的抽象将使将来的更改变得更容易。

作为一名黑客,我想到了这样的事情:

abstract class QueryModifiers
case object Consolidate extends QueryModifiers
// create others as appropriate

class Query(title: String) {
  self =>

  // Create actors
  def createActor(qm: QueryModifiers): Actor = {
    val actor = qm match {
      case Consolidate => // create a consolidator actor
      case //... as needed
    }
    actor.start
    actor
  }

  // The pipeline
  val pipe: List[List[QueryModifiers]] = Nil

  // Build the pipeline
  def ->(qms: List[QueryModifiers]) = new Query(title) {
    override val pipe = qms :: self.pipe
  }
  def ->(qm: QueryModifiers) = new Query(title) {
    override val pipe = List(qm) :: self.pipe
  }
  def ->(c: Consolidate.type) = {
    // Define the full pipeline
    // Because the way pipe is built, the last layer comes first, and the first comes last
    val pipeline = Consolidate :: pipe

    // Create an actor for every QueryModifier, using an unspecified createActor function
    val actors = pipeline map (_ map (createActor(_))

    // We have a list of lists of actors now, where the first element of the list
    // was the last QueryModifiers we received; so, group the layers by two, and for each
    // pair, make the second element send the result to the first.
    // Since each layer can contain many actors, make each member of the second
    // layer send the results to each member of the first layer.
    // The actors should be expecting to receive message SendResultsTo at any time.
    for {
      List(nextLayer, previousLayer) <- actors.iterator sliding 2
      nextActor <- nextLayer
      previousActor <- previousLayer
    } previousActor ! SendResultsTo(nextActor)

    // Send the query to the first layer
    for ( firstActor <- actors.last ) firstActor ! Query(title)

    // Get the result from the last layer, which is the consolidator
    val results = actors.head.head !? Results

    // Return the results
    results
  }
}
Run Code Online (Sandbox Code Playgroud)

编辑

您也可以通过一些技巧来保证订单。我在这里试图避免使用 Scala 2.8,尽管它可以通过命名参数和默认参数使这变得更容易。

sealed abstract class QueryModifiers
case class QMSearcher(/*...*/) extends QueryModifiers
case class QMFilter(/*...*/) extends QueryModifiers
case class QMFetcher(/*...*/) extends QueryModifiers
case object Consolidate extends QueryModifiers

class Query[NextQM] private (title: String, searchers: List[QMSeacher], filters: List[QMFilter], fetchers: List[QMFetcher]) {

// Build the pipeline
  def ->[T <: NextQM](qms: List[NextQM])(implicit m: Manifest[T]) = m.toString match {
    case "QMSearch" => new Query[QMFilter](title, qms, Nil, Nil)
    case "QMFilter" => new Query[QMFetcher](title, seachers, qms, Nil)
    case "QMFetcher" => new Query[Consolidate.type](title, searches, filters, qms)
    case _ /* "Consolidate$", actually */ => error("List of consolidate unexpected")
  }
  // Do similarly for qm: NextQM

  // Consolidation
  def ->(qm: Consolidate.type) = {
     // Create Searchers actors
     // Send them the Filters
     // Send them Fetchers
     // Create the Consolidator actor
     // Send it to Searchers actors
     // Send Searchers the query
     // Ask Consolidator for answer
  }
}

object Query {
  def apply(title: String) = new Query[QMSearcher](title, Nil, Nil, Nil)
}
Run Code Online (Sandbox Code Playgroud)

现在,搜索者参与者保留过滤器列表、获取器列表以及对合并器的引用。他们收听通知他们这些事情的消息并进行查询。对于每个结果,他们为列表中的每个过滤器创建一个 Filter actor,向每个过滤器发送获取器和合并器的列表,然后向它们发送结果。

过滤器参与者保留一个获取器列表和对合并器的引用。他们聆听通知他们这些事情的消息,并寻找搜索者的结果。他们将输出(如果有)发送给新创建的提取器参与者,这些参与者首先被告知合并器。

获取器保留对合并器的引用。他们收听通知他们该引用的消息以及过滤器的结果。他们将结果发送给合并商。

集运商听取两条消息。来自 fetcher actor 的一条消息通知他们积累的结果。来自查询的另一条消息请求该结果并返回该结果。

剩下的唯一一件事就是设计一种方法,让合并商知道所有结果都已处理完毕。一种方法如下:

  1. 在查询中,将创建的每个搜索器告知合并器参与者。合并商保存它们的列表,并带有一个标志指示它们是否完成。
  2. 每个搜索器都会保留其创建的过滤器列表,并等待来自它们的“完成”消息。当搜索器没有剩余处理要做并且已从所有过滤器接收到“完成”时,它会向合并器发送一条消息,通知它已完成。
  3. 每个过滤器依次保存它所创建的提取器列表,并且同样等待来自它们的“完成”消息。当它完成处理并且从所有获取器接收到“完成”时,它通知搜索器它已经完成。
  4. 当其工作完成并发送到合并器时,它的获取器会向创建它的过滤器发送一条“完成”消息。
  5. 合并器仅在收到所有搜索者的“完成”后才侦听查询结果的消息。