Комментарии 51

Так выходит же, что это обычный брокер, просто самописный, нет?

Не совсем похоже. Брокер скорее про очереди сообщений, задач, данных на доставку, а Аппхост про выполнение запроса в твой сервис.

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

В случае Аппхоста вся логика выполнение запроса описана в графе, а клиенты под Аппхостом только обрабатывают запросы.

Брокер в Яндексе тоже есть, но не самописный, конечно :) Мы про него писали некоторое время назад.
А вот это прямо в точку! Когда думали как объяснять новым людям, что такое Аппхост, сошлись, что API Gateway самый близкий аналог. Только API Gateway это обычно кастомный код, а Аппхост это некоторый способ сконфигурировать свой API Gateway.

А про сервис меш не думал никогда, но да, что-то есть.
Apphost это внутренне решение или доступно в вашем облаке? Если доступно интересно было пощупать, из статьи довольно поверхностно впечатление о сервисе.
Пока, увы, только внутреннее. К облаку мы еще не готовы, да и сходу объем спроса не понятен.

О, ещё одна крупная российская компания пилит свой велосипед. С визуализацией и распределеными трассировками.

На самом деле, мы рады взять готовое! Но где? Если знаешь проекты, которые делают то же самое, покажи.
Читал текст и думал — вот тут circuit breaker нужен, тут opentracing, тут еще что-то… Вопрос — вы же в курсе технологий, почему не просто микросервисы с нужными (стандартизированными) фишками?
Вот тут хороший вопрос и на него нет однозначного ответа. То есть, можно грамотно задизайнить то что есть сейчас на классической микросервисной архитектуре и это будет хорошо работать.

Почему конкретно у нас Apphost, я для себя отвечаю так.
— Понятное разделение зоны ответственности. Команда реализует бизнес логику, мы реализуем передачу данных. Когда мы что-то чиним, мы чиним у всех сразу.
— Кто-нибудь обязательно забудет сделать одно из N обязательных действий с использованием правильной стандартизованной технологии и все сломает.
— И шестую проблему из статьи не понятно как решать. Чтобы внедрить фичу, тебе придется комитить в код другой команды, или ставить на них задачу.
Шестую задачу мы стараемся решить тем, что во фреймворке (который содержит абстракции поддерживает трейсинг метрики и тп) добавляем функционал.
Все сервисы создаются грубо говоря xx.NewService, тем самым простое использование renovate бота заставляет его сделать пул реквесты во всех репах как только либа с фреймворком обновилась. (он и мерджить может такое автоматом если тесты прошли успешно)
Вообще звучит хорошо, да. В очень похожем сеттинге живет другой большой поисковик. Как там устроен поиск я доподлинно не знаю, но все сервисы создаются через grpc описание, в моннорепозитории можно пойти и всем закомитить нововеденние. Так что, да схема может работать.

Про шестую проблему, все таки не совсем тебя понял. Чтобы внедрить фичу, мне нужно потрогать все сервисы на пути пользователь -> мой микросервис. То есть пойти кода написать в микросервисы соседних команд, что не очень удобно. Под аппхостом, я добавил вершинку в граф, написал бекенд этой вершинки и готово.
про шестую — не нужно своими руками ничего делать. это делает автоматика обновляя используемую библиотеку фреймворка в модулях и пересобирая бинарник в ci/cd

Так их же полно )
envoy (как основа для API gateway/service mesh, можно самому воспользоваться), gloo, ambassador (правда, k8s only), kong, gravitee, tyk — сотни их
Причем многие опенсурс. Есть и платные.
Основная ценность — именно, что дополнительный функционал помимо retry, circuit breaker, tracings etc., а закрытие АПИшек авторизацией, веб-панели со статистикой, возможность управления из GUI etc. etc.

Написав «ещё одна» вы подразумеваете еще кого-то кроме указанной в посте компании? Когда дело касается действительно высоких нагрузок (даже гигантских) готовое универсальное решение будет проигрывать самописному (при условии что пишут профессионалы со знанием предметной области).
Это здорово что подобные «велосипеды» изобретаются «еще одной» крупной компанией.

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

