Чем хорош Erlang вообще и для веб-разработки в частности?

19 февраля 2014

Доклад для митапа белорусского сообщества Ruby-разработчиков. Митап был 25 января 2014 года, и был посвящен языку Erlang и его применению в вебе.

Есть видео моего доклада и презентация.

Я там в выступлении немного налажал с многопоточностью в других языках и веб-серверах. Плохо подготовился в этом вопросе. Ну ничего, исправлюсь, разберусь с этим. В презентации и в тексте ниже я эту лажу убрал. Но на видео она осталась :)

Вообще митап был хороший, стоит посмотреть все доклады.

Темы:

Что такое Эрланг

Эх, это уже который раз я собираюсь объяснять, что такое Erlang? Ну там у него многопоточность, распределенность, устойчивость к ошибкам и горячее обновление кода. И высокие нагрузки, и полная драматизма история. Хотя не, вру, история не такая уж и драматичная. Но длительностью больше 20 лет.

Представьте себе: середина 80-х годов. Java еще нет. C++ еще нет. Есть C, Fortran, Cobol, Prolog, LISP. ООП только зарождается -- Modula, Smalltalk. Мощности железа так себе, не сравнить с нынешними. А компании Ericsson уже прямо сейчас нужно обслуживать много клиентов и много запросов на своем телекоммуникационном оборудовании. Erlang изначально создавался для этого, полировался в этой сфере больше 20 лет. И сейчас мало какая технология может с ним потягаться. Только немногие, написанные на С, как nginx.

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

Что такое процесс? Это отдельный поток выполнения некого кода. ОС уже давно все многопроцессорные, то есть, умеют выполнять одновременно несколько задач. На самом деле не обязательно одновременно, но хитрый планировщик задач так ловко их переключает, что нам кажется, что типа одновременно.

Процессы бывают разные в разных ОС. Более того, внутри одной ОС они тоже бывают разные. Как образец для рассматривания и сравнения мы возьмем такой процесс, который создается в unix-подобных ОС системным вызовом fork().

При этом создается объект ОС, и ему выделяются ресурсы:

ОС имеет таблицу процессов, для для каждого из них хранится всякое интересное: PID, родительский PID, UID пользователя, GID группы, состояние (все мы знаем, что бывает состояние зомби :)

К чему я это все? А к тому, что объект у нас не маленький, и памяти занимает прилично. И таблица процессов ОС имеет не бесконечный размер. Посему мы не можем создать сколько угодно таких процессов. А можем создать несколько сотен, ну ли пару тысяч. Порядок цифр такой.

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

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

Поскольку такие процессы относительно медленные и относительно неэффективные, возникло желание создавать паралельные потоки выполнения опираясь на более простые и быстрые сущности. И были придуманы нити (thread). Это легковестные процессы, которые запускаются внутри процесса ОС и разделяют его ресурсы между собой.

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

Важность всего этого будет ясна дальше :)

Архитектура веб-серверов

Давайте выделим следующие уровни в работе веб-приложения:

Чаще всего за разные уровни отвечают разные технологии. Например, за первый уровень отвечает nginx, за второй -- ваш код на Ruby, за третий -- memcached и PostgreSQL. Ruby удобен для бизнес-логики, но на нем невозможно эффективно реализовать принятие запросов и хранение данных.

Разработчику привычно работать на уровне бизнес-логики, и часто он неплохо ориентируется в уровне хранения данных. Ну там, может с умным видом порассуждать о преимуществах MongoDB (а некоторые даже умеют ей пользоваться :) Между тем мало кто понимает, что происходит на 1-м уровне. И действительно, а зачем там чего-то понимать? Этот уровень как-то там работает сам по себе, и слава богу :) Необходимость туда лезть обычно не возникает.

Обычно, но не всегда. Бывает, что возникает необходимость обслуживать 10К, 100К, 1M запросов в секунду. И тогда 1й уровень становится очень важным.

Фишка Erlang в том, что он хорош на всех 3х уровнях.

Если мы всегда работаем на втором уровне, то преимущества Erlang показать сложно. Тут можно говорить о неизменяемых структурах данных, о декларативном синтаксисе, об удобстве отладки. Преимущества есть, но их трудно доказать людям, не знакомым с технологией.

