Статьи

Квазар и Акка — Сравнение

Актерская модель — это шаблон проектирования для отказоустойчивых и хорошо масштабируемых систем. Действующие лица являются независимыми рабочими модулями, которые взаимодействуют с другими действующими лицами только посредством передачи сообщений, могут потерпеть неудачу в отрыве от других действующих лиц, но могут контролировать других участников на предмет сбоев и принимать некоторые меры по восстановлению, когда это происходит. Актеры — простые, изолированные, но скоординированные, параллельные работники.

Актерский дизайн приносит много преимуществ:

  • Адаптивное поведение : взаимодействие только через очередь сообщений делает участников слабо связанными и позволяет им:
    • Изоляция сбоев : почтовые ящики отделяют очереди сообщений, что позволяет перезапустить субъект без прерывания работы службы.
    • Управляйте эволюцией : они позволяют замену актеров без прерывания обслуживания.
    • Регулирование параллелизма : очень частый прием сообщений и устранение переполнения или, наоборот, увеличение размера почтового ящика может максимизировать параллелизм за счет надежности или использования памяти соответственно.
    • Регулирование нагрузки : уменьшение частоты приема звонков и использование небольших почтовых ящиков снижает параллелизм и увеличивает задержки, оказывая обратное давление через границы действующей системы.
  • Максимальная емкость параллелизма :
    • Актеры чрезвычайно легки как по потреблению памяти, так и по накладным расходам на управление, поэтому можно порождать даже миллионы в одной коробке.
    • Поскольку актеры не делят государство, они могут безопасно работать параллельно.
  • Низкая сложность :
    • Каждый субъект может реализовывать поведение с состоянием, изменяя свое частное состояние, не беспокоясь о одновременной модификации
    • Участники могут упростить свою логику перехода состояний, выборочно получая сообщения из почтового ящика в логическом порядке, а не в порядке поступления 1 .

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

Это сравнительный обзор двух библиотек актеров для JVM: нашего собственного Quasar и Akka от Typesafe.

квазар

Quasar — это библиотека с открытым исходным кодом для простого и легкого параллелизма JVM, которая реализует истинные легкие потоки (волокна AKA) в JVM. Волокна Quasar ведут себя так же, как простые потоки Java, за исключением того, что у них практически нет затрат памяти и переключения задач, так что вы можете легко создавать сотни тысяч волокон или даже миллионы в одной JVM. Quasar также предоставляет каналы для межволоконной связи, смоделированные по аналогии с языками Go, в комплекте с переключателями каналов. Он также содержит полную реализацию актерской модели, по образцу Эрланга.

Хотя этот пост в основном посвящен реализации актерской модели Quasar, построенной на основе волокон Quasar, имейте в виду, что вы можете использовать Quasar без актеров.

Актеры Quasar реализуют полную парадигму актеров, описанную выше, с некоторыми для Java 7, Java 8, Clojure и Kotlin . Квазар в настоящее время не поддерживает Scala.

Поскольку волокна Quasar работают так же, как потоки, существующие библиотеки легко интегрировать, поэтому можно использовать текущие инструменты и библиотеки без каких-либо изменений кода или с минимальными изменениями, в то же время полностью используя преимущества легковесных потоков. Это позволяет сохранить существующий код и избежать блокировки API. Проект Comsat использует преимущества интеграционных сред Quasar для обеспечения переноса с поддержкой оптоволокна нескольких популярных и стандартных API-интерфейсов с минимальным кодом (он также представляет Web Actors , новый веб-API на основе акторов для HTTP, WebSocket и SSE).

Волокна Quasar реализуются путем создания и планирования задач продолжения, и поскольку JVM (пока) не поддерживает собственные продолжения, Quasar реализует их с помощью выборочных инструментов байт-кода: методы, которые могут блокировать волокно, в настоящее время должны быть явно помечены посредством аннотаций, чтобы Quasar мог вставьте продолжение подвески и возобновите крючки. Тем не менее, экспериментальные автоматические инструменты Clojure доступны, и автоматические инструменты будут распространены и на другие языки JVM. Инструментарий может быть выполнен как дополнительный этап сборки или во время выполнения (через агент JVM или загрузчик классов для большинства распространенных контейнеров сервлетов).

Akka

Akka — это актерская среда, написанная на Scala, которая поддерживает Java 7, Java 8 (экспериментальная с 2.3.10) в дополнение к Scala. Он предлагает асинхронную, основанную на обратном вызове Actor DSL, а не систему актеров на основе оптоволокна в стиле Erlang. Akka не предоставляет легкие потоки, но полагается на потоки JVM, чтобы планировать актеров. Akka — это не библиотека, а полноценный сервис, охватывающий все — от настройки и развертывания до тестирования.

