actor模型:akka
在本文中,我们将探讨另一种Akka类型的模式。 这次,我们将向您展示如何使用接待员模式。 这是有关Akka型的系列文章的第三篇也是最后一篇文章。 其他两篇文章也可以在此站点上找到。 如果您还不了解Akka Typed,最好先阅读“使用Akka Typed的第一步”一文:
接待员模式背后的想法非常简单,ScalaDoc对此进行了解释:
/**
* A Receptionist is an entry point into an Actor hierarchy where select Actors
* publish their identity together with the protocols that they implement. Other
* Actors need only know the Receptionist’s identity in order to be able to use
* the services of the registered Actors.
*/
object Receptionist {
...
因此,基本上,客户只需要知道如何访问接待员,就可以从那里访问在接待员处注册的任何其他参与者。 因此,让我们首先创建几个演员,然后向接待员注册。 为此,我们定义了两个非常简单的参与者:
/**
* Simple service and protocol. Does nothing special, just print out
* the received message.
*/
object FirstService {
sealed trait FirstServiceMsg
final case class FirstServiceMsg1(msg: String) extends FirstServiceMsg
val behavior = Static[FirstServiceMsg] {
case msg:FirstServiceMsg => println("First Service Receiver: " + msg)
}
}
/**
* Another simple service and protocol. Does nothing special, just print out
* the received message.
*/
object SecondService {
sealed trait SecondServiceMsg
final case class SecondServiceMsg1(msg: String) extends SecondServiceMsg
val behavior = Static[SecondServiceMsg] {
case msg:SecondServiceMsg => println("Second Service Receiver: " + msg)
}
}
没什么特别的,只有两个服务可以打印出收到的每条消息。 除了这两个服务,我们还创建了一个服务,它将充当这两个服务的客户端:
object SenderService {
sealed trait SenderMsg
final case class registerAddresses(firstServices: Set[ActorRef[FirstServiceMsg]], secondServices: Set[ActorRef[SecondServiceMsg]]) extends SenderMsg
final case class sendMessage(msg: String) extends SenderMsg
val behavior = Total[SenderMsg] {
case registerAddresses(firstRefs, secondRefs) => {
Static {
case sendMessage(msg) => {
firstRefs.foreach(_ ! FirstServiceMsg1(msg))
secondRefs.foreach(_ ! SecondServiceMsg1(msg))
}
}
}
case _ => Same
}
}
如您所见,还有一个相当基本的参与者,它在收到registerAddresses消息后便会改变行为。 更改其行为后,它将根据sendMessage消息来调用已注册的服务。 现在我们如何将所有这些联系在一起?
为此,我们创建了另一个演员,为我们启动了该场景:
val scenario2 = {
Full[Unit] {
case Sig(ctx, PreStart) => {
val receptionist = ctx.spawn(Props(Receptionist.behavior), "receptionist");
// register three actors that can work with the FirstServiceMsg protocol
val service1a = ctx.spawn(Props(FirstService.behavior), "service1a")
val service1b = ctx.spawn(Props(FirstService.behavior), "service1b")
val service1c = ctx.spawn(Props(FirstService.behavior), "service1c")
// register three actors that can work with the SecondServiceMsg protocol
val service2a = ctx.spawn(Props(SecondService.behavior), "service2a")
val service2b = ctx.spawn(Props(SecondService.behavior), "service2b")
val service2c = ctx.spawn(Props(SecondService.behavior), "service2c")
// and the actor that will eventually send messages
val sender = ctx.spawn(Props(SenderService.behavior),"sender")
// define the service keys we'll use for registering
val serviceKey1 = new ServiceKey[FirstServiceMsg] {}
val serviceKey2 = new ServiceKey[SecondServiceMsg] {}
// register the services with the receptionise
val responseWrapperFirst = ctx.spawnAdapter[Registered[FirstServiceMsg]] {case _ =>}
val responseWrapperSecond = ctx.spawnAdapter[Registered[SecondServiceMsg]] {case _ =>}
receptionist ! Register(serviceKey1, service1a)(responseWrapperFirst)
receptionist ! Register(serviceKey1, service1b)(responseWrapperFirst)
receptionist ! Register(serviceKey1, service1c)(responseWrapperFirst)
receptionist ! Register(serviceKey2, service2a)(responseWrapperSecond)
receptionist ! Register(serviceKey2, service2b)(responseWrapperSecond)
receptionist ! Register(serviceKey2, service2c)(responseWrapperSecond)
// as a client we can now ask the receptionist to give us the actor references for services
// that implement a specific protocol. We pass the result to the sender service. Ugly way
// for now, but more to demonstrate how it works.
val getListingWrapper = ctx.spawnAdapter[Listing[FirstServiceMsg]] {
case firsts : Listing[FirstServiceMsg] => {
val secondWrapper = ctx.spawnAdapter[Listing[SecondServiceMsg]] {
case seconds : Listing[SecondServiceMsg] => {
sender ! registerAddresses(firsts.addresses, seconds.addresses)
}
}
receptionist ! Find[SecondServiceMsg](serviceKey2)(secondWrapper)
}
}
// get message from the first lookup, and pass it to the adapter, which will look up the
// second
receptionist ! Find[FirstServiceMsg](serviceKey1)(getListingWrapper)
// now wait a bit to make sure that through the receptionist we get a list of target actorrefs
Thread.sleep(200)
// these are sent to all the registered service implementations
sender ! sendMessage("Hello1")
sender ! sendMessage("Hello2")
Same
}
}
}
那是很多代码,但是很容易看到发生了什么。 我们要做的第一件事是使用ctx.spawn创建我们刚才讨论的类型的子参与者的集合。 定义参与者后,我们需要向接待员注册FirstService和SecondService参与者。 为此,我们需要发送如下消息:
/**
* Associate the given with the given . Multiple
* registrations can be made for the same key. Unregistration is implied by
* the end of the referenced Actor’s lifecycle.
*/
final case class Register[T](key: ServiceKey[T], address: ActorRef[T])(val replyTo: ActorRef[Registered[T]]) extends Command
在我们的示例中,我们通过以下方式进行操作:
// define the service keys we'll use for registering
val serviceKey1 = new ServiceKey[FirstServiceMsg] {}
val serviceKey2 = new ServiceKey[SecondServiceMsg] {}
// register the services with the receptionise
val responseWrapperFirst = ctx.spawnAdapter[Registered[FirstServiceMsg]] {case _ =>}
val responseWrapperSecond = ctx.spawnAdapter[Registered[SecondServiceMsg]] {case _ =>}
receptionist ! Register(serviceKey1, service1a)(responseWrapperFirst)
receptionist ! Register(serviceKey1, service1b)(responseWrapperFirst)
receptionist ! Register(serviceKey1, service1c)(responseWrapperFirst)
receptionist ! Register(serviceKey2, service2a)(responseWrapperSecond)
receptionist ! Register(serviceKey2, service2b)(responseWrapperSecond)
receptionist ! Register(serviceKey2, service2c)(responseWrapperSecond)
如您所见,我们创建了一个适配器来处理Register消息的replyTo参数(在这种情况下,我们只是忽略响应)。 然后,我们使用这些适配器向接待员注册我们的服务参与者。 至此,我们已经向接待员注册了我们的服务,现在可以使用“查找”消息:
/**
* Query the Receptionist for a list of all Actors implementing the given
* protocol.
*/
final case class Find[T](key: ServiceKey[T])(val replyTo: ActorRef[Listing[T]]) extends Command
..以获取特定类型的已注册演员列表:
// as a client we can now ask the receptionist to give us the actor references for services
// that implement a specific protocol. We pass the result to the sender service. Ugly way
// for now, but more to demonstrate how it works.
val getListingWrapper = ctx.spawnAdapter[Listing[FirstServiceMsg]] {
case firsts : Listing[FirstServiceMsg] => {
val secondWrapper = ctx.spawnAdapter[Listing[SecondServiceMsg]] {
case seconds : Listing[SecondServiceMsg] => {
sender ! registerAddresses(firsts.addresses, seconds.addresses)
}
}
receptionist ! Find[SecondServiceMsg](serviceKey2)(secondWrapper)
}
}
// get message from the first lookup, and pass it to the adapter, which will look up the
// second
receptionist ! Find[FirstServiceMsg](serviceKey1)(getListingWrapper)
需要注意的是第99行:
sender ! registerAddresses(firsts.addresses, seconds.addresses)
在这里,我们将有关注册服务的信息发送给了作为客户的演员。
现在剩下要做的就是将sendMessage消息发送给发件人,并且应该将其发送给所有已注册的服务:
// these are sent to all the registered service implementations
sender ! sendMessage("Hello1")
sender ! sendMessage("Hello2")
现在运行此命令时,输出如下所示:
First Service Receiver: FirstServiceMsg1(Hello1)
First Service Receiver: FirstServiceMsg1(Hello1)
First Service Receiver: FirstServiceMsg1(Hello1)
Second Service Receiver: SecondServiceMsg1(Hello1)
First Service Receiver: FirstServiceMsg1(Hello2)
First Service Receiver: FirstServiceMsg1(Hello2)
Second Service Receiver: SecondServiceMsg1(Hello1)
First Service Receiver: FirstServiceMsg1(Hello2)
Second Service Receiver: SecondServiceMsg1(Hello1)
Second Service Receiver: SecondServiceMsg1(Hello2)
Second Service Receiver: SecondServiceMsg1(Hello2)
Second Service Receiver: SecondServiceMsg1(Hello2)
容易吧!
翻译自: https://www.javacodegeeks.com/2015/11/akka-typed-actors-exploring-the-receptionist-pattern.html
actor模型:akka