LINUX.ORG.RU

Вариант системы распределения задач на Redis Streams

 , , ,


0

1

Вопрос в продолжение Redis streams кто-нибудь юзал?. Пришла в голову идея как сделать очередь на редисе на стримах вместо пачки zset так чтоб было красиво и органично. Вернее не очередь, а скорее распределенное выполнение задач.

Дальше идёт стена текста, но если не лень - попинайте если видите явный косяк

Что нужно:

  • потребитель закидывает задачу через grpc в сервис с воркерами
  • задача идёт в очередь, один из воркеров берет задачу, делает
  • потребитель получает результат.
  • если задача не выполнена за N (отличается в зависимости от задачи) секунд - скорее всего воркер сдох или затупил и её надо выдать другому воркеру
  • нагрузка небольшая, от силы 10рпс. Но таски бывают очень разные по стоимости выполнения
  • таски в целом не критичные, пролюбить пару-тройку при смене мастера в редисе можно

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

Что есть: редис с двумя zset, прям как по манам про reliable queue. Описание задачи в виде строки атомарно через луа переходит из одного сета в другой когда её берет воркер. Есть монитор, который смотрит второй сет и пинает просроченные таски. Результат задачи обычно файл (исторически), идёт на s3, потребитель поллит его ожидая результат.

Что не нравится:

  • сериализация в строку это треш, лучше hash, но тогда нужно отдельно писать в хеш задачу и отдельно пихать её ключ в очередь
  • в задаче появляется служебная информация типа номера попытки
  • если результат не файл - надо что-то где-то извращать, например давать какой-то ключ и поллить его потребителем либо юзать пабсаб
  • луа, без него такое не сделать
  • кастомный код, много
  • монитор должен магическим образом знать через какое время задача считается зафейленной либо это информация опять же пихается в описание задачи (например скором в zset)
  • сложно корректно вернуть ошибку
  • поллить s3 это не очень весело

Что думаю сделать: Очередь на стримах по типу кассы в маке (который самозапретился в рф).

  • Потребитель кидает таск в интерфейс/распределитель/оркестратор. Для простоты - в кассу. Касса кидает его в стрим в виде хеша, и в ответ получает талон с номером в стриме, отдает консьюмеру. Консьюмер открывает пабсаб и и ждёт «возле экрана с номерами над кассой».
  • воркер берет таск и начинает утюжить. Когда он его завершает, он отдает его в кассу.
  • Касса кидает в пабсаб клич с номером на талоне N раз с небольшим интервалом проверяя что таск не закоммичен. Если на выдачу никто не пришёл - пожимает плечами и убирает заказ (коммитит в стрим)
  • Консьюмер идёт к кассе, берет заказ, смотрит что там бургер вместо эксепшена, коммитит в стрим и идёт с подносом обратно в вызывающий сервис. Ну либо берет экспепшн, как повезёт.
  • Монитора нет, но раз в N секунд потребитель тихо спрашивает на кассе где его картоха, и на этом запросе таск перераспределяется другому воркеру (тут видимо +1 очередь и клейм, не нашёл метода как сказать стриму что таск свободен). Это в целом исключение, таймаут заведомо больше типового времени работы таска

Плюсы:

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

Пните кто видит какие проблемы у этого подхода.

П.с. почему не кролик/кафка/зеро/актив: редис уже есть и его поддерживает провайдер, с бэкапами, sla и вот этим всем. Брать ради одной-двух очередей ещё три сервака и пачку софта на поддержу я не буду. Да и оно немного не для того, тут скорее распределение при некой синхронности для консьюмера

★★★★★

Последнее исправление: upcFrost (всего исправлений: 3)

судя по задаче тебе нужен паттерн saga.

ПС стек не указал. python? ты вроде на нем пилишь, нет?

ППС если бы был golang, то мог бы посоветовать ergo. там и pub/sub c backpressure в виде реализации эликсировского stage, и saga с воркерами и таймаутами на ТХ. вся твоя задача вполне реализуется инструментами из коробки этого фреймворка без привлечения сторонних сервисов. но чтобы начать писать на нем, нужно немного голову переформатировать на акторную модель. это по-сути микросервисная модель внутри приложения.

ergo ★★★
()
Последнее исправление: ergo (всего исправлений: 1)
Ответ на: комментарий от ergo

Стек питон с asyncio. В целом все описанное тоже вроде как либо из коробки стримами и пабсабом либо почти из коробки (если оркестратор сдох).

паттерн saga

Почитаю, спасибо

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от upcFrost

насколько я знаю, из коробки у asyncio тот же pub/sub крайне убогий. просто «шоб было». потому он неюзабелен в реальной жизни. про стримы не в курсе.

ну, это я так, за «тоже из коробки» цепляюсь :). просто немного некорректно сравнивать это убожество с уровнем возможностей Stage. Хотя бы взять возможности паблишера - слать данные броадкастом или раунд робином, или шардировать по хешу. Про возможности реагирования на останов паблишера даже не упоминаю.

ergo ★★★
()
Последнее исправление: ergo (всего исправлений: 1)

У тебя клинты доверенные? Говнозапросы/кривые данные/дудос от них невозможен?

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

ya-betmen ★★★★★
()
Ответ на: комментарий от ya-betmen

Клиенты доверенные, таски внутренние, входы успевают раза три провалидироваться минимум. Стрим в плане жрпс стрим или редисовый? Если второе, нарушает изоляцию. Если первое - сложно по техническим причинам (есть пачка нужных нашлепок которые придётся оторвать)

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от OxiD

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

Например стримы берут пары ключ-значение, в то время как сеты берут строку/байты. Или что узнать номер попытки можно прямо из стрима без лишних значений в описании таска. Или пабсаб вместо полла (тем более s3 с негласным «результат = файл»). Или нативные группы консьюмеров. Просто органичнее вписывается

Это как если гвоздь забивать положив на шляпку плоскогубцы и стуча по ним ручкой отвёртки. Можно - да, удобно - ну если один гвоздь и нет молотка то в целом да, если честно. Но когда так забиваешь уже третий десяток то начинаешь думать а может потратить 30 мин и пойти купить молоток

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 2)
Ответ на: комментарий от ya-betmen

В чем именно проще?

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

upcFrost ★★★★★
() автор топика
Ответ на: комментарий от ergo

Ну как минимум это не взлетит если больше одной ноды, оно всё-таки локальное. Суть что реплик точки входа (касс если продолжать сравнение с маком) больше одной, и точек выхода соответственно тоже. Пабсаб на редисе может их все покрыть, ему не важно с какой реплики консьюмер сидит слушает

upcFrost ★★★★★
() автор топика
Последнее исправление: upcFrost (всего исправлений: 1)
Ответ на: комментарий от upcFrost

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

Ты можешь цеплять/отцеплять и клиентов и воркеры. Опционально можно сложить таски и ответы в редис, чтобы пережить падение.

ya-betmen ★★★★★
()
Последнее исправление: ya-betmen (всего исправлений: 1)
Ответ на: комментарий от ya-betmen

Сорри, все равно не врубился.

Хранилище в данном случае не редис? А что тогда, другая база? Или некий сервис? Если второе - как контролировать что из нескольких инстансов сообщение от воркера придет на тот где сидит клиент? И почему клиент получит все ответы?

В плане я если честно не вижу особой разницы между тем что ты написал и тем что в посте помимо использования стримов в жрпс (которые кмк всё-таки для передачи длинных списков и потоковой обработки, а не для очередей)

upcFrost ★★★★★
() автор топика