Мой путь в Elixir/Erlang (часть первая)
Недавно я начал погружаться в мир Elixir и Erlang/OTP. Если вы, как и я, считали эти технологии «хипстерскими», этот пост может поменять ваше мнение.
Почему начал изучать?
Раньше я слышал про Elixir. Веьсма редко мелькал у меня в ленте, но без особого эффекта. Мне он казался каким то "хипстерским" не более.
Причиной резкого интереса стала мощная рекламная кампания этого языка от нашего тех дира на работе (привет Дима если ты это читаешь). Он толкнул такую речь, что я готов был взять Берлин второй раз.
Сначала немного контекста
До этого я писал серверы на go, php и nodejs. У меня в голове была картина того, как работает параллельность и в целом готовить конкурентный код. В Go у меня были гороутины, на ноде треды/libuv, на пыхе гей порно с pthreads или новые файберы где ситуация лучше. В целом это работает, так пишут и всё гуд.
*тут с ноги врывается BEAM (это виртуальная машина на которой исполняется код Elixir/Erlang) и рвет все мои шаблоны про конкурентный код. Это вам не шарашкина контора как в jvm, тут всё иначе.
Я начал восхищаться эликсиром, пошли флешбеки где можно было избежать столько всего, это небо и земля друзья, очень жаль что я поздно подключился в эту тусовку.
Порог входа
Мало материала, это правда, комьюнити гораздо меньше чем у языков на которых я активно писал, но мне было не сложно. У Elixir отличная документация, хорошо структурированная, и я начал именно с неё.
IO.puts("Hello, World")
Синтаксис зашел легко, muscle memory зарешал т.к я очень давно игрался с руби (чисто ради рельсов). В целом, привыкнуть не сложно, зато потом какой кайф от паттерн метчинга. Но всё же, это мой личный опыт, возможно у вас будут/были другие эмоции.
Пробовал писать какие то простые скрипты, на работе предлагали задачки, руки достаточно быстро начали привыкать. Конечно я понимал в чем заключается мощь Elixir, как я говорил ранее, мне толкнули мощную речь про BEAM и Elixir.
Когда я добрался до Erlang/OTP, ознакомился с BEAM, началось самое интересно.
Где потоки?
Сказал я... шучу, тут так не принято.
По старой привычке гуглил "elixir threads" начал искать что то типа spawn_thread()
или async do
, но тут такое готовят совершенно иначе.
В Elixir вообще нет потоков в привычном смысле. Тут все строится на процессах (привет акторная модель).
Процессы
Это не процессы ОС, они супер дешевые и живут внутри BEAM. Не шарят память, не ждут друг друга и работают независимо.
Можно запустить сотни тысяч процессов одновременно, да хоть миллион (это не просто громкие слова, мы вернемся к этой цифре) и что немало важно, они весят очень мало и переключаются за миллисекунды.
А если процесс упал?
Вот это вообще песня. В Elixir другая философия, никаких тебе recover()
, try/catch
чето пошло не так? пусть падает! (let it crash) и это норм.
Что бы понять насколько это круто, представьте -->
Поднимаем http сервак на ноде, он падает из за ошибки в одном запросе, но вместо полного краша, он просто перезапускается без моего участия. Или worker горутинка отвалилась, а система сама её воскресила.
Представили такое? а в Elixir не нужно представлять, это встроено по дефолту. Вся движуха строится на том, что у вас есть BEAM и супервизоры (supervisors), которые перезапускают упавшие процессы, и причем вам особо ничего делать не нужно, просто доверьтесь BEAM.
Чувствую себя как человек, который всю жизнь закручивал гайки руками, а потом мне дали шуруповерт. Шучу, на самом деле язык и цели разные, ни в коем случае не хочу сказать что нода или go дерьмо, просто тут кухня совсем другая и она мне зашла.
Немного про BEAM
Почему всё это работает так круто? Потому что Elixir работает на BEAM (виртуальной машине), которую разрабатывали для телекома (привет Erlang).
В то время когда приходилось думать:
- Как не накосячить с потоками?
- Как пережить падения?
- Как не словить утечку памяти?
В Elixir просто доверяют BEAM. Эта VM из мира телекома, где сбои = потерянные доллары. Она умеет делать то, о чём я мог только мечтать на go/node/php
Представьте, что ваш код запускается не в одном большом процессе, а в миллионах крошечных процессов, каждый из которых живёт своей жизнью. Если один упал, да и чёрт с ним, соседние даже не заметят. Если нагрузка выросла, BEAM сам раскидает задачи по ядрам. Если память засорилась, GC почистит её без остановки программы.
В других языках подобное достигается с помощью сложных фреймворков, брокеров сообщений и devops танцами. В Elixir всё это просто часть платформы.
Где ещё такое есть? Где вы можете менять код в запущенном приложении без его остановки или перезапусков? пока в фоне обрабатываете тысячи запросов, как это делает BEAM?
Про миллион процессов
Сразу простой пример, плодим процесс -->
spawn(fn -> IO.puts("My name is Jeff") end)
spawn(fn -> IO.puts("JVM started with 8gm RAM") end)
И это не просто синтаксис. Как я уже сказал, они не шарят память, не ждут друг друга, никаких тебе мьютексов и рейс кондишенов.
Их можно запустить сотни тысяч, хоть миилион
for _ <- 1..1_000_000, do: spawn(fn -> reoceive do _ -> :ok end end)
мой мак на м1 даже не заикнулся...
Пример лучше -->
defmodule MyWorker do
use GenServer
def init(_), do: {:ok, %{count: 0}}
def handle_call(:fail, _, _), do: raise("Я упал")
end
Supervisor.start_link([{MyWorker, []}], strategy: :one_for_one)
Процесс упал? его перезапустят. Упал миллион раз? будет миллион перезапусков.
В других языках для этого нужен отдельный devops танец с мониторингом, restart policies и всякими systemd
или docker restart
Стоит ли изучать?
Да
p.s самый короткий заголовок второго уровня на диком западе
Что еще?
Будет вторая часть, про распределённые системы и больше про OTP. Я пока сам зелёный в этой кухне, но есть много интересного, о чём хочется написать