Как определить иерархию актера/супервайзера Akka?

Я новичок в Akka (Java lib, v2.3.9). Я пытаюсь следовать рекомендациям по иерархии супервизоров, но поскольку это мое первое приложение Akka, я где-то сталкиваюсь с ментальным барьером.

В моем первом приложении Akka (на самом деле это библиотека, предназначенная для повторного использования в нескольких приложениях), ввод из внешнего мира проявляется как 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 актеров. Я также намерен сделать так, чтобы 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 сообщения (которые, в свою очередь, перехватывают Input из какого-то источника). То, какие Актеры вообще живы/онлайн для обработки этих Process сообщений, полностью определяется конфигурацией. Наконец, каждый раз, когда приложение получает Input, оно внедряется в сообщение Process, и это сообщение Process отправляется всем сконфигурированным/живым акторам.

Учитывая это как заданную предысторию/настройку, я не могу определить, какой должна быть «иерархия актеров/супервайзеров». Похоже, что в моем случае использования все актеры действительно равны, и между ними нет надзорной структуры. StormTrooper просто получает сообщение Process, если этот тип актора был сконфигурирован для существования. То же самое для других подклассов актеров.

Я что-то здесь совсем пропустил? Как определить надзорную иерархию (в целях отказоустойчивости), если все участники равны, а иерархия по своей сути «плоская»/горизонтальная?


person smeeb    schedule 21.04.2015    source источник
comment
Чтобы позаимствовать мыслительный процесс у Виктора Кланга, подумайте, как бы вы это сделали, если бы у вас были только обычные люди, а не компьютеры, а затем представьте каждого человека как актера. Какие люди контролируют других людей?   -  person Ryan    schedule 21.04.2015
comment
Спасибо @Ryan (+1) - если бы у меня были реальные люди в качестве актеров, а не компьютеры, у меня была бы камера содержания с StormTrooper, DarthVader и Emperor внутри нее. Когда кто-то хотел отправить сообщение обитателям этой камеры, они должны были написать одно и то же сообщение на листе бумаги, по одному листу на человека. Если бы они хотели отправить сообщение и StormTrooper, и Emperor, они записывали одно и то же сообщение на двух листах бумаги. Затем они передавали мне все / любые документы, и я доставлял сообщение соответствующим сторонам.   -  person smeeb    schedule 21.04.2015
comment
Другими словами, здесь по-прежнему нет иерархии супервайзеров. :-)   -  person smeeb    schedule 21.04.2015


Ответы (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. Я также показал, что вы можете настроить стратегии контроля для них отдельно.

Если вы хотите, чтобы некоторые акторы по какой-то причине игнорировали сообщения - просто реализуйте эту логику внутри актора, например:

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
    }

}

Это позволит динамически настроить игнорирование/неигнорирование (в противном случае это просто if). Вы можете отправить сообщение Ignore актерам на основе некоторой конфигурации:

val listOfIgnorantPathes = readFromSomeConfig()
context.actorSelection(listOfIgnoredPathes) ! Ignore

Вы также можете создать вещатель для палпатина так же, как маршрутизатор солдата (только используйте группы вместо пулов), если вы хотите управлять гетерогенным вещанием из конфига:

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, чтобы он не получал Process сообщений.

P.S. Актеры никогда не бывают одинокими — всегда есть Актер-Хранитель (см. ), который выключит всю систему в случае исключения. Так что в любом случае SenatorPalpatine действительно может стать вашим спасением.

PS2 context.actorSelection("palpatine/*") фактически позволяет отправлять сообщения всем детям ( как альтернатива широковещательным пулам и группам), поэтому вам не нужно иметь их набор внутри.

person dk14    schedule 23.04.2015
comment
Спасибо @dk14 (+1) - так как же может выглядеть SenatorPalpatine с точки зрения кода? Будет ли он просто содержать Set<ActorRef> (управляемый файлом конфигурации JSON), перебирать этот набор и отправлять сообщения Process всем участникам в списке? - person smeeb; 23.04.2015
comment
нет, Set<ActorRef> здесь не нужно, если вы хотите транслировать - context.actorSelection("../*") ! msg (от Палпатина) или system.actorSelection("palpatine/*") ! msg` выполнит эту работу (в Scala). - person dk14; 23.04.2015
comment
Спасибо @dk14 (еще раз +1) - однако я все еще не вижу леса за деревом. В качестве вознаграждения, могу ли я быть настолько смелым, чтобы увидеть пример простого псевдокода, который показывает: (1) SenatorPalpatine наблюдение за другими типами акторов и (2) SomeApp#run широковещательная рассылка Process сообщений всем настроенным ( помните, возможно, только некоторые из акторов должны получать Process сообщения, возможно, некоторые настроены так, чтобы не получать их) для их получения? Еще раз большое спасибо за помощь! - person smeeb; 23.04.2015
comment
@smeeb Я обновил примеры Scala, это решение основано на маршрутизаторах и akka-config, а не на пользовательской конфигурации. - person dk14; 23.04.2015
comment
Еще раз спасибо @dk14 (+1) - думаю, мы почти у цели! По большей части теперь я понимаю, как будет работать Palpatine, но я все еще не уверен в интеграции между SomeApp и Palpatine или между SomeApp и системой акторов в целом. Когда SomeApp#run генерирует новое сообщение Process, вызывает ли он Palpatine (который, в свою очередь, рассылает сообщение всем сконфигурированным субъектам)? Или что-то еще происходит? Еще раз спасибо! - person smeeb; 23.04.2015
comment
он создает один экземпляр Palpatine с именем palpatine и отправляет ему сообщение Process. - person dk14; 23.04.2015
comment
Кстати, Palpatine здесь может не совпадать с Emperor, на самом деле Emperor может сломать всю метафору, если вы думаете, что империя может жить без него или иметь несколько императоров :). Так что это просто еще один ребенок Palpatine тогда (с точки зрения SW должно быть наоборот - я знаю :), просто забыл о нем) - person dk14; 23.04.2015

Основываясь на вашем комментарии, вам все равно нужен Master актер дублировать и распространять Processes. Концептуально, пользователь (или что-то, что генерирует ваш ввод) не будет вводить один и тот же ввод один раз для каждого актера. Они будут предоставлять сообщение только один раз, а затем вы (или Master актор) должны дублировать сообщение по мере необходимости и отправлять его каждому из соответствующих дочерних акторов.

Как обсуждалось в ответе dk14, этот подход имеет дополнительное преимущество повышенной отказоустойчивости.

person Luke    schedule 23.04.2015
comment
Спасибо @Luke Willis (+1) - см. мой последний комментарий с запросом простого фрагмента кода из dk14 ниже - у меня к вам тот же вопрос! - person smeeb; 23.04.2015