avlasov (avlasov) wrote,
avlasov
avlasov

Мини-учебнег по распределенным системам/протоколам. FIFO и RPC

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

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

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

Посему хотя наверное минимальной операцией на уровне сети будет негарантированная доставка сообщения - типа UDP, и оному есть применения, все же обычно хотелось, чтобы сообщения не терялись, не размножались и не перепутывались. Протоколы типа TCP это обеспечивают. Единственное, что если соединение оборвалось, то мы не можем знать были ли доставлены последние сообщения - разве что мы получили явное или косвенное подтверждение этому от принимающей стороны. Но будем уже считать это проблемой восстановления после сбоев, которую мы рассмотрим отдельно.

Итого помимо негарантированной доставки пакетов, мы хотели бы иметь FIFO канал. Причем, в случае двух нод этого достаточно. Да и если мы, к примеру, раскидываем запросы по разным workers, делаем load-balancing, нам этого тоже может оказаться достаточно.

Рассмотрим как можно реализовать FIFO канал. Мы можем завести счетчик и нумеровать каждое отправляемое сообщение. Соответственно, принимающая сторона сможет детектить пропуски и дублирующиеся сообщения, и восстанавливать порядок, если он был нарушен. Т.е. допустим мы получили сообщение с номером 10, а до этого получили сообщение с номером 6, то мы пропустили 3 сообщения с номерами 7, 8 и 9. Тогда мы посылаем на передающую сторону специальный запрос с просьбой переслать сообщения с 7 по 9. Ну и соответственно, передающая сторона возьмет их из буфера и перешлет (возможно, пометив специальным флажком, что это дубль).

Видно, что передающая сторона должна хранить историю сообщений - но это может стать накладным. Поэтому принимающая сторона может периодически сообщать о том, какое последнее сообщение она получила. Таким образом передающая сторона может чистить буфер.

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

Мы так же можем организовать Remote Procedure Call: т.е. послать специальное сообщение другой ноде - запрос, у идентификатором запроса, и подождать ответа на конкретный запрос. Если мы получили ответ, то оно нам гарантирует, что запрос получен и исполнен. Это также снимает проблемы с (не)казуальным порядком доставки сообщений - ведь прежде чем послать сообщение какой-то другой ноде, мы сперва убеждаемся, что первая нода получила наше первое сообщение. Если мы после получения ответа от первой ноде, пошлем сообщение другой ноде, а та пошлет сообщение ноде первой, то это все будет после того, как будет доставлено самое первое сообщений. Ну и у первой ноды не будет никаких сюрпризов.

Это все замечательно, но нам нужно ждать ответа, что увеличивает время прохождения сообщения через систему. Вообщем, RPC конечно хорошо, но все же через передачу сообщений в общем случае будет быстрее. Но возникают проблемы с казуальным порядком доставки сообщений.

Рассмотрим еще раз пример. А посылает сообщение m1 процессу Б, затем сообщение m2 процессу В, В посылает m3 процессу Б. Процесс Б может получить сообщения m1 и m3 в разном порядке. Хотя если мы используем RPC вместо простой передачи сообщения, то Б сперва получит m1. Таким образом, программисты, привыкшие иметь дело с вызовом функций могут удивиться, если Б сперва получит m3. Ну и код может выдать ошибку. Конечно, тут можно поправить код - можно на процессе Б дождаться прихода обоих сообщений, ну или знать что они могут прийти в разном порядке, и обрабатывать их соответственно. Но все время помнить об этом довольно накладно, тем более, что могут быть сильно более заморочные случаи неказуальной доставки сообщений.

Вообщем, казуальный порядок доставки сообщений - штука полезная, хотя можно обойтись и без него, например, используя RPC. Но только это может оказаться существенно медленнее.
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 0 comments