Nginx тоже когда-то был самописным велосипедом, ведь до него уже были как минимум Apache и IIS.

nginx стал не котироваться, когда он начал хотеть денег. Либо перекомпиляция и искать сторонние модули, либо покупаешь nginx plus.
В результате появляются статьи вроде https://m.habr.com/ru/company/oleg-bunin/blog/423085/
И, да, энвой — кумир 21го века, а энжинкс пора на боковую

Интересный вопрос. А вот те «велосипеды», что конкурируют на рынке — почему их создание «целесообразно и оправдано», а создание альтернатив для них — это «общественно порицаемое деяние»(судя по комментам явно создаётся такое ощущение)? Почему создание нового фреймворка — это плохо. А использование старого и хорошо проверенного временем — это «верх профессионализма»?

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

А ведь в бизнесе ключевой момент успешности любого продукта(сервиса) заключается в наличии у него некоторого «конкурентного приемущества» — т.е. по-определению некоторого свойства, которым конкуренты не обладают. Чего-то абсолютно нового. Иногда это может упираться в отличия их IT-платформы в том числе. За счёт разнообразия и многообразия мы и имеем общий прогресс.

Почему существуют бизнесмены и реализуют всё новые и новые бизнес-идеи? Почему не говорят: «Нафиг что-то придумывать и рисковать делать новое. Возьму франшизу или открою ларёк». Так вот для реализации их идей и требуются нетиповые подходы и разработки. Просто всегда есть риск, что «ставк»а не сыиграет.

А потом со временем получается, что «типовые решения» — это выжившие в конкурентной борьбе прошлые «велосипеды».

Говорить, что «создавать велосипеды — это плохо и непрофессионально» можно только неявно признаваясь в том, что: (1) либо наши задачи очень сильно типовые; (2 )либо собственного профессинализма никогда для реализации подобной задачи не хватит; (3) либо при наличии ресурсов риск реализации кажется неоправданным. Но так думают не все и не во всех случаях. И поэтому, по мне, лучше привествовать всё новое. Иначе так всю жизнь и придётся ездить на «велосипеде»… трёхколёсном… хоть и чертовски надёжном:)
Ну дак гуглу можно пилить велосипеды а яндексу — нет. Всё просто.
Правильно сказано «единая точка отказа»!
«На деле так как инстансов Apphost много, они живут на машинах из разных стоек — значит, сервис выживает при выпадении хоста, стойки или даже целого датацентра»
Это касательно железа. А если баг в софте Apphost?
Если баг в софте, то да, можно все сломать. Но тут как, если баг в ядре, балансировщике нагрузки, софте свитча, в супервизоре облака, все сломается точно так же… Так что добавление Apphost не делает конструкцию более хрупкой.

Остается только качественно тестироваться и аккуратно катать релизы.
Допустим, вы отправили запрос в первый бэкенд, а он вернулся с кодом 500 или затаймаутился. Самая простая гипотеза — обычное невезение: затупила сеть, инстансу бэкенда, в который вы попали, стало плохо. Перезапросить ошибку в таком случае — вполне естественное желание. Но нельзя же повторять запросы бесконечно: если проблема посерьёзнее невезения, получим DDOS своими руками, бэкенд ляжет и не сможет вернуться к работе без вмешательства человека. Чтобы нагрузка не превышала разумную, важно предусмотреть в структуре балансера и фронтенда бюджеты на повторные запросы


Вот тут мне не очень понятна логика. Если у нас что-то временно «не так», можно вернуть клиенту код 503, который означает, что:

The server cannot handle the request (because it is overloaded or down for maintenance). Generally, this is a temporary state


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

Выделять эти ресурсы внутри кластера — дороже для кластера, как в плане ресурсов, так и в плане сложности.

Можно как-то усилить аргументацию в пользу реализации «перезапрашивание» внутри кластера?

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

Пожалуй да, если сервис должен выдать некий статический html, то 503 не подойдет.
Вот тут мне не очень понятна логика. Если у нас что-то временно «не так», можно вернуть клиенту код 503, который означает, что:

нет, есть же концепция graceful degradation ) Когда ты клиенту возвращаешь часть функционала, а что там у тебя под капотом его волновать не особо должно


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

+++ Но есть же AJAX! Viknet и соответственно, половина веб страниц — по факту это уже динамические веб-приложения

Я думаю, конкретно в поиске довольно большая часть запросов идёт напрямую из поисковой строки браузера на выдачу.
Ну и есть такое соображение, что делать повторные запросы внутри ДЦ гораздо быстрее, чем от клиента.

Расскажите на примере, как выглядит взаимодействие со стороны рядового сервиса А, который хочет отправить запрос в сервис Б, а затем в В.


Условно, сервис посылает запрос на http://apphost (допустим, набор реплик получен через service discovery или любым другим способом), где apphost — какая-то реплика AppHost. Каким образом сервис А скажет, что он отправляет запрос в сервис Б/В? Заголовок? Префикс пути? Что-то ещё?

Сервис А не хочет отправлять запросы в Б и В.

На аппхосте добавляется правило: когда клиент пришел по урлу Х — сходить в Б и В, взять из их ответов вот такие данные и сходить с этими данными в А

Как тогда обстоит дело с вложенностью?


Т.е. пришёл юзер на урл X, аппхост отправил этот запрос в сервис А, сделал какую-то часть логики, затем ему потребовалось, допустим, получить информацию о пользователе, которая хранится в сервисе Б. А сервису Б ещё нужно подгрузить ещё какую-то информацию из сервиса В.


В статье есть пример графа взаимодействий, на котором особо ничего не видно. + есть схематичный пример (в разделе с узкими местами), но там вложенности нет. Поэтому неясность остаётся :)


клиент пришел по урлу Х

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

Как тогда обстоит дело с вложенностью?

какая вложенность, не понимаю?


На аппхосте добавляется правило: когда клиент пришел по урлу Х — сходить в Б и В, взять из их ответов вот такие данные и сходить с этими данными в А

чисто функционал api gateway, так-то.


Т.е. пришёл юзер на урл X, аппхост отправил этот запрос в сервис А, сделал какую-то часть логики, затем ему потребовалось, допустим, получить информацию о пользователе, которая хранится в сервисе Б. А сервису Б ещё нужно подгрузить ещё какую-то информацию из сервиса В.

странный сценарий, я так понял, что А, Б и В фактически независимы друг от друга.

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

С точки зрения апхоста другой граф/другой апхост это такой же источник, как и любой другой.
Вложенности нет. И обращений обратно к аппхосту быть не должно. Если сервис А зависит от Б — это должно быть учтено в графе, чтобы аппхост сам, превентивно, сходил в Б и получил что нужно, и только потом дергал А.

По сути получается некий аналог ФП в мире микросервисов, понял, спасибо :)

Это, извините, не «А зависит от Б», а запрос клиента зависит от А и Б(А) (потому что аргументы для Б могут прийти любым другим способом, кроме того, чтобы быть получены из А). Но вообще такие связки выглядят как какой-то своеобразный (кривой?) дизайн. Надеюсь, что он спроектирован осмысленно, а не просто так. И есть реальные причины так делать

Наоборот, запрос клиента зависит от Б и А(Б). Ну да, статья как раз о том, почему это осмысленно и какие реальные причины так делать (проблемы) :)

Допустим, есть сервис А. Он владеет некоторыми данными о приславшем запрос юзере. На основании этих данных А решает, надо ли делать запрос в Б, и с какими параметрами. Как именно аппхост разруливает эту ситуацию?


По графу аппхост знает, что для выполнения задачи А ему может (или нет) понадобиться запрос в Б с неизвестно какими параметрами (потому что они в БД сервиса А). Как аппхост сделает запрос в Б самостоятельно, до запроса в А?


