LINUX.ORG.RU

PHP - реализация отложенных задач

 ,


0

1

Доброго времени суток. Есть следующая задача, попробую описать максимально абстрактно - есть некий клиент, который шлет компаниям запрос. Каждая компания должна получить уведомление об этом запросе. Уведомление запись в БД с привязкой к конкретной компании. Пока количество компаний небольшое, то проблем нет. Но если уведомление должны получить 100+ компаний(нужно создать 100+ записей в БД), то такая логика сильно тормозит страницу. Решил вынести всю эту работу на сервер. Нужные работы будут хранится в базе в формате |Class|Method|Params|Status|Date_create

Логика такая - вызываем метода класса и передаем ему массив params. Скрипт, который вызывается по крону выбирает наиболее старые задачи, у которых статус завершения отсутствует и начинает выполнять. Если метод возвращает true, то ставим статус успешный

Задумался о том, где хранить эти задачи. В той же Mysql базе, что и сам сайт? В файлах? Или может в каком-нибудь Nosql хранилище? Есть ли смысл заморачиваться, или просто хранить в обычной таблице и не мучаться? Буду очень благодарен за советы.



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

или просто хранить в обычной таблице и не мучаться?

This.

Скрипт, который вызывается по крону выбирает наиболее старые задачи, у которых статус завершения отсутствует и начинает выполнять.

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

Deleted
()

Тебе нужен обычный оффлоад с очередью. Для разных фреймворков есть стандартные решения.

no-such-file ★★★★★
()

Я правильно понимаю, что ты пытаешься сделать очередь? Если да, то в простейшем случае можно использовать redis (см. rpop, lpush). В более сложном смотри в сторону RabbitMQ.

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

заводить еще и редис для тупо очереди это как тащить жквери ради тогглинга одного блока на страничке — красава!

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

заводить еще и редис для тупо очереди это как тащить жквери ради тогглинга одного блока на страничке — красава!

Чувак, ты реально странный. Тут выше sidekiq, gearman и messenger (из symphony?) предлагали, но вызвал негодование у тебя redis, который скорее всего и так используется для хранения сессий и кэша запросов к базе.

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

или просто хранить в обычной таблице и не мучаться?

This.

Прикольно. Особенно когда мы не знаем сколько задач в очереди и как как быстро она наполняется. Будешь делать по одному select'у для каждой записи или хранить буфер в памяти скрипта? Если не использовать индексы, то у тебя будут медленные select'ы, а если использовать, то медленные insert'ы. Любители, блин, микроскопом гвозди забивать.

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

Я вообще не пхп-разработчик. ТС описал свой проект не как какой-то хайлоад, требующий обработки миллиона сообщений в секунду, поэтому сойдёт тупое в лоб решение.

Да, по одной записи. Можно лочить их и сделать несколько воркеров.

Deleted
()

Оп это ты сейчас Celery придумал только что. Право не знаю есть ли такое для пхп.

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

выше sidekiq, gearman и messenger (из symphony?) предлагали

Они предлагали готовый инструмент. Но с поправкой что у него там ого-го энтерпрайз.

вызвал негодование у тебя redis

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

который скорее всего и так используется для ...

Врядли.

когда мы не знаем сколько задач в очереди

Зато мы точно знаем что у него есть мускуль. А ты предлагаешь на его велосипед с квадратными колесами присобачить твой велосипед с треугольными.

Вот он пишет:

получить 100+ компаний — сильно тормозит страницу

Ты не понимаешь какой у него здец в коде если пых с мускулем не тянет 100+ компаний? С твоим редисом/кроликом поверх мускуля в его руках оно и от 50+ сдохнет.

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

Ты не понимаешь какой у него здец в коде если пых с мускулем не тянет 100+ компаний? С твоим редисом/кроликом поверх мускуля в его руках оно и от 50+ сдохнет.

Для 100 компаний нужно во-первых создать 100 сущности Answer(ответ компании), во-вторых создать 100 нотисов. 200 вставок в таблицы работают около 2-х секунд. Я не знаю, может это очень большая цифра, может у меня с серваком что-нибудь не так, но хотелось бы ускорить, поэтому я и решил вынести в фон.

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

Я правильно понимаю, что ты пытаешься сделать очередь

да, правильно, очередь отложенных задач

подскажите пожалуйста, в чем будет профит от использования редиса? Будет ли заметный выигрыш при обработке 1000 - 10000 записей?

