Comments 65
Интересно еще узнать какие стандартные поротоколы рассматривались, какие шины? Народ не рекомендует InfluxDB на большой нагрузке как она у вас себя ведет?
Мы смотрели protobuf, thrift, msgpack. У каждого свои плюшки, но мы в итоге пришли к тому, что с json многие вещи проще, понятнее, прозрачнее.
Про influxdb тоже слышал, что не рекомендуют, но у нас нет каких-либо проблем, ну или у нас не достаточно большая нагрузка — тут все относительно.
А шины? Я так понимаю, говоря о них вы говорили о MQ. Какие именно реализации пробовали/использовали?
Доклад замечательный.
Хотелось бы от кого-то услышать, как люди решают задачу, когда для рендера страницы пользователю нужна инфа от N сервисов? Синхронные запросы? Или локальный (для микросервиса) кеш + его асинхронное обновление? Что еще?
Хотелось бы от кого-то услышать, как люди решают задачу, когда для рендера страницы пользователю нужна инфа от N сервисов?
ставите над сервисами еще один сервис-агрегатор который делает нужные выборки из других сервисов и склеивает все.
В случае с рендрингом страниц, у вас скорее всего будет один микросервис который полностью отвечает за страницу в целом, а отдельные блоки (например реклама) можно уже отдельно запросить и вставить.
У нас через микросервисы реализуется только API, прямого рендеринга в HTML — нет. Клиент (не важно будь то single-page application или iOS/android приложение) делает асинхронные вызовы к API и строит результат.
Но, отвечая на ваш вопрос, все зависит от специфики — оба варианта имеют право на жизнь:
- Синхронные запросы — как уже писали, строится сервис который делает синхронные запросы к дочерним сервисам, собирает результат для рендеринга и кеширует, если это возможно. Прокисание кеша или по времени, или по событиям из общей шины данных
- Фоновая сборка данных — сервис держит 2-е копии данных: основная — этими данными сервис отвечает на запросы в текущий момент, вторая — собирается по событиям из шины и в момент полной готовности данных она становится основной. Таким образом сервис всегда готов к ответу, но не всегда актуальными данными
Ещё асинхронные запросы с рендерящего страницу сервиса к N сервисам сразу (предполагая, что они не зависят друг от друга), предварительная обработка ответов (от десериализации джсона до рендеринга отдельных фрагментов страницы, хотя можно ничего не делать предварительно) от них, и окончательный рендеринг страницы когда все ответы придут (или отвалятся по таймауту — обязательно нужно обрабатывать, какие-то ответы могут быть некритичными и можно вывести заглушку, а без каких-то нет смысла рендерить, а рано или поздно ответы перестанут приходить).
У вас 80 ГБит каналы?
Для 300к человек нужно ж порядка 300 ГБит при юникасте.
Можно их как-то себе на сайт поставить (трансляции)?
Уфф, всю статью читал в напряжении, дойдете вы до шины данных с выбрасыванием эвентов, или нет. С этого надо было начинать :)
Респектище за статью и архитектуру, молодцы!
У нас практически каждый сервис в любой момент времени может быть выкачен в продакшен с новой версией.
Каждый сервис сообщает о себе в том числе версию базового протокола в рамках которой у сервиса гарантированно есть в наличии нужные данные, если же это не так, то ошибка проверки по json-схеме у получателя включит механизм circuit breaker и для конкретного клиента этот инстанс уйдет в бан.
А были случат когда для клиента все инстансы нужного микросервиса уходили в бан?
Насколько я понимаю, от этого должен защитить механизм circuit breaker.
Но он работает «с лагом», поэтому возможна ли такая ситуация.
От такой ситуации не защищает паттерн circuit breaker (предохранитель) — он как раз косвенный виновник этой ситуации.
Смотрите — есть сервис А, который должен обратиться к сервису B. Он идет в реестр сервисов и просит адреса доступных инстансев сервиса В. Получает, например, список из 2-х адресов. Далее вступает в игру реализация предохранителя. Мы обращаемся к первому инстансу — он не отвечает за нужное нам время и мы в сервисе А делаем пометку — к этому не обращаться 1 мин — это и есть бан сервиса В в сервисе А. Далее мы идем ко второму инстансу — он ответил, но схема данных не подходит, мы его так же баним по причине несовместимости данных. В итоге получаем, что у нас для сервиса А нет доступных инстансев сервиса B. При этом с сервисом C инстансы сервиса В могут быть прекрасно совместимы и для него с сервисом В будет все в порядке.
1. Сервис при деплое поставляется с мажорной версией протокола данных
2. Сервис-потребитель имеет json-схему проверки корректности данных с его точки зрения
3. Новый релиз сервиса мы выкатываем не как замену текущей версии, а как дополнение. Т.е. в какой-то момент времени у нас работает и версия сервиса 1.2 с протоколом данных версии 6 и версия 1.3 с протоколом данных версии 7.
4. Если новый релиз не совместим по схеме в сервисе-потребителе, то мы в потребителе его баним через паттерн circuit breaker
5. И мы мониторим переезд на новые релизы от каждого сервиса
Таким образом мы не отключаем старые релизы сервисов пока они используются и видим, что на новые никто не переехал по причине не совместимости протоколов данных. Как следствие — работа системы не нарушается. И если мы уверены, что новая версия сервиса должна быть совместима по данным, но этого не наблюдается, то идем и разбираемся с этим конкретным случаем.
В итоге никакого ада с версиями у нас нет, он намечался когда у нас было все на protobuf/grpc, но мы от этого ушли перейдя на json и введя схему данных для каждого сервиса-потребителя, который теперь проверяет только, что в ответе есть те данные которые нужны именно ему, а на остальное он не обращает внимание
Спасибо большое. Было очень интересно читать.
Вы в итоге остановились на использовании opentracing api + zipkin'овская реализация? Какой транспорт от zipkin-reporter'а до сервера используете?
Попробовал вариант с libthrift и очень удивился пачке ошибок при работе Sender
а на localhost'е (версия libthrift, естественно, одинаковая на обоих сторонах).
Есть пара вопросов:
— Правильно ли я понял, что сейчас у вас больше 500 разных типов микросервисов?
— Есть ли у этих микросервисов общий код (какой-нибудь общий фреймворк)?
— Если есть, то как он разрабатывается: отдельной командой или каждый вносит свою лепту? Как осуществляется переход на его новые версии?
— Если нет, то как это сказывается на процессах разработки?
— Правильно ли я понял, что сейчас у вас больше 500 разных типов микросервисов?
Точную цифру не скажу, но много :)
— Есть ли у этих микросервисов общий код (какой-нибудь общий фреймворк)?
Фреймворка нет, есть общий шаблон из которого мы на начальном этапе генерим болванку микросервиса, а дальше кодовая база у каждого сервиса уникальна. У этого шаблона есть мейнтейнер которому каждая команда может отправить пул-реквест с доработками/улучшениями.
общий шаблон из которого мы на начальном этапе генерим болванку микросервиса
А что в него входит (примерно)?
- конфигурация приложения/обращение к сервису единой конфигурации
- хелсчек/рэдичек
- логгирование
- трассировка
- дебаг
- работа с метриками
- реализация circuit breaker
- реализация rate limit
- + специфичные для нас штуки
Инфраструктурные штуки:
- болванка сборки приложения в контейнер
- болванка документации сервиса
- тестирование
Может быть что-то еще, но это основное
— Как появился этот шаблон: вырос самостоятельно, введён директивно мейнтейнером, etc?
— Как распространяются изменения в шаблоне? Допустим, исправили критическую ошибку в логировании, каким образом изменения появятся в зупущенных сервисах и появятся ли они вообще?
Если прям критическое, то выпускается патч и рассылается всем командам, если не критичное, то каждая команда принимает решение об апдейте самостоятельно
— Как появился этот шаблон: вырос самостоятельно, введён директивно мейнтейнером, etc?
Самостоятельно, по мере накопления опыта выделились общие части каждого микросервиса, которые и стали сборкой для шаблона
Не могли бы вы рассказать о некоторых тонкостях, в частности интересует:
1. Как собираете образы? На каждый микросервис свой образ или же вся кодовая база в одном образе и при старте контейнера передаете аргументом название микросервиса? Или как-то еще?
2. Как выполняете, например, миграции? При деплое стартует микросервис, который выполняет необходимые действия и затем умирает?
3. Как структурирован проект? При таком количестве микросервисов, подозреваю, что все это хранятся не в одном репозитории. Тогда как решаете необходимость переиспользования кода в разных микросервисах (общие модели и т.д.)? Подмодулями? Как?
Спасибо.
1. Как собираете образы? На каждый микросервис свой образ или же вся кодовая база в одном образе и при старте контейнера передаете аргументом название микросервиса? Или как-то еще?
1 микросервис — 1 репозиторий — 1 контейнер. Каждый репозиторий самодостаточен, у каждого своя кодовая база.
2. Как выполняете, например, миграции? При деплое стартует микросервис, который выполняет необходимые действия и затем умирает?
Нет, миграции — это часть процесса деплоя, сервис поставляется с файлами миграции, которые запускаются системой деплоя в момент раскатки в среду (прод/стэйдж/лоад/...)
3. Как структурирован проект? При таком количестве микросервисов, подозреваю, что все это хранятся не в одном репозитории. Тогда как решаете необходимость переиспользования кода в разных микросервисах (общие модели и т.д.)? Подмодулями? Как?
Есть шаблон микросервиса — из него генерится новый микросервис на старте разработке, далее у него полностью своя кодовая база, которая развивается самостоятельно. Если какой-то функционал из шаблона должен получить критическое обновление — выпускается общий патч.
По нагрузке точно не скажу кто нагружен сильнее, но могу сказать, что в ситуации обмена сообщения между датацентрами мы используем NSQ.
А ситуацию с контролем нужно рассматривать несколько иначе: входная точка — это корректное создание аккаунта. Если создание прошло, значит есть отправная точка и предварительная проверка корректности данных. Далее у нас есть UserID к которому делается привязка остальных частей — заведение в биллинге, подготовка аватары и тп. И тут вступает в игру такой паттерн как «eventual consistency» — т.е. мы даем системе какое-то время, чтобы собраться к конечному состоянию в правильном виде и получить итоговый статус «регистрация полностью завершена». Если же этого не происходит, то сервис контроля инициирует процедуру отката. И вот наличие этой процедуры отката — один из вопросов при проектировании распределенной системы. Эта ситуация характерна не только для микросервисов — это вопрос именно к любой распределенной многокомпонентной системе.
Ну в таком случае можно генерить чтото типа taskid и некий сервис проверки результата может откатить если статус fail… Спасибо за статью :)
Правильно ли я понимаю, что сервис контроля в этом случае должен знать, какие подтверждения должны быть получены, т.е. сильно связан с контролируемыми сервисами? Т.е. в любом случае не получится изменить состав операций, скажем, при создании пользователя, просто убрав или добавив новый микросервис — придётся изменить ещё и сервис контроля, а затем протестировать его работу вместе со всеми остальными сервисами в этой связке (а не только новый микросервис, как было бы в идеале).
Вообще, посоветуете, что почитать по архитектуре из того, чем вдохновлялись сами?
Спасибо.
Event Bus — это по определению notify, в противном случае вы создаете сильную связанность между сервисами и теряете возможность их независимой разработки и поддержки.Но вроде никто не жалуется когда используют БД в режиме RPC. Очень часто в проектах нужен как раз RPC вместо event/notify (хоть event тоже часто нужен, но это не значит что надо RPC заменить на event везде).
Например, создание аккаунта, через RPC или event, связность одинаковая, а сложность с RPC ниже.
Стал замечать по своему опыту, что хочется относительно маленький проект сразу превратить в google|twitter|uber — way и т.п., а потом после изучения и детального тестирования тех или иных решений приходишь к выводу что всему свое время! :)
Не могли бы вы рассказать чуть подробнее про использование Hystrix:
- Реализуете ли вы каждый раз новую Hystrix-команду для обращения к микросервису в конкретном месте кода, или circuit breaker «вшит» где-то на инфраструктурном уровне?
- Как вы управляете конфигурацией circuit breaker'ов: список инстансов микросервиса, допустимые таймауты и процент ошибок, время бана микросервиса и т.п.
- Как вы «подружили» Hystrix с кодом на Go?
Реализуете ли вы каждый раз новую Hystrix-команду для обращения к микросервису в конкретном месте кода, или circuit breaker «вшит» где-то на инфраструктурном уровне?Не уверен, что правильно понял вопрос, но… каждый вызов внешнего сервиса делается посредством обертки хистрикса
Как вы управляете конфигурацией circuit breaker'ов: список инстансов микросервиса, допустимые таймауты и процент ошибок, время бана микросервиса и т.п.У нас для всех сервисов единая точка конфигурирования на базе KV-хранилища Consul-а. В нем хранится вообще все, что касается конфигурации сервисов в целом и каждого в частности. Каждый сервис на момент инициализации знает только свой ID, версию и то, как достучатся до Consul-а, чтобы забрать из него конфигурацию
Как вы «подружили» Hystrix с кодом на Go?Использовали вот эту библиотеку: https://github.com/afex/hystrix-go
Микросервисы: опыт использования в нагруженном проекте