Блокировка Vs. Неблокируемому

Основное различие между акторами Akka и Quasar заключается в том, что Akka использует асинхронный неблокирующий API, а Quasar — как Erlang, Go, Clojure core.async — использует блокирующий API: В Akka актор реализует метод receive , который обратный вызов срабатывает, когда актер получает сообщение, в то время как в Quasar актер вызывает метод receive , который блокируется до получения сообщения. С теоретической точки зрения асинхронный и прямой (или блокирующий) стили являются двойственными и эквивалентными, поскольку они могут быть преобразованы друг в друга , но на практике детали реализации оказывают существенное влияние на производительность и масштабируемость, а также выбор Язык программирования может сделать один подход проще, чем другой.

Причиной выбора асинхронного подхода, основанного на обратном вызове, было то, что блокировка простых потоков ОС влечет за собой значительные накладные расходы (как и простое существование многих потоков), которых можно избежать с помощью неблокирующего API. Однако, поскольку в Quasar, как и в Erlang и Go, действительно легкие потоки, блокировка практически не требует накладных расходов.

С языковой стороны, в то время как Scala обеспечивает синтаксическую поддержку монад, что упрощает работу с асинхронным кодом, блокирующий подход намного проще в языках, которые не имеют хорошей синтаксической поддержки монад, таких как Java. Преимущество блокирования кода заключается не только в более простом, удобочитаемом и более удобном для обслуживания Java-коде, но и в более знакомом и совместимом коде, который позволяет интегрировать другие стандартные API Java.

Сравнение API

Java API Quasar поддерживает Java 7 и 8. Поддержка Clojure является частью Pulsar , тонкого слоя обертывания вокруг Quasar, который очень идиоматичен и который предлагает API актера, очень похожий на Erlang. Поддержка Kotlin является самым последним дополнением. Kotlin — это многообещающий гибридный язык программирования со статической типизацией, ориентированный на JVM и JavaScript, разработанный и созданный для обеспечения эффективности и интеграции с помощью ведущего разработчика dev-tools JetBrains . Хотя Kotlin делает использование существующих API-интерфейсов Java более эффективным, безопасным, но при этом более простым и приятным, чем сама Java.

Quasar также предлагает набор инструментов для интеграции, который позволяет добавлять поддержку дополнительных языков JVM.

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

Ниже приводится сравнение между Java-API Quasar и Akka. Хотя это далеко не исчерпывающее описание, оно охватывает важные различия. Вот краткое резюме:

Особенности стол

Определение актера

doRun Quasar (Kotlin и Java) реализуют метод doRun (или функцию в Clojure), который, как и в Erlang, может использовать произвольные конструкции потока управления языком и может блокировать операции с нитями, когда разработчик посчитает это подходящим; как правило, он будет использовать по крайней мере receive (обычный или выборочный) и send :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
class MyActor extends BasicActor<String, MyActorResult> {
    private final Logger log = LoggerFactory.getLogger(MyActor.class);
 
    @Suspendable
    @Override
    protected MyActorResult doRun() throws InterruptedException, SuspendExecution {
        // ...Arbitrary code here...
        final String msg = receive(m -> {
            if ("test".equals(m)) return "testMsg";
            else return null; // Defer
        });
        // ...Arbitrary code here...
        return new MyActorResult();
    }
}

API Clojure, предоставленный Pulsar, еще более лаконичен:

1
2
3
4
5
6
7
8
9
(def log (LoggerFactory/getLogger (class *ns*)))
 
(spawn
  #(
    ; ...Arbitrary code here...
    (receive                                ; Single, fiber-blocking, selective receive
      "test" (do (. log info "received test") "testMsg")) ; Other messages will stay in the mailbox
    ; ...Arbitrary code here...
  ))

Акторы квазара — подобно процессам Эрланга — используют блокирующий прием и им разрешено выполнять блокирующий ввод-вывод (хотя блокирующие операции ввода-вывода в конечном итоге не блокируют потоки ОС, а скорее волокна, что делает их очень масштабируемыми).

Актеры Akka реализованы как обратный вызов для события receive , и им запрещено блокировать:

01
02
03
04
05
06
07
08
09
10
11
public class MyUntypedActor extends UntypedActor {
  LoggingAdapter log = Logging.getLogger(getContext().system(), this);
 
  // "receive" must be toplevel
  public void onReceive(Object message) throws Exception {
    if ("test".equals(message))
      log.info("received test");
    else
      log.info("received unknown message")
  }
}