спасибо большое за ответ

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

Не будет профита. Тебе лишь предлагают хранить очередь в редисе, а не в мускуле. Ну и зачем тебе ещё одна сущность?

Профит ты так и так получишь когда заведёшь свою очередь отложенных задач на бекенде. По крону раз в минуту запускаешь скрипт, который выбирает из таблички очереди все записи, которые ещё не отрабатывались (например, WHERE x = 0 LIMIT 0,10) и отрабатывает их, проставляя «x = 1» или даже удаляя (по айдишнику). Так же, что с редисом, что с мускулем, в любом случае тебе надо делать защиту от запуска второй копии твоего скрипта. А то что там предлагали демона писать на пыхе — гон. Демон на пыхе это нестабильно, он может грохнуться. Так что по крону раз в минуту — самое то. Мыло, в самом тривиальном случае так и рассылается неким воркером по крону.

deep-purple ★★★★★
()
Ответ на: комментарий от gwyllum

200 вставок в таблицы работают около 2-х секунд

Структуру таблиц в студию.

может это очень большая цифра

Это ссаные копейки.

deep-purple ★★★★★
()
Ответ на: комментарий от gwyllum

подскажите пожалуйста, в чем будет профит от использования редиса? Будет ли заметный выигрыш при обработке 1000 - 10000 записей?

с чем сравниваем? с mysql? конечно. у тебя одна операция - добавление в очередь и вторая извлечение из нее. В случае mysql это запись в таблицу, поиск по времени и статусу и удаление или изменение записи. C redis'ом тебе не надо думать про блокирование записей при параллельном запуске, ты можешь одновременно запускать столько воркеров, сколько тебе надо. Плюс твоя очередь не аффектит производительность mysql.

adn ★★★★
()
Ответ на: комментарий от deep-purple

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

Чувак, ты не в теме. И реально не представляешь что такое redis и зачем он нужен, не говоря уже про сервера очередей. Не надо пихать во все щели известный тебе mysql, потому что вот именно это приводит к велосипедизму и потери времени и сил на решение тех проблем, которые вообще бы не стояли при выборе правильного инструмента.

adn ★★★★
()
Ответ на: комментарий от deep-purple

Структуру таблиц в студию.

там очень простая структура на самом деле. Вообще, это битрикс, но не думаю, что там сильно на что-то влияет.

CREATE TABLE `global_notices` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `UF_ACTION_LINK` text COLLATE utf8_unicode_ci,
  `UF_IMPORTANT` int(18) DEFAULT NULL,
  `UF_TYPE` int(18) DEFAULT NULL,
  `UF_TIMESTAMP` datetime DEFAULT NULL,
  `UF_VIEWED` int(18) DEFAULT NULL,
  `UF_ADDITIONAL` text COLLATE utf8_unicode_ci,
  `UF_INDEX` text COLLATE utf8_unicode_ci,
  `UF_NOTICE_CLASS` int(18) DEFAULT NULL,
  `UF_MESSAGE_TOKEN` text COLLATE utf8_unicode_ci,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

UF_INDEX - строка типа «COMPANY_23423423» для быстрого поиска по id компании. По ней сделан индекс. Может быть также USER_243234. Не знаю, насколько это правильно, но решили не разделять на несколько таблиц вида уведомления пользователя, уведомления компании и т.п.

CREATE TABLE `hl_company_answers` (
  `ID` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `UF_REQUEST` int(18) DEFAULT NULL,
  `UF_STATUS` int(18) DEFAULT NULL,
  `UF_COMPANY` int(18) DEFAULT NULL,
  `UF_USER` int(18) DEFAULT NULL,
  `UF_ANSWER_TEXT` text COLLATE utf8_unicode_ci,
  `UF_DATE` datetime DEFAULT NULL,
  `UF_LAST_UPDATE` datetime DEFAULT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

UF_REQUEST - привязка к запросу клиента.

остальные поля вроде и так понятны

gwyllum
() автор топика
Последнее исправление: gwyllum (всего исправлений: 1)
Ответ на: комментарий от deep-purple

Профит ты так и так получишь когда заведёшь свою очередь отложенных задач на бекенде. По крону раз в минуту запускаешь скрипт, который выбирает из таблички очереди все записи, которые ещё не отрабатывались (например, WHERE x = 0 LIMIT 0,10) и отрабатывает их, проставляя «x = 1» или даже удаляя (по айдишнику).


Жесть какая - ты еще и буфер в памяти скрипта держишь и id записей.

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

а с redis'ом то зачем? rpush добавит один элемент справа, а lpop заберет первый элемент слева. Два раза ты не сможешь получить одну и ту же запись, поэтому тебе наплевать сколько у тебя одновременых обращений.

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

не представляешь что такое redis

Представляю и даже пользуюсь.

Не надо пихать во все щели известный тебе mysql

Василий, он уже в той щели торчит.

правильного инструмента
Жесть какая - ты еще и буфер в памяти скрипта держишь и id записей
наплевать сколько у тебя одновременых обращений

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

deep-purple ★★★★★
()
Ответ на: комментарий от gwyllum

это битрикс, но не думаю, что там сильно на что-то влияет

Гыыы ))