Есть вариант, когда нужный метод А делится на две части: первая "вернуть из БД параметры для запроса в Б, если этот запрос нужно сделать", и вторая "использовать ответ от Б (если туда отправлялся запрос) из своих параметров и завершить обработку запроса юзера". Тогда аппхост сможет сделать вызовы А1, (опционально) Б(A1), А2(Б(А1)). Но этот вариант заметно усложняет реализацию А (бизнес-логика может быть довольно сложной, на сколько частей надо будет разделить один метод, которому нужно отправить запросы в 10 других сервисов?), плюс переносит часть бизнес-логики из А в аппхост, плюс вынуждает А выставить часть своих внутренностей наружу как публичное API (тот самый запрос A1 возвращающие данные из БД нужные для запроса в Б).

Да, аппхост плохо разруливает эту ситуацию, действительно, будет заметное усложнение логики. Но если вы проектируете сервисы для работы в парадигме аппхоста — вы постараетесь с самого начала делать сервисы так, чтобы таких ситуаций не возникало. Кто главный источник данных в вашем запросе — сервис А или сервис Б? Если сервис А — почему он владеет данными о юзере, не должны ли они быть сосредоточены в сервисе Ю? И цепочка например Ю, А(Ю), Б(Ю)?

Не приводит ли этот подход (проектирование в парадигме аппхоста) к тому, что появляются такие вот сервисы Ю, которые иначе были бы вообще не нужны, и, более того, по сути дают через API прямой доступ к данным, которые, по-хорошему (с точки зрения bounded context, например), должны бы принадлежать другому сервису и вообще не быть доступны напрямую через API?


И стоит ли такое нарушение инкапсуляции и повышенная хрупкость (изменение структуры данных Ю потребует изменения его API, что в свою очередь скорее всего потребует изменения API сервисов принимающих эти данные от Ю) тех плюшек, которые даёт аппхост? Ведь получается, что мы гробим архитектуру ради мелких технических преимуществ (вроде наглядности графа и избавления от логики ретраев в коде сервисов) — в дальней перспективе такое обычно сказывается очень негативно.

Встречный вопрос: почему у вас сервис А хочет ходить в сервис Б? Почему данные, необходимые для похода в Б, лежат в БД сервиса А? Не спутаны ли у вас сервисы?)

А так это всё трейдоффы \(º_o)/

Пока у вас прямолинейный подход дает два сервиса А и Б, и А ходит в Б — вам аппхост, наверно, не нужен. Когда все это приводит к тому что там слоев, скажем, пять — то уже становится пора это как-то поплющить — чтобы не гробить архитектуру. Аппхост — просто системно подходит к тому чтобы плющить, делает архитектуру строго однослойной. Со своими плюсами и минусами, конечно.
Странно это как-то — сделать один общий оркестратор «для всего»…
А иметь в наличии много разных оркестраторов если этого «всего» очень много — это не странно? :)
Нет, это укладывается в концепцию микросервисной архитектуры, где каждый сервис идеологически должен решать ровно одну задачу.

Главное — не переборщить с разбивкой на микросервисы, а то Вы скатываетесь к прям «функциям» (серверлесс), но сложность по факту никуда не девается и это ещё вопрос — что проще — управлять 5 серверами, 50 «микросервисами» или 500 «наносервисами»

В «концепцию» может какую-то и укладывается, но вот в голову не всегда:) Иногда можно получить комбинаторный взрыв на многообразии связей между избыточным множеством компонентов, и, как следствие, увеличение числа точек отказа и увеличением сложности управления такой системы, т.е. прямым увеличение эксплуатационных расходов со снижением общей надёжности. Надо всегда помнить, что декомпозиция и нормализация — это всего лишь инструмент, а не правило. И, как любым инструментом, ими нужно пользоваться только целесообразно ситуации.
Сори, если пропустил — на чем сервис написан и как примерно в него выкладываются конфиги?
Написан на плюсах.

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

Аппхосты уже смотрят в YT и качают оттуда обновления.
Интересная статья, спасибо! На чем написан Apphost, если не секрет? И что с Apach'eм и Перловыми модулями? Отправили их на вечный покой?
Перловые модули едут на покой уже несколько лет :) Насколько я знаю, уже не далеко.

Язык — плюсы.
Спасибо! У меня прям приступ ностальгии, стоило про этот перловый код вспомнить. В то же время приятно, что от него наконец избавились.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.