Жизненный цикл актера и иерархия надзора

Актеры Quasar создаются и запускаются на волокне так же легко, как:

1
ActorRef myActor = new MyActor().spawn();

В то время как и Quasar, и Akka поддерживают мониторинг (также отслеживание) других действующих лиц на предмет сбоев, Akka делает обязательным добавление действующих лиц в надлежащие иерархии контроля, поэтому акторы всегда должны создаваться с помощью рецепта, определяющего класс субъекта и соответствующие аргументы конструктора. Актеры высшего уровня должны порождаться системой акторов, а дочерние акторы — контекстом родителя:

1
ActorRef myActor = system.actorOf(Props.create(MyActor.class), "myactor");

Надзор — это иерархический шаблон управления сбоями, который обеспечивает хорошие методы изоляции отказов : супервизор будет действовать в соответствии со своей стратегией надзора, когда его дочерние акторы завершат работу. Дочерний актер под надзором может прекратить работу из-за постоянных сбоев, временных сбоев или потому, что они просто закончили свою работу. Когда происходит прекращение, как правило, супервизоры могут решить потерпеть неудачу самостоятельно ( эскалация ), перезапустить только отказавшего потомка или перезапустить их всех.

В Quasar, как и в Erlang, надзор является необязательным, а супервизор — это просто предварительно созданный, хотя и настраиваемый субъект, который обеспечивает контроль за счет внутреннего использования операций мониторинга примитивного субъекта (отслеживание и связывание). При использовании супервизора Quasar также требует указания рецепта для создания актера (а также дополнительной информации, такой как количество повторных попыток, которые должен предпринять супервизор, прежде чем сдаться и т. Д.):

1
2
ChildSpec actorSpec = new ChildSpec("myactor", TRANSIENT, 1, 1, MILLISECONDS, 100, MyActor::new);
Supervisor mySupervisor = new SupervisorActor(ALL_FOR_ONE, actorSpec).spawn();

Квазар также позволяет контролировать и перезапускать предварительно созданные локальные экземпляры акторов с помощью Actor.reinstantiate метода Actor.reinstantiate , поэтому он может работать с любым механизмом зависимостей.

Акка предлагает несколько способов выключения актеров. Квазар, как и Эрланг, просто предлагает простое сообщение для обозначения запроса на отключение (хотя этот общий механизм уже является частью всех действий — см. Ниже); резкое завершение возможно путем прерывания основной нити актера (нить или волокно).

поведения

Quasar следует примеру библиотеки Erlang OTP, предоставляя настраиваемые шаблоны акторов для общих типов акторов, называемых поведениями . Поведения все реализуют общие, полезные шаблоны обмена сообщениями, но в Quasar они также добавляют удобные методы для ссылки актера. Поведение Quasar моделируется после OTP:

  • EventSourceActor (смоделированный после gen_event Эрланга) может динамически регистрировать и gen_event регистрацию обработчиков, которые будут просто реагировать на полученные сообщения. Если вы думаете, что этот конкретный тип актера Quasar очень близко соответствует асинхронным актерам Akka только для реакции, то вы на правильном пути.
  • ServerActor (смоделированный после ServerActor Эрланга) моделирует сервис, который предоставляет API запрос-ответ.
  • ProxyServerActor позволяет писать серверы на основе интерфейса: он ProxyServerActor путем передачи любой реализации интерфейса и создает ActorRef , который проксирует интерфейс и отправляет сообщения, соответствующие его методам, ActorRef нижележащего сервера. Конечно, это всего лишь один вариант использования, но я думаю, что этот поведенческий актор может очень помочь при переносе традиционных API на актеров Quasar.
  • FiniteStateMachineActor , добавленный в грядущем Quasar 0.7.0 (смоделированном по образцу Erlang gen_fsm ), позволяет легко писать акторы как явные машины с конечным gen_fsm состояний.

Акка не включает готовые шаблоны актеров такого рода. Вместо этого в стандартном API встроены различные общие поведения.

Актер Системы

Akka может работать как контейнер для автономного развертывания или как библиотека; это настраивается через файлы конфигурации, ссылающиеся на несколько систем акторов , каждая из которых управляется одним супервизором Конфигурация включает в себя ведение журнала, планирование (AKA как диспетчеризация), работу в сети, сериализацию и балансировку сообщений (маршрутизацию AKA) для каждого из них. Также предусмотрены разумные значения по умолчанию, поэтому настройка не обязательна.

