Erlang в Рисоваське, часть 2 — ответы на вопросы

    Данная статья является продолжением цикла статей про язык программирования Эрланг/Erlang и его использование в нашем проекте Рисоваська. В первую очередь я хочу ответить на вопросы заданные в комментариях к первой статье, а вопросов было задано много. Итак продолжим:

    Установка Erlang под Windows


    Установить Erlang достаточно просто. Сначала скачиваем дистрибутив с официального сайта. Нам нужна последняя на данный момент версия R12B-5, колонка Windows binary (incl. documentation). После инсталляции в меня «Пуск» — «Все программы» появится пункт Erlang OTP R12B и в нем подпункт «Erlang». Если его выбрать, то запуститься безымянная нода вместе с оболочкой (shell) в которой уже можно выполнять эрланговский код, в том числе из моих примеров. Я буду придерживаться названия «нода», так как официального названия в русском языке пока не устоялось, хотя слово «node» и переводиться как «узел».

    Вот ссылки про установку Эрланга под Mac OS X и Линукс (на английском).

    Чтобы завершить установку, нужно в переменные среды Windows прописать следующие переменные:
    1. Путь к директории, куда был инсталлирован Эрланг:
    ERL_TOP = C:\Program Files\erl5.6.5
    этот же путь, но с добавлением \bin, т.е. C:\Program Files\erl5.6.5\bin нужно прописать и в переменную PATH, если его там еще нет

    2. Переменная HOME, если она у вас еще не прописана, например:
    HOME = C:\Documents and Settings\tolik

    3. Рекомендую сразу прописать путь к сторонним библиотекам Эрланга:
    ERL_LIBS=C:\Work\erl_libs
    В будущем все сторонние библиотеки, не входящие в поставку Erlang/OTP, кладите в эту директорию. Это позволит легко поставить новую версию Erlang/OTP, удалив старую, не переставляя при этом сторонние библиотеки. Кстати, новые версии Erlang/OTP выходят с регулярностью два раза в год.

    И последнее, в директории HOME создайте файл ".erlang.cookie". Это обычный текстовый файл в котором храниться текстовый cookie в одну строку, например: «AMMVFJKDTENSFHDJDGDERSW». Этот cookie будет использоваться при взаимодействии нод между собой. Дело в том, что две ноды могут взаимодействовать между собой, только если у них одинаковые cookie's. Если вас пока не интересует взаимодействие нескольких нод между собой, то пропустите этот шаг.

    В той же директории HOME удобно создать файл ".erlang" в котором можно писать код на Эрланге, который будет автоматически выполняться при старте любой ноды на данном компьютере. Обычно в этот файл пишут код добавляющий путь к исполняемым файлам вашего приложения в список поиска:

    code:add_pathz("Путь к папке ebin вашего приложения").

    Теперь вы можете запускать ноду прямо из командной строки просто набрав: «erl». Можно запускать ноду и без оболочки или как демон. Если вы запустили ноду без оболочки, то подключиться к ней вы сможете запустив вторую ноду (можно и на другом компьютере) выполнив:

    erl -name test@192.168.0.11 -remsh main@192.168.0.11

    где main@192.168.0.11 — название ноды к которой мы подключаемся, а test@192.168.0.11 — название новой ноды. После подключения вы можете выполнять все команды, как будто вы находитесь на первой ноде.

    Более подробно параметры запуска описаны в модуле erl.

    Emacs, как среда разработки


    До недавнего времени почти единственным удобным вариантом писать и отлаживать программы на Эрланге был Emacs (по ошибке принимаемый за обычный текстовый редактор) в связке с Distel. Достаточно хорошую инструкцию по настройке и работе с обоими пакетами на русском языке можно найти здесь. В своей работе мы по прежнему используем именно их, хотя они и выглядят как древние динозавры, по сравнению с современными средами разработки.

    Новая среда разработки: erlIDE


    На замену Emacs, разрабатывается новая среда разработки erlIDE. Текущая версия 0.4.3 уже достаточно стабильна и не падает, как более ранние версии. В ней присутствуют почти все обычные для современных IDE инструменты: отладчик, контекстная подсказка, простая навигация по коду, подсветка синтаксиса и т.д. Инструкция по установке написана достаточно подробно и вопросов обычно не вызывает. erlIDE работает как плагин к Eclipse, поэтому вам нужно сначала поставить на свой компьютер Java (лучше не ниже 6-й версии), а потом Eclipse. Для работы будет достаточно Eclipse IDE for Java Developers (85 MB). Под Windows я бы рекомендовал сразу начать с erlIDE, чтобы не привыкать к Emacs.

    А теперь вернемся непосредственно к языку.

    Создание первого приложения


    Для начала создайте папку, где у вас будет находиться проект. Внутри нее нужно создать следующие подпапки:
    • ebin — сюда будет класться скомпилированный код (.beam)
    • src — исходники приложения (.erl)
    • include — заголовочные файлы (.hrl)
    • doc — документация
    • priv — все остальное, что нужно приложению в работе, но не вошло в другие папки

    Это стандартная структура приложения, рекомендуемая принципами OTP. В корневой папке приложения создайте файл emakefile со следующим содержимым:

    {"src/*", [debug_info, {i, "include"}, {outdir, "ebin"}]}.

    и тогда в оболочке ноды, находясь в корневой папке приложения, вы сможете просто выполнять команду: make:all(), чтобы перекомпилировать все модули приложения.

    В подпапке «ebin» нужно создать файл your_application.app примерно с таким содержимым:
    {application, your_application.app,
    [{description, "Test application"},
    {vsn, "1"},
    {modules, [you_application, you_application_sup]},
    {registered, []},
    {applications, [kernel, stdlib, sasl]},
    {mod, {your_application,[]}}
    ]}.


    И последнее, в папке «src» нужно создать главный файл проекта: you_application.erl с минимальным содержимым:
    -module(you_application).
    -behaviour(application).

    -export([start/2, stop/1]).

    start(_Type, _Args) ->
    your_application_sup:start_link().

    stop(_State) ->
    ok.


    Таким образом мы создали минимальное приложение на Эрланге, которое пока ничего не делает. Так же пока не реализован главный супервизор приложения (о нем я напишу позже). Более подробно о создании приложения описано в документации OTP

    Горячая замена кода


    Очень полезной особенностью Erlang/OTP является горячая замена кода без остановки системы. Как это работает на примере одного модуля? Например, вы изменили код в одном из модулей приложения и хотите его подменить на лету в работающей системе. В этой ситуации в Эрланге совсем не обязательно останавливать все приложение. Достаточно в оболочке ноды набрать: l(your_module). и после выполнения этой команды будет работать уже новый код. Если говорить более подробно, то виртуальная машина Эрланга хранит в памяти две копии каждого модуля: текущую и предыдущую. Зачем нужно хранить предыдущую версию? Чтобы в случае неправильной работы нового кода быстро откатиться на предыдущую версию так же не останавливая работу приложения.

    Конечно можно заменять на лету не только отдельный модуль, но и все приложение целиком. Для этого:
    • создаем файл обновления приложения (Application Upgrade File), в котором прописываем правила замены старой версии на новую, а так же зависимости между модулями во время замены версии
    • создаем файл обновления релиза (Release Upgrade File)
    • инсталлируем новую версию приложения поверх старой

    Чтобы сократить размер статьи, за дополнительной информацией я отправляю в документацию, где эти шаги описаны более подробно.

    Но что делать, когда у вас работает целая распределенная система, состоящая из нескольких серверов и вам нужно на них всех одновременно заменить код без остановки всей системы. К сожалению готового удобного решения Erlang/OTP тут не предлагает. Тем не менее в состав OTP входит модуль erl_boot_server, который позволяет при старте ноды загружать код приложения, включая файлы конфигурации с другой ноды. Тогда перегрузка всей системы может быть реализована так: сначала происходит горячая замена кода на главном сервере, код с которого загрузили остальные ноды при своем старте, используя модуль erl_boot_server. А потом всем нодам рассылается сообщение на перегрузку ноды. При получении такого сообщения на ноде выполняется команда init:restart/0 перегружающая ноду без ее полной остановки. После перегрузки все ноды забирают уже обновленный код с главного сервера. Примерно такой подход описан в двух статьях Setting up Erlang on Amazon EC2 (вторая часть статьи) и Upgrading your Erlang cluster on Amazon EC2.

    Behaviours


    Еще одной интересной особенностью языка являются behaviours, являющийся аналогами интерфейсов в традиционных языках, а еще точнее абстрактных классов. Если в начале модуля, например, написано -behaviour(application). — это значит две вещи: часть логики уже реализована в OTP и второе, в модуле должен быть реализован определенный набор callback-функций, без которых модуль не будет компилироваться. В нашем случае мы обязаны реализовать функции start и stop. За более детальной информацией отправляю к документации по behaviours.

    Дай процессу умереть


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

    • во-первых, наблюдающий процесс выполняет знаменитую в Эрланге команду:
      process_flag(trap_exit, true).
      Это значит, что данный процесс ловит сообщения о падении связанных с ним процессов. После выполнения данной команды, если падает рабочий процесс, то в наблюдающий процесс приходит сообщение вида: {'EXIT', From, Reason} Важно, что без выполнения этой команды, в случае ошибки в рабочем процессе, падает по цепочке и все связанные с ним процессы, пока на пути не встретиться процесс, который выполнил process_flag(trap_exit, true).
    • во-вторых, наблюдающий процесс создает связанный с ним рабочий процесс:
      spawn_link(?MODULE, worked_function, [Arguments]).
    • и, в третьих, наблюдающий процесс начинает слушать сообщения:
      receive
      {'EXIT', From, Reason} ->
      supervising_function() % выполнить саму себя (см. ниже)
      end

    В сумме, минимальный код наблюдающего процесса будет выглядеть так:
    supervising_function() ->
    process_flag(trap_exit, true).
    spawn_link(?MODULE, worked_function, [Arguments]).
    receive
    {'EXIT', From, Reason} ->
    supervising_function()
    end
    end.


    Строительные кирпичики Erlang/OTP


    Но совсем не обязательно писать самому с нуля наблюдающие процессы, процессы доступа к общим ресурсам и т.д. За неполных двадцать лет развития языка в Erlang/OTP был разработан надежный и отлаженные набор базовых компонентов на которых обычно и строиться приложение написанное на Эрланг. Вот они:
    • gen_server — базовая реализация сервера, поддерживающая клиент-серверное взаимодействие в системе. Замечу, что здесь понятие «сервер», не совпадает с общепринятым понятием, а означает специфический сервер Erlang/OTP. Обычно в системе он отвечает за доступ к общим ресурсам системы.
    • gen_fsm — базовый модуль, реализующий конечную машину состояний
    • gen_event — базовый модуль, реализующий функциональность обработки событий
    • и, конечно, supervisor — модуль предназначен для запуска, остановки и наблюдения за своими дочерними процессами. Чтобы не писать много теории приведу простой пример функции инициализации супервизора:
      init() ->
      RestartStrategy = one_for_one
      MaxRestarts = 10,
      MaxTimeBetRestarts = 3600,
      SupFlags = {RestartStrategy, MaxRestarts, MaxTimeBetRestarts},
      ChildSpecs = [{Some_Module, {Some_Module, Function, []}, permanent, TimeOut,
      worker, % тип дочернего процесса
      [Some_Module]}],
      {ok, {SupFlags, ChildSpecs}}.

      Данный код запустит один дочерний процесс, максимального кол-во перезапусков дочернего процесса = 10 с частотой не более 3600 мс.


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

    К сожалению, подробное описание каждого модуля и связей между ними займет несколько статей, к тому же они хорошо описаны в Design Principles. Да и за примерами использования ходить далеко не надо, берите любое, достаточно известное приложение или библиотеку, написанную на Эрланге и смотрите. Почти все они написаны с использованием этих базовых компонентов.

    В завершении статьи хочу дать ссылку на хорошую статью в Википедии про Эрланг и две хорошие (1, 2) подборки ресурсов по Эрлагу, которую собрал Дима Смолин. Для тех, кому интересен язык Эрланг, этих ссылок вполне достаточно, чтобы глубоко погрузиться в язык.

    Продолжение следует...


    Как и в первой статье список тем для рассмотрения остается тот же: использование распределенной базы Mnesia (которая входит в состав Erlang/OTP), использование Amazon S3 и Amazon EC2 с примерами использования этих технологий на серверах Рисоваськи.
    Метки:
    Поделиться публикацией
    Похожие публикации
    Комментарии 41
    • +1
      полезную работу делаете, спасибо
      пожалуйста, поделитесь инвайтом со страждущими в русской ерланг рассылке
      • +1
        Спасибо и тебе! Извини, инвайт, считай, отдал своему хорошему другу на написание статьи.
        P.S. Комментарий ниже создал по ошибке.
        • 0
          ну значит следующим ,)
          • +1
            Следующим инвайтом обязуюсь поделиться в русской эрланг-рассылке, тому кто захочет писать статью про Эрланг :)
      • +1
        Спасибо и тебе! Извини, инвайт, считай, отдал своему хорошему другу на написание статьи.
        • 0
          Вот на емакс наезжать не надо, лучшей IDE до сих пор не придумали.
          • 0
            Да какие уж наезды. Сам в нем программирую :) Но я бы все же выбрал erlIDE, если бы сразу с него начал. Просто тогда он был еще не готов. Вот еще доработают erlIDE и наверно на него все же перейду. Лично мне все же «традиционные» графические IDE больше нравятся.
            • +1
              > Да какие уж наезды.

              Извини, показалось.

              > Но я бы все же выбрал erlIDE, если бы сразу с него начал. Просто тогда он был еще не готов. Вот еще доработают erlIDE и наверно на него все же перейду. Лично мне все же «традиционные» графические IDE больше нравятся.

              Фишка емакса — многоязыковые проекты и легкость автоматизации мелких задач. Если используется один язык — то лучше конечно писать в заточенной под него ИДЕ.
          • 0
            >gen_fsm — базовый модуль, реализующий конечную машину состояний
            Это то что обычно конечными автоматами называется? :-)
          • 0
            Спасибо, очень доходчиво и понятно пишете.
            Соответственно вопросы:
            Если ноды имеют имя, привязаное к определенному хосту, значит ли это, что приложение сильно зависимо от используемого сетевого окружения и оборудования? Есть ли возможность как-то гибко манипулировать списком имеющихся серверов? Как это настраивается?

            Как обстоят дела с reusability кода?

            Какие основные требования повлияли на выбор именно Erlang, когда вы выбирали язык для реализации Рисоваськи? Какие альтернативы рассматривались?

            Покорнейше благодарю.
            • 0
              Спасибо!

              Отвечаю на вопросы:
              Вообще-то ты можешь назвать ноду как хочешь. Я имею ввиду часть, которая пишется после знака "@", например: «super@supersite». Пока нет потребности взаимодействовать с другими нодами в сети проблем это вызывать не будет. Но если есть задача взаимодействовать с нодами на других, то тогда виртуальная машина полагается на нижестоящую операционную систему при определения IP адреса другой ноды при поиске.
              Списка имеющихся серверов в каждой конкретной виртуальной машине эрланга не храниться. Связь между двумя нодами устанавливается только при первой необходимости, например выполнить в Эрланге простой пинг другой ноды: net_adm:ping(name_of_other_node). То есть если просто стартовать одну ноду, то она не начнет искать другие ноды в сети сразу. По моему все это напоминает простую работу в сети. При обращении из браузера по адресу habrahabra.ru определяется IP адрес сервера через DNS. Эрланг, через операционную систему, поступает точно так же.
              Использовать повторно код можно, тут я не вижу отличий от других языков. Можно оформлять код в библиотеки и использовать их в других проектах. На практике у нас пока один проект на Эрланге, поэтому у нас пока нет такого опыта. Но если в следующем проекте, например, потребуется парсинг XML с проверкой по модели, то мы можем вполне взять кусок из текущего проекта.
              Требования были масштабируемость системы, простота написания системы, высокая надежность и выдерживание большой нагрузки на сервера. В общем мы рассматриваем наш проект сразу, как проект мирового уровня известности с миллионами пользователей. При выборе естественно выбирали и из традиционных языков C++, C#, Java, а так же Python, Ruby, PHP. На «традиционных» языках писать и отлаживать систему такого уровня обычно просто очень долго, а нам нужно было уложиться при разработке с нуля в 4-6 месяцев вместе с отладкой. Плюс желание высокой надежности системы сузили выбор до Эрланга.
            • 0
              гм, а к примеру определить список нод на которых выполняется приложение в одном месте можно каким-либо образом?
              чтобы наращивать мощность системы просто добавлением ноды?
              • 0
                Ответ на второй вопрос: да :) Но у нас сделано скорее наоборот. При запуске новая нода сразу запускает приложение, которое само включает ноду в уже работающую систему, тем самым наращивая мощность системы. Подход: «определить список нод» в одном месте ошибочен в том, что он централизованный. При выходе из строя этого центра выходит из строя и вся система. К тому же в EC2 при зависании компьютера ты теряешь на нем все данные, если они конечно не хранились на EBS.
                • 0
                  ага, вот так. спасибо
                  • 0
                    Но список то нод заранее известен же? Так? Ведь для их работы нужно на них установить куку, значит список можно условно считать фиксированным.

                    Просто я как то слабо представляют себе приложение, которое встраивается в распределенную систему не зная заранее возможный перечень доступных хостов.
                    • 0
                      Всей системе в целом список работающих нод можно считать известным, но это не обязательно на самом деле. Может, например, нода упала и вышла экстренно из системы.
                      Сам Эрланг не требует для своей работы, чтобы каждая нода знала все работающие ноды сразу и постоянно, соответсвенно, можно и систему строить так, чтобы новая нода знала, только то, что ей нужно для своей работы.
                      Если говорить про нашу систему конкретно, то у нас стартующая нода может выполнять одну из перечисленных функций или все сразу:
                      — нода базы данных Mnesia, тут крутиться только база
                      — сервер приложения, т.е. обрабатывает запросы от клиентских приложений,
                      — сервер нотификации

                      Например, если нода только для БД, то ей при старте нужно узнать только любую ноду, которая хранит схему базы, чтобы войти в схему.
                      Куки можно задавать при старте ноды и не нужно хранить на компьютере заранее.
                • 0
                  Хочу вот вакансию на ерланговую работу разместить, а кармы не хватает :(
                  • 0
                    Ну-ка :)
                    • 0
                      habrahabr.ru/job/804/
                      Большое спасибо!
                      • 0
                        Что-то, однако, через поиск по ключевым словам не находится
                        • 0
                          К тому же вакансия почти не касается Erlang'a. Написали бы Эрланг в разделе обязательно знать :)
                          • 0
                            Эрланга это касается напрямую, так как люди писать будут на нём. Но если я напишу его как обязательный, то людей будет ещё меньше.
                            Впрочем, даже если человек не знает Эрланга и не хочет его изучать, но разбирается в основных пунктах, то он нам тоже нужен, так-то :)
                            Может, мы денег мало обещаем? Просто 30-35 тысяч у нас как старт для минимума знаний, потом люди получают заметно больше, если видно, что человек растёт.
                            • 0
                              Понятно. Рад, что действительно на Эрланге :) Возможно, прямо так и написать в вакансии про деньги, как вы написали в этом комментарии выше. По моему 30 тыс. даже минимум для перечисленных требований маловато, а с пояснением становиться понятно.
                              • 0
                                Если будут писать непосредственно на нем, то не очень понятно, зачем к примеру тот же С. Имхо, если человек знает хорошо сетевой стек, особенности настройки ОСи да еще и на сях пишет еще и с опытом работы 2 года, то он наверняка уже где-то работает. И для перехода ему нужен как-то стимул… А в таком контексте даже для регионов это не очень много. Хотя если сделать на скидку на кризис, то може быть…
                      • 0
                        А в PATH нужно наверное C:\Program Files\erl5.6.5\binпрописывать, а не C:\Program Files\erl5.6.5\ который прописали в ERL_TOP?
                        Содержимое для .erlang.cookie генерируется или сочиняется из головы? Какой длины, какие рекомендации?
                        • 0
                          Да, вы правы. Спасибо за замечание. Поправил в статье.
                          Про .erlang.cookie: лучше сочинять самому, любую достаточно длинную последовательность случайных символов. Я не силен в криптографии, но думаю длиной не менее 16 символов или 128 бит.
                          • 0
                            А в документации по erlang этот вопрос как-то обсуждается? А то не ясно, что это за куки, какой может быть длины, какая длина критична для взлома и т.д.
                            • 0
                              Я не встречал. Немного описано здесь:
                            • 0
                              Я не встречал. Немного описано здесь:
                              erlang.org/doc/getting_started/conc_prog.html

                              У меня лично создалось впечатление, что это не серьезная защита от взлома, а просто минимально необходимая. Если вы хотите действительно надежное решение, до немного доработав Erlang можно перейти на общение нод через SSL. Поищите по этой теме в google.
                            • +1
                              Я обратил внимание на следующий факт.
                              Если не создать файл .erlang.cookie в каталоге $HOME, то при запуске распределенной ноды (когда запускаем erl с параметром -sname или -name и именем ноды) он будет создан автоматически и куки будет сгенерирован. Алгоритм генерации куки можно найти в файле C:\Program Files\erl5.6.5\lib\kernel-2.12.5\src\auth.erl. По исходному тексту, сгенеренный куки — это строка заглавных латинских букв длинной в 20 символов.
                              • 0
                                Спасибо за информацию! Не знал.
                                • 0
                                  Я сам совершенно случайно на это наткнулся. По Вашей инструкции сделал .erlang.cookie, но не знал что вписать и вписывать не стал, решил почитать литературу и потом вписать. Но по одному из туториалов, где демонстрировалось создание распределенной ноды, я запустил erl -name ping и у меня оболчка не запустилась, а выдала длинное ругательство и упала с дампом. Прочтя ругательства, я понял, что длина строки куки слишком мала, тогда я сделал поиск в исходниках по строке с ошибкой и все стало понятно.
                                  • 0
                                    Интересно, как вы пришли к открытию! :) Достойно!
                          • 0
                            На английском мне понравилось это введение: Thinking in Erlang, Robert Baruch
                          • 0
                            Я так глазами пробежался. Пара вопросов, возможно ответы были не заметил.
                            Какие http сервера у вас? yaws, nginx или какая-то связка?
                            Вы используете какой-то фреймворк (erlyweb например) или сами все пишете?
                            И последний совсем наглый: вы в конторе серверную часть ерланговскую под виндой писали?
                            • 0
                              Мы используем nginx для балансировки нагрузки между серверами и работы с SSL (https). За nginx стоит mochiweb. Фрэймворки не используем. И… да, мы разрабатываем под Винду, а работает под Ubuntu :)
                            • 0
                              Мы используем nginx для балансировки нагрузки между серверами и работы с SSL (https). За nginx стоит mochiweb. Фрэймворки не используем. И… да, мы разрабатываем под Винду, а работает под Ubuntu :)

                              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.