akka演员的循环依赖

时间:2016-06-27 18:17:55

标签: mongodb scala akka github-api

以下是代码:

package vu.co.kaiyin.calculus.stewart

import org.json4s.JsonAST.JValue
import org.json4s._
import org.json4s.native.JsonMethods._
import akka.actor._
import akka.routing.RoundRobinPool

import scalaj.http._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Failure, Success}
import akka.actor._
import com.mongodb.casbah.Imports._

import scalaj.http._
import scala.concurrent.Future

object Fetcher {
  // message definitions
  case class Fetch(val login:String)

  // Props factory definitions
  def props(
             token:Option[String], responseInterpreter:ActorRef
           ):Props = Props(classOf[Fetcher], token, responseInterpreter)
}

class Fetcher(val token:Option[String], val responseInterpreter:ActorRef)
  extends Actor with ActorLogging {

  import Fetcher._ // import message definition

  def receive = {
    case Fetch(login) => fetchFollowers(login)
  }

  private def fetchFollowers(login:String) {
    val unauthorizedRequest = Http(
      s"https://api.github.com/users/$login/followers")
    val authorizedRequest = token.map { t =>
      unauthorizedRequest.header("Authorization", s"token $t")
    }

    val request = authorizedRequest.getOrElse(unauthorizedRequest)
    val response = Future { request.asString }

    // Wrap the response in an InterpretResponse message and
    // forward it to the interpreter.
    response.onComplete {
      case Success(r) => responseInterpreter ! ResponseInterpreter.InterpretResponse(login, r)
      case Failure(e) => log.warning(s"Failed to fetch $login: $e")
    }
  }
}

class ResponseInterpreter(extractor: ActorRef) extends Actor with ActorLogging {
  def receive = {
    case ResponseInterpreter.InterpretResponse(login, httpResponse) => {
      val body = httpResponse.body
      val followers = parse(body).asInstanceOf[JArray]
      extractor ! FollowerExtractor.Extract(login, followers)
    }
  }
}

object ResponseInterpreter {
  case class InterpretResponse(login: String, followersResponse: HttpResponse[String])
}



object FollowerExtractor {

  // Messages
  case class Extract(val login:String, val jsonResponse:JArray)

  // Props factory method
  def props = Props[FollowerExtractor]
}

class FollowerExtractor(manager: ActorRef) extends Actor with ActorLogging {
  import FollowerExtractor._

  val mongoClient = InsertUsers.getClient
  val db = mongoClient("github")
  val coll = db("graph")
  coll.drop()
  def receive = {
    case Extract(login, followerArray) => {
      val followers = extractFollowers(followerArray)
      followers.foreach { follower  =>
        val _ = Future {
          coll += DBObject("followed" -> login, "follower" -> follower)
        }
        manager ! FetcherManager.AddToQueue(follower)
      }
    }
  }

  def extractFollowers(followerArray:JArray): Seq[String] = for {
    JObject(follower) <- followerArray
    JField("login", JString(login)) <- follower
  } yield login

}

class FetcherManager(router: ActorRef) extends Actor {
  val added = collection.mutable.Set.empty[String]
  def receive = {
    case FetcherManager.AddToQueue(user) => {
      if(! added.contains(user)) {
        router ! Fetcher.Fetch(user)
        added += user
      }
    }
  }
}

object FetcherManager {
  case class AddToQueue(user: String)
}



object GraphDemo extends App {
  val system = ActorSystem("fetch_graph")
  lazy val router: ActorRef = system.actorOf(RoundRobinPool(4).props(
    Fetcher.props(Some("yourtokenhere"), interpreter)
  ))
  val manager: ActorRef = system.actorOf(Props(classOf[FetcherManager], router))
  val extractor: ActorRef = system.actorOf(Props(classOf[FollowerExtractor], manager))
  val interpreter: ActorRef = system.actorOf(Props(classOf[ResponseInterpreter], extractor))
  manager ! FetcherManager.AddToQueue("odersky")
  system.scheduler.scheduleOnce(5.minutes) {
    system.terminate()
  }
}

它试图从github获取用户和关注者并将图形存储在mongodb中。演员的组织是这样的:

enter image description here

我收到了这个错误:

Jun 27, 2016 7:59:21 PM com.mongodb.diagnostics.logging.JULLogger log
INFO: Cluster created with settings {hosts=[localhost:27017], mode=SINGLE, requiredClusterType=UNKNOWN, serverSelectionTimeout='30000 ms', maxWaitQueueSize=500}
Jun 27, 2016 7:59:21 PM com.mongodb.diagnostics.logging.JULLogger log
INFO: No server chosen by PrimaryServerSelector from cluster description ClusterDescription{type=UNKNOWN, connectionMode=SINGLE, all=[ServerDescription{address=localhost:27017, type=UNKNOWN, state=CONNECTING}]}. Waiting for 30000 ms before timing out
Jun 27, 2016 7:59:21 PM com.mongodb.diagnostics.logging.JULLogger log
INFO: Opened connection [connectionId{localValue:1, serverValue:9}] to localhost:27017
Jun 27, 2016 7:59:21 PM com.mongodb.diagnostics.logging.JULLogger log
INFO: Monitor thread successfully connected to server with description ServerDescription{address=localhost:27017, type=STANDALONE, state=CONNECTED, ok=true, version=ServerVersion{versionList=[3, 2, 7]}, minWireVersion=0, maxWireVersion=4, electionId=null, maxDocumentSize=16777216, roundTripTimeNanos=415354}
Jun 27, 2016 7:59:21 PM com.mongodb.diagnostics.logging.JULLogger log
INFO: Opened connection [connectionId{localValue:2, serverValue:10}] to localhost:27017
java.lang.NullPointerException
    at vu.co.kaiyin.calculus.stewart.Fetcher$$anonfun$vu$co$kaiyin$calculus$stewart$Fetcher$$fetchFollowers$1.apply(GitHubGraph.scala:52)
    at vu.co.kaiyin.calculus.stewart.Fetcher$$anonfun$vu$co$kaiyin$calculus$stewart$Fetcher$$fetchFollowers$1.apply(GitHubGraph.scala:51)
    at scala.concurrent.impl.CallbackRunnable.run(Promise.scala:32)
    at scala.concurrent.impl.ExecutionContextImpl$AdaptedForkJoinTask.exec(ExecutionContextImpl.scala:121)
    at scala.concurrent.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.pollAndExecAll(ForkJoinPool.java:1253)
    at scala.concurrent.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1346)
    at scala.concurrent.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
    at scala.concurrent.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

