Статьи

Erlang: обновление в реальном времени

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

Языковые различия

У каждой платформы есть свой способ обновления:

  • Java и JVM первоначально требовали перезапуска виртуальной машины Java, но теперь в более сложных средах допускается «горячее» подключение новых классов или выгрузка, перезапуск и перезагрузка отдельных служб (OSGi).
  • PHP, Ruby и другие интерпретируемые языки обычно могут просто переключать символическую ссылку со старой версии приложения на папку, содержащую новую.
  • Приложения C и C ++ требуют перезагрузки, если они не предназначены для этого (например, Chrome)

Эрланг имеет опыт работы в телекоммуникационной системе, и во многих случаях невозможно заблокировать такую ​​систему во время обновлений; Например, это будет означать сброс всех телефонных звонков в локальной области.

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

Семантика

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

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

После обновления:

  1. любой новый вызов из модуля в anotherModule: function () приведет к вызову новой версии функции.
  2. Внутри модуля только полностью определенные вызовы модуля формы : function () приведут к использованию новой версии модуля.

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

Бегущий пример

Вот модуль, который содержит шов для обновления:

-module(loading_12).
-export([wait_until_correct_answer/0]).

answer(_Question) -> 0.

wait_until_correct_answer() ->
  Answer = answer("6 times 7?"),
  io:format("Answer is: ~w~n", [Answer]),
  check_answer(Answer).

check_answer(42) ->
  ok;
check_answer(_) ->
  timer:sleep(2000),
  loading_12:wait_until_correct_answer().

Шов — это вызов load_12: wait_until_correct_answer () в конце модуля. wait_until_correct_answer / 0 продолжает опрашивать ответ / 1, пока не выдаст правильный результат.

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

[18:35:56][giorgio@Galen:~/erlang-series/src]$ erl
Erlang R14B02 (erts-5.8.3) [source] [smp:2:2] [rq:2] [async-threads:0] [kernel-poll:false]

Eshell V5.8.3  (abort with ^G)
1> c(loading_12).
{ok,loading_12}

Мы можем создать основной цикл в другом процессе, чтобы продолжить работу в оболочке:

2> spawn(loading_12, wait_until_correct_answer, []).
Answer is: 0
<0.39.0>

Процесс продолжает печатать каждый неверный ответ:

Answer is: 0
Answer is: 0
Answer is: 0
Answer is: 0
Answer is: 0
Answer is: 0

Теперь мы можем исправить модуль, вернув 42 из answer / 0, и перекомпилировать его, что приведет к загрузке новой версии.

3> c(loading_12).
{ok,loading_12}

Основной цикл получает правильный ответ и останавливается.

Answer is: 42
4>

Больше обновлений

Книга по программированию на Erlang даже объясняет шаблон для обновлений, который позволяет вам выполнить обновление после загрузки кода, отправив сообщение в цикл.
Этот шаблон состоит из специального сообщения * об обновлении, которое обрабатывается иначе, чем процесс цикла: это единственное сообщение, которое после обработки вызывает полностью определенные вызовы, такие как module: loop (State) вместо loop (State).

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

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

Выводы

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

Код для этой серии доступен на GitHub .