Установка правильных значений может вызвать затруднения. Вам ведь не хочется, чтобы предохранитель срабатывал слишком быстро, и в то же время не хочется слишком долго ждать его срабатывания. Более того, перед возобновлением отправки трафика хочется убедиться в том, что нижестоящий сервис восстановил нормальную работоспособность. Как и при выборе значений времени ожидания, я устанавливаю разумные параметры по умолчанию и использую их повсеместно, а затем изменяю для каждого конкретного случая.
При срабатывании предохранителя у вас есть выбор из нескольких вариантов. Один из них предполагает выстраивание запросов в очередь с последующей повторной попыткой их отправки. Для некоторых сценариев этот вариант вполне приемлем, особенно если вы выполняете работу, являющуюся частью асинхронного задания. Но если этот вызов был сделан как часть цепочки синхронных вызовов, то лучше будет, наверное, как можно скорее констатировать сбой. Это может означать распространение ошибки вверх по цепочке вызовов или более тонкое снижение уровня функциональности.
Располагая таким механизмом (аналогичным домашним предохранителям), мы можем воспользоваться им вручную, чтобы обезопасить свою работу. Например, если в ходе обычного обслуживания системы нужно выключить микросервис, можно вручную перевести в сработавшее положение предохранители зависимых систем, чтобы они смогли быстро констатировать сбой, пока микросервис находится в отключенном состоянии. После его возвращения в рабочее состояние мы можем восстановить сработавшие предохранители и все должно вернуться в нормальное рабочее состояние.
В книге Release It! Нигард дает описание концепции переборок, применяемых в качестве способа изоляции от сбоев. В судостроении переборка является составной частью корабля, которую можно закрыть для защиты помещений корабля от поступления забортной воды. Если корабль дает течь, можно закрыть двери переборки. Часть корабля затопится водой, но все остальное будет не тронуто.
В понятиях архитектуры программ можно рассматривать множество различных переборок.
Возвращаясь к моему личному опыту, мы упустили шанс реализации переборки. Нам нужно было для каждого нижестоящего соединения использовать различные пулы соединений. Таким образом, при исчерпании одного пула соединений это не влияло бы на остальные соединения (рис. 11.3). Существовала бы гарантия того, что замедление работы нижестоящего сервиса повлияет только на один пул соединения, позволяя нормально обрабатывать другие вызовы.
Рис. 11.3. Использование по одному пулу соединения для каждого нижестоящего сервиса с целью создания переборок
Еще одним способом реализации переборок может стать разделение проблем. Разделением функций на отдельные микросервисы можно уменьшить влияние сбоя в одной области на работу других областей.
Внимательно изучите все стороны вашей системы, от которых можно ожидать сбоя, как внутри микросервисов, так и между ними. Установлены ли между ними переборки? Советую начать с разделения пулов соединений, выделяя отдельный пул для каждого нижестоящего соединения. Но можно пойти и дальше и рассмотреть также возможность применения предохранителей.
Можно рассмотреть применение предохранителей в качестве автоматического механизма герметизации перегородки, то есть не только в качестве защиты потребителя от проблем, возникших в нижестоящей системе, но и в качестве потенциальной защиты нижестоящего сервиса от излишних вызовов, способных неблагоприятно воздействовать на него. Учитывая опасность каскадного сбоя, я бы порекомендовал обязательно применять предохранители для всех синхронных вызовов в адрес нижестоящих систем. Но вам не обязательно создавать собственные предохранители. У Netflix есть библиотека Hystrix с абстракцией предохранителя на JVM, поставляемой с эффективной системой мониторинга, есть и другие реализации для различных технологических стеков, например Polly для. NET или миксин circuit_breaker для Ruby.
Во многих отношениях переборки являются наиболее важной из этих трех схем. Настройки времени ожидания и предохранители помогают вам высвободить ресурсы при их истощении, а переборки в первую очередь могут обеспечить невозможность их истощения. Библиотека Hystrix, к примеру, позволяет реализовать переборки, которые фактически при определенных условиях отклоняют запросы, обеспечивая тем самым дальнейшее истощение ресурсов; этот прием называется сбросом нагрузки. Иногда отклонение запроса является наилучшим способом уберечь важную систему от перегрузки и превращения в узкое место для множества вышестоящих сервисов.
Чем больше один сервис зависит от задействования других сервисов, тем больше благополучная работа одного сервиса влияет на выполнение задач другими сервисами. Использование технологий интеграции, позволяющих переводить нижестоящий сервер в режим автономной работы, может снизить вероятность влияния простоев, как плановых, так и внеплановых сбоев на вышестоящие сервисы.
Повышение изолированности сервисов друг от друга дает еще одно преимущество, заключающееся в существенно меньших потребностях в координации усилий между владельцами сервисов. Чем меньше эти потребности, тем большей автономностью будут обладать команды и тем больше возможность свободно распоряжаться своими сервисами и развивать их.
При проведении идемпотентных операций результат после первого применения не меняется, даже если операция последовательно выполняется еще несколько раз. Если операции обладают идемпотентностью, мы можем повторять вызов несколько раз без негативного воздействия. Это нам очень пригодится, если необходимо повторно воспроизвести сообщения, когда нет уверенности, что они обработаны. Это является весьма распространенным способом восстановления после ошибок.
Рассмотрим простой вызов с целью добавления баллов в результате размещения заказа одним из наших клиентов. Мы можем сделать вызов с полезной нагрузкой, показанной в примере 11.1.
Пример 11.1. Зачисление баллов на счет
Если этот вызов будет получен несколько раз, мы столько же раз зачислим 100 баллов. Получается, этот вызов не является идемпотентным. Но как показано в примере 11.2, при наличии дополнительной информации можно позволить банку бонусных баллов сделать этот вызов идемпотентным.
Пример 11.2. Добавление информации к зачислению баллов с целью придания идемпотентности этой операции
Теперь мы знаем, что это зачисление относится к конкретному заказу под номером 4567. Учитывая, что получить бонус за конкретный заказ можно только единожды, мы можем применить зачисление еще раз без увеличения общего количества баллов.
Этот механизм работает и при организации сотрудничества на основе событий и может быть особенно полезен при наличии нескольких экземпляров одного и того же вида сервиса, подписанного на события. Даже при сохранении сведений о том, какие события были обработаны, при некоторых формах доставки асинхронных сообщений могут создаваться небольшие окна, в которых одно и то же сообщение может попадать в поле зрения двух исполнителей. Обрабатывая события идемпотентным образом, мы гарантируем, что они не станут источником ненужных проблем.
Некоторые недопонимают эту концепцию, полагая, что последующие вызовы с такими же параметрами не смогут оказывать какого-либо влияния, оставляя нас в интересном положении. Например, нам по-прежнему хотелось бы, чтобы вызов был получен нашими журналами. Нужно записать время отклика на вызов и собрать данные для мониторинга. Ключевым моментом здесь является то, что рассматриваемые бизнес-операции, которые мы считаем идемпотентными, не распространяются на полное состояние системы.
Некоторые HTTP-глаголы, например GET и PUT, определены в HTTP-спецификации в качестве идемпотентных, но, чтобы это произошло, сервис должен обрабатывать их идемпотентным образом. Если вы начнете отказывать им в идемпотентности, а вызывающая сторона будет уверена в безопасности их повторного применения, может возникнуть запутанная ситуация. Следует запомнить, что сам факт использования HTTP в качестве основного протокола еще не означает, что вы все получите, не прилагая дополнительных усилий!
В основном масштабирование систем выполняется по двум причинам. Во-первых, для того, чтобы легче было справиться со сбоями: если мы переживаем за отказ какого-либо компонента, то помочь сможет наличие такого же дополнительного компонента, не так ли? Во-вторых, для повышения производительности, что позволяет либо справиться с более высокой нагрузкой, либо снизить время отклика, либо достичь обоих результатов. Рассмотрим ряд наиболее распространенных технологий масштабирования, которыми можно будет воспользоваться, и подумаем об их применении к архитектурам микросервисов.
От наращивания мощностей некоторые операции могут только выиграть. Более объемный корпус с более быстрым центральным процессором и более эффективной подсистемой ввода-вывода зачастую способны уменьшить задержки и повысить пропускную способность, позволяя выполнять больший объем работ за меньшее время. Но такая разновидность масштабирования, которую часто называют вертикальным масштабированием, может быть слишком затратной: иногда один большой сервер может стоить намного больше, чем два небольших сервера сопоставимой мощности, особенно когда вы начнете получать по-настоящему большие машины. Иногда само программное обеспечение не способно освоить доступные дополнительные ресурсы. Более крупные машины зачастую предоставляют в наше распоряжение больше ядер центральных процессоров, но подчас у нас нет программных средств, позволяющих использовать такое преимущество. Еще одна проблема заключается в том, что такая разновидность масштабирования не в состоянии внести весомый вклад в устойчивость сервера, если у нас только одна машина! Несмотря на это, такой подход может принести быстрый выигрыш, особенно если вы используете провайдер виртуализации, позволяющий легко и просто изменять объемы ресурсов виртуальных машин.