Существует много способов обновить работающую систему, и одним из ключевых моментов в этих обновлениях является предоставление сервиса для конечных пользователей как можно дольше. В этом контексте доступность — это процент времени, в течение которого система работает, или, что эквивалентно, вероятность обнаружения системы, работающей в случайное время.
Языковые различия
У каждой платформы есть свой способ обновления:
- Java и JVM первоначально требовали перезапуска виртуальной машины Java, но теперь в более сложных средах допускается «горячее» подключение новых классов или выгрузка, перезапуск и перезагрузка отдельных служб (OSGi).
- PHP, Ruby и другие интерпретируемые языки обычно могут просто переключать символическую ссылку со старой версии приложения на папку, содержащую новую.
- Приложения C и C ++ требуют перезагрузки, если они не предназначены для этого (например, Chrome)
Эрланг имеет опыт работы в телекоммуникационной системе, и во многих случаях невозможно заблокировать такую систему во время обновлений; Например, это будет означать сброс всех телефонных звонков в локальной области.
Таким образом, Erlang построен таким образом, что можно заменять модули во время выполнения процесса, и существует явная семантика, когда происходит замена (не просто случайно при загрузке кода).
Семантика
В частности, Erlang предлагает концепцию старой и новой версии модуля: в любой момент на одной машине могут работать две разные и последовательные версии модуля.
Конечно, процессы, которые работают со старой версией модуля, нуждаются в шве, куда можно вставить новый код. Это не происходит внутри выполнения функций, а только при новых полностью определенных вызовах; передаваемые структуры данных остаются неизменными, и программа никогда не останавливается (поэтому сигнатуры функций шва должны оставаться идентичными).
После обновления:
- любой новый вызов из модуля в anotherModule: function () приведет к вызову новой версии функции.
- Внутри модуля только полностью определенные вызовы модуля формы : 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 .