елось их прикручивать. Их просто вытаскивали, бросали в мусорное ведро и крепили на липучке новый диск.
Итак, позвольте повторить: при расширении, даже если будут приобретены самый лучший комплект, самое дорогое оборудование, избежать того, что что-то может дать сбой и сделает это, просто невозможно. Поэтому нужно предполагать, что сбой может произойти. Если это обстоятельство учитывать во всем, что вы создаете, и планировать сбои, можно будет пойти на различные компромиссы. Если известно, что система сможет справиться с тем фактом, что сервер может дать сбой и даст его, то зачем беспокоиться и сильно тратиться на все это? Почему бы не воспользоваться простой материнской платой с самыми дешевыми компонентами (и какими-нибудь липучками), как это было сделано в Google, не слишком переживая за стойкость отдельного узла?
Тема межфункциональных требований уже рассматривалась в главе 7. Представление о межфункциональных требованиях формируется из рассмотрения таких аспектов, как живучесть данных, доступность сервисов, пропускная способность и приемлемое время отклика сервисов. Многие технологии, упоминаемые в этой и других главах, имеют отношение к подходам, позволяющим реализовать эти требования, но о том, какими конкретно могут быть эти требования, знаете только вы.
Обладание автоматически масштабируемой системой, способной реагировать на возросшую нагрузку или отказ отдельных узлов, может быть фантастическим результатом, но окажется избыточным для системы создания отчетов, которую нужно запускать только дважды в месяц и для которой вполне приемлемо пару дней находиться в простое. Аналогично этому отработка способов сине-зеленых развертываний для ликвидации простоев сервиса может иметь смысл для системы электронной торговли, но для корпоративной базы знаний, доступной только во внутренней сети организации (в интранете), будет, наверное, излишней.
Задать количество приемлемых отказов или допустимую скорость системы можно на основе требований, предъявляемых пользователями данной системы. Это поможет вам понять, какие технологии разумнее всего будет применить. Тем не менее пользователи не всегда смогут сформулировать конкретные требования. Следовательно, чтобы получить правильную информацию и помочь им понять, каковы будут относительные затраты на предоставление различных уровней услуг, нужно задавать вопросы.
Как уже упоминалось, межфункциональные требования от сервиса к сервису могут отличаться друг от друга, но мне хотелось бы предложить определить некоторые универсальные межфункциональные требования, а затем переопределить их для конкретных вариантов использования. Когда рассматриваются вопросы о необходимости и способах масштабирования системы, позволяющего лучше справиться с нагрузкой или сбоями, постарайтесь сначала разобраться со следующими требованиями.
• Время отклика/задержка. Сколько времени должно тратиться на ту или иную операцию? Здесь может пригодиться измерение данного показателя в ходе работы различного количества пользователей с целью определения степени влияния возрастающей нагрузки на время отклика. Из-за характерных особенностей сетей неизбежны выпадения, поэтому может пригодиться задание целей для заданной процентили отслеживаемых ответов. Цель должна также включать количество параллельных подключений (пользователей), с которым, как ожидается, сможет справиться ваша программа. Следовательно, вы можете сказать: «Мы ожидаем, что у сайта значение 90-й процентили времени отклика будет порядка 2 секунд при обслуживании 200 параллельных подключений в секунду».
• Доступность. Ожидается ли падение сервиса? Считается ли, что сервис работает в режиме 24/7? Некоторым при оценке доступности нравится смотреть на периоды допустимого вынужденного простоя, но насколько это практично для тех, кто вызывает ваш сервис? Я должен либо полагаться, либо не полагаться на доступность сервиса. Фактически оценка периодов вынужденного простоя более полезна с точки зрения исторической отчетности.
• Живучесть данных. Каков приемлемый объем потери данных? Каков обязательный срок хранения данных? Скорее всего, для разных случаев можно будет дать разные ответы на эти вопросы. Например, можно выбрать вариант годичного хранения журналов регистрации пользовательских сеансов или с целью экономии дискового пространства — менее продолжительный срок, но записи о финансовых транзакциях может потребоваться хранить в течение многих лет.
Когда требования будут определены, вам понадобится способ постоянной систематической оценки их соблюдения. Можно, к примеру, прийти к решению о проведении тестов производительности, чтобы убедиться, что в этом смысле система отвечает приемлемым целям, нужно также обеспечить отслеживание соответствующей статистики и в производственном режиме работы!
Важной частью создания отказоустойчивой системы, особенно когда функциональные возможности распределяются среди нескольких микросервисов, которые могут находиться как в рабочем, так и в нерабочем состоянии, является обеспечение ее способности безопасно снижать уровень функциональности. Представим себе стандартную веб-страницу на нашем сайте электронной торговли. Чтобы собрать вместе различные части этого сайта, требуется участие в работе нескольких микросервисов. Один микросервис может выводить на экран подробности о предлагаемом к продаже альбоме, другой — показывать цену и уровень запасов товара. И мы, наверное, будем показывать также содержимое покупательской корзины, чем может заниматься еще один микросервис. Если при отказе одного из этих сервисов станет недоступной вся страница, то мы, вероятно, создали систему менее устойчивую, чем та, которая требует доступности только одного сервиса.
Нам нужно понять, каково влияние каждого отказа, и выработать способы надлежащего снижения уровня функциональности. Если недоступен сервис покупательской корзины, это, наверное, будет весьма неприятно, но мы по-прежнему сможем показывать веб-страницу с перечнем товаров. Возможно, мы просто скроем покупательскую корзину или выведем вместо нее значок с надписью «Скоро вернусь!».
При работе с единым монолитным приложением нам не приходится принимать множество решений. Здоровье системы зависит от работы двоичного кода. Но при использовании архитектуры микросервисов нужно рассматривать намного более тонкие ситуации. Зачастую правильные действия в любой ситуации не связаны с принятием технического решения. Нам может быть известно, что технически можно сделать при отказе корзины, но пока мы не сможем осмыслить бизнес-контекст, мы не поймем, какое действие нужно предпринять. Возможно, мы закроем весь сайт, позволим людям просматривать каталог товаров или заменим ту часть пользовательского интерфейса, в которой содержатся элементы управления корзиной, номером телефона, по которому можно сделать заказ. Но для каждого показываемого клиенту интерфейса, в котором используются несколько микросервисов, или для каждого микросервиса, зависящего от нескольких нижестоящих, сотрудничающих с ним микросервисов, следует задаться вопросом: «Что произойдет при отказе?» — и знать, что нужно будет делать.
Критический взгляд на каждую из возможностей в понятиях межфункциональных требований позволит намного лучше сориентироваться в определении необходимых действий. Теперь рассмотрим, что можно сделать с технической точки зрения, чтобы при возникновении сбоя мы могли с ним успешно справиться.
Существует несколько схем, которые я в совокупности называю архитектурными мерами безопасности и которые при возникновении нештатных ситуаций могут использоваться, чтобы предотвращать появление раздражающих, распространяющихся за пределы сервиса эффектов. Вам важно усвоить их положения и строго придерживаться стандартизации в системе, чтобы гарантировать, что ни один ее нерадивый компонент не сможет прямо у вас на глазах вызвать всеобщее обрушение. Вскоре мы посмотрим, какие основные меры безопасности заслуживают внимания, но перед этим я хочу поделиться небольшой историей, чтобы очертить круг возможных нештатных ситуаций.
Я был техническим руководителем проекта по созданию сайта классифицированной онлайн-рекламы. Сайт обслуживал довольно большие объемы информации и приносил довольно высокий доход. Наше основное приложение занималось выводом на экран классифицированных рекламных объявлений, а также служило прокси-сервером вызовов, осуществляемых по адресам других сервисов, предоставляющих различные виды продукции (рис. 11.1).
Фактически это пример приложения-душителя, где новая система перехватывает вызовы, совершаемые в адрес ранее существовавших приложений, и постепенно полностью заменяет собой эти приложения. В рамках этого проекта мы уже были на полпути к списанию прежних приложений. Мы только что перешли к использованию самых крупных объемов и самых прибыльных продуктов, но многие рекламные объявления по-прежнему обслуживались целым рядом прежних приложений. С точки зрения как количества поисковых запросов, так и дохода от этих приложений быстро избавиться от них не представлялось возможным.
Рис. 11.1. Сайт классифицированных рекламных объявлений, на котором происходит постепенное избавление от устаревших приложений
Какое-то время наша система демонстрировала высокую живучесть и примерное поведение, справляясь с незначительными нагрузками. Со временем в пиковые моменты нам пришлось обрабатывать 6000–7000 запросов в секунду, и несмотря на то, что большинство запросов интенсивно кэшировалось реверсными прокси-серверами, находящимися перед серверами нашего приложения, поисковые запросы товаров, являющиеся наиболее важным аспектом сайта, в большинстве своем в кэше отсутствовали и требовали обслуживания по полному серверному циклу.
Однажды утром, как раз перед достижением ежедневной пиковой нагрузки, что происходило во время обеденного перерыва, система стала замедляться, а затем постепенно выходить из строя. В новом основном приложении у нас было несколько уровней мониторинга, достаточных для того, чтобы сообщить о достижении каждым узлом приложения 100 %-й пиковой нагрузки на центральный процессор, превышающей нормальные уровни даже для пиковых ситуаций. Вскоре обрушилась вся система.