Выполнение запросов к БД для хранения элементов в коллекции?

#c# #performance #scheduling #shared-hosting #quartz.net

#c# #Производительность #планирование #общий хостинг #quartz.net

Вопрос:

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

В принципе, у меня есть система напоминаний, в которой пользователи могут устанавливать напоминания. Это похоже на календарь Google. Вы устанавливаете дату и время вашего мероприятия, а затем устанавливаете напоминание, говоря «напомните мне за 15 минут до».

Итак, у вас могло бы быть событие 10 мая 2011 года в 9: 59 утра, и вы могли бы сказать, что напомнили мне «за 15 минут до»

Итак, это было 10 мая, 10: 44 утра.

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

Я также использую nhibernate и fluent nhibernate для выполнения запросов к БД. Я использую asp.net mvc 3 для моего веб-сайта.

Вариант 1.

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

Вариант 2.

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

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

Вариант 3

Аналогично варианту 2, но отправляет запрос каждые 15 минут и сохраняет в коллекции.

Это, конечно, означает намного меньше запросов к базам данных, но больше хранится в памяти.

Вариант 4

Выполняйте запрос к базе данных каждые 15 минут и получайте все напоминания в этом блоке и немедленно запускайте их.

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

Например, они сказали напомнить мне в 10: 44 утра. Я бы запустил свой планировщик в 10: 00 утра, и он работал бы с 10: 00 до 10: 15 утра, а затем с 10: 15 до 10: 30 утра, затем с 10: 30 до 10: 45 утра.

Таким образом, электронное письмо фактически пришло бы на 14 минут раньше, чем предполагалось.

Ответ №1:

Вот как я бы решил эту проблему.

  • На уровне базы данных я бы создал простую очередь. Этот список сообщений также будет включать время отправки. При запросе в этом списке будет следующий элемент вверху.

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

Одним из преимуществ этого метода является то, что у вас нет действующего агента, применяющего бизнес-правила, когда он проверяет очередь. Если вы хотите, чтобы она просыпалась каждую минуту (например, чтобы проверить, есть ли новые сообщения, которые необходимо отправить), тогда вы просто убедитесь, что в этой очереди всегда есть событие каждую минуту (это событие может иметь тип, который не отправляет сообщение, сообщение «пробуждение» не имеет целевых объектов). Агент проснется и выполнит проверку. Тогда, если вы хотите применить более сложные правила планирования, они просты. Вам не нужно перекодировать агент, вам просто нужно изменить, какие сообщения помещаются в очередь. (Например, проверяйте каждые 10 минут, когда система активно используется, и каждые 20 минут, когда она мало используется, и прекращайте проверку во время ежевечернего резервного копирования). Все это можно выполнить (и изменить) без изменения кода вашего агента.


Простой пример из реального мира

 QueueTable
----------
ID int
deliverTime datetime
nagCount int
expireTime datetime
active bool
processed datetime (null)
' maybe some audit stuf...
' content of the message -- or external link
' etc
  

ЗАПУСК: агент выполняет вызов, подобный этому

 SELECT TOP 1 * 
FROM QueueTable
WHERE active = true and processed is null
ORDER BY deliverTime DESC
  

Затем агент проверяет, сколько времени доставки:

  • Если оно прошло или в следующей нечеткой границе (1 секунда?), оно отправляет сообщение, затем устанавливает время обработки в текущее время в базе данных и возвращается к началу:

  • Если это в будущем, оно переходит в режим ожидания до этого времени доставки или устанавливает событие для его пробуждения в это время (зависит от платформы).

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


Пример проверки каждые 10 минут, несмотря ни на что.

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

 SELECT TOP 1 * 
FROM QueueTable
WHERE active = true and processed is null
UNION ALL
SELECT NULL, DATEADD(min,GETDATE(),10), null, null, false, null, ...
ORDER BY deliverTime DESC
  

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

Комментарии:

1. @Hogan — Не могли бы вы написать простой пример. У меня возникли небольшие проблемы с последующим. У меня уже есть время отправки, рассчитанное и сохраненное в базе данных, так что именно так я планировал получать свои запросы, выполняя предложение Where, чтобы проверить, попадает ли оно в этот блок. Я не уверен, что вы подразумеваете под своим вторым пунктом. Я не уверен, что вы подразумеваете под агентом сообщений и как это будет действовать на верхний элемент.

2. @Hogon — есть ли что-то в агенте ms sql server или я делаю это из своего кода на C # / nhibernate?

3. @chobo2 — агент — это то, что выполняет уведомления. В вашем случае я полагаю, что это C # (Но решение не привязано к одной архитектуре — то есть агентом может быть что угодно, что выполняет уведомления).

4. @Hogan — Хм, все еще немного сложно следить. Итак, вы принимаете верхний запрос, который соответствует вашему условию (почему вы знаете, проверьте, находится ли он в этом диапазоне сроков доставки?) затем вы отправляете это. Если главное сообщение находится в будущем, вы переводите его в режим ожидания до тех пор, пока оно не достигнет диапазона времени, а затем отправляете его, а затем повторяете. Это и есть процесс?

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

Ответ №2:

Я бы, вероятно, сразу же отказался от варианта 4, если он не соответствует вашим требованиям.

Другие параметры действительно зависят от вашего системного профиля (сколько людей его используют? Сколько напоминаний существует за любой заданный период 5/15 минут?) Это вопросы, на которые вам нужно будет ответить. Кроме того, сколько активности уже происходит на сервере? Если она еще не находится под высоким напряжением, то запрос каждую минуту — это совсем немного.

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

Комментарии:

1. Я. Вариант 4, с которым я, вероятно, мог бы смириться, но я предпочел бы, чтобы он появился в течение нескольких минут после того, как они установили его, а не 14 минут назад. Моя цель — провести стресс-тестирование как минимум с 1000, а затем, возможно, с 10000 пользователями и посмотреть, как работает моя система. Я далек от любого из этих чисел, но это то, что я хочу, чтобы моя вещь могла обрабатывать, по крайней мере. Все эти цифры зависят от того, сколько напоминаний будет отправлено, потому что все это определяется пользователем. Я понимаю, что вы имеете в виду, если они изменят время напоминания, тогда оно все равно сработает в прежнее время. Это не было бы концом света, если бы это произошло.