Pull to refresh

Comments 22

Выглядит интересно. Спасибо.
RabbitMQ хорошо работает когда вам нужно выполнять таски на нескольких серверах. В пределах одного сервера Rabbit представляет из себя суровую пушку стреляющую по воробьям, для этого лучше подходит, например, 0MQ.

На клиент сайде так же можно сделать обвязку поверх транспортного уровня, чтобы не писать напрямую в вебсокет (socket.io или sockjs).

В идее с wsgi есть проблема, по крайней мере если вы рассматриваете торнадо как фронтенд и воркеры как бэкенд обработки. Воркеры работают с чистыми данными, им не нужен request и заголовки, сессии и прочее, с этим должен работать торнадо. Уровень wsgi здесь кажется лишним, так как сразу напрямую можно использовать объекты бизнес-логики без каких-либо дополнительных прослоек.
RabbitMQ хорошо работает когда вам нужно выполнять таски на нескольких серверах. В пределах одного сервера Rabbit представляет из себя суровую пушку стреляющую по воробьям, для этого лучше подходит, например, 0MQ.


Конечно, речь идет о распределенной системе, которая работает на нескольких серверах. На одном сервере тоже можно, но для разработки ИМХО. А вот в боевых условиях, можно тиражировать воркеров как угодно, и единственное что им нужно это связь с RabbitMQ.

На клиент сайде так же можно сделать обвязку поверх транспортного уровня, чтобы не писать напрямую в вебсокет (socket.io или sockjs).


Немного не понял о чем вы.

В идее с wsgi есть проблема, по крайней мере если вы рассматриваете торнадо как фронтенд и воркеры как бэкенд обработки. Воркеры работают с чистыми данными, им не нужен request и заголовки, сессии и прочее, с этим должен работать торнадо. Уровень wsgi здесь кажется лишним, так как сразу напрямую можно использовать объекты бизнес-логики без каких-либо дополнительных прослоек.


Идея с wsgi в том, чтобы взять django/flask/etc. приложение и запустить его «воркером». Тогда на все ноды с приложением, будет равномерно распределена нагрузка + очереди.
Вы в воркере уже можете получить готовые параметры для работы, зачем поверх этого вы еще хотите накрутить дополнительный уровень wsgi? Если так, то nginx умеет делать load balancing и работает с бэкендом по wsgi, так же, судя по всему, умеет отправлять сообщения напрямую в rabbit.
Название статьи можно перефразировать как «Масштабируем Слоупочность lvl 100»

1. AMQP толст — если размер Payload'a будет меньше размера Header'а — получаем Amplification в рамках кластера / ноды, сервис ложится при первом же скачке нагрузки. Согласен с prefer ZMQ менее избыточен.

2. Масштабировать вертикально с помощью AMQP в рамках одной ноды — ересь! Сжечь, и не смотреть где прах закопают!
Реализуйте очереди обработки задач по типу Disruptor'a в рамках приложения, или используйте готовые сурогатные модели актёров по типу Greenlet'ов. Пилите SOA.

3. Хотите строить «распределённые» сервисы — проводите нагрузочное тестирование jMeter'ом и Яндекс.танком и профилируйте до потери пульса! Иначе получите бутылочные горлышки и Amplification'ы в самых не очевидных местах.
AMQP толст — если размер Payload'a будет меньше размера Header'а
Любая распределенная система (я говорю не об одной ноде с RabbitMQ а о кластере хотя-бы из двух трех нод) это overhead на пересылку пакетов по сети сериализацию/десериализацию. Хотите «тонкий» протокол общения ок, используйте 0MQ (кладите все в «shared memory» наконец), хотите работать на 10 виртуалках в облаке, планируйте жизнь с неким overhead.

Масштабировать вертикально с помощью AMQP в рамках одной ноды — ересь!
Именно поэтому слово «распределенный» и является ключевым. В статье нет ни слова о вертикальном масштабировании, crew это именно про горизонтальное масштабирование.

