Akka是一个用于构建高并发、分布式和弹性的消息驱动应用程序的工具包,支持Java和Scala语言。Akka是基于JVM上的Actor模型实现的,Actor模型是一种并发编程范式,它将系统中的每个实体抽象为一个Actor,Actor之间通过异步消息进行通信和协作。
Scala.actors.Actor是Scala语言中提供的一个Actor模型的实现,它是基于Java线程模型的,并不是一个真正的轻量级线程。Scala.actors.Actor已经在Scala 2.10版本中被废弃,并被Akka Actor取代。Akka Actor提供了更高效、更健壮、更灵活、更丰富的功能,并且与Scala语言完美集成。
Akka的模型是基于Actor模型的,Actor模型是一种并发编程范式,它将系统中的每个实体抽象为一个Actor,Actor之间通过异步消息进行通信和协作。
Akka的模型是在JVM上实现的Actor模型,它提供了一套丰富的API和工具来构建高并发、分布式和弹性的消息驱动应用程序。
Akka的模型包括以下几个核心概念:
Actor是Akka的基本构建单元,它是一个封装了状态和行为的并发实体,可以与其他Actor通过异步消息进行通信。
<dependency>
<groupId>com.typesafe.akkagroupId>
<artifactId>akka-actor_2.12artifactId>
<version>2.4.17version>
dependency>
<dependency>
<groupId>com.typesafe.akkagroupId>
<artifactId>akka-remote_2.12artifactId>
<version>2.4.17version>
dependency>
mailbox负责存储actor收到的消息,dispatcher负责从mailbox取消息,分配线程给actor执行具体的业务逻辑。
sender引用代表最近收到消息的发送actor,通常用于回消息,比如 sender() !xxxx。
要创建一个Actor,我们需要遵循以下几个步骤:
akka.actor.Actor
特质,实现receive
方法,用于处理收到的消息。akka.actor.ActorSystem
实例,用于管理和监控Actor。要创建ActorSystem的原因是,它是Akka框架的基础,它提供了创建和管理Actor的能力,以及一些共享的服务和配置。没有ActorSystem,就无法使用Akka的功能。actorOf
方法,传入一个akka.actor.Props
实例,用于描述如何创建Actor。akka.actor.ActorRef
实例,用于与Actor进行通信。receive方法是Actor类的一个抽象方法,它用于定义Actor如何处理收到的消息。
receive方法具有以下特点:
必须在Actor的子类中被重写
,否则会抛出一个异常。偏函数
作为参数,它可以使用case语句来匹配不同类型的消息,并执行相应的逻辑。sender()方法
来获取消息的发送者的ActorRef,以便回复或转发消息。context()方法
来获取当前Actor的上下文信息,以便创建子Actor或访问系统服务。self()方法
来获取当前Actor的ActorRef,以便发送消息给自己或其他Actor。become()或unbecome()方法
来改变当前Actor的行为,即切换不同的receive函数。ActorSystem是Akka的核心组件,它是一个管理和监控Actor的容器,也是创建或查找Actor的入口。
ActorSystem具有以下特点:
分层的结构
,它包含一个根节点
,一个用户节点
,一个系统节点
和一个临时节点
。用户可以在用户节点下创建自定义的Actor,系统节点下是Akka内部的Actor,临时节点下是用于处理Ask模式的临时Actor。重量级的结构
,它会分配多个线程,所以一般一个应用只需要创建一个ActorSystem实例。如果需要创建多个ActorSystem,可以使用不同的配置
来避免冲突。配置容器
,它可以加载配置文件或者代码中的配置,用于设置Actor的部署、调度、远程通信、日志等方面的参数
。协作集合
,它提供了一些共享的服务
,如调度器、日志、扩展等,供Actor使用。akka.actor.ActorSystem
类的apply
方法,传入一个系统名称和可选的配置对象。例如:// 导入Akka相关的包
import akka.actor.ActorSystem
// 创建一个名为hello-system的ActorSystem实例
val system = ActorSystem("hello-system")
要关闭一个ActorSystem,我们需要使用terminate
方法,它会返回一个Future对象,表示关闭的结果。例如:
// 关闭ActorSystem实例
val future = system.terminate()
要获取一个ActorSystem的状态,我们可以使用whenTerminated
方法,它会返回一个Future对象,表示系统终止时的状态。例如:
// 获取ActorSystem终止时的状态
val future = system.whenTerminated
actorOf方法是ActorSystem和ActorContext类的一个成员方法,它用于创建一个新的Actor实例,并返回一个ActorRef实例,用于与Actor进行通信。
actorOf方法有两个重载版本,它们都接受一个Props实例作为参数,用于描述如何创建Actor。
def actorOf(props: Props): ActorRef
:这个版本只接受一个Props实例作为参数,它会自动生成一个唯一的名称给新创建的Actor,这个名称类似于base64编码的整数计数器,反转并加上“$”前缀,但是这个名称可能会在未来改变。def actorOf(props: Props, name: String): ActorRef
:这个版本接受一个Props实例和一个字符串作为参数,它会使用给定的字符串作为新创建的Actor的名称,这个名称必须不为空,不以“$”开头,并且在同一层级中唯一。如果给定的名称已经被使用,会抛出InvalidActorNameException异常。Props实例是一个对象,它用于描述如何创建Actor。
Props实例具有以下特点:
Props
类的构造方法或伴生对象的工厂方法来创建,传入一个Actor的类或对象,以及可选的构造参数。withDispatcher
或withMailbox
方法来指定Actor的调度器或邮箱。withDeploy
方法来指定Actor的部署配置。withFallback
方法来合并另一个Props实例,以便覆盖或补充缺失的属性。actorOf
方法时被复制一份,以保证每个Actor都有自己独立的属性。actorOf方法的返回值是一个ActorRef实例,它是一个轻量级的代理对象,用于与Actor进行通信。
ActorRef是一个抽象类
,它是一个轻量级的代理对象,用于与Actor进行通信。
ActorRef具有以下特点:
!
或tell
方法发送异步消息,使用?
或ask
方法发送同步消息。watch
或unwatch
方法添加或移除监视者,当Actor终止时,会向监视者发送Terminated消息。path
或toString
方法获取Actor的路径,使用path.address
方法获取Actor的地址。stop
方法或发送PoisonPill消息来终止Actor。下面是一个使用actorOf方法创建Actor的简单示例:
// 导入Akka相关的包
import akka.actor.{Actor, ActorSystem, Props}
// 定义一个打印消息的Actor类
class Printer extends Actor {
def receive = {
case msg => println(msg)
}
}
// 创建一个ActorSystem实例
val system = ActorSystem("example")
// 使用actorOf方法创建一个Printer实例,不指定名称
val printer1 = system.actorOf(Props[Printer])
// 使用actorOf方法创建一个Printer实例,指定名称为"printer2"
val printer2 = system.actorOf(Props[Printer], "printer2")
// 使用ActorRef实例发送消息给Printer
printer1 ! "Hello from printer1"
printer2 ! "Hello from printer2"
运行上面的代码,我们可以看到控制台输出了两条消息:
Hello from printer1
Hello from printer2
下面我们来看一个简单的例子,创建一个名为Greeter的Actor,它可以接收两种消息:Greet和Done。当收到Greet消息时,它会打印出"Hello, world!";当收到Done消息时,它会停止自己。
// 导入Akka相关的包
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
// 定义Greeter类,继承自Actor特质
class Greeter extends Actor {
// 实现receive方法
def receive: Receive = {
// 当收到Greet消息时
case Greet =>
// 打印出"Hello, world!"
println("Hello, world!")
// 将Done消息发送给自己
self ! Done
// 当收到Done消息时
case Done =>
// 停止自己
context.stop(self)
}
// 重写postStop方法
override def postStop(): Unit = {
// 打印出"Goodbye, world!"
println("Goodbye, world!")
}
}
// 定义两种消息的样例类
case object Greet
case object Done
object PostStopDemo {
def main(args: Array[String]): Unit = {
// 创建一个ActorSystem实例,命名为hello-system
val system: ActorSystem = ActorSystem("hello-system")
// 使用actorOf方法,传入一个Props实例,用于描述如何创建Greeter
val greeter: ActorRef = system.actorOf(Props[Greeter], "greeter")
// 获取一个ActorRef实例,用于与Greeter进行通信
// 将Greet消息发送给Greeter
greeter ! Greet
}
}
运行上面的代码,我们可以看到控制台输出了
Hello, world!
Goodbye, world!
然后Greeter停止了自己。
在创建Actor时,有一些注意事项需要遵守:
遵循这些注意事项,可以帮助我们更好地使用Akka框架,创建高效和健壮的Actor系统。
Actor的path路径是一个唯一标识Actor在Actor系统中位置的字符串,它由一个锚点和一个元素序列组成。锚点表示Actor系统的地址和协议,元素序列表示从根守护者到指定Actor的名称层次结构。
例如,一个Actor的path路径可能是这样的:
akka://my-system/user/foo/bar
这个路径表示在名为my-system
的Actor系统中,有一个名为foo
的Actor,它是user
守护者的子级,它又有一个名为bar
的子级,这个子级就是我们要标识的Actor。
Actor的path路径有以下作用:
根据Actor所在的位置和层次结构,可以分为以下两种类型:
由于远程部署和监督机制,逻辑path路径和物理path路径可能会发生偏离,即一个Actor可能在不同的Actor系统或网络节点上运行,而不是与其父级相同。
// 创建一个ActorSystem实例,用于管理和监控Actor
val system = ActorSystem("my-system")
// 使用actorOf方法创建一个名为foo的Actor实例,并返回它的ActorRef实例
val foo = system.actorOf(Props[MyActor], "foo")
// 使用actorOf方法在foo的子级中创建一个名为bar的Actor实例,并返回它的ActorRef实例
val bar = foo.actorOf(Props[MyActor], "bar")
在这个例子中,我们创建了两个Actor,一个名为foo
,它是user
守护者的子级,它又有一个名为bar
的子级。这两个Actor的path路径分别是:
akka://my-system/user/foo
akka://my-system/user/foo/bar
Akka有四种核心的Actor消息模式:tell、ask、forward和pipe。你的表格中只列出了tell和ask两种方式,还可以补充forward和pipe的说明,如下:
! | 发送异步消息,没有返回值 |
---|---|
!? | 发送同步消息,等待返回值 |
!! | 发送异步消息,返回值是Future[Any] |
-> | 转发收到的消息给另一个Actor,保持原始发送者不变 |
>> | 将Future的结果发送给sender或另一个Actor |
Tell:!
:向Actor发送一条消息,没有返回值。这是一种fire-and-forget的方式,不需要等待或阻塞发送者。这是最常用和最高效的消息模式。Ask:!?
:向Actor发送一条消息,返回一个Future。这是一种request/reply的方式,需要等待或阻塞发送者。这是在Actor系统外部与Actor通信时常用的方式。使用Ask时,需要定义一个超时参数,如果没有在规定时间内收到回复,Future会失败。Forward:->
:将收到的消息转发给另一个Actor,保持原始发送者不变。这是一种代理或中转的方式,可以将消息传递给更合适的Actor处理。使用Forward时,响应地址就是原始消息的发送者。Pipe:>>
:将Future的结果发送给sender或另一个Actor。这是一种异步回复的方式,可以将Future的结果正确地返回给请求者。使用Pipe时,需要提供一个Actor引用作为目标。这些消息模式可以根据不同的场景和需求进行选择和组合,实现灵活和高效的消息传递。
注意
!!是一个过时的符号,已经被!?替代了。!!的含义是发送异步消息,返回值是Future[Any],但是这样会导致类型不安全,所以不建议使用。!?的含义是发送同步消息,返回值是Future[T],可以指定返回值的类型,更安全和方便。
// 导入Akka相关的包
import akka.actor.{Actor, ActorSystem, Props}
// 定义一个Sender类,继承Actor特质,用于发送消息给Receiver
class Sender extends Actor {
// 重写receive方法,用于处理收到的消息
def receive = {
// 如果收到了"start"消息,就向Receiver发送一个"hello"消息
case "start" =>
println("Sender: start")
context.actorSelection("../receiver") ! "hello"
// 如果收到了Receiver回复的"hi"消息,就向Receiver发送一个"bye"消息
/*actorSelection(“…/receiver”)是一个方法,它用于根据给定的路径来查找一个或多个Actor的引用。
在这个例子中,actorSelection(“…/receiver”)表示查找当前Actor的父级的子级中名为"receiver"的Actor的引用。如果存在这样的Actor,就可以向它发送消息,如果不存在,就会收到一个DeadLetter消息。
actorSelection方法返回一个ActorSelection对象,它可以向该路径指向的Actor对象发送消息。3 但是,这种方法不是很可靠,因为路径可能会变化或失效。通常,建议使用ActorRef来直接引用Actor,或者使用Identify和ActorIdentity消息来解析ActorSelection为ActorRef。*/
case "hi" =>
println("Sender: hi")
context.actorSelection("../receiver") ! "bye"
// 如果收到了Receiver回复的"bye"消息,就停止自己和Receiver
case "bye" =>
println("Sender: bye")
context.stop(self)
context.stop(context.actorSelection("../receiver").anchor)
}
}
// 定义一个Receiver类,继承Actor特质,用于接收消息从Sender
class Receiver extends Actor {
// 重写receive方法,用于处理收到的消息
def receive = {
// 如果收到了Sender发送的"hello"消息,就向Sender回复一个"hi"消息
case "hello" =>
println("Receiver: hello")
sender ! "hi"
// 如果收到了Sender发送的"bye"消息,就向Sender回复一个"bye"消息
case "bye" =>
println("Receiver: bye")
sender ! "bye"
}
}
// 创建一个ActorSystem实例,用于管理和监控Actor
val system = ActorSystem("example")
// 使用actorOf方法创建一个Sender实例和一个Receiver实例,并返回它们的ActorRef实例
val sender = system.actorOf(Props[Sender], "sender")
val receiver = system.actorOf(Props[Receiver], "receiver")
// 向Sender发送一个"start"消息,开始通信
sender ! "start"
运行上面的代码,我们可以看到控制台输出了以下内容:
Sender: start
Receiver: hello
Sender: hi
Receiver: bye
Sender: bye
// 导入Akka相关的包
import akka.actor.{Actor, ActorSystem, Props}
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
// 定义一个Calculator类,继承Actor特质,用于进行简单的加减乘除运算
class Calculator extends Actor {
// 重写receive方法,用于处理收到的消息
def receive = {
// 如果收到了两个整数和一个运算符,就根据运算符进行相应的计算,并将结果回复给发送者
case (x: Int, "+", y: Int) => sender ! (x + y)
case (x: Int, "-", y: Int) => sender ! (x - y)
case (x: Int, "*", y: Int) => sender ! (x * y)
case (x: Int, "/", y: Int) => sender ! (x / y)
// 如果收到了其他类型的消息,就回复一个错误信息
case _ => sender ! "Invalid message"
}
}
// 创建一个ActorSystem实例,用于管理和监控Actor
val system = ActorSystem("example")
// 使用actorOf方法创建一个Calculator实例,并返回它的ActorRef实例
val calculator = system.actorOf(Props[Calculator], "calculator")
// 使用!?方法向Calculator发送一个同步消息,即(10, "+", 5),并等待3秒钟的超时时间
implicit val timeout = Timeout(3.seconds)
val result = calculator ? (10, "+", 5)
// 打印出计算结果或超时信息
result.map(println).recover {
case e: Exception => println(e.getMessage)
}
运行上面的代码,我们可以看到控制台输出了以下内容:
15
这表示我们成功地向Calculator发送了一个同步消息,并得到了正确的回复。
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
object Example {
// 定义一个Receiver类,继承Actor特质,用于接收和回复消息
class Receiver extends Actor {
// 重写receive方法,用于处理收到的消息
def receive: Receive = {
// 如果收到了一个字符串,就向原始发送者回复一个"Hi"的消息
case message: String => sender() ! "Hi"
// 如果收到了其他类型的消息,就忽略
case _ =>
}
}
// 定义一个Forwarder类,继承Actor特质,用于转发消息
class Forwarder extends Actor {
// 重写receive方法,用于处理收到的消息
def receive: Receive = {
// 如果收到了一个ActorRef和一个字符串,就向该ActorRef转发该字符串,并保持原始发送者不变
case (target: ActorRef, message: String) => target ! message
// 如果收到了一个字符串,就打印出来
case message: String => println(s"Forwarder received: $message")
// 如果收到了其他类型的消息,就忽略
case _ =>
}
}
def main(args: Array[String]): Unit = {
// 创建一个ActorSystem实例,用于管理和监控Actor,并定义一个隐式值context
val system = ActorSystem("example")
//implicit val context: ActorSystem = system
// 使用actorOf方法创建一个、一个Receiver实例和一个Forwarder实例,并返回它们的ActorRef实例
val receiver = system.actorOf(Props[Receiver], "receiver")
val forwarder = system.actorOf(Props(new Forwarder), "forwarder")
// 向Forwarder发送一个元组消息,即(receiver, "Hello"),表示让Forwarder向Receiver转发一个"Hello"的消息
forwarder ! (receiver, "Hello")
}
}
输出
Forwarder received: Hi
在Akka中,提供了一个scheduler对象来实现定时调度功能。使用ActorSystem.scheduler.schedule()方法
,就可以启动一个定时任务。
方式一: 采用发送消息
的形式实现.
def schedule(
initialDelay: FiniteDuration, // 延迟多久后启动定时任务
interval: FiniteDuration, // 每隔多久执行一次
receiver: ActorRef, // 给哪个Actor发送消息
message: Any) // 要发送的消息
(implicit executor: ExecutionContext) // 隐式参数:需要手动导入
方式二: 采用自定义方式
实现.
def schedule(
initialDelay: FiniteDuration, // 延迟多久后启动定时任务
interval: FiniteDuration // 每隔多久执行一次
)(f: ⇒ Unit) // 定期要执行的函数,可以将逻辑写在这里
(implicit executor: ExecutionContext) // 隐式参数:需要手动导入
注意: 不管使用上述的哪种方式实现定时器, 都需要
导入隐式转换和隐式参数
, 具体如下://导入隐式转换, 用来支持 定时器. import actorSystem.dispatcher //导入隐式参数, 用来给定时器设置默认参数. import scala.concurrent.duration._
需求
参考代码
import akka.actor.{Actor, ActorSystem, Props}
import scala.language.postfixOps
object MainActor {
//定义一个Actor, 用来循环接收消息, 并打印.
object ReceiverActor extends Actor {
override def receive: Receive = {
case x => println(x) //不管接收到的是什么, 都打印.
}
}
def main(args: Array[String]): Unit = {
//创建一个ActorSystem, 用来管理所有用户自定义的Actor.
val actorSystem = ActorSystem("actorSystem")
//关联ActorSystem和ReceiverActor.
val receiverActor = actorSystem.actorOf(Props(ReceiverActor), "receiverActor")
//导入隐式转换和隐式参数.
//导入隐式转换, 用来支持 定时器.
import actorSystem.dispatcher
//导入隐式参数, 用来给定时器设置默认参数.
import scala.concurrent.duration._
//通过定时器, 定时(间隔1秒)给ReceiverActor发送一句话.
//方式一: 通过定时器的第一种方式实现, 传入四个参数.
//actorSystem.scheduler.schedule(3.seconds, 2.seconds, receiverActor, "Maverick_曲流觞")
//方式二: 通过定时器的第二种方式实现, 传入两个时间, 和一个函数.
//actorSystem.scheduler.schedule(0 seconds, 2 seconds)(receiverActor ! "Maverick_曲流觞")
//实际开发写法
actorSystem.scheduler.schedule(0 seconds, 2 seconds){
receiverActor ! "Maverick_曲流觞"
}
}
}
通过运行我们可以发现控制台每隔1秒打印一次
Maverick_曲流觞
远端actorRef设置参数:
akka.actor.provider = akka.remote.RemoteActorRefProvider
akka.remote.netty.tcp.hostname = $host
akka.remote.netty.tcp.port = $port
远端actorRef是指在远程节点上运行的actor的引用,它可以用来发送和接收消息。要使用远端actorRef,你需要设置一些参数来配置actor系统,比如:
- akka.actor.provider = akka.remote.RemoteActorRefProvider:这个参数指定了actor系统使用的actor提供者,它必须是akka.remote.RemoteActorRefProvider,否则无法创建远端actorRef。
- akka.remote.netty.tcp.hostname = h o s t :这个参数指定了本地节点的主机名或 I P 地址,它用于绑定远程传输层和注册到集群中。 host:这个参数指定了本地节点的主机名或IP地址,它用于绑定远程传输层和注册到集群中。 host:这个参数指定了本地节点的主机名或IP地址,它用于绑定远程传输层和注册到集群中。host是一个变量,你需要替换成你的实际值。
- akka.remote.netty.tcp.port = p o r t :这个参数指定了本地节点的端口号,它用于绑定远程传输层和注册到集群中。 port:这个参数指定了本地节点的端口号,它用于绑定远程传输层和注册到集群中。 port:这个参数指定了本地节点的端口号,它用于绑定远程传输层和注册到集群中。port是一个变量,你需要替换成你的实际值。
除了这些参数,你还可能需要设置一些其他的参数,比如akka.remote.artery.enabled,akka.cluster.seed-nodes等,具体取决于你的应用场景和需求。
person通过对Siri发送不同口令,siri回复不同的话
// 导入必要的库
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
// 定义一个名为Siri的Actor
class Siri extends Actor {
override def receive: Receive = {
case "hello" => sender() ! "你好" // 如果接收到"hello",则回复"你好"
case "eat" => sender() ! "rice" // 如果接收到"eat",则回复"rice"
case "who" => sender() ! "maverick" // 如果接收到"who",则回复"maverick"
case "ip" => sender() ! "123456789" // 如果接收到"ip",则回复"123456789"
case _ => sender() ! "不知道" // 如果接收到其他消息,则回复"不知道"
}
}
// 定义一个名为Siri的伴生对象
object Siri {
def main(args: Array[String]): Unit = {
val conf =
"""
|akka.actor.provider = akka.remote.RemoteActorRefProvider
|akka.remote.netty.tcp.hostname = localhost
|akka.remote.netty.tcp.port = 8888
|""".stripMargin
val config = ConfigFactory.parseString(conf)
val system = ActorSystem("123",config) // 创建一个名为"123"的ActorSystem
system.actorOf(Props(new Siri), "siri") // 创建一个名为"siri"的Siri Actor,并将其注册到ActorSystem中
}
}
需要先启动Siri
可以看到akka.tcp://123@localhost:8888后面person连接siri时要用
等待客户端发送消息,实现交互
// 导入必要的库
import akka.actor.{Actor, ActorSystem, Props}
import com.typesafe.config.ConfigFactory
import scala.io.StdIn
// 定义了一个名为Person的类,继承自Actor
class Person extends Actor {
// 重写了Actor的receive方法,用于接收消息
override def receive: Receive = {
// 当接收到消息时,打印消息内容,并提示用户输入
case msg => println(s"siri = ${msg}") // 打印消息内容
println("输入:") // 提示用户输入
val line = StdIn.readLine() // 读取用户输入的内容
sender() ! line // 将用户输入的内容发送给消息发送者
}
// 重写了Actor的preStart方法,用于在Actor启动前执行一些操作
override def preStart(): Unit = {
println("输入:") // 提示用户输入
val line = StdIn.readLine() // 读取用户输入的内容
val selection = context.actorSelection("akka.tcp://123@localhost:8888/user/siri") // 创建一个ActorSelection对象,用于向远程Actor发送消息
selection ! line // 向远程Actor发送消息
}
}
object Person {
def main(args: Array[String]): Unit = {
val conf =
"""
|akka.actor.provider = akka.remote.RemoteActorRefProvider
|akka.remote.netty.tcp.hostname = localhost
|akka.remote.netty.tcp.port = 6666
|""".stripMargin
val config = ConfigFactory.parseString(conf) // 解析配置文件
val system = ActorSystem("456",config) // 创建一个名为"456"的ActorSystem
system.actorOf(Props(new Person), "person") // 创建一个名为"person"的Person Actor,并将其注册到ActorSystem中
}
}
运行person
进行交互