Comments 104
— Almost all the successful microservice stories have started with a monolith that got too big and was broken up
— Almost all the cases where I've heard of a system that was built as a microservice system from scratch, it has ended up in serious troubl.
Т.е. если начинаешь проект сразу с микросервисов — получаешь проблемы.
Один из вариантов разработки — начать с малого количества «макросервисов», а затем их дробить по мере необходимости.
Another route I've run into is to start with just a couple of coarse-grained services, larger than those you expect to end up with. Use these coarse-grained services to get used to working with multiple services, while enjoying the fact that such coarse granularity reduces the amount of inter-service refactoring you have to do. Then as boundaries stabilize, break down into finer-grained services.
Правда, это было довольно давно (лето 2015 года).
Ну и у Райффайзенбанк есть статья здесь: Микросервисы делают мир проще (а вот и нет)
Конечно, нужно сразу избавляться от взаимных зависимостей в коде, чтобы облегчить отстрел микросервисов по мере необходимости («But if you start with a monolith, the parts will become extremely tightly coupled to each other», ссылка). Согласен, OSGi — отличный способ для этого, по сути, «микросервисы».
Делать четыре микросервиса:
— Client
— Article
— Order
— Payment
и еще к ним непонятное количество микросервисов, которые делают отчеты?
Мне пока видится, что нужны три монолита: BO (редактирование справочников), Sales (заказы, оплаты и проч.) и Reports.
Микросервис per entity — как и практически любое архитектурное разделение per entity — плохая идея за редкими исключениями. Делите по бизнесу, по его процессам.
Часто встречаю такую тему, кстати, "у нас Х таблиц в базе, куда нам столько микросервисов", есть ощущение что это каргокульт: если у меня есть микросервис, скажем, форумного движка, то в нем очевидно окажется сущность Forum и называться он скорее всего будет ForumService. Но при этом в нем будут содержаться и темы, и модераторы, и комментарии, и спасибы, и прочие. Но со стороны все равно появится ощущение, что это сервис только форумов.
И если при проектировании системы в обозримой перспективе эти задачи перед вами не стоят, скорее всего, эта технология вам здесь и сейчас не нужна, какой бы прекрасной она при этом ни была.Полностью согласен и сам не раз сталкивался с такой проблемой. Люди пытаются вкрячить в свои процессы какую-то технологию, просто потому, что она на слуху и «все так пишут». Частный случай карго-культа.
Но вот что обязательно стоит учитывать при планировании своей системы, это возможность расширения или замены какой-то ее части в принципе. Иначе можно столкнуться с ситуацией, когда технология на старте отмечена как «когда-нибудь совсем нескоро», сегодня это «нескоро» наступило, но перейти на нее мы не можем, просто потому, что для этого придется переделывать проект с нуля.
нет ничего невозможного для пыткого велосипедостроителя!
У нас, к примеру, все микросервисы крутятся в openshift и настройку осуществляем тоже мы, через чарты.
Таким образом, при особом желании, можно скрестить несколько микросервисов и напрямую заставить их смотреть в одну базу, к примеру. Или смотреть в базы своих соседей. Особых трудностей сделать это нет.
Разумеется, так никто не делает, но сама возможность никуда не делась.
А про доступ через публичный интерфейс — ничего не мешает написать защищённый системный метод, который будет принимать в себя произвольный запрос и отдавать данные по этому запросу. Это будет лютая дырка в безопасности, но кто же помешает людям сделать именно так?
И вот уже появляется толпа методов в публичном интерфейсе нужных только для того, чтобы форма объекта могла со своим объектом пообщаться. Естественно вся эта толпа публичных, но по факту служебных процедур и функций никем не тестируется на некорректные входные параметры и тд. Так как разработчик знает, что вызывать этот публичный метод будет только он из одного единственного места.
(а когда при доработке другой программист попробует воспользоваться этим публичным методом, то огребет по полной — от неожиданных и плохо описанных структур в параметрах, до сайд эффектов, которые ему не нужны, но про которые он не знает)
Как такой же фрагмент кода будет работать в реактивном стиле? Нить исполняет вычисления, посылает HTTP-запрос и вместо того чтобы заблокироваться и при получении результата синхронно обработать его, описывает код (оставляет callback) который должен быть исполнен в качестве реакции (отсюда слово реактивный) на результат. После этого нить продолжает работу, делая какие-то другие вычисления (может быть, как раз обрабатывая результаты других HTTP-запросов) без переключения контекста.
Это не реактивное, а асинхронное программирование на коллбеках. Хотя реактивные фреймворки зачастую имеют асинхронный реактор, но одной из фич реактивного программирования является как раз избегание коллбэков. Реактивное программирование — это прежде всего про потоки данных внутри системы и распространение изменений. Никаких отложенных вызовов не предполагается. За манифест спасибо, посмотрю.
Откуда такое заключение? Если заглянем в wikipedia.org/Reactive_programming, то там написано: Update process: callbacks versus dataflow versus actors, то есть, никакой дискриминации колбэков. А если учесть, что callbacks, dataflow и actors — разновидности асинхронного вызова процедуры, то выбор конкретной разновидности определяется только удобством.
Реактивное программирование по определению использует асинхронный подход, но нужно понимать что это абстракция более высокого порядка.
В том же .net есть старая парадигма APM когда вместо блокирующих методов создаются пары BeginXXX / EndXXX (в BeginXXX передается колбек, в котором нужно вызвать EndXXX который вернет результат или ошибку).
А реактивный это уже IObservable, и LINQ-подобные операции (Select/Where/Join/Aggregate) над источником событий. За счет того что это модель push (в нашу цепочку операторов событие пропихивается источником), а не pull (когда мы делаем условный вызов и ждем результат), модель неизбежно асинхронная.
Но опять же, важно доказывая вред блокирующего подхода противопоставлять ему не реактивный, а асинхронный
Я взял цифры из исследования компании IBM, если не ошибаюсь, двухлетней давности. Кратко, если мы говорим о дисковых операциях, использовании процессора или доступе памяти, Docker почти не добавляет оверхеда (буквально доли процента), но если речь идет о network latency, задержки вполне ощутимы. Они не гигантские, но в зависимости от того, какое у вас приложение, могут вас неприятно удивить.
Есть один грязный лайфхак, который безумно очевиден и многие его практикуют. Просто не используйте докер сети, можно все в хостовой сети запустить. Они скорее всего вам не нужны, а оверхед от них приличный.
"bridge": "none",
"iptables": "false"
про что написано — Disabling the default bridge network is an advanced option that most users will not need.
Как-то это м-м-м «грязно» :-)
Спасибо за совет!
идентичность среды для продакшна
Не является проблемой в java-разработке.
Не является проблемой в java-разработке.
Разница даже в минорных версиях JRE уже может приводить к тому, что проблема в продакшене не будет воспроизводиться на дев.стенде.
Практически дюбой микросервис требует открытых портов (для Rest, http, socket), которые могут на проде быть заняты другими сервисами.
Редкое Java приложение вообще не использует внешних баз данных или систем кэширования, которые тоже нужно устанавливать и их версии и конфигурации могут отличаться.
Поэтому «идентичность среды для продакшна» хоть для Java, хоть для любого другого языка важна. Особенно понимаешь важность, когда на продакшен у разработчиков нет иного доступа кроме получения логов, а на тестовом сервере проблема вообще не воспроизводится.
Данные с нескольких тасков можно собрать через CompletableFuture.allOf
, Promise.all
, Task.WhenAll
и аналогичные API.
Статья строго по реалиям жизни.
Мое мнение, что играть в эти игры с перекидыванием задач между не блокирующимися потоками — довольно опасное занятие. Очень легко получить сложный, непредсказуемо работающий код.
Я бы решал задачу примерно так
1. обычные требования среднего веб приложения — вообще не использовать реактивный фреймворк, никакой пользы он не принесет. Те 5-10% процентов экономии которые он дает все равно «растворятся» в общих накладных расходах. Длинные, хорошо написанные, последовательные, блокирующиеся, императивные методы — что может быть лучше и понятнее?
2. Есть длинные задачи — использовать пул тредов и асинхронный запуск задач через org.springframework.scheduling.annotation.Async например. Тут все понятно и все это обычно умеют
3. если есть какие-то супер плотные, обоснованные бизнесом требования по использованию процессора — использовать например WebFlux — и изолировать его использование в отдельный, как можно более компактный сервис. Никаких жонглированием задачами между пулами внутри не делать — такой код относительно легко написать, но очень тяжело поддерживать.
Был неправ.
При этом сам статья, про то, что можно и так и этак, главное осознавать к чему это ведет и какие плюсы и минусы.
Тем более что все быстро меняется и «твердые» рецепты быстро устаревают.
В общем случае любое приложение, чувствительное к задержкам в сети в пределах до 250–500 мс, лучше не докеризировать.
Автор точно имеет в виду мс, а не мкс? +250 мс — это грандиозные задержки, особенно для микросервисных архитектур, где обычно реквест проходит несколько слоев обработки.
Да и на диаграмме над цитатой видно, что Docker добавляет средний оверхед при обработке сетевого пакета порядка 30 мкс, а не мс.
Вот уроды, написали нечто интересное и не дают порушить систему!
знаете, я не из тех, которые делают по мануалу без думно. если я чтото делаю — я понимаю что я делаю. да и почему комуто должно быть какоето дело что я порушу СВОЮ систему
Потому что вы(не конкретно вы, а общно — пользователи) потом придёте к создателю этой приблуды и начнёте вопить, «а чего это вы мне всё сломали?!», «не работает ваша херня, а я так много сил потратил, скачивая её! Почините!».
Поэтому, чтобы большинство жило счастливо приходится ограничивать их, тем самым ограничивая и тех, кто способен сам разобраться и починить.
спрашиваешь — а как не в докере? ответ — да хз
А зачем разработчику париться и пооддерживать весь зоопарк операционок, их мажорных версий и программ, которые просто рядом стоят, но не дают поставить очередную зависимость, т.к. конфликтуют?
Сам спросил, сам ответил, сам молодец :)
Как такой же фрагмент кода будет работать в реактивном стиле? Нить исполняет вычисления, посылает HTTP-запрос и вместо того чтобы заблокироваться и при получении результата синхронно обработать его, описывает код (оставляет callback) который должен быть исполнен в качестве реакции (отсюда слово реактивный) на результат. После этого нить продолжает работу, делая какие-то другие вычисления (может быть, как раз обрабатывая результаты других HTTP-запросов) без переключения контекста.
Основное преимущество здесь — отсутствие переключения контекста
Это фантастика какая-то. Какой-то поток все равно должен заблокироваться во время IO и ждать результат. Если это не тот же самый поток, произойдет точно такое же переключение контекста. Чтобы переключения не было, блокироваться и выполнять callback должен тот же поток, а в этом смысла нет в данном случае.
Просто таски в Rx* выполняются на планировщиках, но переключение контекста с ними такое же реальное, как и при любом другом подходе.
А потом поток заблокируется и будет ждать прихода http-ответов. И обработает их точно так же подряд, и всё это без переключений контекста, если всё это каким-то чудом в квант времени уложится.
Но опять же, реактивное программирование здесь не при чем.
Такое поведение достигается за счет планировщика с 2+ тредами, но не за счет «реактивности». Можно сделать то же самое, используя, например, обычный ExecutorService в Java.
Поэтому «отсутствие переключения контекста» — никак не основное преимущество RP.
Мы обсуждаем пример из статьи, а не спорим как должно быть
Ну так читайте статью дальше примера-то!
Далеко не все операции ввода-вывода поддерживают неблокирующие вызовы. Например, JDBC на текущий момент не поддерживает (в этом направлении идут работы см. ADA, R2DBC, но пока все это не вышло на уровень релиза). Поскольку сейчас 90 % всех приложений ходят к базам данных, использование реактивного фреймворка автоматически из достоинства превращается в недостаток. Для такой ситуации есть решение — обрабатывать HTTP-вызовы в одном пуле потоков, а обращения к базе данных в другом пуле потоков. Но при этом процесс значительно усложняется, и без острой необходимости я бы так делать не стал.
Сеть докера работает на linux bridge, это вносит задержку, но она незначительна и измеряется микросекундами. Если задержка критически важна, что 30-50мкс (тут выше путались, так что поясню — это микросекунды, это в тысячу раз меньше миллисекунд) это значительно — выкинуть нужно сначала JVM.
Я отказываюсь понимать пользу оптимизации в 0.012% за счет отказа простоты деплоя.
Я всегда за оптимизацию (если она не преждевременна, конечно).
Но выкидывать докер из JVM приложения — это как лечить перелом позвоночника прикладыванием подорожника. Мне по долгу службы приходится порой считать и байты, и такты, поэтому тешу себя мыслью, что немного понимаю в оптимизацию. Так, вот по моему опыту (который предельно согласуется с вашим графиком от IBM, кстати сказать) — главное, что можно выкинуть из JVM приложения — это JVM.
Не подумайте, я не противник java, но если вы доходите в оптимизации до того, что вам мешает докер, а главная ваша претензия к скорости работы linux bridge — явой тут должно не то что не пахнуть — у разработчиков даже мылей таких в голове быть не должно. Это совершенно разные порядки времени обработки данных.
И насчёт админов: в своё время инициировал введение докера в стек как раз чтобы не выяснять лишний раз где проблема админов, а где разработчиков. С докером очень удачно рутинные обязанности по эксплуатации разделились: админы отвечают за работоспособность кластеров, за мониторинг и т. п., а разработчики обеспечивают работоспособные контейнеры и «скелет» конфигурации оркестратора. При возникновении проблем очень быстро либо выясняется кто и где накосячил, либо приходит понимание, что тут нужно вместе садиться и вырабатывать общее решение. «отфутболивание» практически прекратилось. Причём админы сначала возражали против докера, а потом им схема работы (именно организационная) понравилась.
Впервые за несколько лет увидел адекватную оценку этих техник. Сам понимал всегда что в большинстве проектов они не нужны, и вот в этой статье нашёл подтверждение своему мнению, разложенное по полочкам. Искренне сочувствую тем, кто внедрил в своих проектах не решение конкретных проблем, а модную штуку, которая сделала проект хуже, зато добавила крутую строчку в резюме программиста.
Не хочу сказать, что эти техники плохи, просто не надо возить уголь автобусами — они хоть и вместительные и грузоподъемные, но всё же неудобно как-то…
То есть, всякие консультанты, чтобы напаривать свои никому не нужные услуги, решили вместо слова "асинхронный" написать слово "реактивной"?
Вот что за бред?
По поводу времени запуска — смешно совпало — буквально сегодня у меня был достаточно жаркий спор как раз про время запуска. Коллега жаловался на слишком долгий старт.
Всегда ли нужны Docker, микросервисы и реактивное программирование?