Saturday, February 02, 2008

Erlang: распределенные приложения

Основная фишка erlang — это все-таки его жуткая распределенность.

Модель программирования основана исключительно на посылке сообщений. То есть никакой расшаренной памяти, никаких,
мьютексов, евентов и прочей ерунды, обычно ассоциирующейся с параллельными (concurrent) приложениями.

Примитивных операций есть три:
- можно создать процесс с помощью функции spawn:

spawn(Function)

- можно ему послать сообщение с помощью выражения !

Pid ! Message

- и получить сообщение

receive
    Pattern1 -> ...
    Pattern2 -> ...
    Pattern3 -> ...
after N ->
    ...
end

 

    Конструкция receive очень похожа на выражение case, только для обработки таймаутов можно добавить кейс after N,
где N — время в мс, по истечении которого перестаем ждать ответа.

[Избитый] простейший пример, с процессами, пингующими друг друга:

-module(ping_test).
-compile(export_all).

ping() ->
% создаем процесс pong
    Pid = spawn(fun pong/0),
% посылаем ему сообщение -- атом ping и свой Pid
    Pid ! {ping, self()},
% ждем от него сообщений
    ping_loop(Pid).

% обработка сообщений от процесса pong
ping_loop(Pid) ->
    receive
        {pong, Pid} ->
% получили сообщение pong и Pid отправителя
            io:format("received pong~n"),
% ждем случайный интервал времени от 0 до 1200 мс.
            timer:sleep(random:uniform(1200)),
% шлем ему опять сообщение ping
            Pid ! {ping, self()},
            ping_loop(Pid) % и опять ждем...
        after 1000 ->
            io:format("pong timed out~n")
    end.

% входная точка процесса pong
pong() ->
    receive
        {ping, Pid} ->
% получили сообщение ping и Pid отправителя
            io:format("received ping~n"),
% подождем..
            timer:sleep(random:uniform(1200)),
% шлем обратно pong
            Pid ! {pong, self()},
            pong() % цикл
    after 1000 ->
        io:format("ping timed out~n")
    end.


 

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

1> c(ping_test).
{ok,ping_test}
2> ping_test:ping().
received ping
received pong
received ping
received pong
received ping
received pong
received ping
connection to pong timed out
ok
3> connection to ping timed out

 

Рантайм обеспечивает, что сообщения доставляются в том же порядке, как и были отправлены, и никогда не выпадают из последовательности (то есть, если процесс N+1 сообщение не будет доставлено, если пропало N-ное).

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

Чтобы превратить пример с ping в распределенное приложение, нужно просто заменить вызов

spawn(fun ...)

на

spawn(node, fun ...)

Где node это атом-имя узла на котором требуется запустить процесс. виртуальной машины. (А узел (node) — это просто запущенный экземпляр виртуальной машины Erlang).

Итак, немножко изменим функцию ping (модифицированный исходник тут):

ping(Node) ->
    Pid = spawn(Node, fun pong/0),
    Pid ! {ping, self()},
 ping_loop(Pid).

Теперь запустим два интерпретатора в качестве узлов с именами boo и foo (оба должны иметь доступ к скомпилированному модулю, так что проще запустить их из одной директории):

$ erl -name foo@127.0.0.1
$ erl -name boo@127.0.0.1

(boo@127.0.0.1)2> c(ping_distrib). {ok,ping_distrib} (boo@127.0.0.1)1> ping_distrib:ping('foo@127.0.0.1'). received ping received pong received ping received pong received ping received pong received ping connection to pong timed out ok (boo@127.0.0.1)2> connection to ping timed out

Создание процесса в Эрланге — очень дешевая операция (цифры — порядка сотен тысяч созданных и убитых процессов в секунду, см. бенчмарк здесь ).

Запущенные процессы также жрут очень мало памяти, а в режиме ожидания сообщений — еще и не жрут CPU (например, 500000 таких спящих процессов у меня ест 0% CPU и 600 Мб памяти — около 1.4 кб на процесс).


Механизмы обработки ошибок в Erlang, конечно, тоже распределенные.

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

erlang-process-crash

Связываются процессы с помощью функции link/2. Для удобства есть еще spawn_link/2, и если ее использовать в нашем примере вместо spawn, а также выходить из процессов не нормально, а с помощью erlang:error/1 (см. модифицированный исходник тут), вывод будет немного другим:

4> ping_test:ping().
received ping
received pong
connection to ping timed out