строка типа «COMPANY_23423423»

Плохо. Делай числом. А рядом ENUM компания это или юзер или ещё что-то.

А дальше... А дальше мне не нравится что ты там наваял по структуре таблиц и почему у тебя, если я правильно понял, в таблице ансверов шняга UF_USER и UF_COMPANY когда явно один может быть нулл и тогда надо смотреть что второй не нулл... http://s00.yaplakal.com/pics/pics_original/2/6/5/8818562.jpg

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

Представляю и даже пользуюсь.

ну ну

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

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

adn ★★★★
()

Для подобных задач есть MessageQueues

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

надо следить

$fp = @ fopen('lockfile', 'r+');
if (!$fp) {
    exit('Can\'t open lockfile');
}
$locked = @ flock($fp, LOCK_EX | LOCK_NB);
if (!$locked) {
    fclose($fp);
    exit('Other instance already running! Shutting down now..');
}

// working code here

if ($fp) {
    if ($locked) {
        flock($fp, LOCK_UN);
    }
    fclose($fp);
}

Ну оооооочень сложна!!!

распараллелить запуск задач очень проблематично

Во первых оно тут нахер не нужно. А во вторых, распараллеливание на пыхе? Петросян.

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

Ага еще к твоему массиву с id запись на диск. круто.
Что люди только не делают, лишь бы не изучать что-то новое.

распараллелить запуск задач очень проблематично

Во первых оно тут нахер не нужно. А во вторых, распараллеливание на пыхе? Петросян.

Откуда такая уверенность, если у TC-а тысячи этих «заданий» в очереди.

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

Ага еще к твоему массиву с id запись на диск. круто

Без сброса кеша фактически записи нет (ни для фс ни для бд), а т.к. размер файла нулевой, то кеш и не будет сбрасываться.

Что люди только не делают

лишь бы смузи не перестать нахлёбывать. Меж тем, новое не значит подходящее.

тысячи этих «заданий» в очереди

Если ты запустишь тысячу пыхопроцессов на серваке, то сервак колом встанет.

потоки

Треды в пыхе это жопа. По многим критериям. Их проще не использовать.

Гораздо эффективнее запускать один процесс по крону, который разгребёт часть очереди (конфигуриуемый размер) за один запуск и не поставит раком сервак.

редис

Даже в контексте разгребания части очереди — нахер не нужен. Ибо это новая сущность которую надо сначала внедрить, а затем мониторить, бекапить и поддерживать. И тут я повторю то с чего начинал:

заводить еще и редис для тупо очереди это как тащить жквери ради тогглинга одного блока на страничке — красава!

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

лишь бы смузи не перестать нахлёбывать. Меж тем, новое не значит подходящее.

redis'у 10 лет. Я понимаю, что кроме php+mysql ты ничего так и не осилил

тысячи этих «заданий» в очереди

Если ты запустишь тысячу пыхопроцессов на серваке, то сервак колом встанет.

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

Гораздо эффективнее запускать один процесс по крону, который разгребёт часть очереди (конфигуриуемый размер) за один запуск и не поставит раком сервак.

как ты считал эффективность? мое предложение убрать крон и зациклить исполнение. Пусть разгребает через интервалы времени и запустить 2-3 таких процессов.

Даже в контексте разгребания части очереди — нахер не нужен. Ибо это новая сущность которую надо сначала внедрить, а затем мониторить, бекапить и поддерживать.

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

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

redis'у 10 лет

Да хоть 100500.

Я понимаю ты ничего так и не осилил

Ты и понять то не осилил.

у тебя варианты

Это у тебя какие-то варианты.

