Skip to content

Материалы для собеседования на позицию solution architect.

Notifications You must be signed in to change notification settings

yablokov-interview-materials/architect

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 

Repository files navigation

Solution / software architect

Вопросы для собеседования на позицию solution architect

1. Containers vs. Virtual Machines (VM)

Чем виртуальные машины отличаются от контейнеров.

Имеется ли смысл в использовании виртуальных машин вместо контейнеров в какой-либо ситуации?

Краткий ответ

Виртуальные машины могут использоваться вместо контейнеров в ситуации, когда:

Требуется:

  • максимальный уровень изоляции с точки зрения безопасности (относительно других процессов, запущенных на том же hardware);
  • максимальная оптимизация в коде приложения с учетом низкоуровневых особенностей операционной системы и hardware, в рамках которого запущено приложения.

Допускается:

  • дополнительные затраты ресурсов (каждая VM довольно тяжеловесна, т.к. включает в себя всю операционную систему, включая её ядро);
  • время старта измеримое в минутах (а не в миллисекундах, как в случае с контейнерами).
Развернутый ответ

Смотрите здесь.


2. Principles of container ready application architecture

Каким ключевым требованиям должно удовлетворять приложение для того, чтобы быть container/cloud ready.

Ответ

Единого ответа на данный вопрос, пожалуй, не существует, но я бы отметил следующие требования к приложению:

  • приложение должно быть stateless;
    • не должно иметь состояния, хранимого единолично в рамках отдельно взятого инстанса (экземпляра);
    • данный пункт не касается хранения данных во вне данных сервисов, т.е. в различных хранилищах:
      • persistent БД;
      • распределенных in-memory хранилищ;
      • message broker'ах;
    • sticky session не должен быть must have:
      • не должно ожидаться "залипания" запросов в рамках клиентской сессии на один и тот же инстанс сервиса (наличие актуальных данных в локальном cache не должно являться обязательной частью бизнес процесса приложения);
  • приложение должно быть готово к работе в рамках оркестрирующей среды:
    • должны быть предусмотрены меры для:
      • трассировки запросов:
        • например, посредством каких-либо инфраструктурных решений по типу proxy sidecar'ов (например, istio), которые возьмут на себя задачу обогащения запросов информацией для возможности трассировки;
      • журналирования (logging and crash reporting):
        • выбрать централизованную или децентрализованную систему логирования (либо их гибрид, например, все логи уровня ERROR отправлять в централизованную систему, а остальные оставлять в рамках инстанса);
        • предусмотреть возможность поиска и работы с логами (при поиске логов по конкретному запросу данный вопрос напрямую пересекается с трассировкой запросов);
      • мониторинга:
        • выбрать подходящую модель мониторинга в рамках распределенной среды:
          • например, воспользоваться асинхронной моделью работы Prometheus:
            • discover targets;
            • polling each target every x time;
      • аудита:
        • выбрать между синхронной или асинхронной моделью отправки событий, либо выбрать гибрид из этих моделей:
          • критически важные события отправлять синхронно;
          • остальные события отправлять асинхронно (для того, чтобы не замедлять процесс обработки запросов / фоновый процесс на сетевые взаимодействия с внешним сервисом аудита, который может оказаться недоступен);
    • приложение должно быть готово сообщать информацию о своем состоянии оркестрирующей среде:
      • поддерживать механизм сообщения состояния в соответствии с используемой средой оркестрации.
        Рассмотрим на примере k8s, который с некоторой частотой отправляет определенные http(s) запросы к инстансам сервиса.
        Данные запросы разбиты на 3 типа и называются пробами:
        • startup prob:
          • Kubelet использует startup пробы, чтобы понять, когда приложение в контейнере было запущено.
            Если проба настроена, он блокирует liveness и readiness проверки, до момента пока проба не станет успешной, и проверяет, что эта проба не мешает запуску приложения.
            Это может быть использовано для проверки работоспособности медленно стартующих контейнеров, чтобы избежать убийства kubelet'ом прежде, чем они будут запущены.
        • liveness prob:
          • приложение должно отвечать на эту пробу, если приложение работает;
          • отсутствие ответа на эту пробу в течение определенного интервала времени / количества попыток опроса будет являться сигналом для перезапуска инстанса приложения;
        • readiness prob:
          • приложение должно отвечать на эту пробу успехом, если оно готово принимать трафик, т.е. если с точки зрения бизнес логики приложение имеет все доступне ресурсы (помимо своей работоспособности также должна проверяться работоспособность критических важных для данного приложения внешних сервисов);
          • пока pod не перейдет в статус ready, он не будет включен в балансировку нагрузки на данный сервис;
    • приложение должно быть готово к тому, что оно может быть перезапущено при необходимости:
      • причины перезапуска см. выше в пункте с liveness prob;
      • приложение должно быть достаточно легковесным и по возможности автономным:
        • в отличие от виртуальных машин, среды оркестрации контейнерами предполагают, что количество контейнеров может как быстро увеличиваться, так и быстро уменьшаться;
        • время старта приложения должно измеряться в секундах;
        • принцип разделения приложений должен стремиться к fine-grained;
      • желательно, чтобы приложение поддерживало graceful shutdown:
        • сигнал о необходимости завершения работы приложения в штатном режиме по той или иной причине:
          • количество инстансов сервиса было уменьшено в конфигурации k8s;
          • часть инстансов сервиса выключается для замены их на следующую версию согласно той или иной стратегии deployment (canary, blue/green, rolling deployment);
        • частично обработку сигнала graceful shutdown может на себя взять proxy sidecar (например, istio), который после (сразу или спустя некоторый таймаут) получения TERM сигнала от среды оркестрации будет отклонять все поступающие новые запросы:
          • такой подход поможет решить проблему поступления новых запросов и взятия их в обработку, но не решит проблему той или иной фоновой работы, которую теоретически может выполнять приложение (например, посредством scheduler'а взять из БД в обработку асинхронную задачу и начать двигать её по бизнес процессу, изменяя состояние в БД и общаясь с внешними системами);
        • поддержать обработку graceful shutdown сигнала на уровне приложения:
          • поддержка необходима в случае наличия механизма фоновой обработки задач (подробнее см. в предыдущем пункте);
          • в случае с k8s это выглядит как pre stop hook;
  • приложения должны стремиться к низкому уровню связности между друг другом (loosely coupled services):
    • такой подход позволит сделать приложения более независимыми друг от друга;
    • пример подходов, которые при разумном применении в нужных местах помогут снизить связанность сервисов:
      • использование хореографии вместо оркестрации в рамках асинхронных процессов взаимодействия между сервисами;
      • использование sidecar для сбора из локальных данных (временных файлов / in-memory данных) логов / метрик / событий в рамках централизованной системы журналирования / мониторинга / аудита, соответственно;
      • версионирование сервисов согласно semver 2.0.0 и декларирование стандартизированных API:
        • использование стандартизированных спецификаций API (например, в виде OpenAPI), не теряющих обратную совместимость в рамках мажорной версии сервиса;
  • при разработке приложения следует использовать наиболее подходящие средства (best-of-breed languages and frameworks):
    • в связи с тем, что сервисы имеют низкую связность (изолированы друг от друга на уровне сетевых интерфейсов и явно самостоятельно не взаимодействуют с инфраструктурными компонентами - см. предыдущий пункт), то в рамках каждого из них можно использовать наиболее подходящие средства для создания сервиса:
      • например:
        • для создания решений на базе искусственного интеллекта следует использовать стек поверх Python (в виду обилия библиотек и фреймворков в данной области, адаптированных под python);
        • для создания утилитных средств может быть удобнее использоваться стек поверх Go;
        • для создания "стандартных" enterprise веб приложений достаточно удобно использовать стек технологий Java, который имеет множество библиотек и фреймфорков для построения решений в данной области;
      • в качестве разумного и важного ограничения в данном пункте будет выступать возможность компании поддерживать эти средства разработки в дальнейшем (вероятно, компания сразу же выберет ограниченный стек технологий, в рамках которого и будет предоставляться выбор).

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

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

Примеры средств оркестрации
  • k8s или решений, работающих поверх него:
    • RedHat OpenShift;
    • Rancher;
    • cloud-based managed container orchestration tools:
      • AWS Elastic Kubernetes Service (EKS);
      • Amazon EC2 Container Service (ECS);
      • Google Container Engine (GKE);
      • Azure AKS Service;
      • Digital Ocean Kubernetes Service;
    • Hashicorp Nomad;
    • Docker Swarm;
    • Mesos;
    • orchestration tools to run containers in a serverless way:
      • AWS Fargate;
      • Google Cloud Run;
      • Azure Container Instances.

Подробнее см. здесь.

Для предоставления более развернутого ответа следует изучить следующие статьи:


Задачи для собеседования на позицию solution architect

1. Monolith to MicroServices

Спроектировать переход от монолитного сервиса к микросервисам.

Учитывать следующие особенности:

  • монолит не удовлетворял принципам mission critical системы (в частности, имел уровень доступности 99.99%);
  • микросервисы должны удовлетворять принципам mission critical системы (в частности, иметь уровень доступности 99.99%).
Ответ

Обозначим критерии mission critical системы:

Для достижения уровня доступности mission critical систем (99.99%) должны выполняться следующие минимальные требования:

  • приложение должно быть георезервировано:
    • во избежание проблем (отключение электричества / любое происшествие) с отдельно взятым ЦОД (центром обработки данных, т.е. местом, где находятся сервера), необходимо диплоить приложение сразу в несколько ЦОД, желательно достаточно разнесенных друг от друга (например, находящихся в разных городах);
  • в рамках каждого ЦОД приложение должно быть развернуто в необходимом количестве инстансов:
    • количество инстансов приложения рассчитывается по формуле: (EL / SL) * MF:
      • EL (expected load) - ожидаемая нагрузка на сервис со стороны потребителей;
      • SL (service load) - максимально выдерживаемая инстансом сервиса нагрузка, количественный показатель которой должен быть получен по результатам проведения НТ (нагрузочного тестирования);
      • MF (multiplication factor) - коэффициент умножения, точный количественный показатель которого может зависеть от особенностей приложения:
        • например, приложения использующие принципы кворума рекомендуется развертывать в нечетное количество инстансов:
          • стоит заметить, что в таком случае также следует использовать нечетное количество ЦОД'ов, т.к. например, при разрыве сети между двумя ЦОД'ами будет наблюдаться эффект split brain (при одинаковом количестве инстансов сервиса в каждом ЦОД), который может иметь крайне сложно разрешимые (даже в ручном режиме) последствия после восстановления сети;
        • обычно, данный коэффициент >= 2 даже в условиях, когда один инстанс сервиса с запасом выдерживает заявленную нагрузку (на случай всплесков и возникновения неожиданных проблем с одним из инстансов).

Примечание:

Я специально выше использовал слово "нагрузка" (load), а не "пропускная способность" (throughput) в единицу времени, чтобы избежать некоторых тонкостей при оперировании данным понятием.

Понятие пропускной способности (throughput) удобно использовать в ситуациях, когда все поступающие запросы и действия, необходимые для их обработки одинаковы по размеру и сложности, соответственно:

  • запросы могут сильно отличаться по размеру:
    • например, особенно актуально для файловых запросов;
      • для обработки крупных файловых запросов может потребоваться:
        • больше оперативной памяти:
          • если содержимое запроса обрабатывается не потоком;
        • больше места во временном каталоге:
          • если перед обработкой содержимого запроса необходимо выполнить какую-либо проверку (например, проверить на соответствие ЭЦП);
        • больше времени на потоковую обработку запроса;
  • запросы могут сильно отличаться по количеству действий, необходимых для их обработки:
    • продолжим пример с файловыми запросами, если в рамках файлового запроса может опционально (будет указываться в параметрах запроса) требоваться шифрование/дешифрование и/или наложение/проверка подписи на серверной стороне.

Таким образом, в общем случае, сначала потребуется определить специфику ожидаемой нагрузки, после чего можно будет рассчитать минимальное количество инстансов сервиса, необходимое для того, чтобы выдержать данную нагрузку (см. выше EL / SL), после чего это количество уже можно будет домножить на коэффициент (см. выше (EL / SL) * MF).

Приступим к выполнению обозначенных критериев:

Разделим задачу на несколько более мелких шагов:

Сетевой уровень

Для балансировки запросов между несколькими ЦОД можно воспользоваться одним из следующих подходов:

  1. используем технологию VRRP (Virtual Router Redundancy Protocol):
    • заводим один виртуальный IP адрес в сети, доступной потребителю (например, в internet):
      • заводим доменное имя (для удобства потребителя) DNS type A (для IPv4) / type AAAA (для IPv6), которое резолвится в данный виртуальный IP;
    • в каждом из ЦОД заводим свой граничный маршрутизатор (border gateway);
      • для каждого из маршрутизаторов заводим отдельный IP адрес в нашей внутренней сети;
      • настраиваем граничные маршрутизаторы нашей сети на работу в режиме VRRP (следует использовать маршрутизаторы с поддержкой данной технологии);
      • данное решение:
        • самое дорогое;
        • обеспечивает самые высокие гарантии доступности сервиса;
        • может обеспечивать высокие гарантии равномерности распределения нагрузки;
  2. используем балансировку за счет особенностей DNS type A (для IPv4) / type AAAA (для IPv6):
    • в каждом из ЦОД заводим свой граничный маршрутизаторов (border gateway);
    • для каждого из маршрутизаторов заводим отдельный IP адрес в сети, доступной потребителю (например, в internet);
    • заводим доменное имя, которое может резолвиться в любой из данных IP адресов (по умолчанию, с использованием алгоритма round-robin);
    • данное решение:
      • самое дешевое и простое;
      • при обновлении DNS записи (удалении/добавлении/изменении IP адреса(ов)) обновленные данные будут гарантировано получены на каждом клиенте только спустя определенный выставленный для этой записи TTL;
      • не проверяет доступность сервисов по IP адресам, по возвращаемым в ответе на DNS запрос (не для fail-over систем);
      • не гарантирует равномерности распределения нагрузки:
        • например, если каждый второй запрос - это health check, а ЦОД всего два, то вся реальная нагрузка будет уходить на один и тот же ЦОД, а все health check запросы - на второй;
  3. ToDo: интервьювер сказал, что имеется еще какая-то возможность балансировки за счет особенностей работы DNS typa A, если специфичный ответ от DNS сервера поддерживает браузер, но я сходу не нашел как даже в RFC1035;
  4. используем технологию client-side load balancing:
    • в каждом из ЦОД заводим свой граничный маршрутизатор (border gateway);
    • для каждого из маршрутизаторов заводим отдельный IP адрес в сети, доступной потребителю (например, в internet);
    • клиентская сторона сама выбирает на IP адрес какого из ЦОД направиться:
      • для достижения условий равномерной нагрузки и доступности сервисов необходимо:
        • периодически опрашивать каждый из IP адресов информацию о нагрузке (отсутствие ответа воспринимается, как недоступность):
          • чтобы основной сервис не упал под наплывом таких запросов в рамках каждого ЦОД следует на отдельный IP адрес выделить самостоятельный сервис:
            • собирающий информацию о доступности и нагруженности сервисов (с использованием метрик оркестрирующей среды);
            • выдающий на запросы потребителей заранее собранную информацию;
    • для удобства использования данного подхода на клиентской стороне должна использоваться библиотека для работы с данным сервисом (в несколько ином use-case, но схожий подход используется в таком продукте как Kafka);
    • данное решение:
      • самое неудобное для потребителя (клиента);
      • может обеспечить высокие гарантии доступности сервиса;
      • может обеспечить достаточно равномерную распределенность нагрузки:
        • достижимо только в случае, если библиотека в том или ином виде запрашивает у сервеной стороны информацию о текущей нагруженности точек балансировки.
Уровень приложений
  • разметить в монолитном сервисе самодостаточные с точки зрения бизнес логики области:
    • таким образом, чтобы бизнес-приложениям не приходилось общаться друг с другом;
      • исключением будут являться:
        • платформенные сервисы (аудит/мониторинг/журналирование);
        • в некоторых случаях часто переиспользуемые части бизнес-сервисов:
          • по возможности, общение с такими сервисами строиться с учетом низкого уровню связности (loosely coupled services);
  • каждую размеченную бизнес область оформить в виде отдельного микросервиса:
    • при разработке приложения следует использовать наиболее подходящие средства (best-of-breed languages and frameworks):
      • в связи с тем, что сервисы имеют низкую связность (изолированы друг от друга на уровне сетевых интерфейсов и явно самостоятельно не взаимодействуют с инфраструктурными компонентами - см. предыдущий пункт), то в рамках каждого из них можно использовать наиболее подходящие средства для создания сервиса, например:
        • для создания решений на базе искусственного интеллекта следует использовать стек поверх Python (в виду обилия библиотек и фреймворков в данной области, адаптированных под python);
        • для создания утилитных средств может быть удобнее использоваться стек поверх Go;
        • для создания "стандартных" enterprise веб приложений достаточно удобно использовать стек технологий Java, который имеет множество библиотек и фреймфорков для построения решений в данной области;
        • в качестве разумного и важного ограничения в данном пункте будет выступать возможность компании поддерживать эти средства разработки в дальнейшем (вероятно, компания сразу же выберет ограниченный стек технологий, в рамках которого и будет предоставляться выбор);
  • постараться избежать блокирующих вызовов внешних сервисов:
    • если имеется возможность асинхронного метода общения не в ущерб бизнес логике приложения и если внешний сервис поддерживает данный способ общения;
    • при необходимости среднесрочного (на какой-либо интервал времени, в процессе которого приложение выполняет какие-то шаги) блокирования ресурсов в БД по возможности использовать optimistic вместо pessimistic lock;
Уровень внешних сервисов
  • по возможности, общение с такими сервисами строить с учетом низкого уровню связности (loosely coupled services):
    • например, если имеется возможность использования асинхронного метода общения не в ущерб бизнес логике приложения, то можно прибегнуть в хореографии, если внешний сервис поддерживает данный способ общения;
  • используемое хранилище информации должно быть:
    • персистентным (существовать дольше, чем какой-либо инстанс приложение, работающего с ним);
    • иметь отдельный инстанс БД, развернутый в рамках каждого из ЦОД;
    • объединить инстансы БД в единый кластер, работающий в режиме active-active:
      • обычно, данная технология работает посредством online репликации данных между инстансами БД в разных ЦОД'ах с использованием механизма redo log (фактически, это event sourcing модель), т.е. события изменений в данных (в виде version vector), накладываемых на ту или иную структуру БД (например, таблицу) в рамках отдельно взятого инстанса БД:
        • такие события асинхронно реплицируются между инстансами БД в рамках кластера;
      • стоит заметить, что при работе в режиме active-active с асинхронным механизмом репликации (иначе уровень доступности 99.99% недостижим) потенциально будут возникать коллизии в изменениях данных, влекущие определенные бизнес риски, к разрешению которых нужно быть готовым:
        • например, если вы одновременно продали один и тот же последний билет на поезд/самолет двум разным людям, запросы которых одновременно обрабатывались в разных ЦОД;
    • рассмотрим пару примеров такой реализации:
      • PostgreSQL: развернуть отдельный инстанс postgres в каждом ЦОД, а затем посредством cluster manager Patroni объединить их в единый кластер, работающий в режиме active-active;
      • Oracle Database:
        • использовать готовый solution - Oracle RAC (Real Applications Clusters);
        • развернуть отдельный инстанс Oracle в каждом ЦОД и самостоятельно настроить репликацию между ними с использованием технологии Oracle GoldenGate (необходимо будет написать свои publisher'ы и subscriber'ы, а также самостоятельно разрешать возможные конфликты в изменениях данных).

2. Design a communication channel between systems

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

Требования:

  • обеспечить канал общения one-to-one:
    • по возможности, обеспечить иные схемы общения (one-to-many / many-to-one / many-to-many);
  • количество участников общения 5 000;
  • message per second: 50 000:
    • это общее количество по всем участникам общения;
  • average message size: 1 MiB;
  • обеспечить семантику доставки сообщений: exactly-once.

Разрешено:

  • использование только стандартных протоколов стека TCP/IP.

Запрещено:

  • использование готовых брокеров.
Ответ в стиле Kafka

Для решения данной задачи можно воспользоваться паттернами, заложенными брокером сообщений kafka.

Общее:

  • вводится концепция очереди / топика - хранилища сообщений:
    • для возможности распараллеливания процесса обработки запросов очередь/топик разбивается на партиции;
      • пользовательский порядок сообщений соблюдается только в рамках партиции, но не между ними;
      • одновременно писать в одну партицию может не более, чем один Producer;
      • одновременно читать данные из партиции может не более, чем один Consumer;
  • вводится концепция producer / consumer:
    • producer - отправитель данных:
      • при отправке данные обогащаются ключом на основании которого алгоритмом consistent hashing выбирается та или иная партиция для размещения сообщения;
    • consumer - получатель данных:
      • каждый consumer получает в монопольное чтение одну или несколько партиций (избавляемся от конкуренции на этапе чтения сообщений);
      • каждый consumer использует polling модель получения данных от брокера (с определенной частотой запрашивает новые данные из полученных на монопольное чтение партиций очереди / топика);
  • для пар систем, нуждающихся one-to-one канале общения используется одно из следующих решений:
    • выделяется отдельная очередь / топик;
    • используется одна общая очередь / топик, которая разбивается на партиции на основании статичных correlation ID:
      • correlation ID рассчитываются на основании уникальных идентификаторов систем получателей;
      • каждая партиция по correlation ID для возможности параллельной работы нескольких Consumber'ов также дробится на некоторое количество sub-партиций.

На стороне клиента/получателя:

  • использовать канал общения с гарантированной доставкой (HTTP / либо что-то поверх TCP);
  • поддержка идемпотентности:
    • обогащать отправляемые запросы следующей информацией:
      • PID (Producer ID) - уникальный идентификатор отправителя запроса;
      • message sequence number - уникальный идентификатор сообщения в рамках отправителя (within producer);
  • использование batch при отправке:
    • например, отправка одного сообщения размером в 10 MiB за один раз значительно быстрее, чем отправка 10 000 отдельных сообщений по 1 KiB;
    • использование сжатия batch'а данных (например, посредством GZIP или Snappy compression protocols):
      • использовать лишь в случае, когда пропускная способность соизмеримо ниже, чем время, затрачиваемое на компрессию / декомпрессию данных;
  • если данные не генерируются, а берутся с жесткого диска, то можно воспользоваться технологией zero copy (подробности технологии см. в описании работы брокера).

На стороне брокера:

  • каждая партиция очереди / топика представляет из себя обычный файл;
    • запись в файл выполняется последовательно в порядке поступления запросов на размещение сообщений в партицию от Procuder'ов;
  • используется оптимизация при работе с данными посредством использования технологии передачи данных zero copy:
    • отсутствие переключения контекста application <-> kernel в рамках процесса отправки данных с диска;
    • минимизация количества копирования данных между буферами до двух:
      • DMA механизм считывает содержимое файла и сохраняет его в буфер адресного пространства ядра;
      • DMA механизм передает данные из буфера ядра в буфер контроллера сетевого интерфейса (NIC, network interface controller), чтобы отправить их по сети.
    • активное использование страничных кэшей для оптимизации процесса чтения недавно записанных данных;
  • вводится поддержка идемпотентности:
    • каждая партиция дополнительно хранит пары: PID - max message sequence number в рамках этого PID;
    • при поступлении запроса на запись в партицию сообщения, которое уже присутствует в партиции, будет проигнорировано:
      • с учетом использования технологии batch будет вероятнее всего проигнорировано не одно сообщение, а сразу же часть batch'а или даже весь за раз.
Ответ в стиле any in-memory solution
  • ToDo: описать

About

Материалы для собеседования на позицию solution architect.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published