Статьи

Akka Notes — ActorSystem (Конфигурирование и планирование) — 4

Как мы видели из наших предыдущих постов, мы могли бы создать Actor, используя метод actorOf системы ActorSystem . На самом деле с ActorSystem можно сделать гораздо больше. Мы коснемся только бита Конфигурация и Планирование в этой статье.

Давайте посмотрим на подмножества методов, доступных в ActorSystem .

ActorSystemAPI

1. Управление конфигурацией

Помните файл application.conf который мы использовали для настройки уровня нашего журнала в предыдущей записи ? Этот файл конфигурации похож на файлы .properties в приложениях Java и многое другое. Скоро мы увидим, как мы могли бы использовать этот файл конфигурации для настройки наших диспетчеров, почтовых ящиков и т. Д. (Я даже близко не оцениваю мощь конфигурации типа safeafe . Пожалуйста, ознакомьтесь с некоторыми примерами, чтобы действительно оценить его удивительность) .

Поэтому, когда мы создаем ActorSystem с помощью метода apply объекта ActorSystem без указания какой-либо конфигурации, он ищет application.conf , application.json и application.properties в корне пути к классам и загружает их автоматически.

Так:

1
val system=ActorSystem("UniversityMessagingSystem")

такой же как:

1
val system=ActorSystem("UniversityMessagingSystem", ConfigFactory.load())

Чтобы предоставить доказательства этому аргументу, проверьте метод apply в ActorSystem.scala :

1
2
3
4
5
def apply(name: String, config: Option[Config] = None, classLoader: Option[ClassLoader] = None, defaultExecutionContext: Option[ExecutionContext] = None): ActorSystem = {
    val cl = classLoader.getOrElse(findClassLoader())
    val appConfig = config.getOrElse(ConfigFactory.load(cl))
    new ActorSystemImpl(name, appConfig, cl, defaultExecutionContext).start()
  }

а. Переопределение конфигурации по умолчанию

Если вы не заинтересованы в использовании application.conf (как в тестовых случаях) или хотели бы иметь свой собственный файл конфигурации (как при тестировании другой конфигурации или развертывании в других средах), вы можете отменить это, передав свой собственная конфигурация вместо того, чтобы хотеть тот из classpath.

ConfigFactory.parseString является одним из вариантов

