Пользователь
0,0
рейтинг
15 января 2014 в 18:53

Разработка → Масштабировать просто

От B2C-порталов ожидается прежде всего масштабирование. К сожалению, масштабирование слишком часто объявляется вопросом Технологии — достаточно выбрать модную технологию и все проблемы решены. То, что это не так, может проявиться, позднее всего, уже в production mode (на рабочей системе).
Вместо того, чтобы махать технологической булавой, расскажу о том, как при помощи продуманной архитектуры и сознательного отказа от модели данных разработать высоко доступный (highly available), масштабируемый (scalable) портал. Первая часть опишет общие концепты, а возможные сценарии и их решения последуют.


Часть первая. Теория.

Давным-давно, в темные времена начала интернета, то есть где-то в конце прошлого — начале этого тысячелетия, вопрос выбора правильной архитектуры часто сводился к выбору правильной базы данных. Когда директор какого-нибудь стартапчика ставил перед разработчиками задачу построить новый портал, команда в основном дискутировала о том, надо ли покупать Oracle Enterprise Edition, или можно обойтись стандартной лицензией. Продвинутые товарищи экспериментировали с Роеt, Versant или другими объектно-ориентированными базами данных. После этого создавались модели данных, которые в большинстве случаев были моделями баз данных, и всё это еще до того, как задаться вопросом: а что, собственно, система должна делать, и как?

Сегодня, 10 лет и кучу интересных разработок в области ПО спустя, всё происходит очень похоже, разве что вместо выбора Oracle или Informix спорят о том, брать ли Mongo, Hadoop или ElasticSearch. Без сомнения, это хорошие и очень полезные технологии. Однако выбор технологии не должен предшествовать выбору архитектуры! Другими словами: технология, какой бы продвинутой она не была, должна служить архитектуре, выполняя определённые задачи в ее рамках. Эти задачи должны определятся архитектурой и требованиями к системе.

Подход Technology First, который можно часто встретить в окопах разработки ПО, очень привлекателен для руководства, плохо подкованного технически: «Если стартап Х использует Mongo, Bootstrap, ElasticSearch и/или Ruby, то и мне этот коктейль поможет, а если нет, то я всегда отмажусь перед инвесторами: мол, использовал все самые модные технологии и значит — не виноват!» К сожалению (для судьбы стартапа и к счастью для всех остальных), такой подход редко приводит к правильному решению конкретной проблемы.
Я же ратую за противоположный подход: Architecture First. Это означает, что проблема решается в первую очередь архитектурно, а технология является лишь способом реализации архитектуры. Соответственно, технология — только часть решения и только тогда, когда она приносит конкретную пользу в контексте данного решения.

Вот пример.

Долгие годы люди пытались решить все проблемы порталостроения при помощи реляционных СУБД, и долгие же годы все попытки масштабирования этих порталов венчались прахом, как только Schema (Схема БД) становилась достаточно сложной. В результате этой беды и появилось поколение наследников РСУБД — NoSQL СУБД (являются ли базы NoSQL чем-то принципиально новым или лишь реанимацией старых идей, в данном контексте не суть важно). Интересно другое: успех NoSQL СУБД основан на том, что они распознали главную проблему SQL СУБД, — а именно Joins, и попросту их не поддерживают. Но если построить архитектуру так, чтобы обойтись без Joins, то есть сознательно от них отказаться, то и старые добрые базы SQL масштабируются без особых проблем.

Архитектура — что это?

Прежде чем говорить о том, как найти правильную архитектуру, которая будет поддерживать такие стандартные требования как flexibility (гибкость), scalability (масштабируемость) и manageability (управляемость), нужно определиться: а что, собственно, является архитектурой? Тут мнения расходятся. Одни рассматривают архитектуру как очень абстрактный вид описаний требований к системе, эдакий requirement analysis; другие — как распределение классов по пакетам (packages). Большой выбор разнообразных определений понятия «архитектура программного обеспечения» можно найти на этой странице.
Я считаю наиболее удачным следующее:
Архитектурой (программного обеспечения) является структура системы, состоящая из компонентов, видимых свойств этих компонентов и отношений между ними.


