akka: как я перестал бояться и полюбил асинхронный код

Post on 21-Jul-2015

282 Views

Category:

Engineering

8 Downloads

Preview:

Click to see full reader

TRANSCRIPT

Как я перестал бояться и полюбил асинхронный код

JUG@VRN, 2014

Гребенников Роман,Sociohub

Содержание

● Модель акторов:o зачем она нужна;o что это такое;o Akka 2 и Scala;o какие возможности даёт.

● Пример системы:o система сбора и парсинга данных.

● Личный опыт:o типичные ошибки;o подводные камни;o производительность;o перспективы.

Free lunch is over

Одно ядро, один поток - путь в никуда:

[1]: http://www.gotw.ca/publications/concurrency-ddj.htm[2]: PassMark, http://www.cpubenchmark.net

Free lunch is over

Одно ядро, один поток - путь в никуда:

[1]: http://www.gotw.ca/publications/concurrency-ddj.htm[2]: PassMark, http://www.cpubenchmark.net

Concurrency vs Parallelism

[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html

Concurrency vs Parallelism

Parallel programmingстрого одновременное исполнение нескольких задач

[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html

Concurrency vs Parallelism

Concurrent programmingкомпозиция независимо выполняющихся задач

(не обязательно параллельно)

Parallel programmingстрого одновременное исполнение нескольких задач

[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html

Concurrency vs Parallelism

Concurrent programmingкомпозиция независимо выполняющихся задач

(не обязательно параллельно)

Parallel programmingстрого одновременное исполнение нескольких задач

[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html

Всё это сложно

Корень проблемы

Корень проблемы

Shared mutable state

Корень проблемы

Shared mutable stateи его друзья

Корень проблемы

Shared mutable stateи его друзья

Зачем нужны акторы?

Зачем нужны акторы?

● Чтобы перестать думать о плохом:o блокировки, race conditions, дедлоки;o shared state, state visibility;o потоки, concurrent collections и т.д.

Зачем нужны акторы?

● Чтобы перестать думать о плохом:o блокировки, race conditions, дедлоки;o shared state, state visibility;o потоки, concurrent collections и т.д.

● И начать думать о хорошем:o single execution flow:

актор работает последовательно;o слабая связанность всех компонентов:

легко тестировать;o асинхронность:

больше никакого Await;o нет никакого shared mutable state.

Зачем нужны акторы?

● Чтобы перестать думать о плохом:o блокировки, race conditions, дедлоки;o shared state, state visibility;o потоки, concurrent collections и т.д.

● Легкость масштабирования:o часть платформы, не надо ничего изобретать;o есть восстановление после сбоев: “let it crash”.

● И начать думать о хорошем:o single execution flow:

актор работает последовательно;o слабая связанность всех компонентов:

легко тестировать;o асинхронность:

больше никакого Await;o нет никакого shared mutable state.

Модель акторов

● Придумана в 1972 году, до сих пор актуальна [1]

● Стала популярной благодаря Erlang● Всё - актор, даже небо, даже звезды

[1]: http://letitcrash.com/post/20964174345/carl-hewitt-explains-the-essence-of-the-actor

Модель акторов

● Придумана в 1972 году, до сих пор актуальна [1]

● Стала популярной благодаря Erlang● Всё - актор, даже небо, даже звезды

[1]: http://letitcrash.com/post/20964174345/carl-hewitt-explains-the-essence-of-the-actor

● Акторы:o функционируют параллельно;o асинхронно обмениваются сообщениями;o имеют персональный адрес и почтовый ящик.

● при получении сообщения можно:o отправить новое сообщение другим акторам;o создать новых акторов;o изменить свое поведение для следующего сообщения.

● Напоминает Email

Akka

● Jonas Bonéro Java champion;o Terracotta JVM clustering,

JRockit JVM, AspectWerkz AOP, Eclipse AspectJ.

● Ресурсыo http://akka.io/docs/o http://letitcrash.com/

● Кодo http://github.com/akka/akkao Apache 2.0 Licenseo Scala API, Java API

Áhkká (Lule Sami: "old woman")

Actor внутри

ActorRef

Actor

Почтовый ящик

Указывает на актора.Умеет класть сообщения в ящик.

Ваш код тут

Actor внутри

ActorRef

Actor

Почтовый ящик

Указывает на актора.Умеет класть сообщения в ящик.

Ваш код тут

Берет сообщения по-одному.Что-то с ними делает.

Единственный способ взаимодействия - отправка сообщения. Во внутренности

актора доступа нет ни у кого.

Создание актора

Почтовые ящики

Пути как в файловой системе:● akka://system/user/foo/bar● akka.tcp://system@srv.example.com/user/foo/bar

Почтовые ящики

Пути как в файловой системе:● akka://system/user/foo/bar● akka.tcp://system@srv.example.com/user/foo/bar

Выбор актора:● каждый актор знает себя, родителя и детей;● акторов можно выбирать по пути из ActorRegistry:

Почтовые ящики

Пути как в файловой системе:● akka://system/user/foo/bar● akka.tcp://system@srv.example.com/user/foo/bar

Выбор актора:● каждый актор знает себя, родителя и детей;● акторов можно выбирать по пути из ActorRegistry:

Типы:● UnboundedMailbox;● BoundedMailbox;● UnboundedPriorityMailbox;● BoundedPriorityMailbox.

Диспетчеры

● Актор != поток

поток 1

поток 2

A1

A2

A4 A1

A1 A2

A4 A4A1

A3

Диспетчеры

● Актор != поток

поток 1

поток 2

A1

A2

A4 A1

A1 A2

A4 A4A1

A3

● Каждый актор жив, только когда работает● В остальное время он освобождает поток

Диспетчеры

● Актор != поток

поток 1

поток 2

A1

A2

A4 A1

A1 A2

A4 A4A1

A3

● Каждый актор жив, только когда работает● В остальное время он освобождает поток● Диспетчер занимается раскладыванием акторов

по потокам:o Dispatcher;o PinnedDispatcher;o BalancingDispatcher;o CallingThreadDispatcher.

Диспетчеры

● Актор != поток

поток 1

поток 2

A1

A2

A4 A1

A1 A2

A4 A4A1

A3

● Каждый актор жив, только когда работает● В остальное время он освобождает поток● Диспетчер занимается раскладыванием акторов

по потокам:o Dispatcher;o PinnedDispatcher;o BalancingDispatcher;o CallingThreadDispatcher.

● Для непосредственного исполнения:o fork-join-executor;o thread-pool-executor.

Падший актор

● Код иногда ломается● Как жить дальше и что делать?

ActorRef

Parent

Actor

MessageBox

Падший актор

● Код иногда ломается● Как жить дальше и что делать?

ActorRef

Parent

Actor

MessageBox

Падший актор

● Код иногда ломается● Как жить дальше и что делать?

ActorRef

Parent

Падший актор

● Код иногда ломается● Как жить дальше и что делать?

ActorRef

Parent

deadLetters Могильник для потерянных сообщений

Падший актор

● Код иногда ломается● Как жить дальше и что делать?

ActorRef

Parent

deadLetters Могильник для потерянных сообщений

● И что?

Supervision

● Можно делать иерархии акторов● Каждый актор в ответе за своих детей● Упавшего ребенка можно:

Supervision

● Можно делать иерархии акторов● Каждый актор в ответе за своих детей● Упавшего ребенка можно:

● Stop: остановить;● Restart: перезапустить;● Continue: оставить полу-лежачим;● Escalate: передать обработку выше.

Supervision

● Можно делать иерархии акторов● Каждый актор в ответе за своих детей● Упавшего ребенка можно:

● Stop: остановить;● Restart: перезапустить;● Continue: оставить полу-лежачим;● Escalate: передать обработку выше.

● Решение рекурсивно● Есть хуки preStart, preRestart, postStop, etc.

Supervision

● Можно делать иерархии акторов● Каждый актор в ответе за своих детей● Упавшего ребенка можно:

● Stop: остановить;● Restart: перезапустить;● Continue: оставить полу-лежачим;● Escalate: передать обработку выше.

● Решение рекурсивно● Есть хуки preStart, preRestart, postStop, etc.

Akka remoting/cluster

● Волшебный ActorRef: может указывать куда угодно

Akka remoting/cluster

● Волшебный ActorRef: может указывать куда угодно

● Нет никакой разницы:o Акторы в одной JVM;o Акторы в разных JVM;o Акторы на разных серверах;o Акторы в нескольких DC, континентах и галактиках.

Akka remoting/cluster

● Волшебный ActorRef: может указывать куда угодно

● Набор полезных примитивов:o Publish-subscribe;o Singleton;o Cluster state feed;o Cluster-aware routers.

● Нет никакой разницы:o Акторы в одной JVM;o Акторы в разных JVM;o Акторы на разных серверах;o Акторы в нескольких DC, континентах и галактиках.

Akka remoting/cluster

● Волшебный ActorRef: может указывать куда угодно

● Набор полезных примитивов:o Publish-subscribe;o Singleton;o Cluster state feed;o Cluster-aware routers.

● Легко отстрелить ногу

● Нет никакой разницы:o Акторы в одной JVM;o Акторы в разных JVM;o Акторы на разных серверах;o Акторы в нескольких DC, континентах и галактиках.

Akka в бою: задача

● Кейс: классический web-crawlingo Поисковик по людям (как http://people.yandex.ru, только круче).o Все просто на первый взгляд.o Но становится сложно, когда возрастают объемы.

Akka в бою: задача

● Кейс: классический web-crawlingo Поисковик по людям (как http://people.yandex.ru, только круче).o Все просто на первый взгляд.o Но становится сложно, когда возрастают объемы.

● Проблемы:o распределенная очередь;o ненадежная сеть;o ненадежные сервера;o разные сценарии сбора для разных сайтов;o невалидный HTML;o невозможно парсить весь рунет

на одном сервере.

Статистика

● Три машины: 32Gb RAM, Linux● 500 запросов/с на машину, поток 50мбит● 30Тб данных в месяц ● в очереди ~300 млн. целей● полный пересбор раз в два месяца

0 AD

Прототип паука:● был написан на С++ и libcurl;● работал на одном сервере.

0 AD

Прототип паука:● был написан на С++ и libcurl;● работал на одном сервере.

Наступили на грабли:● Упёрлись в один сервер

0 AD

Прототип паука:● был написан на С++ и libcurl;● работал на одном сервере.

Наступили на грабли:● Упёрлись в один сервер

● Слали запросы синхронно:o 2000 простаивающих потоков, ждущих данные из сети;o 2k*8Mb = 16Gb памяти только на стек для потоков;o Нельзя делать select из >1024 дескрипторов.

0 AD

Прототип паука:● был написан на С++ и libcurl;● работал на одном сервере.

Наступили на грабли:● Упёрлись в один сервер

● Слали запросы синхронно:o 2000 простаивающих потоков, ждущих данные из сети;o 2k*8Mb = 16Gb памяти только на стек для потоков;o Нельзя делать select из >1024 дескрипторов.

● Сегфолты: падающий поток мог вынести всё:o Coredump на 20Гб;o полчаса чтобы посмотреть бектрейс.

0 AD

Прототип паука:● был написан на С++ и libcurl;● работал на одном сервере.

Наступили на грабли:● Упёрлись в один сервер

● Слали запросы синхронно:o 2000 простаивающих потоков, ждущих данные из сети;o 2k*8Mb = 16Gb памяти только на стек для потоков;o Нельзя делать select из >1024 дескрипторов.

● Сегфолты: падающий поток мог вынести всё:o Coredump на 20Гб;o полчаса чтобы посмотреть бектрейс.

● Жизнь слишком коротка для С++

2013

После хождения по граблям, поняли что хотим:● асинхронное IO;● масштабирование из коробки;● сказать нет shared mutable data;● восстановление после сбоев;● легкость отладки и тестирования.

2013

После хождения по граблям, поняли что хотим:● асинхронное IO;● масштабирование из коробки;● сказать нет shared mutable data;● восстановление после сбоев;● легкость отладки и тестирования.

Варианты:● Scala + Akka;● Java + Akka;● Erlang;● boost: asio, phoenix, cppnetlib.

2013

После хождения по граблям, поняли что хотим:● асинхронное IO;● масштабирование из коробки;● сказать нет shared mutable data;● восстановление после сбоев;● легкость отладки и тестирования.

Варианты:● Scala + Akka;● Java + Akka;● Erlang;● boost: asio, phoenix, cppnetlib.

Архитектура

сеть

Node

Master

Bot

Bot

Bot

Log store

Data store

NodeBot

Bot

Dequeue

Очередь

Crawl Save

Работа с сетью

Несколько вариантов:● spray.io

o написан на idiomatic Scala, свой DSL;o Akka под капотом;o немного для других вещей;o spray-can сбоку на изоленте, многое не умеет;o разработку штормит, в процессе слияния с akka-http.

Работа с сетью

Несколько вариантов:● spray.io

o написан на idiomatic Scala, свой DSL;o Akka под капотом;o немного для других вещей;o spray-can сбоку на изоленте, многое не умеет;o разработку штормит, в процессе слияния с akka-http.

● Apache http-async-cliento Java;o стабильный как мамонт;o умеет вообще всё;o модульный и расширяемый

(inb4 MyConnectionKeepAliveStrategyBuilderFactory);o свои, ни с чем не совместимые Future, ThreadPool и т.д.

AHC

● Не такой уж и асинхронный:o решили использовать общий с akka thread-pool;o Java DNS lookup - синхронный и через UDP;

AHC

● Не такой уж и асинхронный:o решили использовать общий с akka thread-pool;o Java DNS lookup - синхронный и через UDP;

o UDP иногда теряется, это норма;o потеря пакета - вечная блокировка потока в thread-pool;o через два дня работы весь thread-pool заблокирован.

AHC

● Не такой уж и асинхронный:o решили использовать общий с akka thread-pool;o Java DNS lookup - синхронный и через UDP;

o UDP иногда теряется, это норма;o потеря пакета - вечная блокировка потока в thread-pool;o через два дня работы весь thread-pool заблокирован.

● Как жили дальше:o раздельные thread-pool для akka и AHC;o свой dns lookup, который умеет в таймауты;o регулярное изучение thread dump’ов на предмет блокировок.

Одноразовые акторы

● Создать нового актора - дешево и быстро:o ~300 байт памяти;o можно создать хоть миллион.

Одноразовые акторы

● Создать нового актора - дешево и быстро:o ~300 байт памяти;o можно создать хоть миллион.

● Одна подзадача - один актор:o получил задачу;o сделал её, послал результат и помер.

● Чистые иммутабельные акторы

Одноразовые акторы

● Создать нового актора - дешево и быстро:o ~300 байт памяти;o можно создать хоть миллион.

● Одна подзадача - один актор:o получил задачу;o сделал её, послал результат и помер.

● Чистые иммутабельные акторы

Одноразовые акторы

● Создать нового актора - дешево и быстро:o ~300 байт памяти;o можно создать хоть миллион.

асинхронный коллбек!

Future[T]

● Одна подзадача - один актор:o получил задачу;o сделал её, послал результат и помер.

● Чистые иммутабельные акторы

Ask pattern● Возможность что-то асинхронно спросить у актора● Ответ - монада Future[T]

Ask pattern● Возможность что-то асинхронно спросить у актора● Ответ - монада Future[T]● Под капотом:

o создается временный актор, который ждет ответа;o ответ заворачивается в результат Future.

Ask pattern● Возможность что-то асинхронно спросить у актора● Ответ - монада Future[T]● Под капотом:

o создается временный актор, который ждет ответа;o ответ заворачивается в результат Future.

Ask pattern● Возможность что-то асинхронно спросить у актора● Ответ - монада Future[T]● Под капотом:

o создается временный актор, который ждет ответа;o ответ заворачивается в результат Future.

Ask pattern● Возможность что-то асинхронно спросить у актора● Ответ - монада Future[T]● Под капотом:

o создается временный актор, который ждет ответа;o ответ заворачивается в результат Future.

порядок вычисления

Akka ask puzzle

● Вылетел exception● Что окажется в result?

Akka ask puzzle

● Вылетел exception● Что окажется в result?

1. Failure(e:UnsupportedOperationException).2. “”3. Failure(e:AskTimeoutException).4. Failure(e:ClassCastException).

Akka ask puzzle

● Вылетел exception● Что окажется в result?

1. Failure(e:UnsupportedOperationException).2. “”3. Failure(e:AskTimeoutException).4. Failure(e:ClassCastException).

Akka ask puzzle

● Вылетел exception● Что окажется в result?

1. Failure(e:UnsupportedOperationException).2. “”3. Failure(e:AskTimeoutException).4. Failure(e:ClassCastException).

Akka ask puzzle

● Вылетел exception● Что окажется в result?

1. Failure(e:UnsupportedOperationException).2. “”3. Failure(e:AskTimeoutException).4. Failure(e:ClassCastException).

Временный актор создается вне общей

иерархии

Мы и ask pattern

● Если уметь готовить аски, то жить можно● Используем длинные цепочки асков:

Bot ScriptAction ScriptRunner

RequestExetutorThrottlerHttpClient

Мы и ask pattern

● Если уметь готовить аски, то жить можно● Используем длинные цепочки асков:

Bot ScriptAction ScriptRunner

RequestExetutorThrottlerHttpClient

● Легкая декомпозиция задачи на части● Каждую часть легко тестировать● Есть overhead на создание временного актора

FSM, Конечные автоматы

FSM, Конечные автоматы

● Можно и без них, но с ними удобнее● Помогают в задачах, когда:

o актору нужно долго подниматься;o есть четкие переходы между состояниями.

FSM, Конечные автоматы

● Можно и без них, но с ними удобнее● Помогают в задачах, когда:

o актору нужно долго подниматься;o есть четкие переходы между состояниями.

● Увлеклись: o у бота 10 состояний;o ~30 правил перехода;o 500 строк лапши;o тестировать почти невозможно.

FSM, Конечные автоматы

● Можно и без них, но с ними удобнее● Помогают в задачах, когда:

o актору нужно долго подниматься;o есть четкие переходы между состояниями.

● Увлеклись: o у бота 10 состояний;o ~30 правил перехода;o 500 строк лапши;o тестировать почти невозможно.

● Сейчас: o у бота 2 состояния (просыпаюсь и активный) и 5 правил;o вся логика вынесена в отдельные акторы;

есть и дочерние FSM.

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

Два подхода к тестированию:

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

● Синхронное через TestActorRefo можно руками дернуть актора за receive();o в самый раз для простой логики.

Два подхода к тестированию:

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

● Синхронное через TestActorRefo можно руками дернуть актора за receive();o в самый раз для простой логики.

● Асинхронное через akka.TestKito полный запуск системы;o дополнительный синтаксис для assert-ов;o можно подсовывать mock-акторов.

Два подхода к тестированию:

Логгинг

● Есть интеграция c slf4j● log.debug(“foo”) - отправка сообщения логгеру● MDC - message diagnostic context

Логгинг

● Есть интеграция c slf4j● log.debug(“foo”) - отправка сообщения логгеру● MDC - message diagnostic context

У нас:● Свой логгер на основе akka.event.slf4j.Slf4jLogger● Пишем логи в кассандру:

o 5-10Гб в сутки;o разный time-to-live для DEBUG/INFO/WARN/ERROR;o агрегация на лету при записи;o простой web-gui, который рисует статистику.

Производительность

● 2M сообщений в секунду - легко!

[1]: http://letitcrash.com/post/17607272336/scalability-of-fork-join-pool

Производительность

● 2M сообщений в секунду - легко!

[1]: http://letitcrash.com/post/17607272336/scalability-of-fork-join-pool

● На мощном сервере еще больше: [1]

Производительность

● 2M сообщений в секунду - легко!

[1]: http://letitcrash.com/post/17607272336/scalability-of-fork-join-pool

● У нас: ~1000 msg/s, оверхед близок к нулю

● На мощном сервере еще больше: [1]

“Динамическая” типизация

[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka[2]: Akka 2.x Roadmap

“Динамическая” типизация

● Боль: несоответствие типов сообщений вылезает наружу только в run-time

● Хочется строгой типизации

[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka[2]: Akka 2.x Roadmap

“Динамическая” типизация

● Боль: несоответствие типов сообщений вылезает наружу только в run-time

● Хочется строгой типизации

[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka[2]: Akka 2.x Roadmap

“Динамическая” типизация

● Боль: несоответствие типов сообщений вылезает наружу только в run-time

● Хочется строгой типизации

Старая проблема:● TypedActor в Akka 2.0● TAkka [1]

● “Gålbma”, Akka 3.0 [2]

[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka[2]: Akka 2.x Roadmap

“Динамическая” типизация

● Боль: несоответствие типов сообщений вылезает наружу только в run-time

● Хочется строгой типизации

Старая проблема:● TypedActor в Akka 2.0● TAkka [1]

● “Gålbma”, Akka 3.0 [2]

[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka[2]: Akka 2.x Roadmap

У нас:● Интеграционные тесты● Акторы обычно умеют только

в один тип сообщений

Ошибки: Thread starvation

Что это:

● Блокировка потоков в thread-pool.

Ошибки: Thread starvation

Что это:

● Блокировка потоков в thread-pool.

Откуда берутся:● Thread.sleep(), Await.result() внутри актора;● долгие вычисления;● интеграция с легаси-кодом.

Ошибки: Thread starvation

Методы борьбы:● ломать пальцы за Thread.sleep() внутри актора;● отдельные dispatcher’ы;● мониторинг стека вызовов и загруженности

потоков.

Что это:

● Блокировка потоков в thread-pool.

Откуда берутся:● Thread.sleep(), Await.result() внутри актора;● долгие вычисления;● интеграция с легаси-кодом.

Ошибки: OutOfMemoryException

Причины:● переполнение почтового ящика;● слишком много акторов.

Ошибки: OutOfMemoryException

Причины:● переполнение почтового ящика;● слишком много акторов.

Что делать:● мониторить загруженность почтового ящика;● прореживать очередь сообщений;● периодически изучать heap-dump и GC logs на

предмет утилизации памяти;● следить за логическими утечками (замыкания).

В итоге● прошли путь от akka 2.0 до 2.3.x● один год в production

В итоге● прошли путь от akka 2.0 до 2.3.x● один год в production● в начале приходится тяжело:

В итоге● прошли путь от akka 2.0 до 2.3.x● один год в production

● много скрытых граблей, но жить можно● исключительно простой и выразительный

формализм● удобно разрабатывать и тестировать

● в начале приходится тяжело:

Ссылки

● Typesafe activator [1]

● Coursera: Principles of reactive programming [2]

● Книги:o Jamie Allen: Effective Akka, 2013;o Derek Wyatt: Akka concurrency, 2013.

● Блоги:o letitcrash.com [4]

[1]: https://typesafe.com/activator[2]: https://www.coursera.org/course/reactive[3]: https://letitcrash.com

Q&A

top related