1
val actorSystem=ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]"""))

или просто в вашем Testcase как:

1
2
3
4
class TeacherTestLogListener extends TestKit(ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]"""))) 
  with WordSpecLike
  with MustMatchers
  with BeforeAndAfterAll {

Там также ConfigFactory.load

1
val system = ActorSystem("UniversityMessageSystem", ConfigFactory.load("uat-application.conf"))

Если вам нужен доступ к вашим собственным параметрам конфигурации во время выполнения, вы можете сделать это через его API следующим образом:

1
2
val system=ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")) 
println (system.settings.config.getValue("akka.loggers")) // Results in > SimpleConfigList(["akka.testkit.TestEventListener"])

б. Расширение конфигурации по умолчанию

Помимо переопределения, вы также можете расширить конфигурацию по умолчанию с помощью своей пользовательской конфигурации, используя метод withFallback Config .

Допустим, ваш application.conf выглядит так:

1
2
3
4
5
akka{ 
    loggers = ["akka.event.slf4j.Slf4jLogger"]
    loglevel = DEBUG
    arun="hello"
}

и вы решили переопределить свойство akka.loggers например:

1
2
val config=ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")
    val system=ActorSystem("UniversityMessageSystem", config.withFallback(ConfigFactory.load()))

В итоге вы получите объединенную конфигурацию:

1
2
println (system.settings.config.getValue("akka.arun")) //> ConfigString("hello")
    println (system.settings.config.getValue("akka.loggers")) //> SimpleConfigList(["akka.testkit.TestEventListener"])

Итак, почему я рассказал всю эту историю о конфигурации? Потому что наша ActorSystem является той, которая загружает и предоставляет доступ ко всей информации о конфигурации.

ВАЖНАЯ ЗАМЕТКА

Следите за порядком отступления здесь — это значение по умолчанию и конфигурация расширения. Помните, что вы должны вернуться к конфигурации по умолчанию. Так:

1
config.withFallback(ConfigFactory.load())

будет работать, но:

1
ConfigFactory.load().withFallback(config)

не получит результаты, которые вам могут понадобиться.

2. Планировщик

планировщик

Как вы можете видеть из API ActorSystem , в ActorSystem есть небольшой мощный метод, называемый scheduler который возвращает Scheduler . В планировщике есть множество методов schedule с помощью которых мы могли бы делать забавные вещи в среде Actor.

а. Запланируйте что-нибудь для выполнения

DelayedActorTimer

На нашем примере Student-Teacher , предположим, что StudentActor захочет отправить сообщение учителю только через 5 секунд после получения InitSignal из нашего Testcase, а не сразу, наш код выглядит следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
class StudentDelayedActor (teacherActorRef:ActorRef) extends Actor with ActorLogging {
 
  def receive = {
    case InitSignal=> {
      import context.dispatcher
      context.system.scheduler.scheduleOnce(5 seconds, teacherActorRef, QuoteRequest)
      //teacherActorRef!QuoteRequest
    }
    ...
    ...
  }
}

Прецедент

Давайте подготовим тестовый сценарий, чтобы проверить это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
"A delayed student" must {
 
    "fire the QuoteRequest after 5 seconds when an InitSignal is sent to it" in {
 
      import me.rerun.akkanotes.messaging.protocols.StudentProtocol._
 
      val teacherRef = system.actorOf(Props[TeacherActor], "teacherActorDelayed")
      val studentRef = system.actorOf(Props(new StudentDelayedActor(teacherRef)), "studentDelayedActor")
 
      EventFilter.info (start="Printing from Student Actor", occurrences=1).intercept{
          studentRef!InitSignal
      }
    }
 
  }

Увеличение времени ожидания перехвата Eventfilter

Уч. Время ожидания по умолчанию для EventFilter для ожидания появления сообщения в EventStream составляет 3 секунды. Давайте увеличим это до 7 секунд, чтобы проверить наш тестовый пример. Свойство конфигурации filter-leeway помогает нам достичь этого.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
class RequestResponseTest extends TestKit(ActorSystem("TestUniversityMessageSystem", ConfigFactory.parseString(""
                                            akka{
                                              loggers = ["akka.testkit.TestEventListener"]
                                              test{
                                                  filter-leeway = 7s
                                              }
                                            }
                                    """)))
  with WordSpecLike
  with MustMatchers
  with BeforeAndAfterAll
  with ImplicitSender {
  ...
  ...

б. Запланируйте что-нибудь для выполнения повторно

Для повторного выполнения чего-либо вы используете метод расписания Планировщика.

Одна из часто используемых перегрузок метода расписания — это то, что отправляет сообщение актеру на регулярной основе. Он принимает 4 параметра:

  1. Как долго должна быть начальная задержка до начала первого выполнения
  2. Частота последующих казней
  3. Целевой ActorRef, на который мы собираемся отправить сообщение
  4. Сообщение
1
2
3
4
5
case InitSignal=> { 
      import context.dispatcher
      context.system.scheduler.schedule(0 seconds, 5 seconds, teacherActorRef, QuoteRequest)
      //teacherActorRef!QuoteRequest
    }

TRIVIA

Импорт- import context.dispatcher здесь очень важен.

Методы расписания требуют очень важного неявного параметра — ExecutionContext , причина которого будет очевидна, как только мы увидим реализацию метода schedule :

01
02
03
04
05
06
07
08
09
10
11
12
13
final def schedule( 
    initialDelay: FiniteDuration,
    interval: FiniteDuration,
    receiver: ActorRef,
    message: Any)(implicit executor: ExecutionContext,
                  sender: ActorRef = Actor.noSender): Cancellable =
    schedule(initialDelay, interval, new Runnable {
      def run = {
        receiver ! message
        if (receiver.isTerminated)
          throw new SchedulerException("timer active for terminated actor")
      }
    })

Метод schedule просто помещает сообщение в Runnable которое в конечном итоге исполняется с помощью ExecutionContext, который мы передаем.

Чтобы сделать ExecutionContext доступным в области видимости как неявный, мы используем неявный диспетчер, доступный в контексте.

Из ActorCell.scala (Context):

1
2
3
4
5
/**
   * Returns the dispatcher (MessageDispatcher) that is used for this Actor.
   * Importing this member will place an implicit ExecutionContext in scope.
   */
  implicit def dispatcher: ExecutionContextExecutor

Код

Как всегда, весь проект можно скачать с github здесь .

Ссылка: Akka Notes — ActorSystem (Конфигурирование и планирование) — 4 от нашего партнера JCG Аруна Маниваннана в блоге Rerun.me .