=ERROR REPORT==== 2-Feb-2008::14:30:23 ===
Error in process <0.43.0> with exit 
    value: {ping_timeout,[{ping_test,pong,0}]}

** exception exit: pong
     in function  ping_test:pong/0

 

Не рекомендуется использовать другие механизмы, такие как коды ошибок — идеология "let it crash". Скажем, если функция принимает на вход строку — это проблема вызывающего не передавать туда атомы, числа и прочую хрень.

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


Wednesday, January 30, 2008

Синтаксис Erlang в двух словах.

Чтобы писать веб-приложения на эрланге, надо его хотя бы немного знать, поэтому попробую в пару постов уложить основные понятия.

Тут лучше конечно почитать какой-нибудь более серьезный туториал, начиная с собственно "Getting Started with Erlang" (русский перевод на рсдн), или "Thinking In Erlang". Однако ж, поскольку 30-50 страниц осилить не у каждого поднимется мозг, я здесь изобразил небольшую шпаргалку (не претендующую на полноценное введение).

В принципе Эрланг весьма прост, по крайней мере для тех, кто хоть краем глаза видел какой-нибудь другой функциональный язык.

Для начала самые примитивные вещи:

  • Прежде всего, Эрланг — это язык с динамической типизацией, никакой статической проверки типов тут нет.

  • Литералы-числа в Erlang: 1, 4, 15, 25, 16#1F (в шестнадцатеричной системе), 2#01110110 (в двоичной).
    > 1.
    1
    > 1+2.
    3
    > 1+1.5.
    2.50000
  • Литералы-символы: на самом деле это не отдельный тип, а просто целое число — код символа : $a, $b, $\n $_
    > $a.
    97
    > $\n.
    10
  • Списки задаются в квадратных скобках:
    > [].
    []
    > X = [1,2].
    [1,2].
    > [3 | X].
    [3, 1, 2]
  • Строки — "aba a", "ga a ga".
    > [$a, $b, $c].
     "abc"
    Строки — это на самом деле просто списки символов. Поэтому памяти они занимают немало — в общей сложности 8 байт на один символ строки (на 32-битной машине).
  • Битовые строки (binaries) — эффективное представление бинарных данных
    > <<1, 3, "ba">>.
     <<1,3,98,97>>
    
    Их можно сопоставлять с шаблонами, и быстро передавать туда-сюда. Очень полезная фича Эрланга (правда мы ее использовать сильно не будем).
  • Атомы
    abds
    more
    hello
    ok
    undefined
    
    Начинаются с маленькой буквы, и представляют собой что-то среднее между enum'ами и константными строками, пишутся без кавычек и удобны для разных меток и идентификаторов. Атомы очень похожи на строки, но их нельзя склеивать, вычислять длину и пр., длина их ограничена 256 байтами. Они существенно эффективнее строк в обращении.
  • Еще любые значения еще можно объединять в пары, тройки, четверки, пятерки и так далее, с помощью фигурных скобок: 
    {14, 15} 
    {0, "ba", 3} 
    {$a, [1,2], {3,4}} 
    

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

Имена функций и модулей обязательно начинаются с маленькой буквы. Сигнатура функции состоит из ее имени и количества параметров — то есть format/2 и format/3 это разные функции.

Паттерн матчинг (сопоставление с шаблоном) в Erlang выглядит так:

case A of
    Pattern1 -> ...;
    Pattern2 -> ...;
    Pattern3 -> ...;
end

В паттернах можно использовать значок _ который матчит все что угодно (wildcard). В жизни используется как-то так:

case Foo of
    {"HELLO", Name} -> send_response("Hello, " ++ Name);
    "BYE" -> send_response("Bye");
    _ -> send_response("Unknown command")
end

Еще паттерны можно использовать прямо в списке параметров функции:

foo({_, B}) ->
    ...;

foo(X) ->
    ...

что эквивалентно

foo(Y) ->
    case Y of
        {_, B} -> ...
        X -> ...
end.


Плюс, значения можно матчить с помощью знака "равно":

X = {1, 3},
{A, B} = X.

теперь A = 1, а B = 3

Причем, если переменная слева уже определена, в паттерне используется ее значение:

   > B = 4.

Получаем run-time ошибку badmatch, потому что B у нас уже равно трем.