Хотите строить «распределённые» сервисы — проводите нагрузочное тестирование jMeter'ом и Яндекс.танком и профилируйте до потери пульса!
Согласен, сурового highload добиться можно только так. Сейчас речь идет скорее о надежности (непроизвольный отвал нескольких нод и тому подобные жизненные реалии). Сейчас версия модуля 0.7.0, у меня он покрывает все кейсы, работает и все суровые баги я уже почистил, сейчас начинаю всестороннее тестирование. Если вам интересен проект, давайте проведем нагрузочные или интеграционные тесты, помогите проекту.

Статья называется «Как просто написать распределенный веб-сервис на Python + AMQP» и тут нет и намека на суровый highload. Но у меня на практике в основном все упиралось не в amqp и не в данный модуль, а скорее в базу, которую ходят воркеры.

В случае highload слово «просто» вообще отпадает. Кто нибудь видел «простой» highload кластер наяву?
У меня распределёнка и highload'ы начинаются с 10Гбит трафика, я просто сужу по своим потребностям и задачам которые приходится решать. Не могу сказать что разработку подобных решений можно назвать простой, но она и не особо отличается от разработки любых других RESTful SOA приложений, просто есть свои особенности в архитектуре, и в организации работы.

Если у меня будет 10ток машин в виртуалке, то перво-наперво я сделаю так что бы они между собой не разделяли общих состояний, а только логировали запросы. В итоге это CQRS-ES с логированием текущих транзакций посредством Raft алгоритма.

Нужно отличать вертикальное масштабирование от горизонтального.
Вертикальное — эффективная утилизация мощности каждой конкретной машины с минимальными издержками на коммуникацию, тут работают модели актёров и разнообразные очереди с приоритетами (типа Disruptor), sharedmem тут не обязателен так как почти все сервисы выполняются в рамках одного процесса, ну в общем по аналогии с tasklet'ами линуксовского ядра.

Горизонтальное — распределение нагрузки на несколько машин без существенного увеличения времени отклика, тут нужно понимать что всякие master-master репликации — непозволительная роскошь. У меня обычно всё сводится к Raft'у и нескольким ведомым машинам дублирующим текущие запросы посредством зеркалирования трафика. Шардинг и репликация в таких условиях решается уже на уровне приложения, и с БД совсем не нужно заморачиваться… но это тема отдельной статьи.

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

У вас судя по всему есть свой проверенный временем подход. В моей реализации (на моем проекте где используется crew), в целом, все workers — stateless и роль «ведущих машинок» выполняет RabbitMQ и пяток серверов с tornado, по которым размазывает 2 haproxy. База MongoDB поэтому вопрос шардирования и репликаций решен та том уровне. Но никто не мешает написать логику шардирования в воркере и бегать по шарду из SQLBased СУБД.
Комменты можно перефразировать так: «в моей системе раздачи контента AMQP не нужен».

Ну да. Если задача стоит в пересылке данных, то пересылать данные дополнительно через AMQP смысла нет. Но статья же не про это.

Хотя оверхед на AMQP вообще-то меньше чем на HTTP.

ps. 10гбит трафика — «распределенка и highload»?.. Ребята из Netflix на YaC'е рассказывали что у них по 40гбит с ноды отдается… :-)
Зачем CDN'ам CQRS-ES'ы и реактивность?
Отдавать 40Гбит DWDM'у не проблема…

Я не раздаю контент, в основном Restful API и различные конкурирующие транзакции с большой стоимостью отката и синхронизации.
Возможно, это глупый вопрос, но почему нельзя было взять Сelery?
В задачи celery не входит доставка результата в браузер, правда для этого можно использовать celery + pusher. Но если нужно без обращений на сторонние сервера, то ничего лучше не приходит, как использовать для этих целей tornado.
Отличный вопрос, у celery действительно похожая идеология, но немного не тот подход. А так да, почти celery, но заточена на amqp-фичи, типа DLX и прочего.
Возможно, об этом стоит расписать поподробее, в смысле, описать кейсы, где это может быть нужно.
На самом деле ответ кроется в названии. «распределенный веб-сервис» это именно тот самый кейс. Когда у вас части системы живут отдельно на разных машинках.
Я не хочу чтобы вы это воспринимали как еще один клон celery. Тут идея в асинхронном приеме заданий, и синхронных обработчиках.
Когда eigrad предложил написать прослойку, которой можно запустить любое wsgi приложение с помощью crew, мне эта идея сразу понравилась. Только представьте, запросы хлынут не на ваше wsgi приложение, а будут равномерно поступать через очередь на wsgi-worker.


