用Scala语言轻松开发多线程、分布式以及集群式程序
原文:http://blog.csdn.net/pengyanhong/article/details/17112177
Akka framework现在已经是Scala语言的一部分了,用它编写分布式程序是相当简单的,本文将一步一步地讲解如何做到scale up & scale out。
- 简单的单线程程序
- package com.newegg.demo
- import scala.concurrent.duration._
- import scala.collection.mutable.ListBuffer
- object PerfectNumber {
- def sumOfFactors(number: Int) = {
- (1 /: (2 until number)) { (sum, i) => if (number % i == 0) sum + i else sum }
- }
- def isPerfect(num: Int): Boolean = {
- num == sumOfFactors(num)
- }
- def findPerfectNumbers(start: Int, end: Int) = {
- require(start > 1 && end >= start)
- val perfectNumbers = new ListBuffer[Int]
- (start to end).foreach(num => if (isPerfect(num)) perfectNumbers += num)
- perfectNumbers.toList
- }
- def main(args: Array[String]): Unit = {
- val list = findPerfectNumbers(2, 100)
- println("\nFound Perfect Numbers:" + list.mkString(","))
- }
- }
- 多线程程序
这个示例中要用到的“消息”定义在Data.scala文件中,内容如下:
- package com.newegg.demo
- import akka.actor.ActorRef
- sealed trait Message
- case class StartFind(start: Int, end: Int, replyTo: ActorRef) extends Message
- case class Work(num: Int, replyTo: ActorRef) extends Message
- case class Result(num: Int, isPerfect: Boolean) extends Message
- case class PerfectNumbers(list: List[Int]) extends Message
- package com.newegg.demo
- import akka.actor.Actor
- import akka.actor.ActorRef
- class Worker extends Actor {
- private def sumOfFactors(number: Int) = {
- (1 /: (2 until number)) { (sum, i) => if (number % i == 0) sum + i else sum }
- }
- private def isPerfect(num: Int): Boolean = {
- num == sumOfFactors(num)
- }
- def receive = {
- case Work(num: Int, replyTo: ActorRef) =>
- replyTo ! Result(num, isPerfect(num))
- print("[" + num + "] ")
- }
- }
- package com.newegg.demo
- import scala.collection.mutable.ListBuffer
- import akka.actor.Actor
- import akka.actor.ActorRef
- import akka.actor.Props
- import akka.routing.FromConfig
- import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope
- sealed class Helper(count: Int, replyTo: ActorRef) extends Actor {
- val perfectNumbers = new ListBuffer[Int]
- var nrOfResult = 0
- def receive = {
- case Result(num: Int, isPerfect: Boolean) =>
- nrOfResult += 1
- if (isPerfect)
- perfectNumbers += num
- if (nrOfResult == count)
- replyTo ! PerfectNumbers(perfectNumbers.toList)
- }
- }
- class Master extends Actor {
- val worker = context.actorOf(Props[Worker].withRouter(FromConfig()), "workerRouter")
- def receive = {
- case StartFind(start: Int, end: Int, replyTo: ActorRef) if (start > 1 && end >= start) =>
- val count = end - start + 1
- val helper = context.actorOf(Props(new Helper(count, replyTo)))
- (start to end).foreach(num => worker ! Work(num, helper))
- }
- }
那个单线程程序的主函数改造如下:
- package com.newegg.demo
- import scala.concurrent.duration._
- import scala.collection.mutable.ListBuffer
- import akka.actor.ActorSystem
- import akka.actor.Props
- import akka.actor.Actor
- import com.typesafe.config.ConfigFactory
- import akka.routing.FromConfig
- object PerfectNumber {
- def main(args: Array[String]): Unit = {
- val system = ActorSystem("MasterApp", ConfigFactory.load.getConfig("multiThread"))
- system.actorOf(Props(new Actor() {
- val master = context.system.actorOf(Props[Master], "master")
- master ! StartFind(2, 100, self)
- def receive = {
- case PerfectNumbers(list: List[Int]) =>
- println("\nFound Perfect Numbers:" + list.mkString(","))
- system.shutdown()
- }
- }))
- }
- }
- multiThread{
- akka.actor.deployment./master/workerRouter{
- router="round-robin"
- nr-of-instances=10
- }
- }
- 分布式程序
新建一个MasterApp.scala文件:
- package com.newegg.demo
- import com.typesafe.config.ConfigFactory
- import akka.actor.Actor
- import akka.actor.ActorRef
- import akka.actor.ActorSelection.toScala
- import akka.actor.ActorSystem
- import akka.actor.Props
- import akka.kernel.Bootable
- import akka.cluster.Cluster
- class Agent extends Actor {
- var master = context.system.actorSelection("/user/master")
- def receive = {
- case StartFind(start: Int, end: Int, replyTo: ActorRef) if (start > 1 && end >= start) =>
- master ! StartFind(start, end, sender)
- }
- }
- class MasterDaemon extends Bootable {
- val system = ActorSystem("MasterApp", ConfigFactory.load.getConfig("remote"))
- val master = system.actorOf(Props[Master], "master")
- def startup = {}
- def shutdown = {
- system.shutdown()
- }
- }
- object MasterApp {
- def main(args: Array[String]) {
- new MasterDaemon()
- }
- }
- akka {
- actor {
- provider = "akka.remote.RemoteActorRefProvider"
- deployment{
- /remoteMaster{
- router="round-robin"
- nr-of-instances=10
- target{
- nodes=[
- "akka.tcp://MasterApp@127.0.0.1:2551",
- "akka.tcp://MasterApp@127.0.0.1:2552"
- ]
- }
- }
- /master/workerRouter{
- router="round-robin"
- nr-of-instances=10
- }
- }
- }
- remote {
- transport = "akka.remote.netty.NettyRemoteTransport"
- netty.tcp {
- hostname = "127.0.0.1"
- port = 2551
- }
- }
- }
修改PerfectNumber.scala中的main函数为:
- def main(args: Array[String]): Unit = {
- val system = ActorSystem("MasterApp", ConfigFactory.load.getConfig("remote"))
- system.actorOf(Props(new Actor() {
- val agent = context.system.actorOf(Props(new Agent()).withRouter(FromConfig()), "remoteMaster")
- dispatch
- private def dispatch = {
- val remotePaths = context.system.settings.config.getList("akka.actor.deployment./remoteMaster.target.nodes")
- val count = end - start + 1
- val piece = Math.round(count.toDouble / remotePaths.size()).toInt
- println("%s pieces per node".format(piece))
- var s = start
- while (end >= s) {
- var e = s + piece - 1
- if (e > end)
- e = end
- agent ! StartFind(s, e, self)
- s = e + 1
- }
- println(agent.path)
- }
- def receive = {
- case PerfectNumbers(list: List[Int]) =>
- println("\nFound Perfect Numbers:" + list.mkString(","))
- system.shutdown()
- }
- }))
- }
这种分布式程序实现起来简单,但是有个缺点:参与分担任务的守护程序的地址必须全部在target.nodes配置中列出,且一旦其中有守护程序宕掉,整体将不能正确地对外服务。
因此,我们需要有一个有更具扩展能力和高容错能力的集群式应用。
- 集群式应用
- akka {
- actor {
- provider = "akka.cluster.ClusterActorRefProvider"
- deployment {
- /master/workerRouter {
- router = "consistent-hashing"
- nr-of-instances = 10
- cluster {
- enabled = on
- max-nr-of-instances-per-node = 3
- allow-local-routees = on
- }
- }
- }
- }
- remote {
- log-remote-lifecycle-events = off
- netty.tcp {
- hostname = "127.0.0.1"
- port = 2551
- }
- }
- cluster {
- min-nr-of-members = 2
- seed-nodes = [
- "akka.tcp://MasterApp@127.0.0.1:2551",
- "akka.tcp://MasterApp@127.0.0.1:2552"]
- auto-down=on
- }
- }
这里采用的是consistent-hashing Router,集群节点之间有心跳检测,集群实现中内部采用的是与Cassandra一样的Gossip协议,用一致性哈希来维护集群节点“环”。
改造Master.scala文件中其中一行代码,将
- worker ! Work(num, helper)
- worker.tell(ConsistentHashableEnvelope(Work(num,helper), num), helper)
改造上面的MasterApp.scala,在其中代码行val master = system.actorOf(Props[Master], "master")下面增加两行代码:
- val agent = system.actorOf(Props(new Agent), "agent")
- Cluster(system).registerOnMemberUp(agent)
新建一个ClusterClient.scala文件,内容如下:
- package com.newegg.demo
- import scala.concurrent.forkjoin.ThreadLocalRandom
- import akka.actor.Actor
- import akka.actor.ActorRef
- import akka.actor.ActorSelection.toScala
- import akka.actor.Address
- import akka.actor.RelativeActorPath
- import akka.actor.RootActorPath
- import akka.cluster.Cluster
- import akka.cluster.ClusterEvent.CurrentClusterState
- import akka.cluster.ClusterEvent.MemberEvent
- import akka.cluster.ClusterEvent.MemberRemoved
- import akka.cluster.ClusterEvent.MemberUp
- import akka.cluster.MemberStatus
- class ClusterClient extends Actor {
- val cluster = Cluster(context.system)
- override def preStart(): Unit = cluster.subscribe(self, classOf[MemberEvent])
- override def postStop(): Unit = cluster unsubscribe self
- var nodes = Set.empty[Address]
- val servicePath = "/user/agent"
- val servicePathElements = servicePath match {
- case RelativeActorPath(elements) => elements
- case _ => throw new IllegalArgumentException(
- "servicePath [%s] is not a valid relative actor path" format servicePath)
- }
- def receive = {
- case state: CurrentClusterState =>
- nodes = state.members.collect {
- case m if m.status == MemberStatus.Up => m.address
- }
- case MemberUp(member) =>
- nodes += member.address
- case MemberRemoved(member, _) =>
- nodes -= member.address
- case _: MemberEvent => // ignore
- case PerfectNumbers(list: List[Int]) =>
- println("\nFound Perfect Numbers:" + list.mkString(","))
- cluster.down(self.path.address)
- context.system.shutdown()
- case StartFind(start: Int, end: Int, resultTo: ActorRef) =>
- println("node size:" + nodes.size)
- nodes.size match {
- case x: Int if x < 1 =>
- Thread.sleep(1000)
- self ! StartFind(start, end, resultTo)
- case _ =>
- val address = nodes.toIndexedSeq(ThreadLocalRandom.current.nextInt(nodes.size))
- val service = context.actorSelection(RootActorPath(address) / servicePathElements)
- service ! StartFind(start, end, resultTo)
- println("send to :" + address)
- }
- }
- }
改造上述main函数如下:
- val system = ActorSystem("MasterApp", ConfigFactory.load.getConfig("cluster"))
- system.actorOf(Props(new Actor() {
- context.system.actorOf(Props[ClusterClient], "remoteMaster") ! StartFind(2, 100, self)
- ...
运行此集群客户端程序,可以看到客户端也join到集群中,集群中所有的节点都在分担计算任务,任意增减集群节点数目都是如此。