Akka: как я перестал бояться и полюбил асинхронный код
1. Как я перестал бояться и полюбил
асинхронный код
JUG@VRN, 2014
Гребенников Роман,
Sociohub
2. Содержание
● Модель акторов:
o зачем она нужна;
o что это такое;
o Akka 2 и Scala;
o какие возможности даёт.
● Пример системы:
o система сбора и парсинга данных.
● Личный опыт:
o типичные ошибки;
o подводные камни;
o производительность;
o перспективы.
3. Free lunch is over
Одно ядро, один поток - путь в никуда:
[1]: http://www.gotw.ca/publications/concurrency-ddj.htm
[2]: PassMark, http://www.cpubenchmark.net
4. Free lunch is over
Одно ядро, один поток - путь в никуда:
[1]: http://www.gotw.ca/publications/concurrency-ddj.htm
[2]: PassMark, http://www.cpubenchmark.net
5. Concurrency vs Parallelism
[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html
6. Concurrency vs Parallelism
Parallel programming
строго одновременное исполнение
нескольких задач
[1]: Rob Pike: Concurrency is not Parallelism (it's better!), http://concur.rspace.googlecode.com/hg/talk/concur.html
7. 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
8. 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
Всё это сложно
14. Зачем нужны акторы?
● Чтобы перестать думать о плохом:
o блокировки, race conditions, дедлоки;
o shared state, state visibility;
o потоки, concurrent collections и т.д.
15. Зачем нужны акторы?
● Чтобы перестать думать о плохом:
o блокировки, race conditions, дедлоки;
o shared state, state visibility;
o потоки, concurrent collections и т.д.
● И начать думать о хорошем:
o single execution flow:
актор работает последовательно;
o слабая связанность всех компонентов:
легко тестировать;
o асинхронность:
больше никакого Await;
o нет никакого shared mutable state.
16. Зачем нужны акторы?
● Чтобы перестать думать о плохом:
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.
17. Модель акторов
● Придумана в 1972 году, до сих пор актуальна [1]
● Стала популярной благодаря Erlang
● Всё - актор, даже небо, даже звезды
[1]: http://letitcrash.com/post/20964174345/carl-hewitt-explains-the-essence-of-the-actor
18. Модель акторов
● Придумана в 1972 году, до сих пор актуальна [1]
● Стала популярной благодаря Erlang
● Всё - актор, даже небо, даже звезды
[1]: http://letitcrash.com/post/20964174345/carl-hewitt-explains-the-essence-of-the-actor
● Акторы:
o функционируют параллельно;
o асинхронно обмениваются сообщениями;
o имеют персональный адрес и почтовый ящик.
● при получении сообщения можно:
o отправить новое сообщение другим акторам;
o создать новых акторов;
o изменить свое поведение для следующего сообщения.
● Напоминает Email
19. Akka
● Jonas Bonér
o 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/akka
o Apache 2.0 License
o Scala API, Java API
Áhkká (Lule Sami: "old woman")
21. Actor внутри
ActorRef
Actor
Почтовый ящик
Указывает на актора.
Умеет класть сообщения в ящик.
Ваш код тут
Берет сообщения по-одному.
Что-то с ними делает.
Единственный способ взаимодействия -
отправка сообщения. Во внутренности
актора доступа нет ни у кого.
23. Почтовые ящики
Пути как в файловой системе:
● akka://system/user/foo/bar
● akka.tcp://system@srv.example.com/user/foo/bar
24. Почтовые ящики
Пути как в файловой системе:
● akka://system/user/foo/bar
● akka.tcp://system@srv.example.com/user/foo/bar
Выбор актора:
● каждый актор знает себя, родителя и детей;
● акторов можно выбирать по пути из ActorRegistry:
25. Почтовые ящики
Пути как в файловой системе:
● akka://system/user/foo/bar
● akka.tcp://system@srv.example.com/user/foo/bar
Выбор актора:
● каждый актор знает себя, родителя и детей;
● акторов можно выбирать по пути из ActorRegistry:
Типы:
● UnboundedMailbox;
● BoundedMailbox;
● UnboundedPriorityMailbox;
● BoundedPriorityMailbox.
27. Диспетчеры
● Актор != поток
поток 1
поток 2
A1
A2
A4 A1
A1 A2
A4 A4A1
A3
● Каждый актор жив, только когда работает
● В остальное время он освобождает поток
28. Диспетчеры
● Актор != поток
поток 1
поток 2
A1
A2
A4 A1
A1 A2
A4 A4A1
A3
● Каждый актор жив, только когда работает
● В остальное время он освобождает поток
● Диспетчер занимается раскладыванием акторов
по потокам:
o Dispatcher;
o PinnedDispatcher;
o BalancingDispatcher;
o CallingThreadDispatcher.
29. Диспетчеры
● Актор != поток
поток 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.
30. Падший актор
● Код иногда ломается
● Как жить дальше и что делать?
ActorRef
Parent
Actor
MessageBox
31. Падший актор
● Код иногда ломается
● Как жить дальше и что делать?
ActorRef
Parent
Actor
MessageBox
32. Падший актор
● Код иногда ломается
● Как жить дальше и что делать?
ActorRef
Parent
33. Падший актор
● Код иногда ломается
● Как жить дальше и что делать?
ActorRef
Parent
deadLetters
Могильник для
потерянных сообщений
34. Падший актор
● Код иногда ломается
● Как жить дальше и что делать?
ActorRef
Parent
deadLetters
Могильник для
потерянных сообщений
● И что?
35. Supervision
● Можно делать иерархии акторов
● Каждый актор в ответе за своих детей
● Упавшего ребенка можно:
36. Supervision
● Можно делать иерархии акторов
● Каждый актор в ответе за своих детей
● Упавшего ребенка можно:
● Stop: остановить;
● Restart: перезапустить;
● Continue: оставить полу-лежачим;
● Escalate: передать обработку выше.
37. Supervision
● Можно делать иерархии акторов
● Каждый актор в ответе за своих детей
● Упавшего ребенка можно:
● Stop: остановить;
● Restart: перезапустить;
● Continue: оставить полу-лежачим;
● Escalate: передать обработку выше.
● Решение рекурсивно
● Есть хуки preStart, preRestart, postStop, etc.
38. Supervision
● Можно делать иерархии акторов
● Каждый актор в ответе за своих детей
● Упавшего ребенка можно:
● Stop: остановить;
● Restart: перезапустить;
● Continue: оставить полу-лежачим;
● Escalate: передать обработку выше.
● Решение рекурсивно
● Есть хуки preStart, preRestart, postStop, etc.
40. Akka remoting/cluster
● Волшебный ActorRef: может указывать куда угодно
● Нет никакой разницы:
o Акторы в одной JVM;
o Акторы в разных JVM;
o Акторы на разных серверах;
o Акторы в нескольких DC, континентах и галактиках.
41. Akka remoting/cluster
● Волшебный ActorRef: может указывать куда угодно
● Набор полезных примитивов:
o Publish-subscribe;
o Singleton;
o Cluster state feed;
o Cluster-aware routers.
● Нет никакой разницы:
o Акторы в одной JVM;
o Акторы в разных JVM;
o Акторы на разных серверах;
o Акторы в нескольких DC, континентах и галактиках.
42. Akka remoting/cluster
● Волшебный ActorRef: может указывать куда угодно
● Набор полезных примитивов:
o Publish-subscribe;
o Singleton;
o Cluster state feed;
o Cluster-aware routers.
● Легко отстрелить ногу
● Нет никакой разницы:
o Акторы в одной JVM;
o Акторы в разных JVM;
o Акторы на разных серверах;
o Акторы в нескольких DC, континентах и галактиках.
43. Akka в бою: задача
● Кейс: классический web-crawling
o Поисковик по людям (как http://people.yandex.ru, только круче).
o Все просто на первый взгляд.
o Но становится сложно, когда возрастают объемы.
44. Akka в бою: задача
● Кейс: классический web-crawling
o Поисковик по людям (как http://people.yandex.ru, только круче).
o Все просто на первый взгляд.
o Но становится сложно, когда возрастают объемы.
● Проблемы:
o распределенная очередь;
o ненадежная сеть;
o ненадежные сервера;
o разные сценарии сбора для разных сайтов;
o невалидный HTML;
o невозможно парсить весь рунет
на одном сервере.
45. Статистика
● Три машины: 32Gb RAM, Linux
● 500 запросов/с на машину, поток 50мбит
● 30Тб данных в месяц
● в очереди ~300 млн. целей
● полный пересбор раз в два месяца
47. 0 AD
Прототип паука:
● был написан на С++ и libcurl;
● работал на одном сервере.
Наступили на грабли:
● Упёрлись в один сервер
48. 0 AD
Прототип паука:
● был написан на С++ и libcurl;
● работал на одном сервере.
Наступили на грабли:
● Упёрлись в один сервер
● Слали запросы синхронно:
o 2000 простаивающих потоков, ждущих данные из сети;
o 2k*8Mb = 16Gb памяти только на стек для потоков;
o Нельзя делать select из >1024 дескрипторов.
49. 0 AD
Прототип паука:
● был написан на С++ и libcurl;
● работал на одном сервере.
Наступили на грабли:
● Упёрлись в один сервер
● Слали запросы синхронно:
o 2000 простаивающих потоков, ждущих данные из сети;
o 2k*8Mb = 16Gb памяти только на стек для потоков;
o Нельзя делать select из >1024 дескрипторов.
● Сегфолты: падающий поток мог вынести всё:
o Coredump на 20Гб;
o полчаса чтобы посмотреть бектрейс.
50. 0 AD
Прототип паука:
● был написан на С++ и libcurl;
● работал на одном сервере.
Наступили на грабли:
● Упёрлись в один сервер
● Слали запросы синхронно:
o 2000 простаивающих потоков, ждущих данные из сети;
o 2k*8Mb = 16Gb памяти только на стек для потоков;
o Нельзя делать select из >1024 дескрипторов.
● Сегфолты: падающий поток мог вынести всё:
o Coredump на 20Гб;
o полчаса чтобы посмотреть бектрейс.
● Жизнь слишком коротка для С++
51. 2013
После хождения по граблям, поняли что хотим:
● асинхронное IO;
● масштабирование из коробки;
● сказать нет shared mutable data;
● восстановление после сбоев;
● легкость отладки и тестирования.
52. 2013
После хождения по граблям, поняли что хотим:
● асинхронное IO;
● масштабирование из коробки;
● сказать нет shared mutable data;
● восстановление после сбоев;
● легкость отладки и тестирования.
Варианты:
● Scala + Akka;
● Java + Akka;
● Erlang;
● boost: asio, phoenix, cppnetlib.
53. 2013
После хождения по граблям, поняли что хотим:
● асинхронное IO;
● масштабирование из коробки;
● сказать нет shared mutable data;
● восстановление после сбоев;
● легкость отладки и тестирования.
Варианты:
● Scala + Akka;
● Java + Akka;
● Erlang;
● boost: asio, phoenix, cppnetlib.
55. Работа с сетью
Несколько вариантов:
● spray.io
o написан на idiomatic Scala, свой DSL;
o Akka под капотом;
o немного для других вещей;
o spray-can сбоку на изоленте, многое не умеет;
o разработку штормит, в процессе слияния с akka-http.
56. Работа с сетью
Несколько вариантов:
● spray.io
o написан на idiomatic Scala, свой DSL;
o Akka под капотом;
o немного для других вещей;
o spray-can сбоку на изоленте, многое не умеет;
o разработку штормит, в процессе слияния с akka-http.
● Apache http-async-client
o Java;
o стабильный как мамонт;
o умеет вообще всё;
o модульный и расширяемый
(inb4 MyConnectionKeepAliveStrategyBuilderFactory);
o свои, ни с чем не совместимые Future, ThreadPool и т.д.
57. AHC
● Не такой уж и асинхронный:
o решили использовать общий с akka thread-pool;
o Java DNS lookup - синхронный и через UDP;
58. AHC
● Не такой уж и асинхронный:
o решили использовать общий с akka thread-pool;
o Java DNS lookup - синхронный и через UDP;
o UDP иногда теряется, это норма;
o потеря пакета - вечная блокировка потока в thread-pool;
o через два дня работы весь thread-pool заблокирован.
59. 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’ов на предмет блокировок.
61. Одноразовые акторы
● Создать нового актора - дешево и быстро:
o ~300 байт памяти;
o можно создать хоть миллион.
● Одна подзадача - один актор:
o получил задачу;
o сделал её, послал результат и помер.
● Чистые иммутабельные акторы
62. Одноразовые акторы
● Создать нового актора - дешево и быстро:
o ~300 байт памяти;
o можно создать хоть миллион.
● Одна подзадача - один актор:
o получил задачу;
o сделал её, послал результат и помер.
● Чистые иммутабельные акторы
63. Одноразовые акторы
● Создать нового актора - дешево и быстро:
o ~300 байт памяти;
o можно создать хоть миллион.
асинхронный
коллбек!
Future[T]
● Одна подзадача - один актор:
o получил задачу;
o сделал её, послал результат и помер.
● Чистые иммутабельные акторы
65. Ask pattern
● Возможность что-то асинхронно спросить у актора
● Ответ - монада Future[T]
● Под капотом:
o создается временный актор, который ждет ответа;
o ответ заворачивается в результат Future.
66. Ask pattern
● Возможность что-то асинхронно спросить у актора
● Ответ - монада Future[T]
● Под капотом:
o создается временный актор, который ждет ответа;
o ответ заворачивается в результат Future.
67. Ask pattern
● Возможность что-то асинхронно спросить у актора
● Ответ - монада Future[T]
● Под капотом:
o создается временный актор, который ждет ответа;
o ответ заворачивается в результат Future.
68. Ask pattern
● Возможность что-то асинхронно спросить у актора
● Ответ - монада Future[T]
● Под капотом:
o создается временный актор, который ждет ответа;
o ответ заворачивается в результат Future.
порядок
вычисления
70. Akka ask puzzle
● Вылетел exception
● Что окажется в result?
1. Failure(e:UnsupportedOperationException).
2. “”
3. Failure(e:AskTimeoutException).
4. Failure(e:ClassCastException).
71. Akka ask puzzle
● Вылетел exception
● Что окажется в result?
1. Failure(e:UnsupportedOperationException).
2. “”
3. Failure(e:AskTimeoutException).
4. Failure(e:ClassCastException).
72. Akka ask puzzle
● Вылетел exception
● Что окажется в result?
1. Failure(e:UnsupportedOperationException).
2. “”
3. Failure(e:AskTimeoutException).
4. Failure(e:ClassCastException).
73. Akka ask puzzle
● Вылетел exception
● Что окажется в result?
1. Failure(e:UnsupportedOperationException).
2. “”
3. Failure(e:AskTimeoutException).
4. Failure(e:ClassCastException).
Временный актор
создается вне общей
иерархии
74. Мы и ask pattern
● Если уметь готовить аски, то жить можно
● Используем длинные цепочки асков:
Bot ScriptAction ScriptRunner
RequestExetutorThrottlerHttpClient
75. Мы и ask pattern
● Если уметь готовить аски, то жить можно
● Используем длинные цепочки асков:
Bot ScriptAction ScriptRunner
RequestExetutorThrottlerHttpClient
● Легкая декомпозиция задачи на части
● Каждую часть легко тестировать
● Есть overhead на создание временного актора
77. FSM, Конечные автоматы
● Можно и без них, но с ними удобнее
● Помогают в задачах, когда:
o актору нужно долго подниматься;
o есть четкие переходы между состояниями.
78. FSM, Конечные автоматы
● Можно и без них, но с ними удобнее
● Помогают в задачах, когда:
o актору нужно долго подниматься;
o есть четкие переходы между состояниями.
● Увлеклись:
o у бота 10 состояний;
o ~30 правил перехода;
o 500 строк лапши;
o тестировать почти невозможно.
79. FSM, Конечные автоматы
● Можно и без них, но с ними удобнее
● Помогают в задачах, когда:
o актору нужно долго подниматься;
o есть четкие переходы между состояниями.
● Увлеклись:
o у бота 10 состояний;
o ~30 правил перехода;
o 500 строк лапши;
o тестировать почти невозможно.
● Сейчас:
o у бота 2 состояния (просыпаюсь и активный) и 5 правил;
o вся логика вынесена в отдельные акторы;
есть и дочерние FSM.
81. Тестирование
● Синхронное через TestActorRef
o можно руками дернуть актора за receive();
o в самый раз для простой логики.
Два подхода к тестированию:
82. Тестирование
● Синхронное через TestActorRef
o можно руками дернуть актора за receive();
o в самый раз для простой логики.
● Асинхронное через akka.TestKit
o полный запуск системы;
o дополнительный синтаксис для assert-ов;
o можно подсовывать mock-акторов.
Два подхода к тестированию:
83. Логгинг
● Есть интеграция c slf4j
● log.debug(“foo”) - отправка сообщения логгеру
● MDC - message diagnostic context
84. Логгинг
● Есть интеграция 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, который рисует статистику.
89. “Динамическая” типизация
● Боль: несоответствие типов сообщений вылезает
наружу только в run-time
● Хочется строгой типизации
[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka
[2]: Akka 2.x Roadmap
90. “Динамическая” типизация
● Боль: несоответствие типов сообщений вылезает
наружу только в run-time
● Хочется строгой типизации
[1]: H.E. Jiansen: Typecasting actors, from Akka to TAkka
[2]: Akka 2.x Roadmap
91. “Динамическая” типизация
● Боль: несоответствие типов сообщений вылезает
наружу только в 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
92. “Динамическая” типизация
● Боль: несоответствие типов сообщений вылезает
наружу только в 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
У нас:
● Интеграционные тесты
● Акторы обычно умеют только
в один тип сообщений
94. Ошибки: Thread starvation
Что это:
● Блокировка потоков в thread-pool.
Откуда берутся:
● Thread.sleep(), Await.result() внутри актора;
● долгие вычисления;
● интеграция с легаси-кодом.
95. Ошибки: Thread starvation
Методы борьбы:
● ломать пальцы за Thread.sleep() внутри актора;
● отдельные dispatcher’ы;
● мониторинг стека вызовов и загруженности
потоков.
Что это:
● Блокировка потоков в thread-pool.
Откуда берутся:
● Thread.sleep(), Await.result() внутри актора;
● долгие вычисления;
● интеграция с легаси-кодом.
97. Ошибки: OutOfMemoryException
Причины:
● переполнение почтового ящика;
● слишком много акторов.
Что делать:
● мониторить загруженность почтового ящика;
● прореживать очередь сообщений;
● периодически изучать heap-dump и GC logs на
предмет утилизации памяти;
● следить за логическими утечками (замыкания).
98. В итоге
● прошли путь от akka 2.0 до 2.3.x
● один год в production
99. В итоге
● прошли путь от akka 2.0 до 2.3.x
● один год в production
● в начале приходится тяжело:
100. В итоге
● прошли путь от akka 2.0 до 2.3.x
● один год в production
● много скрытых граблей, но жить можно
● исключительно простой и выразительный
формализм
● удобно разрабатывать и тестировать
● в начале приходится тяжело:
101. Ссылки
● 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