我是Akka的新手(Java lib,v2.3.9)。我试图关注supervisor hierarchy best practices,但由于这是我的第一个Akka应用程序,我在某个地方遇到了心理障碍。
在我的第一个Akka应用程序(实际上是一个旨在跨多个应用程序重用的库)中,来自外部世界的输入表现为传递给actor的Process
消息。使用我的应用程序的开发人员将提供基于文本的配置文件,最终配置哪些演员发送Process
个实例,哪些不发送。换句话说,这些是我的演员类:
// Groovy pseudo-code
class Process {
private final Input input
Process(Input input) {
super()
this.input = deepClone(input)
}
Input getInput() {
deepClone(this.input)
}
}
class StormTrooper extends UntypedActor {
@Override
void onReceive(Object message) {
if(message instanceof Process) {
// Process the message like a Storm Trooper would.
}
}
}
class DarthVader extends UntypedActor {
@Override
void onReceive(Object message) {
if(message instanceof Process) {
// Process the message like Darth Vader would.
}
}
}
class Emperor extends UntypedActor {
@Override
void onReceive(Object message) {
if(message instanceof Process) {
// Process the message like the Emperor would.
}
}
}
// myapp-config.json -> where the actors are configured, along with other
// app-specific configs
{
"fizzbuzz": "true",
"isYosemite": "false",
"borderColor": "red",
"processors": [
"StormTrooper",
"Emperor"
]
}
正如您在配置文件中看到的那样,仅选择了StormTrooper
和Emperor
来接收Process
条消息。这最终会导致创建零(0)DarthVader
个actor。我的意图是,这会导致Set<ActorRef>
可用于填充StormTrooper
和Emperor
的应用程序,如下所示:
class SomeApp {
SomeAppConfig config
static void main(String[] args) {
String configFileUrl = args[0] // Nevermind this horrible code
// Pretend here that configFileUrl is a valid path to
// myapp-config.json.
SomeApp app = new SomeApp(configFileUrl)
app.run()
}
SomeApp(String url) {
super()
config = new SomeAppConfig(url)
}
void run() {
// Since the config file only specifies StormTrooper and
// Emperor as viable processors, the set only contains instances of
// these ActorRef types.
Set<ActorRef> processors = config.loadProcessors()
ActorSystem actorSystem = config.getActorSystem()
while(true) {
Input input = scanForInput()
Process process = new Process(input)
// Notify each config-driven processor about the
// new input we've received that they need to process.
processors.each {
it.tell(process, Props.self()) // This isn't correct btw
}
}
}
}
所以,正如你可以(希望)看到的那样,我们拥有处理UntypedActor
个消息的所有这些演员(实际上是许多Process
个impl)(反过来,捕获{{1}来自某些来源)。至于哪些Actors甚至还活着/在线处理这些Input
消息完全是配置驱动的。最后,每次应用收到Process
时,都会将其注入Input
消息,并将Process
消息发送给所有已配置/生活的角色。
将此作为给定的背景故事/设置,我无法识别&#34;演员/主管层次结构&#34;需要是。看起来在我的用例中,所有参与者都是真正平等的,他们之间没有监督结构。如果该类型的actor配置为存在,Process
只会收到StormTrooper
消息。其他actor子类也一样。
我在这里完全遗漏了什么吗?如果所有参与者都是平等的并且层次结构本质上是平坦的&#34; /水平的,那么如何定义监督层级(用于容错目的)?
答案 0 :(得分:2)
如果您想为每个演员实例化不超过一个实例 - 您可能希望SenatorPalpatine
来监督这三个实例。如果您可以让我们说多个StormTrooper
- 您可能希望让JangoFett
演员负责制作(并且可能会杀死)他们,某些router也是不错的选择(它会自动监督它们)。如果一个失败(OneForAllStrategy
),广播能力,保持一些常见的统计数据等,这也将使你能够重启所有的士兵。
带路由器的示例(伪Scala):
//application.conf
akka.actor.deployment {
/palpatine/vader {
router = broadcast-pool
nr-of-instances = 1
}
/palpatine/troopers {
router = broadcast-pool
nr-of-instances = 10
}
}
class Palpatine extends Actor {
import context._
val troopers = actorOf(FromConfig.props(Props[Trooper],
"troopers").withSupervisorStrategy(strategy) //`strategy` is strategy for troopers
val vader = actorOf(FromConfig.props(Props[Vader]), "vader")
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1) //stategy for Palpatine's children (routers itself)
val strategy = OneForOneStrategy(maxNrOfRetries = 100, withinTimeRange = 1) //stategy for troopers
def receive = {
case p@Process => troopers ! p; vader ! p
case t@Terminted => println(t)
}
}
根据标准akka-config创建广播池。我还表明你可以单独为他们定制监督策略。
如果你想让一些演员因某种原因忽略消息 - 只需在actor中实现这个逻辑,比如:
class Vader extends Actor {
def receive {
case p@Process => ...
case Ignore => context.become(ignore) //changes message handler to `ignore`
}
def ignore = {
case x => println("Ignored message " + x)
case UnIgnore => context.become(process)//changes message handler back
}
}
这将动态配置ignore / unignore(否则它只是一个简单的if
)。您可以根据某些配置向演员发送Ignore
消息:
val listOfIgnorantPathes = readFromSomeConfig()
context.actorSelection(listOfIgnoredPathes) ! Ignore
如果你想从配置中控制异构广播,你也可以像trooper的路由器一样为palpatine创建广播器(只使用组而不是池):
akka.actor.deployment {
... //vader, troopers configuration
/palpatine/broadcaster {
router = broadcast-group
routees.paths = ["/palpatine/vader", "/palpatine/troopers"]
}
}
class Palpatine extends Actor {
... //vader, troopers definitions
val broadcaster = actorOf(FromConfig.props(), "broadcaster")
def receive = {
case p@Process => broadcaster ! p
}
}
只需从routees.paths
中排除vader即可使其无法收到Process
条消息。
P.S。演员永远不会孤单 - 总会有守护者演员(见The Top-Level Supervisors),如果发生异常,它会关闭整个系统。因此,无论如何SenatorPalpatine
可能真的成为你的救星。
P.S.2 context.actorSelection("palpatine/*")
实际上允许您向所有孩子发送信息(作为广播池和群组的替代方案),因此您不需要在其中包含一组信息。
答案 1 :(得分:1)
根据your comment,您仍然希望Master
actor能够复制和分发Process
个es。从概念上讲,您不会让用户(或任何生成您输入的内容)为每个actor提供相同的输入。他们只提供一次消息,然后您(或Master
演员)将根据需要复制该消息并将其发送给每个适当的子演员。
正如dk14's answer中所讨论的,这种方法具有增加容错能力的附加好处。