С операторами все почти как обычно:

  • арифметические, +-/*%
  • сравнения =/ , ==, =>, =<
  • склеивание списков ++
  • декомпозиция списков (оператор cons) |
  • битовые band, bor, bxor, bnot

Вот, собственно, практически все. Раздражают в Эрланге поначалу только немного странные правила расстановки точек, запятых и точек с запятой. В двух словах — точка ставится в конце определения функции, запятая — между выражениями в определении функции, а точка с запятой разделяет выражения в case .. of и if.

 


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

> H = erlang:md5("boooo").
<<174,62,131,226,250,179,167,216,104,61,142,239,171,209,231,77>>

Нам же понадобится хэш в виде обычной строки, в шестнадцатиричном представлении, поскольку он используется в процедуре аутентификации на last.fm

Для начала можно преобразовать битовую строку в список байт с помощью стандартной функции:

> binary_to_list(H).
[174,62,131,226,250,179,167,216,104,61,142,239,171,209,231,77]

Теперь функция, которая преобразует 4 бита в один hex символ:

hex(V) ->
    if
    V < 10 ->
            $0 + V;
    true ->
        $a + (V - 10)
     end.

Да, if в erlang — лишь упрощенный вариант case .. of, где вместо паттернов — булевые выражения. Так что кейс "true ->" тут играет роль else

Теперь один байт можно преобразовать в hex так:

> E = 31.
> [hex(E bsr 4), hex(E band 16#F)].
"1f"

Ну и с помощью функции lists:foldl применить это ко всей исходной строке:

binary_to_hex(Bin) ->
    lists:foldl(fun (E, Acc) ->
            [hex(E bsr 4) | [hex(E band 16#F) | Acc]] end,
        [],
        lists:reverse(binary_to_list(Bin))).
% returns hex representation of md5 in reverse byte order
md5_hex(Bin) ->
    binary_to_hex(erlang:md5(Bin)).

Тут

  • fun (E, Acc) -> ... end
         это определение лямбда-функции
  • lists:foldl() это известный функциональным программистам foldl.
  • lists:reverse() разворачивает список задом наперед
  • Конструкция вида [A | B] приклеивает в начало списка B элемент A (см. cons).

Итого получаем

>md5_hex("booo").
"1c052f260d1b34423c32e7c7b29026b9"

Весь исходник (utils.erl)

Про ключевую фичу Erlang — поддержку распределенных приложений поговорим в следующей серии.


Crash Course: веб приложения на Erlang [Дисклеймер]

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

В общем, по мотивам своих экзерсисов в erlang, я решил написать небольшой туториал, такой crash course про erlang, причем как средство программирования именно веб-приложений. В качестве примера и сверхзадачи я выбрал веб-клиент (прокси) для социального интернет радио last.fm.

Плюс к этому, меня тут даже рекламировали как пишущего про эрланг — будем оправдывать ;)

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

В конце концов можно слушать ласт.фм без установки клиента, например, на КПК/мобильнике с вайфаем.


Friday, December 14, 2007

Erlang @ Amazon

А между тем замечательный язык erlang действительно напрашивается на то чтоб стать next big thing в веб-приложениях. Последнее время все больше и больше интересных новостей из этой области — особенно тут отличился Amazon.

Однако ж обо всем по порядку.

Последнее время Amazon становится одним из основных ньюсмейкеров со своими весьма инновационными сервисами по хостингу данных и приложений.

Первой ласточкой был EC2 — нечто, крайне напоминающее обычный VPS хостинг — амазон предоставляет вам в пользование виртуальные машины с операционной системой на выбор, однако деньги берет не помесячно, а за машино-часы и переданные гигабайты (от $0.10 до $0.80 за машино-час, плюс $0.10-$0.20 за переданный гигабайт).

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

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

Народ сразу оживился — действительно, EC2 и распределенный erlang будто созданы друг для друга. Можно запускать сколько нужно нодов на нужном количестве инстансов EC2, при этом не заморачиваясь никакими оргвопросами связанными с покупкой и администрированием настоящих серверов.

Второе — это S3, сервис по хранению данных. Деньги амазон берет за количество запросов + гигабайты в месяц, без всяких фиксированных тарифных планов.

И теперь амазон объявляет о запуске еще одного сервиса под названием SimpleDb — и все встает на свои места.

Это база данных, однако больше всего она похожа на большой excel-евский spreadsheet:

  • данные организуются в таблицы (которые называются доменами), 
  • никаких сложных запросов, но работает, говорят крайне быстро -- чуть ли не реалтайм;
  • в одной ячейке можно хранить несколько значений (как список или кортеж)
  • никаких типов у хранимых данных нет
  • таблицы распределены по многим серверам

Если кто читал про ерланговскую родную базу данных mnesia, сразу увидит много знакомых фраз. Точнее, практически все эти фичи есть и у mnesia.

Вкупе с распределенным подходом в EC2 это начинает казаться не случайным совпадением — похоже, в амазоне появилась ячейка любителей ерланга.

И действительно, в блоге амазоновского разработчика находим подтверждение: SimpleDb построена на erlang!

Если SimpleDb будет популярна — а предпосылки для этого есть (это ж Amazon) — это будет самый убийственный пример применения erlang в боевых условиях.

Да и просто факт, что уже доступна отличная и недорогая платформа для хостинга эрланговских приложений — уже очень хорошая новость.


Tuesday, November 27, 2007

Erlang Job

Для тех, кто не читает форумы рсдн (что, конечно, идеологически правильно): cовершенно неожиданно оказалось что Joel Reymont (чувак с покером на эрланге) родился в Санкт-Петербурге и отлично говорит по-русски. И вообще треть жизни прожил тут.

Оказалось это в связи с тем, что Джоель сейчас ищет программистов на Erlang и OCaml (пока part-time). На сайте у него говорится про разработку бэкенда для того самого OpenPoker, но из треда на рсдне видно что планы у него намного глобальнее.

Так как это уже не первое предложение работы на erlang на русском языке за последнее время, уже очень хочется это назвать тенденцией.


Thursday, November 08, 2007

Угадайка

Как думаете, какой язык поддерживает сразу

  • First-class функции, лямбды и замыкания;
  • параметризованные типы;
  • хвостовые вызовы;
  • array comprehensions;
  • ООП с классами и интерфейсами;
  • ООП на основе прототипов;
  • мультиметоды;
  • coroutines (оператор yield);
  • структурные и номинальные подтипы;
  • перегрузку операторов;
  • Nullable/Non nullable аттрибуты для всех типов.

Ну и по мелочи: for-each, array slicing а-ля питон, регулярные выражения, открытые пространства имен и пр.

Однако ж это все ожидается в чудесном новом Javascript ECMAScript 4!

Еще  одна из самых интересных возможностей в четвертой версии — опциональная статическая проверка типов. Это должно упростить написание сложных систем (засчет строго описанных интерфейсов), и помочь [jit] компиляторам иногда генерировать более эффективный код. Опциональные статические типы, похоже, становятся модными: в Python 3000 тоже не так давно добавили что-то похожее.

Четвертая версия пока получается довольно пестрой — появилась куча новых механизмов и языковых конструкций (см. выше), а совместимость с предыдущей версией надо, по возможности, сохранить — поэтому синтаксис местами получился довольно странный: скажем, [int] это массив целых чисел, а [int, string] — это уже кортеж из целого и строки.

Еще из забавных вещей — явная поддержка структурных типов. В частности, с помощью специального оператора like можно задать переменной структурный тип (то есть ей можно присвоить только объект, обладающий набором заданных свойств):

var foo : like {x : int, y : int};

А переменная, объявленная с ключевым словом wrap автоматически дополняет хранимый в ней объект нужными свойствами:

var bar : wrap foo { x : int, y : int} = {x : 10, y : 20}.

Может и не революционная фича, конечно, но выглядит крайне интересно.

В ECMAScript 4 обещают также обязательную оптимизацию хвостовых вызовов. В Python 3000 их точно не будет, с руби ситуация туманна —что делает ES4 лучшим выбором в качестве платформы для компиляторов с функциональных языков. В общем-то уже сейчас есть компиляторы, как минимум, OCaml, SML и Haskell, генерирующие код на ECMASCript 3.

Собственно мощь и текущей-то версии js во многом недооценена. Кстати, только я не знал, что автор один из авторов первой версии Javascript — Гай Стил мл., а в качестве основных источников вдохновения при разработке Javascript 3 авторы называют языки Scheme и Self?

Новый Javascript явно претендует на что-то большее. Если он действительно будет "принят на вооружение" — определенно можно ожидать появления изрядного количества серверных приложений и фреймворков на js. Во всяком случае, язык получился явно не слабее питона и руби — хотя, конечно, вряд ли средства интеграции с кодом на C и C++ будут тут столь же развиты, ниша у языка все-таки другая.

Ссылки: