{"id":14276,"url":"\/distributions\/14276\/click?bit=1&hash=721b78297d313f451e61a17537482715c74771bae8c8ce438ed30c5ac3bb4196","title":"\u0418\u043d\u0432\u0435\u0441\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u0432 \u043b\u044e\u0431\u043e\u0439 \u0442\u043e\u0432\u0430\u0440 \u0438\u043b\u0438 \u0443\u0441\u043b\u0443\u0433\u0443 \u0431\u0435\u0437 \u0431\u0438\u0440\u0436\u0438","buttonText":"","imageUuid":""}

Как построить успешную информационную систему: подходы и практики

Приветствую вас, дорогие друзья! Меня зовут Алексей Солонков, я с 2016 года занимаюсь разработкой высоконагруженных систем. Подписывайтесь на мой телеграм-канал Go HypeLoad, чтобы ничего не пропустить. Сегодня мы поговорим об основных подходах к проектированию высоконагруженных систем. Под высоконагруженной мы будем подразумевать высоконагруженную данными систему (data-intensive).

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

Мы рассмотрим три определяющих вопроса к построению большинства систем:

1. Надежность. Система должна выполнять заложенный в нее функционал на требуемом уровне производительности. Даже в случае неблагоприятных обстоятельств: аппаратные и программные сбои, ошибки пользователя.

2. Масштабируемость. При росте системы (объемы данных, трафика, сложности вычислений) должны быть предусмотрены разумные способы решения проблем.

3. Удобство сопровождения. Разработчикам и специалистам технической поддержки должна быть обеспечена возможность эффективной работы с системой.

Надежность

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

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

Сбой (fault) и отказ (failure) – разные вещи. Сбой обычно определяется как отклонение одного или нескольких компонентов системы от рабочих характеристик. Отказ – это ситуация полного прекращения предоставления сервиса пользователю. Снизить вероятность сбоев до нуля невозможно, однако следует проектировать механизмы устойчивости к сбоям, которые бы предотвращали переход сбоев в отказы. Наша задача состоит в том, чтобы научиться создавать надежные системы из ненадежных составных частей.

Для повышения устойчивости к сбоям вашей системы, необходимо, как ни парадоксально, повышать частоту сбоев с помощью их умышленной генерации. Для этого можно прерывать работу отдельных, выбранных случайным образом, процессов. Без предупреждения. Многие критические ошибки, как правило, происходят из-за недостаточной обработки ошибок. Изучить данный подход вы можете на примере сервиса Chaos Monkey компании Netflix.

Аппаратные сбои. Это первое, что приходит в голову при мыслях о сбоях. Раньше борьба с аппаратными сбоями происходила на уровне избыточности отдельных компонентов отдельно взятой машины (RAID-массивы, дублирование электропитания, горячая замена дисков и CPU). Однако, по мере роста объемов данных и вычислительных запросов росло и количество дорогостоящих машин (с дублированием компонентов). Программное обеспечение эволюционировало в сторону использования большего количества машин. Современные, грамотно спроектированные системы, способны переносить потерю целых машин. Все это благодаря применению методов устойчивости к сбоям вместо избыточности компонентов аппаратного обеспечения. У подобных систем есть и эксплуатационные преимущества: система с одним сервером требует планового простоя при необходимости перезагрузки машины, в то время как устойчивая к аппаратным сбоям система допускает установку исправлений по узлу за раз, без вынужденного бездействия всей системы. Это называется плавающим обновлением.

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

1. Программная ошибка, приводящая к фатальному сбою конкретный экземпляр приложения из-за «плохих» входных данных.

2. Исчерпание ресурсов, таких как CPU, оперативная память или дисковая память.

3. Замедление или отказ в обслуживании сервисов, от которых зависит работа системы.

4. Каскадные сбои, когда сбой в одном компоненте вызывает цепочку последующих сбоев в других компонентах.

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

Быстрого решения программных сбоев не существует. Выделим набор рекомендаций:

1. Тщательное обдумывание допущений и взаимодействий внутри системы.

2. Всестороннее тестирование.

3. Изоляция процессов.

4. Предоставление процессам возможности перезапуска после фатальных сбоев.

5. Оценка, мониторинг и анализ поведения системы при промышленной эксплуатации.

Человеческий фактор. Даже при самых благих намерениях люди ненадежны. Как обеспечить надежность системы с учетом ненадежности людей? Рекомендую использовать сочетание следующих подходов:

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

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

3. Выполнять тщательное тестирование на всех уровнях, начиная с модульных тестов и заканчивая комплексным тестированием всей системы и ручными тестами. Широко используется автоматизированное тестирование, вполне приемлемое и особенно ценное для пограничных случаев, редко возникающих при нормальной эксплуатации.

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

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

6. Внедрение рекомендуемых управленческих практик и постоянное обучение специалистов.

Масштабируемость

Даже если сегодня система работает надежно, нет гарантий, что это не изменится завтра. Одна из частых причин снижения эффективности – рост нагрузки. Она может быть вызвана как ростом числа пользователей, так и увеличением объемов обработки данных.

Масштабируемость (scalability) – способность системы справляться с возросшей нагрузкой. Обсуждение вопросов масштабирования сводится к следующим вопросам: «Какими будут наши варианты решения проблемы, если система вырастет определенным образом от X до Y?» и «Каким образом мы можем расширить вычислительные ресурсы для учета дополнительной нагрузки?».

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

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

1. Как изменится производительность системы, если увеличить параметр нагрузки при неизменных ресурсах системы (CPU, оперативная память, пропускная способность сети)?

2. Насколько нужно увеличить ресурсы при увеличении параметра нагрузки, чтобы производительность системы не изменилась?

Для ответа на оба вопроса понадобятся характеристики производительности, так что рассмотрим вкратце описание производительности системы.

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

Время отклика различается от попытки к попытке. На практике, в обрабатывающей множество разнообразных запросов системе, время отклика способно существенно различаться. Следовательно, необходимо рассматривать время отклика не как одно число, а как распределение значений, характеристики которого можно определить. Довольно часто смотрят именно на среднее время отклика. Термин «среднее» не подразумевает значения, вычисленного по какой-либо конкретной формуле, но на практике под ним обычно понимается арифметическое среднее: при n значениях сложить их все и разделить на n. Однако среднее значение далеко не лучшая метрика для случаев, когда нужно знать «типичное» время отклика, поскольку оно ничего не говорит о том, у какого количества пользователей фактически была такая задержка. Обычно удобнее применять процентили. Если отсортировать список времен отклика по возрастанию, то медиана — средняя точка: например, медианное время отклика, равное 200мс, означает, что ответы на половину запросов возвращаются менее чем через 200мс, а половина запросов занимает более длительное время.

Это делает медиану отличной метрикой, когда нужно узнать, сколько пользователям обычно приходится ждать: половина запросов пользователя обслуживается за время отклика меньше медианного, а оставшиеся обслуживаются более длительное время. Медиана также называется 50-м процентилем, который иногда обозначают p50. Обратите внимание: медиана относится к отдельному запросу; если пользователь выполняет несколько запросов (за время сеанса или потому, что в одну страницу включено несколько запросов), вероятность выполнения хотя бы одного из них медленнее медианы значительно превышает 50%.

Чтобы выяснить, насколько плохи аномальные значения, можно обратить внимание на более высокие процентили: часто применяются 95-й, 99-й и 99.9-й (сокращенно обозначаемые p95, p99 и p999). Это пороговые значения времени отклика, для которых 95%, 99% или 99,9% запросов выполняются быстрее соответствующего порогового значения времени. Например, то, что время отклика для 95-го процентиля равно 1,5с, означает следующее: 95 из 100 запросов занимают менее 1,5с, а 5 из 100 занимают 1,5с, либо дольше.

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

