Создание микросервисов — страница 17 из 69

о работать поверх сетевых протоколов, отличных от TCP.

Само же использование полезных нагрузок требует большего объема работы, чем тот, который предоставляется некоторыми RPC-реализациями, поддерживающими улучшенные механизмы сериализации и десериализации. Это само по себе может стать точкой связанности между клиентом и сервером, поскольку реализация приемлемых механизмов чтения данных является не такой уж простой задачей (о чем мы вскоре поговорим), но с точки зрения получения готовой работоспособной технологии они могут быть весьма привлекательными.

Несмотря на указанные недостатки, REST с использованием HTTP является вполне разумным исходным выбором для взаимодействия между сервисами. Если хотите углубить свои знания, я рекомендую почитать книгу REST in Practice (O’Reilly), в которой тема REST с использованием HTTP раскрывается намного лучше.

Реализация асинхронной совместной работы на основе событий

Мы уже немного поговорили о некоторых технологиях, содействующих реализации схем «запрос — ответ». А как насчет асинхронного обмена данными на основе событий?

Выбор технологии

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

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

Но со связующими системами нужно проявлять разумную осторожность, ведь брокер сообщений составляет лишь малую их часть. В череде поставок имеется еще множество весьма полезных программ. Поставщики, как правило, стремятся включить в пакет наряду с основной массу других программ, способных развить интеллектуальную составляющую, внедряемую в связующие системы, о чем свидетельствуют такие программы, как Enterprise Service Bus. Вы должны понимать, что именно приобретаете: связующие системы не должны проявлять какую-либо инициативу, а интеллектуальные компоненты должны оставаться только в конечных точках.

Еще один подход заключается в попытке использования HTTP в качестве способа распространения событий. Для публикации каналов ресурсов используется такая REST-совмеcтимая спецификация, как ATOM, в которой наряду с другими вещами определяется соответствующая семантика. Существует множество клиентских библиотек, позволяющих создавать и потреблять подобные каналы. Поэтому наш сервис обслуживания клиентов может просто опубликовать событие в таком канале при каких-либо происходящих в нем изменениях. Потребители просто подписываются на канал в поисках изменений. Тот факт, что мы можем воспользоваться уже существующей спецификацией ATOM и любой связанной с ней библиотекой, можно считать положительным, и нам известно, что HTTP весьма неплохо справляется с масштабируемостью. Но HTTP недостаточно хорошо справляется с требованиями малого времени ожидания (в чем преуспевают некоторые брокеры сообщений), и нам все еще нужно считаться с тем фактом, что потребителям требуется отслеживать просматриваемые сообщения и управлять собственным графиком опроса.

Мне попадались люди, тратившие много времени на реализацию все новых и новых линий поведения, которые позволяли воспользоваться ими с соответствующим брокером сообщений и приспособить ATOM для работы в ряде различных сценариев. Например, в системе Competing Consumer описывается метод, позволяющий организовать соревнование за получение сообщений среди нескольких рабочих экземпляров, хорошо подходящий для расширения количества исполнителей, обрабатывающих список независимых заданий. Но нам хотелось бы избежать такого сценария, при котором два и более исполнителя выискивают одно и то же сообщение, поскольку в результате мы получим выполнение одного и того же задания большее количество раз, чем требуется. При использовании брокера сообщений с этим справляется обычная очередь. А при использовании ATOM нам потребуется управлять нашим общим состоянием, вовлекая в это всех исполнителей с целью уменьшения воспроизводимых усилий.

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

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

Сложности асинхронных архитектур

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

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

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

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

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

Кроме самой ошибки, мы не удосужились определить лимит максимального числа попыток для запуска задания в очереди. Мы исправили ошибку, а также настроили максимальное количество повторных попыток. А кроме этого, поняли, что нужен способ просмотра и потенциального воспроизведения подобных ошибочных сообщений. В итоге мы пришли к выводу, что нужно создать изолятор сообщения (или очередь мертвых точек), куда должны попадать сбойные сообщения. Мы также создали пользовательский интерфейс для просмотра таких сообщений и повторной попытки их обработки по мере надобности. Если раньше вы сталкивались только с синхронным обменом данными двух конечных корреспондентов, то сразу же заметить подобные проблемы вам было бы нелегко.

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

Я также настоятельно рекомендую ознакомиться с книгой Enterprise Integration Patterns (Addison-Wesley), в которой содержится намного больше подробностей о различных шаблонах программирования, чем вам может понадобиться рассмотреть в данной области.

Сервисы как машины состояний

Если вы решите стать мастером REST-технологии или остановите свой выбор на таком механизме на основе RPC, как SOAP, в действие вступит понятие сервиса как машины состояния. Раньше мы уже достаточно (возможно, даже чрезмерно) наговорились о сервисах, выстраиваемых вокруг ограниченных контекстов. Наши потребительские микросервисы содержат всю логику, связанную с поведением в конкретном контексте.