Обработка тайм-аутов веб-служб при выполнении долгосрочных задач базы данных

Архитектура одного из наших продуктов является типичным 3-уровневым решением:

  • Клиент С#
  • Веб-сервис WCF
  • База данных SQL Server

Клиент запрашивает информацию из веб-сервиса. Веб-сервис обращается к базе данных за информацией и возвращает ее клиенту.

Здесь проблема. Некоторые из этих запросов могут занимать очень много времени, и мы не знаем заранее, какие из них будут медленными. Мы знаем некоторые, которые часто медленнее других, но даже самые простые запросы могут быть медленными при наличии достаточного количества данных. Иногда используется запрос или запуск отчетов на больших объемах данных. Запросы могут быть оптимизированы только до тех пор, пока объем данных не замедлит их.

Если запрос в базе данных достигает максимального времени ожидания запроса на сервере SQL, запрос к базе данных прекращается, и веб-служба возвращает ошибку клиенту. Это понятно. Мы можем справиться с этими ошибками.

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

Мне любопытно посмотреть, как другие справились с этой проблемой. Какие стратегии вы использовали для предотвращения влияния времени ожидания веб-службы на вызовы базы данных?

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

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

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

Ответ 1

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

Например, мы разбили некоторые огромные процессы на ряд шагов, таких как "Пуск", "Процесс 1", "Готово" и "Сбор данных отчета". Шаги обработки процесса могут выполняться параллельно, но они не могут запускаться до тех пор, пока не будет завершен шаг "Начать". На этапе завершения необходимо дождаться завершения всех этапов работы процесса.

Поскольку клиент контролирует процесс, клиент может сообщить о прогрессе, на каком именно этапе он находится.

Ответ 2

В прошлом я столкнулся с подобными проблемами и использовал один из следующих 3 методов для его решения:

  • Добавьте все длинные запросы в очередь и обработайте их последовательно.
    В моем случае это были все сложные отчеты, которые затем отправлялись клиенту по электронной почте или хранятся в постоянных "временных" таблицах для просмотра клиентами после того, как они были уведомлены.
  • Мы вызвали веб-сервис, используя вызов JQuery, который затем вызвал метод обратной передачи javascript, когда он был завершен.
    Это работало хорошо, когда мы не хотели, чтобы загрузка страницы синхронизировалась с тем, что делал веб-сервис.
    Однако это означало, что эта часть функциональности недоступна до тех пор, пока не завершится длительный процесс.
  • Самый сложный.
    Мы вывели еще одно окно, на котором отобразился индикатор выполнения, который также периодически опросил сервер.
    Это использовало переменную сеанса, чтобы определить, насколько далеко показывается индикатор выполнения.
    После запуска строки выполнения был запущен новый поток, который периодически обновлял ту же самую переменную сеанса.
    Когда значение переменной сеанса было установлено равным 100, всплывающее окно закрылось.
    Клиентам понравился этот метод.

В любом случае, я надеюсь, что одна из них поможет вам.

Ответ 3

Веб-служба может запускать запросы в поточном пуле, и если поток не заканчивается внутри, скажем, 5 секунд (см. Thread.Join()), вызов веб-службы возвращает клиенту идентификатор задания вместо набора результатов, который клиент может затем использовать для опроса сервера каждые несколько секунд, чтобы узнать, завершен ли его запрос. Когда поток заканчивается, результаты могут храниться в хеш-таблице до тех пор, пока клиент не опросит снова.

Ответ 4

Проблема с небольшими кусками - это, безусловно, хорошая идея.

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

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

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

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

Я не уверен, насколько это ортодоксально, BTW, но это действительно решило актуальные проблемы.