Например, процентили часто используются в требованиях к уровню предоставления сервиса (service level objectives, SLO) и соглашениях об уровне предоставления сервиса (service level agreements, SLA) — контрактах, описывающих ожидаемые производительность и доступность сервиса. В SLA, например, может быть указано: сервис рассматривается как функционирующий нормально, если его медианное время отклика менее 200мс, а 99-й процентиль меньше 1с (когда время отклика больше, это равносильно неработающему сервису), причем в требованиях может быть указано, что сервис должен работать нормально не менее 99,9% времени. Благодаря этим метрикам клиентские приложения знают, чего ожидать от сервиса, и обеспечивают пользователям возможность потребовать возмещения в случае несоблюдения SLA.

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

Как справиться с нагрузкой?

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

Распределение сервисов без сохранения состояния (stateless) по нескольким машинам особых сложностей не представляет. Поэтому стремитесь к stateless-архитектуре ваших для ваших микросервисов.

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

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

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

Удобство сопровождения

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

Можно и нужно проектировать программное обеспечение так, чтобы максимально минимизировать «головную боль» при сопровождении, а следовательно, избегать создания унаследованных систем своими руками. Отталкиваясь от этого соображения, мы уделим особое внимание трем принципам проектирования программных систем:

1. Удобство эксплуатации. Облегчает команде сопровождения поддержание беспрепятственной работы системы.

2. Простота. Облегчает понимание системы новыми разработчиками путем максимально возможного ее упрощения.

3. Возможность развития. Упрощает разработчикам внесение в будущем изменений в систему, адаптацию ее для непредвиденных сценариев использования при смене требований. Известна под названиями «расширяемость» (extensibility) и «модифицируемость» (modifiability).

Удобство эксплуатации

В обязанности команды сопровождения, обычно, включают следующие обязанности:

1. Мониторинг состояния системы и восстановление сервиса в случае сбоев.

2. Выяснение причин проблем. Например, отказов системы или снижения производительности.

3. Поддержание актуальности программного обеспечения и платформ, включая исправления безопасности.

4. Отслеживание влияния различных систем друг на друга во избежание проблемных изменений до того, как они нанесут ущерб.

5. Предупреждение и решение возможных проблем до их возникновения. Например, планирование производительности.

6. Введение в эксплуатацию рекомендуемых практик и инструментов для развертывания, управления конфигурацией (CI/CD).

7. Выполнение сложных работ по сопровождению. Например, переноса приложения с одной платформы на другую.

8. Поддержание безопасности системы при изменениях в конфигурации.

9. Сохранение знаний организации о системе, несмотря на уход старых сотрудников и приход новых.

Простота

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

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

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

Упрощение системы не обязательно означает сокращение ее функциональности. Оно может означать также исключение побочной сложности. Принято определять сложность как побочную, если она возникает вследствие конкретной реализации, а не является неотъемлемой частью решаемой программным обеспечением задачи (с точки зрения пользователей).

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

Возможность развития

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

В терминах организационных процессов рабочие паттерны Agile обеспечивают инфраструктуру адаптации к изменениям. Сообщество создателей Agile разработало также технические инструменты и паттерны, полезные при проектировании программного обеспечения в часто меняющейся среде, например при разработке через тестирование (test-driven development, TDD) и рефакторинге.

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

Резюме

Чтобы приносить пользу, приложение должно соответствовать различным требованиям. Выделяют функциональные требования (что приложение должно делать, например обеспечивать возможность хранения, извлечения, поиска и обработки информации различными способами) и некоторые нефункциональные требования (такие общие характеристики, как безопасность, надежность, соответствие нормативным документам, масштабируемость, совместимость и удобство сопровождения).

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

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

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

Спасибо, что прочли мой лонгрид. Подписывайтесь на мой телеграм-канал Go HypeLoad, чтобы не пропустить полезную и актуальную информацию. И до скорых встреч, друзья!

0
Комментарии
-3 комментариев
Раскрывать всегда