Иными словами, архитектура занимается компонентами системы и коммуникацией между ними. Это определение основывается на понятии компонент, а что же такое компонент?
Компоненты — это составляющие нашей архитектурной мысли, которые мы определяем по различным признакам: в частности, я — по ответственности за какой-либо бизнес процесс или данные.
Отдельный компонент — совокупность сущностей (например классов/объектов), выполняющих общую задачу. Например, MessagingService — компонент, отвечающий за отправку сообщений и состоящий из нескольких классов (в том числе и самого MessagingService interface).
Размер компонента должен быть максимально маленьким, но достаточным, чтобы решить задачу (для Messaging — отправка и прием сообщений).

Возвращаясь к B2C порталам, отметим их общие, с точки зрения архитектуры, свойства:
  • высокий коэффициент соотношения между читающими и пишущими операциями: количество читающих может быть в 9-10 раз выше, чем пишущих;
  • четко отличаемые функционалы — например, внутренние сообщения (messaging) или профиль;
  • посещаемость порталов подвержена пикам, которые создают множественное число от нормальной загрузки в зависимости от времени дня, недели или года;
  • порталы постоянно и быстро меняются. Это касается и кода, и контента, и данных.


Общие архитектурные принципы

Одна из самых популярных архитектур для построения таких порталов — сервисно-ориентированная (SOA). В недалеком прошлом её реноме пострадало от популярности WebServices, архитектуры, с SOA имеющей мало общего, но которую часто с ней путают. SOA, будучи архитектурой гораздо старшей и более зрелой, чем WebServices, при правильном использовании предлагает решение многих проблем масштабирования.

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

  • Прямая или синхронная коммуникация — вызов методов, то есть обращение клиента к сервису.
  • Косвенная, или асинхронная коммуникация — оповещение об изменении состояния (event), которое компонент публикует «по секрету всему свету», не заботясь о том, есть ли у него конкретные слушатели.


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

Изоляция до мозга костей (до базы данных)

Один из основных и наиболее полезных принципов SOA: изоляция компонентов друг от друга. Помимо прочего, это означает, что каждый компонент является абсолютным хозяином своих данных. Никто не имеет права их изменять, не поставив, как минимум, хозяина в известность, а лучше — попросив его сделать модификацию самому.
Сервисно-ориентированная архитектура умеет много гитик, но основное её преимущество — в отсутствии глобальной модели данных. Интерфейсы каждого конкретного компонента — это всё, что о нём известно «снаружи». Внутренняя же его жизнь остаётся делом сугубо личным, никому не ведомым. У этого принципа есть не только сторонники — ведь провести сложное расследование при помощи одного (трехэтажного) SQL запроса так удобно! Да, это правда, связь между данными разных сервисов на уровне СУБД могла бы принести определенную пользу при расследовании, статистическом анализе и дата майнинге (data mining, глубинном, или интеллектуальном анализе данных). Но где сказано, что эти связи должны существовать в рабочей среде? Если кому-то нужны данные для анализа, никто не мешает регулярно переносить их из рабочей системы в аналитическую, и при этом создавать сколько угодно и какие угодно связи, а также переворачивать данные боком, вверх головой или как еще понравится. Но сама рабочая система не должна быть загрязнена этими «субпродуктами» — балластом, делающим из юркого, быстрого «Феррари» грузный и неповоротливый «Пассат».

Добровольный отказ от глобальной модели данных с точки зрения архитектуры системы означает следующее:
  • Место модели данных занимает модель сервисов. Её можно было бы назвать Enterprise-моделью, если бы это слово не использовалось направо и налево, для чего придётся. Сервисная модель состоит из сервисов в системе, артефактов, которыми они управляют, и связей между этими артефактами.
  • Каждый сервис и каждый компонент совершенно свободны в выборе их персистентности. Сервис, который управляет хорошо структурированными данными, может записывать их в базу данных SQL; сервис, занимающийся блобами (blobs), будь то картинки или большие тексты, может использовать более подходящие методы персистентности.
  • Нагрузка на сервисы варьируется в пределах системы. В 2-tier (двухуровневой) системе, ориентированной на базы данных, рост нагрузки всегда ложится на всю систему целиком. В SOA же легко идентифицировать сервис, нагрузка на который выросла, что даёт возможность бороться с ней именно в этом месте, путем оптимизации или масштабирования.


Чтобы в полноте насладиться преимуществами SOA, сервисы должны быть грамотно «нарезаны». Большие, монстроподобные сервисы часто превращаются в «приложениe в приложении», и сами подвержены тем проблемам, которые мы собирались побороть. Слишком мелконарезанные сервисы приводят к переизбытку (overhead) коммуникации, которая убивает всё масштабирование в корне. Как же найти правильный размер сервиса?