Реализовать именно прослойку будет скорее всего достаточно просто, т.к. WSGI достаточно простой протокол. Меня лично здесь стали бы волновать другие вещи: логирование (должно быть централизовано или централизуемо), graceful restart и версионность кода.
graceful restart


Должен работать уже, т.к. ACK на задачу посылается после ее обработки. И если убить воркер до посыла ACK задачу RabbitMQ переназначит другому.

версионность кода


Этот вопрос я бы на вашем месте расписал подробнее. Что понимается под версионностью кода.

логирование (должно быть централизовано или централизуемо)


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

Опять же на совести разработчика. Worker это просто функция, лично у меня есть декоратор который шлет в sentry ошибки.


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

Этот вопрос я бы на вашем месте расписал подробнее. Что понимается под версионностью кода.


Я про версии кода приложения. Вот у вас обновляется код wsgi-приложения, например, разработчик фиксит баг какой-то или меняет бизнес-логику. Обновить везде код и разом перезапустить — плохой вариант. Перезапускать просто всё по-очереди — вариант лучше, но есть некоторый риск в том, что у нас будут одновременно работать две версии кода (что, при смене бизнес-логики иногда бывает очень нехорошо). Было бы неплохо, если бы такой сервер (или шина) позволяли делать рестарт так, чтобы направлять новые запросы на обновлённые обработчики, давая старым обработчикам доработать своё, обновиться и перезапуститься.
С распределением нагрузки по серверам не так уж и плохо справляется nginx
Да только вот если к вам придет 10к запросов, nginx сразу их пошлет на backend. И тут уж вашему приложению придется самому справляться с нагрузкой. В данном же случае, у вас все ляжет в очередь, и workerы потихоничку и без напряга все отработают.

Зачем же вообще wsgi это для того чтобы вы максимально не переписывали код, просто запустили уже работающее приложение.

Я про версии кода приложения. Без проблем называете несовместимый по запросу/ответу таск «app.mysupertask.v1» и фронтенд с уже обновленным кодом все будет слать на новые воркеры. Даже если вы остановите все воркеры, у вас приходящие сообщения будут копиться в очереди (учитывая timeout который вы уже обработаете сами), а после запуска воркеров, все побежит.
В данном же случае, у вас все ляжет в очередь, и workerы потихоничку и без напряга все отработают.


Может быть я это не заметил в статье, но у вас же есть таймауты для задач в очереди?

Я про версии кода приложения. Без проблем называете несовместимый по запросу/ответу таск «app.mysupertask.v1» и фронтенд с уже обновленным кодом все будет слать на новые воркеры. Даже если вы остановите все воркеры, у вас приходящие сообщения будут копиться в очереди (учитывая timeout который вы уже обработаете сами), а после запуска воркеров, все побежит.


А, вот и про таймаут. Не очень понятно, что означает «обрабатываете сами», правда.

Кажется, я понял, в чём заключается предлагаемый подход. Согласен, он может применятся, хотя я предпочёл бы, чтобы фреймворк/сервер/шина умела решать это за меня.
В примере есть TimeoutError и ExpirationError которые выбрасываются в зависимости от того что с задачей. Первая в случае если таск был взят, но воркер не обработал его за ttl. Вторая если таск так никто и не взял. А обрабатывайте сами — это значит, что можно попробовать повторить или сказать клиенту 500 404 etc. Вобщем в зависимости от требований. А идея wsgi-worker и предпологает решение всего что касается wsgi средствами этого wsgi слоя.
Sign up to leave a comment.

Articles