В Akka актерская система является тяжеловесным объектом и соответствует логическому приложению. Quasar , будучи библиотекой, а не фреймворком, вообще не имеет понятия системы акторов, поскольку ему не нужно охватывать все ваше приложение. Разумеется, возможны различные конкретные конфигурации:

  • Планировщик по умолчанию для волокон — это fork-join (work-украсть), но его можно выбрать даже для каждого волокна. Актеры просто наследуют механизм планирования, используемый для нитей, на которых они работают, что означает, что им самим не требуется настройка планирования / диспетчеризации.
  • Иерархия надзора является необязательной, поэтому нет необходимости в «корневых» супервизорах.
  • Можно использовать любой механизм ведения журнала, но (необязательное) поведение использует «стандартный» API ведения журнала SLF4J для этой цели.
  • Quasar предлагает сетевые акторы и миграцию действующих лиц в кластере Galaxy из коробки, но может поддерживать больше. Функции кластеризации настраиваются в конфигурации поставщика кластера (например, Galaxy), а не в самом Quasar.
  • Квазар не занимается развертыванием. Для отличных решений для развертывания любого приложения JVM (которое также хорошо работает для приложений, использующих Quasar), мы рекомендуем вам взглянуть на Capsule .

API внутреннего актера

Внутренний API-интерфейс Quasar по умолчанию включает только следующее:

  • Методы receive / tryReceive и перезаписываемое filterMessage для отбрасывания сообщений до их получения.
  • Внешняя, непрозрачная ссылка на актера.
  • Базовый мониторинг акторов handleLifecycleMessage link , watch и переопределяемый handleLifecycleMessage .

Дополнительные функции, такие как ссылки на отправителей, встроенные по умолчанию, ведение журналов, обработка запросов на завершение, обслуживание запросов, обработка событий и контроль, могут быть получены путем расширения предварительно созданного поведения или добавления вами. Кроме того, поскольку благодаря волоконно-оптическим линиям Quasar операции отправки и получения могут быть блокированными и эффективными одновременно, нет необходимости в асинхронном варианте с возвратом в Future таком как ask Akka.

Такие функции Akka , как мониторинг и надзор, всегда включены для всех участников, поэтому внутренний API является обширным:

  • Метод receive .
  • Внешняя, непрозрачная ссылка на актера.
  • Ссылка на последнего отправителя сообщения (если есть).
  • Переопределенные методы жизненного цикла.
  • Стратегия супервизора в использовании.
  • Свойство context с дополнительными возможностями, такими как:
    • Фабричные методы для создания под присмотром детей.
    • Ссылка на систему актера, владеющую актером.
    • Родительский руководитель
    • Контролируемые дети.
    • Базовые методы мониторинга актеров («DeathWatch»).
    • Горячая замена (AKA «стать») объектов.

Akka также предлагает дополнительную черту Stash которая позволяет управлять второй очередью сообщений, которые были получены, но обработка которых должна быть отложена. В отличие от этого, Quasar, как и Erlang, допускает выборочный прием, поэтому ему не требуется, чтобы разработчик управлял дополнительными очередями сообщений только ради задержки обработки сообщений.

Горячее обновление

Quasar позволяет полностью и автоматически обновлять актеров во время выполнения, загружая новые классы через JMX или назначенный каталог «module». Квазар также позволяет обновлять состояние актера контролируемым образом с помощью методов, аннотированных @OnUpgrade .

Akka поддерживает замену частичной функции актера новой функцией во время выполнения через метод become , но не поддерживает переопределение классов, поэтому либо поведение актера должно быть заменено байтовым кодом, уже присутствующим в работающей JVM, либо новый код должен быть загружен через какой-то другой инструмент.

Сеть, удаленное взаимодействие, надежность и кластеризация

Quasar поддерживает удаленных актеров «из коробки» как часть кластерного ансамбля поверх Galaxy, но можно добавить больше поставщиков удаленного взаимодействия и кластеризации. Akka предоставляет аналогичные возможности, а также встроенные возможности прямого вызова актера на удаленном узле и распределения сообщений о распределении нагрузки между субъектами на отдельных узлах.

Quasar также экспериментально поддерживает миграцию акторов — возможность приостановить работающий актер и возобновить его на другом компьютере.

Постоянство почтового ящика

Akka включает в себя экспериментальную поддержку персистентности почтовых ящиков, основанную на базовом механизме поиска событий, и требует, чтобы субъект расширял черту PersistentActor и предоставлял два отдельных обработчика событий для нормального поведения и восстановления, а также явные вызовы для persist .

Квазар в настоящее время не поставляется с поддержкой постоянства почтового ящика актеров.