Зато если нам нужно выйти за пределы второго уровня, то тут преимущества становятся очевидны. На 3-м уровне Erlang вполне справляется с кешированием данных в оперативной памяти и заменяет специализированные тулы, типа memcached или Redis.

А уж на первом уровне Erlang просто прекрасен. Это его родная стихия. Что бы понять, почему, нужно вернуться к нашим процессам ОС.

Как этот уровень работает в каком-нибудь не очень свежем веб-сервере, например, у старичка apache? Создается пул процессов, допустим 100 штук, и все они висят и ждут запросов от клиентов. Когда появляется запрос, на его обслуживание выделяется процесс из пула. Он принимает данные, выполняет код бизнес-логики, отвечает клиенту, очищает свою память и возвращается в пул. Если будет приходить много запросов, или они будут долго выполняться (или и то, и другое сразу), то все процессы будут заняты, пул будет пустой, и часть запросов сервер не сможет обслужить.

Чтобы такого не случалось, в идеале надо бы иметь столько процессов, сколько к нам приходит запросов. Начать с какого-то пула, но быстро создавать новые процессы по мере надобности. Но что мы говорили про процессы ОС? Их количество ограничено, и создаются они не быстро. Нужен сервер, работающий на легковестных процессах. На нитях ОС, как nginx, или на каких-то своих, как Erlang.

Отдельным, 4-м уровнем можно выделить отложенное выполнение длительных задач, типа: сбор большого количества данных по базе и их анализ, конвертация видео и графики, анализ логов и т.д. Для этого используют разные инструменты, от простого cron до мощных AMQP (Advanced Message Queuing Protocol) тулов, типа RabbitMQ.

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

То есть, все веб-приложение, на всех уровнях может быть выполнено как единое Erlang-приложение. Разве что долговременное хранение данных на диске все-таки стоит отдать базе данных :)

Преимущества Erlang для веб

Из-за эффективного 1-го уровня Erlang позволяет на том же железе обслуживать больше запросов. В некоторых случаях, например, в сравнении с проектами, построенными на Ruby On Rails, на порядки больше.

Там, где сферический в вакууме Ruby-код обслужит 500-800 запросов в секунду, Erlang-код, написанный программистом средней квалификации, обслужит 5-8К запросов в секунду. А код Erlang-гуру выдаст и 50-100K запросов в секунду.

Но это мы говорим об одном сервере. А Erlang предназначен для разработки распределенных систем. Так что если вы изначально планируете кластер из нескольких (или многих десятков) машин, то Erlang в таком проекте самое место :) Ибо на нем разрабатывать такие системы настолько просто, насколько это вообще может быть простым.

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

Я выше говорил, что на Erlang можно построить однородный проект, берущий на себя все уровни. Но в высоконагруженных системах все-таки используют несколько разных технологий. Ибо узкая специализация дает максимальную эффективность, и только так можно выжать из железа максимум. В таких проектах Erlang может взять на себя 1-й уровень, а также выполнять роль архитектурного клея, склеивающего вместе другие компоненты системы. Примеры таких систем мы увидим дальше, когда будем говорить про Heroku и Github.

С преимуществами понятно, теперь о недостатках. Они есть. Причем это и недостатки, присущие любым маргинальным технологиям, и свои специфические :)

Маргинальные технологии и языки -- это те, которые не имеют большого количества разработчиков и проектов:

Лет 5 назад и Ruby можно было причислить к маргинальным языкам, но сейчас это скорее мейнстрим. С точки зрения Erlang разработчика, так уж точно мейнстрим :)

Недостатки всех маргинальных технологий:

Недостатки собственно Erlang:

Пару слов о фреймворках:

Для веба Erlang начал использоваться относительно недавно, где-то с середины 2000х годов. До этого он имел свою нишу в телекомах и банковских системах. Так что по части фреймворков Erlang в роли догоняющего.

