Categories: Разработка

distributed-systems-checklist/

Большинство современных приложений представляют собой распределенные системы. Допустим, ваша компания делает «просто» приложение для мобильных устройств. Но помимо самого приложения, с которым работает пользователь, наверняка есть и какой-то сервер-сайд. Он состоит из балансировщиков нагрузки (например, Nginx ), некоторого количества микросервисов, а те в свою очередь ходят в некие СУБД ( PostgreSQL ), кэши ( Redis , Memcached ) и service discovery ( Consul ). СУБД скорее всего крутится не на одном сервере, а имеет энное количество реплик — для распределения нагрузки, аналитики и снятия бэкапов. По моему скромному опыту, многие люди не сильно задумываются над проблемами, которые могут возникать в подобных системах. Давайте же выясним, что это за проблемы.

Итак, вот примерный список:

  • Машины падают . Это самый простой сценарий, и большинство людей, к счастью, про него в курсе. Как следствие, исполнение любой программы или скрипта может быть прервано в произвольном месте. Отсюда в свою очередь есть ряд других следствий, например, что запись логически связанных данных должна производиться в базу в одной транзакции. Когда одна из машин в системе недоступна, система должна либо продолжать работать нормально, либо переходить в режим read only (если упал мастер СУБД), либо как-то иначе корректно обрабатывать эту ситуацию. После поднятия машины система должна приходить в консистентное состояние.
  • Вообще, железо дохнет . Падение машины является частным, и наиболее простым, случаем этого пункта. Все становится чуточку интереснее, когда железо дохнет частично . Например, зафиксированны случаи [PDF] , когда на жестких дисках портились отдельные байты, и файловая система этого не ловила. (Кстати, если вы используете «облачные» диски типа EBS, то там вообще нет каких-либо гарантий в отношении поведения диска.) Отсюда следует, что из базы данных нужно периодически вычитывать «холодные» данные и проверять их целостность. По той же причине бэкапы должны храниться больше, чем в одном месте, а их целостность и вообще возможность восстановится должны регулярно проверяться. Если в бэкапе испорчен случайный байтик, процедура восстановления должна завершаться с соответствующей ошибкой. Аналогичным образом нельзя полагаться на контрольные суммы TCP-пакетов .
  • Нетсплиты . Смерть роутера может приводить к ситуации, когда один или несколько серверов оказываются отрезаны от остальной сети. Типичная стратегия на этот случай заключается в том, что часть системы, оставшаяся в меньшинстве, должна немедленно завершиться, а остальная часть системы продолжить работу. После восстановления работы сети система должна приходить в консистентное состояние.
  • Сообщения теряются . Компоненты системы в том или ином виде обмениваются какими-то сообщениями. Любое сообщение может потеряться, и эта ситуация должна корректно обрабатываться. Возможных причин потери сообщений много — уже упомянутые падения машин и нетсплиты, перезапуск/обновление приложения на принимающей стороне, высокая нагрузка на приложение, и так далее. Для UDP потеря пакета вообще является штатной ситуацией. Если на отправленное сообщение ожидается ответ, на его получение должен стоять некий таймаут. Потеря сообщения не должна приводить к неконсистентности приложения.
  • Порядок и время доставки сообщений не гарантируется . Если только это как-то явно не реализовано в приложении. Рассмотрим простейший сценарий. Сервис A шлет сообщение M1 сервису B, при получении которого B шлет M2 сервису C. Одновременно A шлет сообщение M3 напрямую С. Нет абсолютно никаких гарантий касаемо порядка или времени доставки M2 и M3 (ровно как и самого факта доставки, см выше). Если же мы говорим о UDP, в нем изменение порядка сообщений является совершенно штатной ситуацией.
  • Сообщения могут дублироваться . Опять же, это совершенно штатная ситуация для UDP. Если же используется иной протокол, одно и то же сообщение может приходить больше одного раза как часть стратегии ретрая (если ответ на первое сообщение не был получен в заданный таймаут — он мог просто потеряться, см выше) или как результат действий злонамеренного пользователя. Система должна обеспечивать идемпотентность обработки сообщений. То есть, повторная обработка сообщения не должна приводить к каким-либо новым эффектам по сравнению с первой обработкой. Часто для этого используют уникальные request id и реализуют механизм дедупликации сообщений.
  • Malicious ноды . Если модель угроз системы включает в себя наличие мелишес (злонамеренных) узлов, и система должна сохранять корректную работу при наличии таких узлов, то система называется византийской системой . Мелишес нода может творить любую фигню — посылать сообщения с мусором, обрабатывать сообщения очень медленно, дублировать сообщения, и так далее. Большинство реальных систем не являются византийскими. В них предполагается, что всем узлам системы можно доверять. Тем не менее, даже в таких системах может возникнуть мелишес нода, например, если одну из нод забудут обновить вместе с остальными. На тестовом окружении мелишес нодой является QA. Любая система, византийская или нет, должна быть готова к получению сообщений (или чтению данных с диска) в старом формате , получению испорченных данных, и так далее.
  • Дэдлоки . В распределенных системах часто возникают сценарии, при которых нода A шлет сообщение ноде B, та шлет ноде C, эта нода шлет сообщение ноде A, а нода A висит в ожидании ответа от B. Классический дэдлок. Это решается реализацией механизма deadlock detection или loop detection. В простейшем случае можно просто отваливаться по таймауту, но такое решение подходит не для всех систем. Более сложное решение заключается в передаче с каждым сообщением уникального trace id запроса. Если ноде приходит сообщение с trace id, который нода уже обрабатывает, это является признаком дэдлока. Возможны и иные подходы, в зависимости от специфики приложения.
  • Несколько ДЦ . Если система работает в нескольких датацентрах, то возникает ряд проблем. Как минимум, это большие сетевые задержки между ДЦ, порядка сотен миллисекунд. Как следствие, если пользователь ходит с запросами на запись сразу в несколько ДЦ, то возникает проблема консистентности данных. Кроме того, возможно падение сразу целого ДЦ, например, в результате отключения питания. В зависимости от специфики приложения могут быть и другие нюансы. Например, юридические — где территориально по закону должны хранится персональные данные конкретного пользователя.
  • Консистентное видение данных . Рассмотрим классический пример. Есть интернет-магазин. Пользователь кладет товар в корзину (запрос на запись). Затем открывает корзину (запрос на чтение) и не видет в ней товара. Происходит это по той причине, что запросы на запись идут в мастер PostgreSQL, а запросы на чтение — в асинхронную реплику. Репликация затупила, товара в корзине не видно. При дизайне системы необходимо четко определить, какие уровни изоляции (serizlizable, snapshot isolation, …) и в каких случаях она предоставляет. Проблема особенно актуальна для систем с шардингом и распределенными транзакциями .
  • Время относительно . Вы не можете полагаться на то, что часы на всех нодах показывают одно и то же время, идут с примерно одинаковой скоростью и вообще, что время идет линейно. Внезапный перевод часов вперед или назад, замедление или ускорение часов никак не должно сказываться на функционировании системы.
  • Каскадные сбои . Все описанное выше может происходить одновременно или в короткий промежуток времени, в случайном порядке. Например, сеть мигает и какая-то из машин в то же время перезагружается. Не такой уж невероятный случай при сбое питания. Или другой пример — сервер СУБД перезапускается, начинается процедура recovery. Во время работы этой процедуры машина снова перезагружается. Если система устойчива к одной из описанных выше проблем, это еще не значит, что она устойчива к их комбинациям. Таким образом, имеем комбинаторный взрыв с точки зрения тестирования и документирования поведения системы. Отсюда возникают Jepsen-подобные тесты, гоняемые на стендах по несколько месяцев, а также Erlang’овый принцип let it crash.

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

А что бы вы добавили к приведенному списку, и с чем из перечисленного вам доводилось сталкиваться на практике?

admin

Share
Published by
admin

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

4 недели ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

4 недели ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

4 недели ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

4 недели ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

4 недели ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

4 недели ago