调试后,我发现responseInterpreter未初始化(因此为null)。

如何解决这个问题?

1 个答案:

答案 0 :(得分:1)

嗯,错误非常明确 - 在设置路由器时尚未创建interpreter,因此它是一个空指针。前向引用总是危险的。

有多种方法可以处理它;最明显的是,在创建interpreter之后,您可以向路由器下的所有Fetchers广播一条消息,并指向它。但重新排列创建顺序可能更容易 - 首先创建interpreter,并在安装结束时向其发送带有指向FetcherManager的指针的消息。 (因此避免了广播消息的需要。)

其中任何一个基本归结为两阶段初始化;对于像这样的循环,这并不是非常不寻常......

修改后的代码:

import org.json4s.JsonAST.JValue
import org.json4s._
import org.json4s.native.JsonMethods._
import akka.actor._
import akka.routing.RoundRobinPool

import scalaj.http._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.util.{Failure, Success}
import akka.actor._
import com.mongodb.casbah.Imports._

import scalaj.http._
import scala.concurrent.Future

object Fetcher {
  // message definitions
  case class Fetch(val login:String)

  // Props factory definitions
  def props(
             token:Option[String], responseInterpreter:ActorRef
           ):Props = Props(classOf[Fetcher], token, responseInterpreter)
}

class Fetcher(val token:Option[String], val responseInterpreter:ActorRef)
  extends Actor with ActorLogging {

  import Fetcher._ // import message definition

  def receive = {
    case Fetch(login) => fetchFollowers(login)
  }

  private def fetchFollowers(login:String) {
    val unauthorizedRequest = Http(
      s"https://api.github.com/users/$login/followers")
    val authorizedRequest = token.map { t =>
      unauthorizedRequest.header("Authorization", s"token $t")
    }

    val request = authorizedRequest.getOrElse(unauthorizedRequest)
    val response = Future { request.asString }

    // Wrap the response in an InterpretResponse message and
    // forward it to the interpreter.
    response.onComplete {
      case Success(r) => responseInterpreter ! ResponseInterpreter.InterpretResponse(login, r)
      case Failure(e) => log.warning(s"Failed to fetch $login: $e")
    }
  }
}

class ResponseInterpreter(extractor: ActorRef) extends Actor with ActorLogging {
  def receive = {
    case ResponseInterpreter.InterpretResponse(login, httpResponse) => {
      val body = httpResponse.body
      val followers = parse(body).asInstanceOf[JArray]
      extractor ! FollowerExtractor.Extract(login, followers)
    }
  }
}

object ResponseInterpreter {
  case class InterpretResponse(login: String, followersResponse: HttpResponse[String])
}



object FollowerExtractor {

  // Messages
  case class Extract(val login:String, val jsonResponse:JArray)

  // Props factory method
  def props = Props[FollowerExtractor]
}

class FollowerExtractor(manager: ActorRef) extends Actor with ActorLogging {
  import FollowerExtractor._

  val mongoClient = InsertUsers.getClient
  val db = mongoClient("github")
  val coll = db("graph")
  coll.drop()
  def receive = {
    case Extract(login, followerArray) => {
      val followers = extractFollowers(followerArray)
      followers.foreach { follower  =>
        val _ = Future {
          coll += DBObject("followed" -> login, "follower" -> follower)
        }
        manager ! FetcherManager.AddToQueue(follower)
      }
    }
  }

  def extractFollowers(followerArray:JArray): Seq[String] = for {
    JObject(follower) <- followerArray
    JField("login", JString(login)) <- follower
  } yield login

}

class FetcherManager extends Actor {
  private var router: ActorRef = _
  val added = collection.mutable.Set.empty[String]
  def receive = {
    case ref: ActorRef => router = ref
    case FetcherManager.AddToQueue(user) => {
      if(! added.contains(user)) {
        router ! Fetcher.Fetch(user)
        added += user
      }
    }
  }
}

object FetcherManager {
  case class AddToQueue(user: String)
}



object GraphDemo extends App {
  val system = ActorSystem("fetch_graph")
  val manager: ActorRef = system.actorOf(Props(classOf[FetcherManager]))
  val extractor: ActorRef = system.actorOf(Props(classOf[FollowerExtractor], manager))
  val interpreter: ActorRef = system.actorOf(Props(classOf[ResponseInterpreter], extractor))
  val router: ActorRef = system.actorOf(RoundRobinPool(4).props(
    Fetcher.props(Some("yourtoken"), interpreter)
  ))
  manager ! router
  manager ! FetcherManager.AddToQueue("odersky")
  system.scheduler.scheduleOnce(15.seconds) {
    system.terminate()
  }
}