тогда нахрена очередь?

Вот именно. Она там нахер не нужна. Тупо есть некий список с метками «отработали/нет» и тупо выбираем те которых не отработали (за раз не все) и отрабатываем.

как ты считал эффективность?

Один процесс вхолостую: https://i.imgur.com/XQvSsVv.png треды так же не дешёвые.

зациклить исполнение

Демон на пхп? Ловите наркомана!

воркеры

(Ловите наркомана!)^2

2-3 таких процессов

(Ловите наркомана!)^3

теперь я вижу

Так же как и «понимаешь»?

как хорошо, что ты не погромист

Fixed.

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

Один процесс вхолостую: https://i.imgur.com/XQvSsVv.png треды так же не дешёвые.

я увидел 25Мб памяти, а что хотел показать мне ты? или ты не знаешь чем отличается VIRT от RES?
Запусти параллельно 3 таких процесса и удивись

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

где хранить эти задачи

В идеале — хранить в очереди сообщений либо сразу передавать чему-нибудь (отдельному сервису, принимающему задачи в любом удобном виде, например, через http-запросы или kinesis, load balancer поверх этого по вкусу). redis, да и mysql — ок очереди сообщений при не сильно высокой загрузке.

Скрипт, который вызывается по крону выбирает наиболее старые задачи, у которых статус завершения отсутствует и начинает выполнять

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

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

redis, да и mysql — ок очереди сообщений при не сильно высокой загрузке.

mysql плохо. Блокировки нужны, чтобы не прочитать несколько раз одно значение, пока ты не удалил/изменил запись. Мы обсуждали тут выше.

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

Если MySQL, то я бы делал что-то про CREATE EVENT

это да, если кроме изменения базы ничего не надо делать.

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

а с redis’ом то зачем? rpush добавит один элемент справа, а lpop заберет первый элемент слева. Два раза ты не сможешь получить одну и ту же запись, поэтому тебе наплевать сколько у тебя одновременых обращений.

Джоб упал и не отработал, в редисе его уже нет.

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

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

adn ★★★★
()
Ответ на: комментарий от deep-purple

чувак, ну запусти man top. там разжевано прямо что такое shared, virtual и resident memory. как хорошо что ты не администрируешь сервера.

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

но так с любыми очередями

нормальный брокер ждет подтверждение, чтобы убрать сообщение из очереди как обработанное

wxw ★★★★★
()
Ответ на: комментарий от deep-purple

но так с любыми очередями

Я ж говорил:

Она там нахер не нужна

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

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

нормальный брокер ждет подтверждение, чтобы убрать сообщение из очереди как обработанное

согласен. только это уже не просто очередь, а система отправки сообщений. хотя мне все-равно кажется, что rabbitMQ для ТСа чересчур.

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

Блокировки нужны, чтобы не прочитать несколько раз одно значение

Явные не нужны. Достаточно использовать уникальные id обработчика.

no-such-file ★★★★★
()
Ответ на: комментарий от adn

не понял

Что непонятного? Нужно сначала сделать UPDATE ... LIMIT 1 с id обработчика и т.о. атомарно застолбить задачу, а потом по этому id уже сделать SELECT.

no-such-file ★★★★★
()
Ответ на: комментарий от no-such-file

Хотя, я не вижу чем плохо для выборки задачи использовать select ... for update skip locked

no-such-file ★★★★★
()
Ответ на: комментарий от adn

там разжевано

Ответь на вопрос: при форке процесса, данные копируются или используется одна копия для обоих? А при запуске разных процессов тоже используется одна копия данных?

как хорошо что ты

Мне хорошо. А ты и дальше продолжай обмазываться смузи микросервисов.

в твоем случае, если скрипт грохнется, то ты потеряешь весь свой буфер с id и еще lock останется

Как грохнется, так и заново поднимется по крону. Да и вообще, это твои скрипты походу грохаются, мои вот не грохаются.

еще раз все отработает

Ещё раз про те же задачи, только в случае предыдущего неуспеха.

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

Ответь на вопрос: при форке процесса, данные копируются или используется одна копия для обоих? А при запуске разных процессов тоже используется одна копия данных?

каких данных? иди читай про процессы, балбес. даже man top не осилил.

Как грохнется, так и заново поднимется по крону. Да и вообще, это твои скрипты походу грохаются, мои вот не грохаются.

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

adn ★★★★
()
Последнее исправление: adn (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.