Scala认为Java线程通过共享数据以及通过锁来维护共享数据的一致性是糟糕的做法,容易引起锁的争用,而且线程的上下文切换会带来不少开销,降低并发程序的性能,甚至会引入死锁的问题。
Akka是Actor编程模型的高级类库,类似于JDK 1.5之后越来越丰富的并发工具包,简化了程序员并发编程的难度。Akka是一款提供了用于构建高并发的、分布式的、可伸缩的、基于Java虚拟机的消息驱动应用的工具集和运行时环境。
下面是基于模拟spark的akka做的分布式通信
一、配制服务端的akka
1,构造akka的配制文件,可以有三类:application.conf, application.json和application.properties. 通常会用application.conf,该配制文件需要放classpath下面
//这个文件是放在工程的resource下面的,即classpath下面
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
//使用kryo系列化,效率比java系列化更高
serializers {
kryo = "com.twitter.chill.akka.AkkaSerializer"
}
serialization-bindings {
"scala.Product" = kryo
}
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
//可以认为每个akka即是服务端也是客户端
hostname = "192.168.4.41"
port = 4444
}
log-sent-messages = on
log-received-messages = on
}
}
2,启动远程的ActorSystem,类似Spark的rpc通信中的RpcEnv,而actorRef类似RepEndpointRef
package com.util.remote
import java.util.concurrent.TimeUnit
import akka.actor.{Props, ActorSystem}
import akka.util.Timeout
import scala.concurrent.Await
import scala.io.StdIn
object RemoteDemo extends App {
//1,ActorSystem创建:和以前学的一样,使用ActorSystem的apply方法
val system= ActorSystem("RemoteDemoSystem")
//2,创建Actor:actorOf(Props[Actor具体类型],标识)
val remoteActor= system.actorOf(Props[RemoteActor], name = "RemoteActor")
//3,发信息:叹号左侧是收件人,右侧是邮件正文
remoteActor ! "TheRemoteActor is alive"
StdIn.readLine()
//4,关闭actorSystem:以前是shutDown,现在用terminate。不想停止,就不用调用terminated
Await.ready(system.whenTerminated,Timeout(4,TimeUnit.SECONDS).duration)//指定时间去关掉akka
}
3,构造交互用的Actor,类似RpcEndPoint
package com.util.remote
import akka.actor.Actor
import com.util.domain.JoinEvt
//1,自定actor:需要继承Actor,类似java的runnable,重写receive方法
class RemoteActor extends Actor {
def receive= {
//2,如何接收caseclass复合对象:client和remote,这个复合对象的包路径必须一样
//3,sender:指的是执行"!"的actor
//4、scala字符串插值:在字符串前面要有s符号,如果不写成${},需要写 $变量。类似shell
case j:JoinEvt => sender() ! s"the${j.name} hasJoin"
case msg:String =>
println(s"RemoteActor received message '$msg'")
sender ! "Hello from the RemoteActor"
}
}
====èok 远程的akka就做完了
二、配制客户端的akka
1,还是配制akka的配制文件,可以有三类:application.conf, application.json和application.properties. 通常会用application.conf,该配制文件需要放classpath下面
akka {
actor {
//provider固定写法,serializers,表示使用哪个类,不写默认是java.io的,同时要写serialization-bindings。
provider = "akka.remote.RemoteActorRefProvider"
serializers {
kryo = "com.twitter.chill.akka.AkkaSerializer"
}
serialization-bindings {
"scala.Product"= kryo
}
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
//调用端:hostname就是调用端的ip,端口为0表示自动选择
hostname = "127.0.0.1"
port = 0
}
}
}
//自定义的结构:在actorSytem下面创建的actor都是在user目录下面
remote {
actor {
name {
test = "akka.tcp://RemoteDemoSystem@192.168.4.41:4444/user/RemoteActor"
}
}
}
2,构造客户端的ActorSystem
package com.util.local
import akka.actor.{ActorSystem, Props}
import scala.io.StdIn
object LocalDemo extends App {
//1、创建actorSystem:使用apply方法
val system= ActorSystem("LocalDemoSystem")
//val localActorUsingIdentity =system.actorOf(Props[LocalActorUsingIdentity], name ="LocalActorUsingIdentity")
val localActor= system.actorOf(Props[LocalActor], name = "LocalActor")
//3,如何发信息:叹号左侧是收件人,右侧是邮件正文(因为在同一个jvm中肯定不用系列化)
localActor ! Init
localActor ! SendNoReturn
localActor ! SendHasReturn
localActor ! SendSerialization
// StdIn.readLine()
// system.terminate()
// StdIn.readLine()
}
3,构造客户端和远程交互的actor
import com.util.domain.JoinEvt
import scala.concurrent.ExecutionContext.Implicits.global
/**
* Created by panguansen on 17/8/14.
*/
case object Init
case object SendNoReturn
case object SendHasReturn
case object SendSerialization
/**
* 本地actor与远程actor进行交互:本地的actor负责具体的逻辑
*/
class LocalActor extends Actor {
//1、取得application,自定义属性的方法,即远程actorSystem下面RemoteActor的路径
val path= ConfigFactory.defaultApplication().getString("remote.actor.name.test")
//2、ActorSelection是谁及如何取:它就是远程actor的引用。context.actorSelection(path)返回ActorSelection
val remoteActor= context.actorSelection(path)
//3、ActorSelection得到ActorRef,它会返回Future[ActorRef],再通过Await.result得到
// (不需要得到ActorRef,因为ActorSelection就可以干这些)
/* val futureActorRef =remoteActor.resolveOne()
val actorRef = Await.result[ActorRef](futureActorRef,Timeout(4,TimeUnit.SECONDS).duration)*/
//?如何接收TimeOut:使用隐式值是给下面ask或?使用的。(?就是ask)
implicit val timeout = Timeout(4,TimeUnit.SECONDS)
def receive: Receive = {
case Init=> "init local actor"
case SendNoReturn=> remoteActor ! "hello remote actor"
case SendHasReturn=>
//ActorSelection,异步取收件人回复数据: 可以使用?也可以使用ask方法
for {
r <- remoteActor ? "helloremote actor"
} yieldprintln(r)
//复合对象给远程,需要系列化,在一个jvm中是不需要系列化的
case SendSerialization=>
for {
r <- remoteActor.ask(JoinEvt(1L,"godpan"))
} yield println(r)
}
}
服务端打印结果:
客户端打印
[INFO][04/17/2018 17:14:53.288] [main] [akka.remote.Remoting] Starting remoting
[INFO][04/17/2018 17:14:53.669] [main] [akka.remote.Remoting] Remoting started;listening on addresses :[akka.tcp://LocalDemoSystem@127.0.0.1:56627]
[INFO][04/17/2018 17:14:53.670] [main] [akka.remote.Remoting] Remoting now listens onaddresses: [akka.tcp://LocalDemoSystem@127.0.0.1:56627]
[WARN][04/17/2018 17:14:53.948][LocalDemoSystem-akka.remote.default-remote-dispatcher-5][akka.serialization.Serialization(akka://LocalDemoSystem)] Multiple serializersfound for class com.util.domain.JoinEvt, choosing first: Vector((interfacescala.Product,com.twitter.chill.akka.AkkaSerializer@1df5a470), (interfacejava.io.Serializable,akka.serialization.JavaSerializer@29d31b94))
Hello from the RemoteActor
the godpan has Join
==============》就这么简单,akka在分布式并发上还是很强大的《================