Наша индустрия все больше и больше движется в сторону микросервисов. Но это также означает, что один запрос от пользователя может пройти несколько служб через сетевые запросы. Но сетевые запросы могут пойти не так. Что делает нашу общую систему более хрупкой. Вот несколько шаблонов того, как решить эту проблему и сделать нашу распределенную систему более надежной и отказоустойчивой.
Прежде всего, давайте классифицируем различные типы сетевых запросов.
- Читает
- Пишите с проверкой на нашей стороне
- Пишите с проверкой на их стороне
- Пишите и читайте в одном звонке
Чтение запросов
Запросы на чтение — это запросы, которые просто читают данные с другой стороны, не манипулируя ими. Чтобы сделать эти варианты использования более отказоустойчивыми, нам нужно как-то получить данные на нашей стороне. Вот 3 способа сделать это
Кэширование
Это может быть самым простым делом. Мы просто кэшируем исходящие сетевые вызовы. Этот шаблон очень прост в реализации, но недостатком является то, что ваши данные не свежие и, следовательно, могут быть неправильными. Кроме того, это эффективно только тогда, когда звонки часто одинаковы. Например, кэширование профиля пользователя, который используется только в одном вызове, может быть неэффективным. Этот подход работает довольно хорошо для данных, которые не меняются так часто и часто используются повторно. Например: допустим, наши приложения поддерживают несколько языков, и у нас есть центральный сервер, на котором мы храним все переводы. После того, как перевод установлен, очень маловероятно, что он изменится, и даже если он изменится, это не так критично, если перевод не будет применен немедленно. Также многим входящим запросам потребуются одни и те же данные. Таким образом, кэширование — идеальный способ сделать наши приложения отказоустойчивыми по отношению к центральной службе перевода.
Синхронизация данных
Вы также можете иметь планировщик, который запускается каждую ночь и извлекает данные с другой стороны и сохраняет их в своей собственной базе данных. Такой подход может иметь смысл, когда можно иметь данные 1-дневной давности, а сам вызов может занимать уже много времени. Это также дает вам возможность преобразовать данные в структуру, в которой они оптимизированы для вашего варианта использования, или вы все равно хотите обогатить данные. Например, другая сторона предоставляет большой список продавцов, но из-за структуры их базы данных один звонок может занять до 20 секунд. Если вы получаете эти данные в течение ночи, вы даже можете сопоставить заказы с идентификатором продавца и повысить согласованность.
Инверсия вызова
Третий и самый сложный способ — просто инвертировать вызов. Таким образом, вместо того, чтобы ваше приложение читало с другой стороны, другая сторона будет отправлять вам данные при каждом обновлении, а ваша служба хранит копию данных. Несмотря на то, что реализация этого подхода очень сложна, его преимущество заключается в том, что ваши данные почти свежие*, и он работает с данными, которые используются только один раз. Но будь осторожен. По сравнению с обычной сетью назовите вашу торговую согласованность с доступностью. Если другая служба не работает и больше не отправляет вам события, ваша служба этого не заметит. Поэтому, если у вас есть критические варианты использования, этот подход может быть не очень хорошим. Допустим, вы банк и вам нужно авторизовать транзакцию. Если вы сверитесь со старым балансом счета, вы можете позволить пользователю совершить транзакцию, даже если у него недостаточно средств.
Написать запросы
Сделать запросы на запись более надежными немного сложнее. Самое главное, что нам нужно, это то, что каждый вызов должен быть идемпотентным на стороне получателя.
Идемпотентность
для всех запросов на запись идемпотентность имеет решающее значение. Идемпотентность означает, что не имеет значения, как часто я звоню, общий результат одинаков. Допустим, я хочу отправить приветственное письмо новому клиенту. Это должно иметь значение, делаю ли я вызов 1 или 100 раз. Клиент должен получить только одно электронное письмо. Почему это важно? Другая служба может дать сбой несколькими способами. Например, это может быть внутренняя ошибка сервера или тайм-аут. В этих случаях ваша служба не будет знать, был ли вызов успешным или нет, и вы окажетесь в неизвестном состоянии. Ваше письмо было отправлено или нет? Если вы, например, отправляете идентификатор запроса с каждым запросом, вы можете просто повторить попытку с тем же идентификатором запроса, и другая служба либо обработает ваш запрос, либо пропустит его, если он уже был успешным.
Пишите с проверкой на нашей стороне
Допустим, у вас есть запрос на запись, который вы можете полностью проверить с точки зрения бизнеса на своей стороне. Единственное, что может встать у вас на пути, это технические трудности, которые со временем разрешимы. Например, другая служба в данный момент отключена. Решение состоит в том, что вы просто повторяете попытку в другой раз. И просто показать пользователю, что вызов прошел успешно. Что-то вроде притворяйся, пока не получится.
Очередь повторных попыток
вы можете повторить попытку, поместив свои запросы в какую-то очередь. Это может быть что-то вроде Amazon SQS или RabbitMQ. Таким образом, вместо того, чтобы делать прямой вызов службы, вы помещаете свою команду в очередь, и ваша служба затем захватит ее оттуда, попытается обработать ее, и если это не удастся, она вернет ее в очередь. Если запросы не выполняются более, скажем, 10 раз. Это поставит его в очередь недоставленных писем. Затем вы можете поместить сообщения обратно в основную очередь, когда проблема будет решена.
Задание Cron из базы данных
Это очень похоже на очередь повторных попыток. Вместо того, чтобы отправлять запрос в виде прямого вызова, вы сохраняете его в базе данных, и у вас есть запущенное задание cron, которое будет извлекать запросы из базы данных, обрабатывать их и в случае успеха либо удалять их, либо помечать как обработанные.
Оба подхода хороши, но будут очень сложными с точки зрения тестирования. Допустим, у вас есть компонентный тест, и вы не используете ни один из этих двух шаблонов. Ваш тест будет выглядеть очень легко. Вы издеваетесь над другой службой, делаете вызов своему приложению, ждете его завершения и проверяете, была ли вызвана имитация с правильными данными. Если теперь у вас реализован асинхронный механизм повторных попыток, вы не можете просто ждать завершения вызова, чтобы проверить, был ли вызван ваш макет, потому что вызов макета теперь отделен. Чтобы сделать это тестируемым, вам также нужно смоделировать либо ваш планировщик, либо реализацию вашей очереди, что значительно усложняет ваши тесты, но это выполнимо и, в конце концов, полезно, если вам нужна надежность. Кроме того, вы добавляете к своему сервису уровень косвенности, который делает код менее читаемым. Потому что вместо того, чтобы отправлять что-то напрямую в другую службу и ждать ответа. Теперь вы отправляете что-то в очередь, и читатель понятия не имеет, где эти сообщения потребляются. Поэтому убедитесь, что все ваши прослушиватели очередей или планировщики находятся в одном классе или пакете, и задокументируйте это. В противном случае читателю вашего кода будет трудно его прочитать.
Пишите с проверкой на их стороне
Итак, допустим, у нас есть вызов, в котором вы не можете знать, будет ли он успешным или нет. Это время, когда вам снова нужно найти компромисс между доступностью и согласованностью. Если это что-то критическое, вы хотите иметь последовательность, а это значит, что вам нужно изящно ошибиться и попросить пользователя высохнуть позже. Если сбой не критичен, вы можете отдать предпочтение доступности и подумать о резервных механизмах. В обоих случаях важно
Пригласите своего дизайнера
Если вы обнаружите эти сценарии, вы не сможете исправить их чисто технически. Подойдите к своему дизайнеру и скажите ему: Привет, вот вариант использования, который может пойти не так, что мы можем с этим сделать? Если ваш дизайнер знает, он может, например, разработать «состояние обработки», которое сообщает пользователю, что ему нужно подождать неизвестное время. Или, может быть, вы решите, что достаточно уведомления по электронной почте или push-over. Эти способы намного лучше, чем просто сбой с ошибкой, чтобы сообщить пользователю, пожалуйста, повторите попытку позже.
Пишите и читайте в одном звонке
Это вызовы, при которых другая сторона будет генерировать некоторые данные, необходимые пользователю для продолжения работы. К сожалению, нет другого пути, кроме провала. Поэтому вы должны попытаться уменьшить количество таких случаев, если это возможно, или найти изящный способ потерпеть неудачу.