интеграция

Квазар не принуждает разработчика использовать все функции системы акторов и вообще не использовать актеров. Фактически, Quasar предлагает простую в использовании интегрированную среду для сторонних технологий, в которой используются асинхронные, основанные на будущем или блокирующие API-интерфейсы , так что они могут использоваться с легкими потоками («волокнами»), а не с обычными тяжелыми. Квазар позволяет свободно смешивать актерский и неакторный код или использовать любую интегрированную библиотеку из актерского кода без необходимости в специализированном API.

Comsat использует эту среду для интеграции стандартных и популярных технологий Java и Clojure:

Comsat также включает в себя Web Actors , новый API-интерфейс для обработки HTTP, WebSocket и SSE обменов.

В настоящее время проект Akka предлагает:

  • Интеграция с Apache Camel .
  • API на основе HTTP-актера ( Spray ).
  • ZeroMQ API на основе акторов.
  • API на основе TCP-актеров.
  • API на основе актера UDP.
  • API-интерфейс для файлового ввода-вывода.

Интеграция Akka с системами, не основанными на передаче сообщений, — это обязательно новые API акторов.

тестирование

Quasar не включает в себя специальный набор для тестирования, потому что это блокирующая среда с поддержкой временных участников, чья цепочка может давать значение после завершения, поэтому любые обычные инструменты тестирования, такие как JUnit, могут быть приняты вместе с обычными многопоточными методами тестирования.

Akka — это асинхронный фреймворк, поэтому он должен предлагать специальные API в форме блокировки TestActorRef вызовов тестирования ( TestActorRef , TestFSMRef ). Он также предоставляет специальные акторы с поддержкой утверждений ScalaTest для выполнения внешнего интеграционного тестирования целых подсистем TestKit ( TestKit или TestProbe ). Существует поддержка утверждений о времени, контрольного тестирования, регулирования обмена сообщениями, обмена сообщениями и отслеживания ошибок.

Системный мониторинг и управление

Quasar предоставляет расширенные данные мониторинга акторов (почтовый ящик, трассировка стека) через стандартный JMX MBean, который можно отслеживать с помощью JMX-инструмента, такого как свободно доступные JDK JVisualVM и JConsole, или с помощью REST API с использованием Jolokia . Кроме того, Quasar предоставляет инструменты для точной настройки измерительных приборов и записи подробных трасс исполнения волокна.

Приложения Akka могут отслеживаться и управляться с помощью проприетарного программного обеспечения (Typesafe Console), для которого требуется коммерческая лицензия для производственных систем.

Новые Relic и App Dynamics поддерживают как Akka, так и Quasar (через JMX).

Сравнение в полном приложении: акции Quasar и реактивные акции

Нет лучшего способа понять сходства и различия между Akka и Quasar, чем взглянуть на код идентичного приложения, написанного с использованием обоих. Quasar Stocks — это Java-порт шаблона активации Reactive Stocks / Akka для актеров Quasar и веб-актеров Comsat.

Имея 385 строк кода, приложение на чистом Java Quasar близко к тому, чтобы быть таким же компактным, как и наполовину Scala Typesafe (285 loc), и это особенно хорошо, учитывая, что актеры и веб-актеры делают только одно: все JSON-библиотека независима, поэтому вам не нужно использовать только одну веб-среду и принимать ее мнения по вопросам веб-разработки.

И все же я думаю, что Quasar один проще для понимания, потому что это простой старый императивный стиль Java, работающий только на гораздо более эффективной реализации легких потоков: никакая декларативная / функциональная / монадическая / асинхронная не заставляет вас работать, просто чтобы обойти JVM тяжелый след ниток.

Например, будущие веб-сервисы «Stock Sentiment» в версии Typesafe могут быть заменены столь же эффективной и полностью традиционной версией JAX-RS Jersey, только с блокировкой оптоволокна вместо потоковой блокировки. Таким образом, вместо того, чтобы использовать асинхронные операции Future s и выделенный, нестандартный DSL для их составления, как в версии Typesafe:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
object StockSentiment extends Controller {
  case class Tweet(text: String)
 
  implicit val tweetReads = Json.reads[Tweet]
 
  def getTextSentiment(text: String): Future[WSResponse] =
    WS.url(Play.current.configuration.getString("sentiment.url").get) post Map("text" -> Seq(text))
 
  def getAverageSentiment(responses: Seq[WSResponse], label: String): Double = responses.map { response =>
    (response.json \\ label).head.as[Double]
  }.sum / responses.length.max(1) // avoid division by zero
 
