2010-05-27 3 views
3

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

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

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

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

Есть ли какие-либо другие идеи, которые мне не хватает? Это даже имеет смысл? Дайте мне знать, если мне нужно уточнить.


РЕЗУЛЬТАТ:

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

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

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

+0

Вариант 2 звучит гораздо более предпочтительным для меня на первый взгляд. Что происходит с мертвыми орденами? Получают ли они подтвержденные мертвые в какой-то момент или просто вымерли? –

+0

@Martin Smith: нет официального тайм-аута - они просто не создаются на удаленной системе (которая принадлежит поставщику), и поэтому мне нужно в какой-то момент отказаться, но это сколь угодно долго. Ничто никогда не становится «подтвержденным мертвым» как таковым, и это делает его трудным, хотя, поскольку большинство заказов создаются (и подтверждаются) в течение нескольких минут, я, вероятно, могу рассматривать что-нибудь старше, чем один день, как подтвержденный мертвый. – SqlRyan

ответ

7

Я рекомендую читать и интернализировать Using tables as Queues.

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

Одна вещь, от которой вы должны абсолютно избавиться - это случайность. Если есть одна вещь, которую трудно воспроизвести в запросе, это случайность. ORDER BY NEWID() будет сканировать каждую строку, сгенерировать направляющую, затем СОРТИРОВАТЬ, а затем вернуть вам верхушку 100. Вы ни при каких обстоятельствах не можете каждый рабочий поток сканировать всю таблицу, вы будете убивать сервер по мере увеличения количества необработанных записей.

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

+0

Очень хорошая статья. Единственная проблема, с которой я вижу, может заключаться в том, что он использует OUTPUT, который удалит строку. Если рабочий процесс выйдет из строя/потеряет мощность, работа будет полностью потеряна. Есть способы обойти это, но я бы предпочел поставить решение в ту же таблицу. – csauve

+0

Я согласен с тем, что ORDER BY NEWID() - это ужасная идея в долгосрочной перспективе - это был просто быстрый способ сделать это, и я понял, что ее нужно изменить до того, как приложение вступит в производство. Спасибо за ваши предложения. Я посмотрю, есть ли простой способ попробовать столбец «проверка последнего подтверждения». – SqlRyan

+0

Это хорошая статья. Тем не менее, это не дает 100% надежности, и есть еще шанс, когда одно и то же сообщение будет обработано два или более раз. Единственный способ справиться с этим - иметь центральный брокерский сервис, который ставит в очередь и делит сообщения. Таким образом, только один поток обновляет и обращается к данным очереди. Это может быть оптимизировано путем набора наборов сообщений вместо одного сообщения за раз. – IMHO

2

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

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

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

+0

Служба захватывала следующие заказы X, а затем откручивала потоки, чтобы вызвать вызов webservice, что может занять несколько секунд, и я не хотел останавливать службу, пока она ждет. – SqlRyan

0

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

JobID BIGINT PK не нулевой, WorkerID INT/NVARCHAR (макс) нулевой

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

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

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

+0

Это в основном то, что я пытался напечатать, когда получил уведомление о новом ответе. Единственное, что я собирался сказать, - это выбрать только одну запись за раз и выбрать их (WorkerID NOT NULL) в порядке даты создания заказа (то есть старейший сначала обрабатывается). – kevinw

Смежные вопросы