Проще всего это сделать, придерживаясь двух следующих парадигм разработки ПО: Design by Responsibility и Layer Isolation. С помощью последнего можно определить основополагающие границы ответственности сервисов — что является сервисом (как business logic), а что нет (например, презентационная логика). Design by Responsibility помогает нарезать сервисы по вертикали, разбивая их по предметной или функциональной специализации (messaging, search и т.д.).

Правильная нарезка сервисов
Схема 1: Правильная нарезка сервисов

После корректной идентификации сервисов нужно подумать о том, как они должны между собой «общаться».

Разрешенные (зеленые) и запрещенные (красные) пути коммуникации
Схема 2: Разрешенные (зеленые) и запрещенные (красные) пути коммуникации

Как избежать глобальной модели данных?

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

Зависимости между слоями
Схема 3: Зависимости между слоями

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

Изоляция персистентностей
Схема 4: Изоляция персистентностей

Другая парадигма, следование которой абсолютно необходимо — это KISS (Keep it Simple, Stupid). Касательно архитектуры ПО, KISS означает, что лишь абсолютно необходимые компоненты должны быть частью нашей архитектуры. Всё, что не приносит непосредственной пользы, и к этому могут относиться модные технологии, должно быть исключено. Другими словами, лишь те технологии, которые оправдывают расходы на свою поддержку, заслуживают право входить в конечное решение.

В хорошей архитектуре много винтиков

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

Тем не менее, совсем не страшно заниматься тюнингом уже после запуска в рабочем режиме, ведь все мы хорошо помним, что такое преждевременная оптимизация (premature optimization). Важно мониторить (Привет MoSKito!) каждый компонент, чтобы вовремя распознавать «узкие места» и реагировать до полной перегрузки этого компонента. Зная такое «узкое место», у нас есть разные возможности реагирования. О двух из них, Кэшах и Маршрутизации, мы поговорим в следующих частях.
Leon Rosenberg @dvayanu
карма
34,0
рейтинг 0,0
Реклама помогает поддерживать и развивать наши сервисы

Подробнее
Реклама

Самое читаемое Разработка