Вы все хорошо знаете, что такое современные веб фреймворки. Я же не очень хорошо. Но я проникся их мощью, когда полтора месяца назад пришел на работу в новую компанию и начал работать над новым проектом. Мы используем Erlang и Python/Django. Пользовательская часть сайта делается на Erlang, а зона администрирования на Django. Мне нужно было делать и то, и другое. Django я не знал совсем, а с Python был немного знаком.

Ну что, открыл сайт Django, прочитал тутор. Описал модель в питоновских классах -- сгенерировалась база даных. И админка сразу заработала. Подкрутил, подконфигурировал, что показывать, как сортировать, где искать -- и все, вполне качестванная и юзабельная админка. Сделана за 1 рабочий день человеком, вообще не знакомым с фреймворком )

Полагаю, Ruby on Rail работает аналогично. Так вот, такого на Erlang нету. Есть фреймворк Chicago Boss, сделанный по идеям Ruby on Rail, но вряд ли он дотягивает до оригинала.

Вообще Erlang-фреймворки пытаются выдвигать свои, оригинальные концепции. Например, фреймворк Nitrogen предлагает строить событийные веб-приложения, интерактивно взаимодействующие с пользователем без перегрузки страниц. И при этом весь код пишется на сервере, на Erlang и специализированном DSL, а клиентский JS-код генерируется автоматически.

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

Однако я в теме фреймворков ориентируюсь плохо. Больше об этом расскажет Максим Сохацкий :)

Примеры использования: компании и продукты

Учитывая аудиторию, начнем с компаний, хорошо знакомых Ruby-разработчикам :) И первой компанией в этом списке будет...

37signals

Один из продуктов этой компании -- Campfire -- групповой веб-чат для общения команды.

Mark Imbriaco на Erlang Factory London 2009 рассказал об этом проекте: Campfire Loves Erlang

Первая версия была сделана на RubyOnRails/MySQL. Но она не справлялась с планируемой нагрузкой в 1500 запросов в секунду и не пошла в production. Затем часть кода была переписана на C. Эта версия была очень эффективна по CPU и памяти и уверенно справлялась с нагрузкой, но была не масштабируема, требовала по 1 процессу ОС на каждое клиентское соединение. Поэтому код опять был переписан, уже на Erlang. Эта версия оказалась такой же эффективной, как вариант на С, но при этом легко масштабировалась.

В презентации Марка есть такая таблица:

Ruby C Erlang
LOC 127 397 273
Req/sec 250-350 1800 1800
Responce 20ms 2-3ms 2-3ms
OS Processes n/a 80 1
Extensible Yes No Yes

Проект совсем небольшой по количеству строк кода. Но видно, что на некоторых задачах Erlang может иметь производительность, сопоставимую С. И при этом более краткий, понятный и поддерживаемый код, чем код на С.

Я только уточню еще, что Erlang использует не 1 процесс ОС, а по 1 процессу на каждое ядро CPU.

Heroku

Облачная PaaS-платформа. Сначала поддерживала хостинг только Ruby проектов, сейчас поддерживает проекты на нескольких языках (в т.ч. и на Erlang). Heroku хостит десятки тысяч проектов в многопользовательском окружении

Heroku активно использует Erlang. На нем реализованы ключевые части сервиса:

Про Erlang, в сравнении с другими языками, они говорят следующее:

Perl: Making easy things easy and hard things possible!

Ruby: Making easy things trivial and hard things fun!

Erlang: Making easy things possible and impossible things trivial!

Разработчики Heroku не редко выступают на конференциях Erlang Factory.

Blake Mizerany и Orion Henry на Erlang Factory London 2009: How the Cloud Got Its Groove Back

Jacob Vorreuter на Erlang Factory London 2011: Utilizing Redis in distributed Erlang systems

Geoff Cant на Erlang Factory SF Bay Area 2013 про Logplex: High Throughput Erlang

Fred Hebert, хорошо известный среди Erlang-разработчиков, автор отличной книги "Learn You Some Erlang for Great Good!" на NYC Erlang Factory Lite 2013: Why Heroku (still) uses Erlang

Есть еще длинный и эпичный пост Fred Hebert Troubleshooting Down the Logplex Rabbit Hole о борьбе с утечками памяти в Logplex, но для его чтения требуется глубокое знание Erlang платформы :)