  def loadSentimentFromTweets(json: JsValue): Seq[Future[WSResponse]] =
    (json \ "statuses").as[Seq[Tweet]] map (tweet => getTextSentiment(tweet.text))
 
  def getTweets(symbol:String): Future[WSResponse] = {
    WS.url(Play.current.configuration.getString("tweet.url").get.format(symbol)).get.withFilter { response =>
      response.status == OK
    }
  }
 
  def sentimentJson(sentiments: Seq[WSResponse]) = {
    val neg = getAverageSentiment(sentiments, "neg")
    val neutral = getAverageSentiment(sentiments, "neutral")
    val pos = getAverageSentiment(sentiments, "pos")
 
    val response = Json.obj(
      "probability" -> Json.obj(
        "neg" -> neg,
        "neutral" -> neutral,
        "pos" -> pos
      )
    )
 
    val classification =
      if (neutral > 0.5)
        "neutral"
      else if (neg > pos)
        "neg"
      else
        "pos"
 
    response + ("label" -> JsString(classification))
  }
 
  def get(symbol: String): Action[AnyContent] = Action.async {
    val futureStockSentiments: Future[Result] = for {
      tweets <- getTweets(symbol) // get tweets that contain the stock symbol
      futureSentiments = loadSentimentFromTweets(tweets.json) // queue web requests each tweets' sentiments
      sentiments <- Future.sequence(futureSentiments) // when the sentiment responses arrive, set them
    } yield Ok(sentimentJson(sentiments))
 
    futureStockSentiments.recover {
      case nsee: NoSuchElementException =>
        InternalServerError(Json.obj("error" -> JsString("Could not fetch the tweets")))
    }
  }
}

Можно написать полностью стандартный, знакомый сервис JAX-RS, единственное отличие состоит в том, что дополнительные @Suspendable аннотации и порождающие волокна, а не потоки для параллельных операций:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
@Singleton
@Path("/")
public class Sentiment {
    final CloseableHttpClient client = FiberHttpClientBuilder.
            create(Runtime.getRuntime().availableProcessors()).
            setMaxConnPerRoute(1000).
            setMaxConnTotal(1000000).build();
 
    @GET
    @Path("{sym}")
    @Produces(MediaType.APPLICATION_JSON)
    @Suspendable
    public JsonNode get(@PathParam("sym") String sym) throws IOException, ExecutionException, InterruptedException {
        List<Fiber<JsonNode>> agents = new ArrayList<>();
        List<JsonNode> sentiments = new ArrayList<>();
        for (JsonNode t : getTweets(sym).get("statuses"))
            agents.add(sentimentRetriever(t.get("text").asText())); // spawn worker fibers
        for (Fiber<JsonNode> f : agents) // join fibers
            sentiments.add(f.get());
        return sentimentJson(sentiments);
    }
 
    private JsonNode sentimentJson(List<JsonNode> sentiments) {
        Double neg = getAverageSentiment(sentiments, "neg");
        Double neutral = getAverageSentiment(sentiments, "neutral");
        Double pos = getAverageSentiment(sentiments, "pos");
 
        ObjectNode ret = Application.Conf.mapper.createObjectNode();
        ObjectNode prob = Application.Conf.mapper.createObjectNode();
        ret.put("probability", prob);
        prob.put("neg", neg);
        prob.put("neutral", neutral);
        prob.put("pos", pos);
        String c;
        if (neutral > 0.5)
            c = "neutral";
        else if (neg > pos)
            c = "neg";
        else
            c = "pos";
        ret.put("label", c);
        return ret;
    }
 
    private Double getAverageSentiment(List<JsonNode> sentiments, String label) {
        Double sum = 0.0;
        final int size = sentiments.size();
        for (JsonNode s : sentiments)
            sum += s.get("probability").get(label).asDouble();
        return sum / (size > 0 ? size : 1);
    }
 
    private Fiber<JsonNode> sentimentRetriever(String text) throws IOException {
        return new Fiber<> (() -> {
            HttpPost req = new HttpPost(Application.Conf.sentimentUrl);
            List<NameValuePair> urlParameters = new ArrayList<>();
            urlParameters.add(new BasicNameValuePair("text", text));
            req.setEntity(new UrlEncodedFormEntity(urlParameters));
            return Application.Conf.mapper.readTree(EntityUtils.toString(client.execute(req).getEntity()));
        }).start();
    }
 
    @Suspendable
    private JsonNode getTweets(String sym) throws IOException {
        return Application.Conf.mapper.readTree (
            EntityUtils.toString(client.execute(new HttpGet(Application.Conf.tweetUrl.replace(":sym:", sym))).getEntity()));
    }
}

