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

Фрагментация для операций записи может позволить увеличить масштаб, чтобы справиться с объемом записей, но при этом нисколько не улучшить отказоустойчивость. Если клиентские записи в диапазоне A — M всегда попадают в экземпляр X и экземпляр X окажется недоступен, доступ к записям A — M может быть утрачен. Здесь Cassandra предлагает дополнительные возможности, позволяющие гарантировать репликацию данных на несколько узлов кольца (так в Cassandra называется коллекция узлов).

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

Совместно используемые инфраструктуры баз данных

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

CQRS

Схема разделения ответственности на команды и запросы (CQRS) относится к альтернативной модели хранения и запроса информации. При использовании обычных баз данных одна и та же система применяется как для внесения изменений в данные, так и для запроса этих данных. А при использовании CQRS часть системы имеет дело с командами, которые отлавливают запросы на изменение состояния, в то время как другая часть системы работает с запросами на извлечение данных.

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

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

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

Но при этом следует иметь в виду, что схема такого рода весьма сильно отходит от модели, в которой все CRUD-операции обрабатываются в едином хранилище данных. Мне приходилось наблюдать, как несколько весьма опытных команд разработчиков бились над получением приемлемых результатов от использования данной схемы!

Кэширование данных

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

Даже при использовании единого монолитного веб-приложения вариантов того, где и как проводить кэширование, совсем немного. При использовании архитектуры микросервисов, где каждый сервис имеет собственные источник данных и поведение, мы располагаем намного большим количеством вариантов того, где и как проводить кэширование. При использовании распределенных систем кэширование задумывается либо на стороне клиента, либо на стороне сервера. Но какое лучше?

Кэширование на стороне клиента, прокси-сервере и стороне сервера

При кэшировании на стороне клиента результат кэширования сохраняется клиентом. Именно клиент решает, когда обращаться за свежей копией и нужно ли это делать. В идеале нижестоящий сервис будет давать подсказки, помогающие клиенту понять, что делать с ответом, чтобы он знал, когда выполнять новый запрос и надо ли это вообще. При прокси-кэшировании прокси-сервер помещается между клиентом и сервером. Хорошим примером может послужить использование обратного прокси-сервера или сети доставки контента (CDN). При кэшировании на стороне сервера ответственность за кэширование возлагается на сервер, возможно, с использованием таких систем, как Redis, или Memcache, или даже простой организацией кэша в памяти.

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

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

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

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

Кэширование при использовании технологии HTTP

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

Во-первых, при применении HTTP в ответах клиентам мы можем воспользоваться инструкциями по управлению кэшированием — cache-control. В них клиентам говорится о том, должны ли они вообще использовать кэширование ресурсов, и если должны, то как следует проводить кэширование в секундах. У нас также есть возможность настройки заголовка Expires (истечения срока годности), где вместо указания длины кэшируемого содержимого указываются время и дата, при наступлении которых ресурс должен считаться несвежим и подлежащим повторному извлечению. Какая из настроек вам больше подойдет, зависит от характера совместно используемых ресурсов. Для стандартного статичного содержимого сайта, например для CSS или изображении зачастую хорошо подходит простая инструкция cache-control с указанием времени жизни информации (TTL). Однако, если заранее известно, когда поступит новая версия ресурса, разумнее будет воспользоваться Expires-заголовком. В первую очередь все это может принести большую пользу и избавить клиента от необходимости отправки запроса на сервер.