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

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

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

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

Что такое устойчивость программного обеспечения?

В блоге Института программной инженерии Университета Карнеги-Меллона указывается:

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

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

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

Факторы устойчивости программного обеспечения

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

Приведенные ниже примеры относятся к электронной коммерции, поскольку я работаю в сфере электронной коммерции модной одежды уже почти 9 лет.

Давайте начнем.

Постепенное развертывание / развертывание

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

Дело в том, что даже если это ручная задача, это очень важно для отказоустойчивого программного обеспечения. Представьте, что вы меняете Платежный шлюз для веб-сайта электронной коммерции. Если вы совершите большой взрыв, 100% транзакций пойдут с бывшего платежного шлюза A на новый платежный шлюз B, вы попадете в серьезные неприятности.

Но если вы можете опробовать 1% клиентов в течение 1 недели, устраните любые ошибки с помощью интеграции нового шлюза, она очень помогает, а радиус действия составляет всего 1% транзакций.

Медленно, неделя за неделей, вы можете переходить от 1 к 5, затем к 10 и, наконец, к 100 с полной уверенностью. По той же логике выполняется проверка работоспособности при развертывании. Если проверка работоспособности завершается неудачно, развертывание автоматически откатывается. В зависимости от сервисов, которые вы используете, вы можете даже делать постепенное развертывание, что означает, что эта конкретная версия получает только 2% трафика. Постепенное развертывание поддерживается такими сервисами, как Google Cloud Run на уровне инфраструктуры, а не на уровне кода.

Еще одно важное соображение в отношении отказоустойчивого программного обеспечения - развертывание - это не выпуск.

Повторите попытку для повышения устойчивости программного обеспечения

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

Если не удается создать обзор, вы можете легко повторить попытку еще 1-2 раза, чтобы получить успешный ответ.

Ниже приведен очень простой пример завитка:

Здесь curl всегда будет повторять 3 попытки, так как он вернет ошибку 500. Приведенный ниже локон будет запущен только один раз, так как с первой попытки он вернется на 200:

Повторная попытка - простой, но эффективный способ повысить отказоустойчивость программного обеспечения.

Таймауты

Внешние системы могут работать медленно, и вы не можете контролировать их время отклика. Это, в свою очередь, может замедлить работу систем, которые вы разрабатываете. Когда-то мы интегрировались с «популярной» курьерской службой. К сожалению, их время ответа на отправку было в секундах, а не в миллисекундах.

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

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

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

Отступать

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

Другой пример с точки зрения чистого кода может быть таким же простым, как:

В приведенном выше фрагменте кода он ищет fees.shipping, если он недоступен, возвращается к значению 10.00. Мы могли бы реализовать то же самое для вызова API, если мы не получим желаемый результат от вызова API, он будет постепенно снижаться до использования значения по умолчанию.

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

Это может вызвать проблемы для систем с высокой посещаемостью.

Идемпотентные операции обеспечивают отказоустойчивость программного обеспечения

Один ответ переполнение стека хорошо подводит итог:

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

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

Вы разрабатываете API, чтобы пометить сообщение как прочитанное.

Независимо от того, сколько раз вызывается API, чтобы пометить это отдельное сообщение как прочитанное, первое переведет его из непрочитанного в прочитанное, а все остальные не изменят состояние.

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

Транзакция базы данных

Самый простой способ понять транзакции базы данных - all or nothing. Если у вас есть 3 шага для выполнения задачи, а на шаге 2 есть проблема, выполняется откат всей операции.

Классический пример - перевод денег между 2 банковскими счетами: либо перевод идет полностью, либо ничего не происходит.

Не должно быть случая, когда деньги списываются со счета A, но не добавляются к счету B. Транзакции базы данных очень важны для согласованности данных.

При правильном использовании уровней изоляции мы можем использовать транзакции базы данных для противодействия условиям гонки. Например, 20 записей должны быть обновлены cron, и для флага synced будет установлено значение true, если строки успешно синхронизированы с другой системой, такой как ERP. Это можно сделать, выполнив следующие действия, чтобы другой cron не выполнял ту же задачу одновременно:

  1. Подготовьтесь к основной задаче, такой как синхронизация этих строк с программным обеспечением для планирования ресурсов предприятия (ERP).
  2. Начать транзакцию с базой данных
  3. SELECT ... FOR UPDATE с уровнем изоляции подтверждено чтение и более длительный, чем обычно, таймаут для сеанса
  4. Синхронизируйте строки с ERP
  5. Установите флаг синхронизации на 1 для выбранных строк с запросом на обновление
  6. Зафиксировать транзакцию
  7. Если есть какие-то проблемы, откатите всю транзакцию

Итак, в приведенном выше случае, если шаг 4 завершится неудачно, транзакция будет отменена. Пока строки заблокированы с помощью выбора для обновления, другой cron не сможет их прочитать, поскольку он заблокирован для UPDATE и выполняется с подтверждением чтения на уровне изоляции.

Это помогает в создании отказоустойчивого и отказоустойчивого программного обеспечения, останавливая синхронизацию одних и тех же строк дважды. Если другой cron даже по ошибке запускается во время работы первого, он будет ждать, пока эти строки освободятся, чтобы их прочитал новый запрос SELECT… FOR UPDATE.

Ограничение скорости

К настоящему времени вы наверняка выяснили, что для того, чтобы программное обеспечение было более отказоустойчивым, оно должно оптимально использовать ресурсы. Этот фактор, ограничивающий скорость, спасает наши ресурсы от неправильного использования. Например, вызов ограничения скорости Twitter API. Давайте возьмем пример /statuses/user_timeline в Twitter API, он говорит 900 запросов / 15-минутное окно (пользовательская авторизация) и 100 000 запросов / 24-часовое окно (уровень приложения). Поэтому, если вы как потребитель делаете более 900 вызовов для получения статуй пользователем, будет ответ с кодом состояния 429.

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

Если у вас есть хорошее ограничение скорости, другая служба начнет обнаруживать ошибки раньше и сможет быстрее решить проблему.

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

Другие вещи, которые следует учитывать

Для повышения отказоустойчивости программного обеспечения необходимо учитывать еще много вещей. Разделение базы данных для чтения и записи - хорошая практика. Где есть одна главная база данных для записи и несколько реплик для чтения.

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

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

Другой важный паттерн устойчивости программного обеспечения - это паттерн Автоматический выключатель.

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

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

Устойчивые программные системы автоматически масштабируются. Они складывают ресурсы в зависимости от нагрузки. Этот момент также связан с масштабируемостью программного обеспечения, как правило, масштабируемость и отказоустойчивость программного обеспечения идут рука об руку. Системы автомасштабирования зависят от проверок работоспособности.

Чтобы системы были устойчивыми к нагрузке, они должны иметь возможность добавлять ресурсы при высокой нагрузке и уменьшать ресурсы по мере уменьшения трафика.

Это обеспечивает отказоустойчивость программного обеспечения и оптимальные затраты.

Вывод

Устойчивость и самовосстановление программного обеспечения очень важны для продолжительной безотказной работы.

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

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

Первоначально опубликовано на https://geshan.com.np.