У стиля блокировки есть еще одно преимущество: API Quasar меньше и проще . Например, специальная поддержка Akka для запланированных сообщений вообще не требуется, потому что в Quasar тело субъекта может использовать регулярные конструкции потока управления. Так что вместо:

1
2
// Fetch the latest stock value every 75ms
val stockTick = context.system.scheduler.schedule(Duration.Zero, 75.millis, self, FetchLatest)

Регулярного приема по волоконно-оптическим каналам в цикле обработки сообщений более чем достаточно:

1
2
3
4
5
6
7
for(;;) {
    Object cmd = receive(75, TimeUnit.MILLISECONDS);
    if (cmd != null) {
        // ...
    } else self().send(new FetchLatest());
    // ...
}

Кроме того, Quasar Web Actors по умолчанию автоматически назначает нового субъекта для нового соединения HTTP sesssion или WebSocket, поэтому контроллер приложений на основе обратного вызова в версии Typesafe вообще не нужен в Quasar, где все непосредственно обрабатывается субъектом, который видит браузер (или мобильный клиент) просто как другого участника, за которым он может следить за завершением работы клиента.

В руководстве Typesafe о приложении упоминается несколько типов шаблонов дизайна:

  • Реактивная передача в основном означает эффективное распределение потоков между участниками для обмена WebSocket. Это достигается с такой же эффективностью благодаря использованию акторов Quasar на основе волокон и без ограничения использования нормальных конструкций потока управления.
  • Reactive Requests и Reactive Composition в основном подразумевают использование и монадическую композицию асинхронных конструкций, подобных Future , для достижения эффективного использования потоков в веб-сервисах. Эта сложность совершенно не нужна при работе на волокнах: вы можете использовать обычные, простые блокирующие вызовы и поток управления, а планировщик волокна обрабатывает потоки для достижения того же эффекта и производительности.
  • Реактивные интерфейсы в основном только что скопированы в Quasar Stocks.

Наконец, веб-акторы на 100% совместимы с сервлетами, поэтому нет необходимости запускать нестандартный встроенный сервер, если вы этого не хотите. В отличие от Play должен работать автономно 2 .

Сравнение производительности

Набор тестов JMH для кольцевого стенда , основанный на и проверенный оптоволоконным тестом , сравнивает несколько реализаций передачи сообщений на основе Akka, Quasar Actors, потоков Java и волокон Quasar с каналами разных типов или без них.

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

  • Количество рабочих актеров (по умолчанию = 503)
  • Длина цикла (по умолчанию = 1E + 06 обмен сообщениями)
  • Количество звонков (по умолчанию = 1)
  • Бизнес-логика, выполняемая перед каждым обменом сообщениями, и ее параметры (по умолчанию = нет).

Все тесты были выполнены на алюминиевом MacBook Pro в конце 2008 года, 8 ГБ ОЗУ, Core 2 Duo P8600 2,4 ГГц под Mint Linux (универсальный Linux 3.13.0-49) с JDK 1.8.0_45-b14 с агрессивной оптимизацией и многоуровневой компиляцией. включен. Используемая версия JMH была 1.8 с 5 вилками, 5 итерациями разминки и 10 итерациями.

Давайте сначала посмотрим на объем памяти с параметрами по умолчанию:

производительность MEM-таблица

По сравнению с волокнами и актерами Quasar, у Akka самое высокое использование кучи, самое большое количество событий GC и самое большое общее время GC, поэтому у Quasar в целом меньший объем памяти.

Что касается скорости, первое, что следует отметить, это то, что варьирование числа рабочих действующих лиц, даже до миллионов, не меняет показателей производительности в одном кольце ни для Quasar, ни для Akka: это подтверждает, что действующие лица (и волокна) действительно очень легкие ,

Затем были выполнены два набора измерений: первый с фиксированным числом обменов сообщениями 1E + 04 и изменяющейся рабочей нагрузкой показывает, что Quasar запускается немного быстрее, но по мере того, как рабочая нагрузка начинает доминировать, Akka и Quasar начинают работать очень схожим образом:

производительность увеличение нагрузки-диаграмма

Вместо этого, без рабочей нагрузки и различного числа обменов сообщениями, мы измеряем чистые издержки инфраструктуры . Снова Quasar запускается быстрее, но затем Akka берет на себя инициативу, и дополнительные накладные расходы Quasar достигают и стабилизируются примерно на 80% больше, чем Akka:

производительность увеличивающегося-сбщ-диаграмма

