AKKA 实现 并行 Word Count

刚接触akka不久,之前看spark源码的时候,发现spark的master 跟 worker之间消息传递等都是用的akka。所以决定先了解下akka再说。作为typesafe公司下的三个产品(play! ,scala,akka)之一,akka的强大不必另说。使用“let it crash”模型以及强大的抽象actor,akka是我们开发高容错性以及并发行好助手。

这个小例子是利用akka进行简单的word count。刚刚写好,有意见可以交流。此为前言。

我会一边展示程序一边讲解akka使用。

一:主程序

  def calcultate(): Unit ={
    //create system actor
    val system = ActorSystem("WordCountwithakka")

    //create listener
    val listener = system.actorOf(Props[Listener],name = "WCListener")

    //create master
    val master= system.actorOf(Props(new Master(listener,4)),name="WCMaster")
    println("master inited")

    //begin to calculate
    master ! Map

  }

在主程序中定义ActorSystem,首先,什么是actor?

Actor是封装状态和行为的对象,他们的唯一通讯方式是交换消息,交换的消息存放在接收方的邮箱里。从某种意义上来说,actor是面向对象的最严格的形式,在使用actor来对解决方案建模时,可以把actor想象成一群人,把子任务分配给他们,将他们的功能整理成一个有组织的结构。每个actor监督他所生成的子actor。

然后我们分别定义了一个listener以及一个master。

Listener负责返回输出结果以及关闭最后的系统。

  class Listener extends Actor{
    override def receive= {
      case FinalRes(du,res) =>{
        println("\n\tCalculation time: \t%s".format(du))
        println("\n\tRes: \t%s".format(res))
        context.system.shutdown()
      }

    }
  }

master ! Map : 代表我们向master发送了map消息。master在接受到消息后则开始工作。

二:Master

Master总体来说负责分配任务,收集各个worker的工作结果并组装最后结果。

  class Master(listener: ActorRef,numWorker : Int=4) extends Actor{
    //original data
    val data = "hello world hello this is my first akka app plz use scala Cause scala is really smart " +
      "I have used it so far in two real projects very successfully. both are in the near real-time traffic " +
      "information field (traffic as in cars on highways), distributed over several nodes, integrating messages"

    val dataArray =  data.split(" ")
    val sumSize = dataArray.size
    val jobPerWork = sumSize/numWorker

    val workerRouter = context.actorOf(Props[Worker].withRouter(RoundRobinRouter(numWorker)), name = "workerRouter")

    var totalRes = new mutable.HashMap[String,Int]
    var receivedMsg = 0
    val start: Long = System.currentTimeMillis

    def receive = {
      case Map =>{
        println("begin to map")
        for(i <- 1 to numWorker){
          var endIndex = 0
          if (i ==numWorker){
            endIndex = sumSize-1
          }else{
            endIndex = i*jobPerWork
          }

          val sendData =  dataArray.slice((i-1)*jobPerWork,endIndex)
          println("send worker "+i+" to work")
          workerRouter ! Task(sendData)
        }
      }

      case Reduce(partData) =>{
        println("received one res")
        totalRes = mergeRes(totalRes,partData)

        receivedMsg = receivedMsg + 1
        if (receivedMsg == numWorker){
          println("all job finished")
          listener ! FinalRes(du = (System.currentTimeMillis - start).millis,totalRes.toList.toString())
          // Stops this actor and all its supervised children
          context.stop(self)
        }
      }

    }

 这里首先生成我们需要计算的word的data,然后根据worker的数目,计算每个worker需要处理的单词数目。

在分发任务的时候,使用worker router进行分发,分发的算法是轮训算法,每个节点都轮流进行计算。

Receive 方法接受到的消息有两种情况:

Map:为接受到主函数的消息,接受到后则进行消息分发。

Reducer:接受到worker的计算结果后,进行合并。然后通知listener并关闭自己。

三:Worker

worker是负责统计工作的,在本例中则是计算分配的数据中每个单词出现的次数,并返回给worker。

  class Worker() extends Actor{

    def wordCount(data:Array[String]):HashMap[String,Int]={
      val count = new mutable.HashMap[String,Int]()
      data.foreach(word =>{
        val numExisted = count.getOrElse(word,0)
        val newNum = numExisted + 1
        count.put(word,newNum)
      })

      count
    }

    override def receive= {

      case Task(splitedData) =>{
        println("one to work")
        sender ! Reduce(wordCount(splitedData))
      }

    }
  }

其中,receive代表接受到Task任务后,所做的动作。

sender表示task任务的发送方(也就是master)。

计算完毕后,给sender回复计算结果。

四:消息定义

worker,master之间都是通过消息互相通讯。

  //all messages
  sealed trait wcMessage
  case class FinalRes(du:Duration,res:String=null) extends wcMessage

  case object Map extends wcMessage
  case class Task(splitedData:Array[String]) extends wcMessage
  case class Reduce(partData:mutable.HashMap[String,Int]) extends wcMessage

Tips: 在scala当中,使用sealed trait wcMessage ,则在case当中,只能匹配extends wcMessage的类或者object的实例,否则编译会出错。

这样可以保证排除来历不明的消息。


另外,源码下载在 https://github.com/YulinGUO/akkaSimpleWordCount

你可能感兴趣的:(akka,word,count)