Github

Вряд ли нужно объяснять, что это такое. Зато нужно сказать, что исходные коды Erlang/OTP хранятся именно там :)

Github также использует Erlang как часть своей инфраструктуры.

Сервис Erlectricity, позволяет Ruby-узлам обмениваться сообщениями друг с другом, аналогично, как это делают Erlang-процессы. Erlang-узел создает слой коммуникации, и к нему подключаются Ruby-узлы.

Сервис GitHub Pages, позволяет создавать статические веб сайты. На самом деле, html-код, который этот сервис отдает клиентам, не хранится в виде html-файлов на диске. Сервис является распределенным, все данные хранятся в Riak, а бизнес-логика реализована на Erlang.

Tom Preston-Werner, со-основатель github, любит и Ruby, и Erlang, и ищет способы совместить преимущества этих двух языков. В его блоге можно найти несколько постов на эту тему.

Ну и разработчики github тоже отметились на Erlang Factory:

Tom Preston-Werner на Erlang Factory SF Bay Area 2009 Mixing Erlang and Ruby with Erlectricity

Tom Preston-Werner на Erlang Factory London 2010: Contributing to Erlang: Making the most of Git and GitHub

Jesse Newland на Erlang Factory SF Bay Area 2012: Rewriting GitHub Pages with Riak Core, Riak KV, and Webmachine

Facebook

Chat backend in Erlang

Данные 2011 года: 1 миллиард сообщений в сутки 10 миллионов активных пользователей в пике 100 серверов

Eugene Letuchy на Erlang Factory SF Bay Area 2009 Erlang at Facebook

Amazon

SimpleDB распределенная база данных, часть Amazon Web Services http://en.wikipedia.org/wiki/SimpleDB

Yahoo!

Использует Erlang в своих проектах. Подробностей не нашел.

Yogish Baliga на Erlang Factory SF Bay Area 2010 Deploying Erlang into a Large Organization - A Case Study

Yandex

Серверная часть Яндекс.Диск.

Как мы делали Яндекс.Диск

DemonWare

Mассовоая многопользовательская игра Call of Duty Black Ops 2.5 млн пользователей онлайн. Логика игры на питон, инфраструктура на эрланг.

Malcolm Dowse на Erlang Factory London 2011 Erlang and First-Person Shooters in online games

Wooga

Серверная часть для социальных игр.

Knut Nesheim на Erlang Factory London 2011 Designing online games for scale with Erlang

WhatsApp

Instant messaging для мобильных устройств.

Rick Reed на Erlang Factory SF Bay Area 2012 Scaling to Millions of Simultaneous Connections

Известные продукты на Erlang

Riak Распределенная Key-Value база данных

CouchDB Распределенная документ-ориентированная база данных

Rabbit MQ Брокер сообщений, одна из реализаций AMPQ (Advanced Message Queuing Protocol)

ejabberd Во многих устаревших источниках ejabberd можно увидеть как первый и единственный пример продукта на Erlang. Действительно, это один из первых продуктов для широких масс пользователей. До этого Erlang использовался во внутренних разработках в телекомах и в банковском секторе. Но, как мы видим, на сегодняшний день это далеко не единственный такой продукт.

Место Эрланг в вебе сейчас и в будущем

Итак, из примеров выше ясны области применения Erlang:

А что в будущем?

Ждем, что веб-сокеты будут набирать популярность. С ними традиционному вебсерверу будет не просто справиться. Клиентское соединение устанавливается надолго, связывает процесс сервера, и он не скоро вернется в пул. По сути сервер может обслужить ровно столько клиентов, сколько у него есть процессов, и не больше.

Ждем SPDY и HTTP 2.0. Они тоже подразумевают, что соединение клиента с сервером удерживается довольно долго.

Ждем Интернет вещей, когда каждый холодильник, утюг и тапочки будут подключены к интернету. Это значит, что количество клиентов и запросов от них значительно возрастет.

Понадобятся новые веб-сервера, понадобятся распределенные архитектуры. Erlang и Cowboy готовы к этому уже сейчас.

comments powered by Disqus