perfasm профилирования производительности perfasm подчеркивает дополнительную стоимость реальных легких потоков в Quasar, связанных с управлением пользовательским стеком, из-за отсутствия собственных продолжений в JVM. Akka не предлагает настоящие легкие нити, поэтому у нее нет таких накладных расходов.

Конечно, любые накладные расходы — независимо от того, насколько они малы — сравнительно намного больше, чем никаких накладных расходов. Чтобы понять, значительны ли накладные расходы на практике, мы должны сравнить их с фактической рабочей нагрузкой. Бизнес-нагрузка для каждого сообщения эквивалентна сортировке массива int из 224 элементов или, что эквивалентно, предварительно скомпилированному регулярному выражению регулярных выражений длиной 6 цифр из 6 длин (с ошибкой) для текста длиной 1700 байт (только от 3 до 4 микросекунд). система эталонных тестов), Quasar работает менее чем на 1% медленнее, чем Akka.

производительность-практический стол

Это означает, что в худшем случае для приложения, которое в среднем соответствует по крайней мере эквивалентному регулярному выражению с 6 символами для 1700 байтов текста при обмене сообщениями, разница в производительности будет меньше 1% . Поскольку большинство приложений делают гораздо больше, на практике вы можете получить массу дополнительных возможностей программирования, которые могут предложить волокна Quasar и актеры с той же производительностью Akka 3 .

Вывод

Quasar — это быстрая, бережливая и прагматичная библиотека параллелизма для Java, Clojure и Kotlin, предлагающая очень легкие потоки и множество проверенных парадигм параллелизма, включая реализацию модели актера, практически идентичную модели Эрланга. Квазар также имеет низкие затраты на интеграцию, адаптацию и отказ от участия. Его функциональность сфокусирована, и там, где он предоставляет дополнительные функции, такие как ведение журнала и мониторинг, он использует стандартные API (SLF4J, JMX и т. Д.).

Akka — это инфраструктура приложений и, как и другие платформы Typesafe, такие как Play, — это тотальный выбор, охватывающий все приложение, представляющий собственные богатые API-интерфейсы (даже для ведения журналов), средства тестирования, мониторинга и развертывания.

На Akka, даже на его Java API, большое влияние оказывает Scala, и он может показаться чуждым разработчикам Java. Актеры Quasar чувствуют себя очень знакомыми и идиоматичными, пишете ли вы Java, Clojure или Kotlin.

API Akka основан на обратном вызове. Quasar предоставляет настоящие волокна — такие как Erlang или Go — поэтому блокировка бесплатна, а актерский API Quasar проще, более знаком и более совместим с другим кодом. Блокирование и использование волоконно-оптических линий позволяет использовать очень небольшое количество базовых концепций — как в Erlang — тогда как Akka вводит много незнакомых и избыточных концепций. Например, чтобы обойти отсутствие простого блокирующего избирательного приема (предложенного Эрлангом и Квазаром), Акка должен ввести сохранение сообщений. Другие концепции (например, монадические фьючерсы) не имеют ничего общего с бизнес-логикой или моделью акторов, но являются чисто случайной сложностью.

Акка, безусловно, путь, если:

  • Вы приняли Scala и вам нравится его стиль программирования.
  • Вы не боитесь делать ставки на фреймворк и придерживаться его, а также не платить потенциально высокую цену за редизайн / переписывание за отказ от участия.
  • Вы готовы платить за мониторинг производства или готовы написать собственное решение для мониторинга.

В противном случае я предлагаю вам попробовать Quasar: он готов к производству, легок, быстр, основан на стандартах, легко интегрируется, полностью бесплатен и имеет открытый исходный код и предлагает больше, чем асинхронные акторы Akka для меньшей сложности.

  1. Есть несколько систем акторов, которые не поддерживают выборочный прием, но Эрланг делает. В докладе « Смерть от случайной сложности » Ульфа Вигера показано, как использование избирательного приема позволяет избежать реализации полной, сложной и подверженной ошибкам матрицы переходов. В другом докладе Вигер сравнил неселективное получение (FIFO) с игрой в тетрис, где вы должны вложить каждую часть в головоломку по мере ее появления, а выборочное получение превращает проблему в головоломку, где вы можете найти часть, которая Вы знаете, подойдет.
  2. Если вы не используете сторонний плагин с некоторыми ограничениями.
  3. Свыше 2048 байт Квазар становится быстрее, чем Акка, но причины пока неясны, возможно, это связано с более благоприятным встраиванием.
Ссылка: Quasar и Akka — сравнение от нашего партнера по JCG Фабио Тудоне в блоге Parallel Universe .