Обработка очереди базы данных в нескольких потоках - совет по проектированию - PullRequest
3 голосов
/ 27 мая 2010

У меня есть таблица SQL Server, полная заказов, по которым моя программа должна «следить» (позвоните в веб-службу, чтобы узнать, что-то с ними было сделано). Мое приложение является многопоточным и может иметь экземпляры, работающие на нескольких серверах. В настоящее время очень часто (в таймере потоков) процесс случайным образом (10000 *) выбирает 100 строк из списка «неподтвержденных» заказов и проверяет их, отмечая любые, которые успешно возвращаются.

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

Что бы я предпочел, так это систематически проверять все невыполненные заказы. Я могу придумать два простых способа сделать это:

  1. Приложение извлекает один заказ для проверки за раз, передавая последний заказ, который он проверил в качестве параметра, и SQL Server возвращает обратно следующий неподтвержденный заказ. Больше обращений к базе данных, но это гарантирует, что каждый заказ проверяется в разумные сроки. Однако разные серверы могут перепроверять один и тот же порядок подряд без необходимости.
  2. SQL Server отслеживает последний порядок, который он запрашивал у процесса для проверки, возможно, в таблице, и дает уникальный порядок каждому запросу, увеличивая его счетчик. Это включает в себя сохранение последнего порядка где-то в SQL, чего я хотел избежать, но также гарантирует, что потоки не будут без необходимости проверять одни и те же порядки одновременно

Есть ли другие идеи, которые я пропускаю? Имеет ли это смысл? Дайте мне знать, если мне понадобятся некоторые разъяснения.


РЕЗУЛЬТАТ:

В итоге я добавил столбец LastCheckedForConfirmation к моей таблице с завершенными заказами, и я добавил хранимую процедуру, которая обновляет одну строку Неподтвержденный с помощью GETDATE () и выводит заказ номер, так что мой процесс может проверить его. Он раскручивает столько, сколько может (учитывая количество потоков, которые процесс хочет запустить), и использует хранимую процедуру для получения нового OrderNumber для каждого потока.

Чтобы справиться с проблемой «Не пытаться строки слишком много раз или когда они слишком старые», я сделал следующее: SP будет возвращать строку только если «Время с последней попытки»> «Время между созданием и последним» try ", так что каждый раз, прежде чем повторить попытку, потребуется вдвое больше времени - сначала он ждет 5 секунд, затем 10, затем 20, 40, 80, 120, а затем после того, как его попробовали 15 раз (6 часов), он отказывается на этот заказ, и SP никогда не вернет его снова.

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

Ответы [ 3 ]

7 голосов
/ 27 мая 2010

Я рекомендую прочитать и усвоить Использование таблиц в качестве очередей .

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

Одна вещь, от которой вы должны абсолютно избавиться, это случайность. Если есть одна вещь, которую трудно воспроизвести в запросе, это случайность. ORDER BY NEWID() будет сканировать каждую строку, генерировать guid, затем сортировать, а затем возвращать вас к началу 100 . Ни при каких обстоятельствах нельзя, чтобы каждый рабочий поток сканировал всю таблицу каждый раз, вы убьете сервер по мере увеличения числа необработанных записей.

Вместо этого используйте ожидающую обработку дату. Организовать (кластеризовать) очередь по столбцу даты обработки (когда элемент должен быть повторен) и удалить из очереди методы, описанные в моей статье. Если вы хотите повторить попытку, очередь должна отложить элемент вместо его удаления, т.е. WITH (...) UPDATE SET due_date = dateadd(day, 1, getutcdate()) ...

2 голосов
/ 27 мая 2010

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

Я бы не получил 100 заказов одновременно, есть риск изменения 50-го порядка в базе данных до того, как ваш поток достигнет его. Получите один заказ, а когда закончите, получите следующий.

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

0 голосов
/ 27 мая 2010

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

JobID bigint PK не ноль, WorkerID int / nvarchar (max) ноль

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

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

РЕДАКТИРОВАТЬ: Забыл упомянуть, вам нужно будет удалить на работу, когда она будет завершена, или иметь поле состояния, чтобы указать завершение. В дополнительном поле можно указать параметры для задания, чтобы сделать вашу таблицу заданий общей: т.е. не просто принимайте решение для ваших заказов, а создайте менеджера по работе, который сможет обработать все, что вам понадобится в будущем.

...