akka框架是actor模型的最佳实践
Akka是使用Scala语言编写的,基于Actor模型的框架。可以用来开发高并发、分布式、基于消息驱动的应用程序。
它被应用在Spark中以实现节点之间的通信。
Akka必须先导入包,Scala支持Maven,也有个sbt(国内支持不是很好)两种都行。
------------------------------------------------------------------------------------------------------------------------
把一个复杂的问题不断分解成更小规模的子问题通常是一种可靠的解决问题的技术。这个方法对于计算机科学特别有效(单一职责),
因为这样容易产生整洁的、模块化的代码,产生的冗余很少甚至没有,复用率高,而且维护起来相对容易。
在基于Actor的设计里,Actor系统提供了一个基础框架,通过这个系统Actor之间可以进行交互。
Actor实际就是一类用来通信的轻量级对象,每个Actor中维护自己的消息队列。Actor之间的交互都是基于发送消息来完成,
而不是采用阻塞的处理形式。可以把Actor理解为一群人,他们都有自己的一个邮箱,这群人之间通过发送邮件消息来完成。
将要执行的大任务的分解成足够小任务,派发给多个Actor来完成。Actor比线程更轻量、在scala中可以创建数以百万计的Actor对象,
奥秘在于Actor可以复用线程。基于事件机制,监听消息。
它是对并发写入以及分布式的高度抽象。
Actor的出现改变了程序员的编程习惯,使得他们不需要直接和锁、线程、远程调用打交道,有助于写出正确的、并行的代码.
Actor模型最初被Erlang语言实现,并在爱立信的电信系统中获取了巨大成功。
Akka开发就是面对Actor开发。
在Akka里面,和Actor通信的唯一方式就是通过ActorRef。ActorRef代表Actor的一个引用,可以阻止其他对象直接访问或操作这个
Actor的内部信息和状态。消息可以通过一个ActorRef以下面的语法协议中的一种发送到一个Actor:
!(“告知”) //发送消息并立即返回
?(“请求”) //发送消息并返回一个Future对象,代表一个可能的应答
------------------------------------------------------------------------------------------------------------------------
package com.scala.scala
import scala.actors._, Actor._
object TestActor{
def main(args: Array[String]): Unit = {
val badActor = actor{
receive{ //接收消息
case msg => println(msg)
}
}
badActor ! "今天感觉如何,娜娜?" //发送消息
}
}
注意:如果消息类型不一致,消息就会被忽略掉。如下面的msg:Int要求接收的是整数类型,如果传递上面的字符串,将不会接收,
而receive一直等待。而如果发送的消息是整形,则立刻接收处理。
package com.scala.scala
import scala.actors._, Actor._
object TestActor{
def main(args: Array[String]): Unit = {
val badActor = actor{
receive{ //接收消息
case msg:Int => println(msg)
}
}
badActor ! 123 //发送消息
}
}
------------------------------------------------------------------------------------------------------------------------
可以配置多个
package com.scala.scala
import scala.actors._, Actor._
object TestActor{
def main(args: Array[String]): Unit = {
val badActor = actor{
receive{ //接收消息
case msg:String => println(msg)
case msg:Int => println(msg)
}
}
badActor ! "这个世界真美好!" //发送消息
}
}
只要消息实现了序列化的接口。
------------------------------------------------------------------------------------------------------------------------
序列化
可以是scala.Serializable,也可以是java.io.Serializable
package com.scala.scala
import scala.actors._, Actor._
class Student(val name:String) extends Serializable{
val age = 16
}
object TestActor{
def main(args: Array[String]): Unit = {
val badActor = actor{
receive{ //接收消息
case stu:Student => println(stu.name+" "+stu.age)
}
}
badActor ! new Student("娜娜") //发送消息
}
}
可以直接用case类,因为它内部已经实现了序列化接口
case class Student(val name:String){
val age = 16
}
------------------------------------------------------------------------------------------------------------------------
Actor向另一个Actor发消息
使用maven工程,需要actor依赖包
package com.scala.scala
import akka.actor.ActorSystem
import akka.actor.Props
import akka.actor.Actor
class Actor1() extends Actor {
override def receive = {
case msg:String => {
println("Actor1 收到的消息是:"+msg)
//由上下文创建actor,context actor的上下文对象
val b = context.actorOf(Props[Actor2])
b ! "这是发给Actor2的消息"
}
}
}
class Actor2 extends Actor {
override def receive = {
case msg : String =>{
println("Actor2 收到的消息是:"+msg)
}
}
}
object ActorToActor {
def main(args: Array[String]): Unit = {
val sys = ActorSystem("sys") //创建运行环境
//由系统创建actor,classOf[Actor1]获得Actor1类
//val a = sys.actorOf(Props(classOf[Actor1]))
val a = sys.actorOf(Props[Actor1])
a ! "这是发给Actor1的消息"
}
}
Actor1接收到消息,然后调用context,context因为Actor2继承了Actor,就带过来一个context上下文对象,它就相当于system.actorOf环境。
然后它就创建了Actor2,发送消息,然后Actor2就接收消息。
其实后面我们要做的分布式系统就是相互之间发送消息,来达到交互的目的。比如Actor1是map,Actor2是reduce。
那map的结果发送给reduce,让它进行运算,那就发送消息把map结果发过去。如何发消息,就是拿到context上下文。
远程互相发消息
--------------------------------------
远程发消息比本地就是配置多些,配置远程的IP地址等
step1:服务端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import akka.actor.Props
import akka.actor.Actor
//测试远程Actor
object RemoteActor {
class Actor4 extends Actor{
override def receive = {
case msg:String => println(msg)
}
}
def main(args: Array[String]): Unit = {
//参数配置
val conf = new java.util.HashMap[String,Object]()
val IP = "127.0.0.1"
val PORT = "2550"
val list = new java.util.ArrayList[String]()
list.add("akka.remote.netty.tcp")
conf.put("akka.remote.enabled-transports", list) //参数是个集合
conf.put("akka.actor.provider", "akka.remote.RemoteActorRefProvider")
conf.put("akka.remote.netty.tcp.hostname", IP)
conf.put("akka.remote.netty.tcp.port", PORT)
val sys = ActorSystem("master", ConfigFactory.parseMap(conf))
sys.actorOf(Props[Actor4], "jt") //设定Actor的名字
//访问地址akka.tcp://ActorSystem名称@IP:端口/user/jt [akka.tcp://[email protected]:2550]
}
}
启动后会自动启动侦听
step2:客户端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
object ClientActor {
def main(args: Array[String]): Unit = {
//参数配置
val conf = new java.util.HashMap[String,Object]()
val IP = "127.0.0.1"
val PORT = "2551"
val list = new java.util.ArrayList[String]()
list.add("akka.remote.netty.tcp")
conf.put("akka.remote.enabled-transports", list) //参数是个集合
conf.put("akka.actor.provider", "akka.remote.RemoteActorRefProvider")
conf.put("akka.remote.netty.tcp.hostname", IP)
conf.put("akka.remote.netty.tcp.port", PORT)
val sys = ActorSystem("client", ConfigFactory.parseMap(conf))
//根据路径找到远程的Actor
sys.actorSelection("akka.tcp://[email protected]:2550/user/jt") ! "2551发出消息."
}
}
注意:enabled-transports后面是一个[“”]括起来的,它就不是字符串了,这点刚开始学习是很容易出错,它这个可以配置多个值,所以是一个List。
端口4位,自己定义值。
配置文件方式
------------------------------------------------
默认配置文件名称为application.conf,akka会自动到classes目录下查找。
val sys = ActorSystem("master", ConfigFactory.load().getConfig("ClientConf"))
step1:配置文件
/scala/src/main/resources/application.conf
RemoteConf{
akka{
actor{
provider = "akka.remote.RemoteActorRefProvider"
}
remote{
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp{
hostname = "127.0.0.1"
port = 2550
}
}
}
}
ClientConf{
akka{
actor{
provider = "akka.remote.RemoteActorRefProvider"
}
remote{
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp{
hostname = "127.0.0.1"
port = 2551
}
}
}
}
step2:服务端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import akka.actor.Props
import akka.actor.Actor
object RemoteActor {
class Actor4 extends Actor{
override def receive = {
case msg:String => println(msg)
}
}
def main(args: Array[String]): Unit = {
//参数配置
val sys = ActorSystem("master", ConfigFactory.load().getConfig("RemoteConf"))
sys.actorOf(Props[Actor4], "jt") //设定Actor的名字
}
}
step3:客户端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
object ClientActor {
def main(args: Array[String]): Unit = {
//参数配置
val sys = ActorSystem("master", ConfigFactory.load().getConfig("ClientConf"))
//根据路径找到远程的Actor
sys.actorSelection("akka.tcp://[email protected]:2550/user/jt") ! "2551发出消息."
}
}
Trait方式
----------------------------------------------------
Scala里相当于Java接口的是Trait(特征)。Trait的英文意思是特质和性状(本文称其为特征),实际上他比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。利用它scala实现多继承。
创建Scala Trait:
step1:配置的Trait接口
package com.scala.scala
trait Conf {
def getConf(PORT:String) = {
val conf = new java.util.HashMap[String,Object]()
val IP = "127.0.0.1"
val list = new java.util.ArrayList[String]()
list.add("akka.remote.netty.tcp")
conf.put("akka.remote.enabled-transports", list) //参数是个集合
conf.put("akka.actor.provider", "akka.remote.RemoteActorRefProvider")
conf.put("akka.remote.netty.tcp.hostname", IP)
conf.put("akka.remote.netty.tcp.port", PORT)
conf //scala不用return语句,最后一句就作为返回的对象
}
}
step2:服务端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
import akka.actor.Props
import akka.actor.Actor
object RemoteActor extends Conf{
class Actor4 extends Actor{
override def receive = {
case msg:String => println(msg)
}
}
def main(args: Array[String]): Unit = {
//参数配置
val sys = ActorSystem("master", ConfigFactory.parseMap(getConf("2550")))
sys.actorOf(Props[Actor4], "jt") //设定Actor的名字
}
}
step3:客户端
package com.scala.scala
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
object ClientActor extends Conf{
def main(args: Array[String]): Unit = {
//参数配置
val sys = ActorSystem("master", ConfigFactory.parseMap(getConf("2551")))
//根据路径找到远程的Actor
sys.actorSelection("akka.tcp://[email protected]:2550/user/jt") ! "2551发出消息."
}