Комментарии (10)

  • 0
    Респект.
    Тоже пишу про SOA, но не на хабре, а в личном блоге.
    На мой взгляд, осталась нераскрытой тема межсервисной коммуникации.
    А это очень важно, например вопрос — синхронная она или нет. Должен ли отправляющий сервис ждать, пока получатель получит и обработает вызов. Может ли один бизнес-процесс распараллелиться на несколько одновременных межсервисных взаимодействий, а потом собраться в единый результат.
    Мы на этом достаточно голову поломали в своё время.
    • 0
      Спасибо, об этом напишу в одной из следующих частей ;) Ну и конечно обменяться опытом можно всегда и вне поста.
      В общем сложно ответить на этот вопрос априори, для разных систем подходят разные модели, тут нужно смотреть по конкретной ситуации.
    • 0
      у нас на проекте используется самопальный фреймворк для общения сервисов между машинами на базе 0mq. Обычные зпросы типа user_service.online_users() делается синхронно и передаются данные в бинарном формате. Для случаев когда сервис должен знать о создании или изменении определенного объекта, мы использует слушатели. Например руби код при создании юзера вызывает broadcast(user, :create), слушатели в сервисах перехватывают данный вызов и записывают айди и нужные данные этого пользователя в локальную базу.
  • 0
    Хорошая статья, для вравления мозгов

    Несколько вопросов
    1. Вы в статье используете различные термины, например Technology First, Architecture First, это ваши или есть источник?
    2. SOA замечательно звучит и проектируется в теории, но SOА сам по себе это абстрактный подход к проектированию, реализацией которого как раз и является SOAP (ака WS) из вашей статьи я так и не понял выбранную Вами альтернативу, что вы нашли лучше SOAP для SOA?
    • +1
      SOAP — это не совсем реализация SOA. SOAP — это одна из технологий, которую можно (а можно и не) применять в SOA.
    • +1
      Я думаю, что если погуглить то можно найти использования этих терминов и другими авторами, так не буду себе приписывать авторство. На мой взгляд это сочетание настолько банально, что не стоит его возводить в ранг 'аксиомы' или 'термина'. Примерно как «сначала главное» (first things first).
  • 0
    Мы как раз сейчас экспериментируем с архитектурой, сделали Spring-приложение, в котором пошли как раз от сервисов. БД у нас хоть и одна, но поскольку сервисы независимы, ничто не мешает сделать так как вы говорите — свою персистентность для каждого сервиса.

    Мы не используем Hibernate, взяли реализацию DataMapper — MyBatis. Таким образом, наши модели — чистые POJO, без жестко заданных связей через какой-нибудь JPA.

    Вопрос: что бы вы посоветовали в данном случае сделать, чтобы изолировать сервисы друг от друга по данным? Если нужно, текущее состояние кода можно посмотреть здесь: github.com/7bits/jade-fff (проект учебный, но по мотивам реального).
    • 0
      Добрый день Анна,

      для начала позвольте заметить что у Вас очень симпатичный сайт. Единственное — при разрешении 1280х800 claim не виден. Это мне возразил один из специалистов по usability, когда я послал ему Ваш сайт как пример для хорошего IT-Сайта.

      К делу:
      1) Вы уверены что у Вас есть сервисы? Employer- и Recruiter- сервисы я бы такими не назвал, так как все проблемы там решаются при помощи SQL (ну или вызова к repository что в итоге тоже самое). Ваши сервисы при этом используют одни и те же repository, так что ни о каком разделении сервисов говорить не приходится: оба используют DealRepository, ApplicantRepository, UserRepository. Что ещё страшнее: RecruiterService использует EmployerRepository.
      В Вашу защиту: то что spring называет сервисом и то что является сервисом с точки зрения СОА — две большие разницы.

      2) Ваше разделение на пакеты! Ваше разделение на пакеты!
      Разделение на пакеты должно быть по… затрудняюсь найти русское слово, на английском это Domain, а на немецком: Fachlichkeit. С таким разделением как у Вас построить СОА будет невозможно (ну или очень сложно), ведь практически невозможно проследить от каких пакетов зависит какой сервис и какой ещё зависит от тех же пакетов. Раз персистентность сервиса EmployerService его и только его, почему бы ей не находиться в под-пакете employer? Тоже касается и его данных. У вас всё в одной куче.
      Если вы посмотрите на Схему 3: начинать надо с сервисов. Если бы Вы начали так, то у вас были бы совсем другие сервисы (попробуйте если не верите).

      3) Ваше разделение сущностей emoployer и recruiter и последующая интеграция их при помощи общего объекта user… Это как-то ни то не сё. Либо у Вас таки есть employer и recruiter, либо их нет. Если уж собирать их в одну таблицу, то назовите это User и дайте ему какой-то enum type.

      Ещё два маленьких замечания

      1) javax.servlet-api надо декларировать provided. Я Ваш проект дальше compile не собирал, он пытался создать какие-то таблицы и т.д. и мне попросту было лень создавать специально базу, но теоретически томкат должен ругаться при старте.

      2) Копирование разной конфигурации через мавен профайл: посмотрите на этот проект: www.configureme.org он позволяет хранить все конфиги в одном файле (stackable/cascading), и переключать нужные через параметр в рантайм. Плюс в том что Вы деплоите на все системы один и тот же, тестированный артефакт.

      П.С. Я уже упоминал Ваше разделение на пакеты?
      • 0
        Спасибо за слова про сайт, но он в жесткой разработке, это далеко не окончательный вариант. Там и картинок под ретину нет, например :-)

        1. В-общем, мы изначально не ставили себе целью сделать SOA. Наша цель была сделать максимально независимыми Use cases. Сейчас каждый Use case реализуется методом какого-то *Service и поэтому их можно менять независимо. По сути — это сервисы с точки зрения DDD, но не SOA, конечно. Прошу прощение, за то, что ввела в заблуждение.

        2) Нет, разделения персистентности у нас пока нет и не будет. Поскольку все текущие модели сейчас относятся к одному Bounded context (это опять DDD), то их и не нужно разделять. А вот если появится mailer или еще какой-то технический сервис, то вполне можно будет для него использовать другое хранилище.

        Если посмотреть на пакеты и остальное под углом DDD, не пытаясь разделить Employer и Recruiter на сервисы с точки зрения SOA (а мы и не планируем этого), то описанных вами проблем просто нет.

        3) Это разные роли. Они являются агрегатами сущностей (опять DDD) и поэтому методы собраны именно так. Здесь разделение только логическое, а не физическое.

        Про servvlet-api и maven посмотрю, спасибо!
  • 0
    Отличная статья. Что вы посоветуете почитать из литературы, блогов по архитектуре java приложений. Очень интересуют реальные примеры. Может быть какой-то OpenSource проект на java, чтобы детально почитать исходники.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.