Ну, мы поняли это. Не было ничего, что выскочило и сообщило нам, в чем проблема, и потребовалось много мозгового штурма, но вот что мы сделали:
Включена трассировка WCF. Прошел по следам и был в состоянии понять достаточно, чтобы в основном видеть, что движение не выглядело необычным. Все события, казалось, были для ожидаемого количества и типов сервисных звонков. Просмотр в svctraceviewer , похоже, это не атака DOS или что-то в этом роде. Мы только что использовали конфигурацию по умолчанию из этой ссылки, но похоже, что она может быть очень настроена для предоставления конкретной информации, которая вам нужна, если вы знаете, что это такое.
Что действительно помогло в этом случае, так это поиск Счетчиков производительности WCF . Первоначально мы использовали счетчики производительности ASP.NET для просмотра открытых сессий, что было неправильной метрикой. Это руководство по проекту кода помогло нам включить счетчики производительности WCF, чтобы дать нам представление о количестве сеансов и ограничении в реальном времени.
Это также помогло понять, как связаны сеансы и экземпляры WCF, а также создать контекст безопасности:
Мы смогли увидеть процент использованных максимальных сессий WCF и наблюдали, как он поднимается все выше и выше, достигая предельного значения по умолчанию 200 (100 на процессор), но в конечном итоге выравнивается между 150 и 200. Это выравнивание вместе с гораздо большим количеством сеансов, существующих в данное время, чем среднее число запросов в минуту, наблюдаемое в нашей трассировке WCF, указывало, что сеансы закрывались, но, казалось, оставались открытыми до истечения времени ожидания, а не закрытия, как только сервер завершил запрос.
Где-то в stackoverflow, которое мне не удалось найти, я однажды спросил о назначении метода [ClientBase<TChannel>.Close][4]
(он же метод close прокси-сервера службы WCF) и, несколько ошибочно, пришел к выводу, что все он установил флаг прокси-объекта, отмечающий его закрытие, чтобы его нельзя было использовать снова. Представленное в документации описание метода выглядит следующим образом:
Заставляет объект ClientBase переходить из его текущего
состояние в закрытое состояние.
Хорошо, в тот момент, когда я бы назвал Close
, мои ссылки всегда просто выходят из области видимости, так что сборщик мусора может очистить его так, что это кажется бессмысленным. Но я думаю, что ключевым фактором было то, что это касалось базовых HttpBindings, которые не имеют состояния. В этом случае мы используем wsHttpBindings, которые сохраняют состояние, что означает, что сервер оставляет сеанс и оставляет соединение открытым после завершения запроса, чтобы последующие вызовы от клиента могли быть сделаны в том же соединении. Итак, хотя я не смог найти никакой документации или отследить в исходном коде, где это происходит, кажется, что клиенты WCF должны вызвать Close
на своем прокси-сервере службы после того, как они сделали последний запрос, чтобы сообщить сервер может закрыть соединение и освободить этот сеанс. У меня не было возможности искать сообщение, отправленное на сервер при вызове Close
, чтобы сделать это, но мы смогли наблюдать, используя счетчик производительности, количество сеансов, уменьшившихся с 1 до 0, где раньше это было бы оставайтесь на 1 после того, как наш клиент позвонил в службу.
Но мы говорим, что клиент WCF, который мы не можем контролировать, способен нанести ущерб производительности сервера и, возможно, создать отказ в обслуживании, если он не усердно работает в своем коде и не забывает звонить Close
и сервер не имеет контроля над собственной производительностью ?? Это звучит как рецепт катастрофы. Ну, есть две вещи, которые вы можете сделать на сервере, чтобы смягчить это. Сначала вы можете увеличить максимальное количество сеансов. В нашем случае мы колеблемся около 175, но иногда при пиках трафика, превышающих 200. Мы временно увеличили его до 800, чтобы не превысить максимум. Компромисс состоит в том, чтобы выделять больше серверных ресурсов для проведения тех сеансов, которые, вероятно, никогда не будут использоваться снова, пока не истечет время ожидания. К счастью, сервер также контролирует время ожидания. Служба может контролировать продолжительность этих сеансов, используя ReceiveTimeout
и InactivityTimeout
. Оба по умолчанию 10 минут, но будет использоваться меньшее из двух. Если вы думаете: «Тайм-аут приема звучит неправильно. Это определяет количество времени, которое может занять служба для получения большого сообщения», вы не одиноки. Тем не менее, это неправильно . На стороне сервера:
ReceiveTimeout - используется уровнем Service Framework для инициализации тайм-аута простоя сеанса, который контролирует, как долго сеанс может простаивать до истечения времени ожидания.
А на стороне клиента он не используется. Таким образом, мы установили ReceiveTimeout
на 30 секунд, и сессии значительно упали. Возможно, это было на самом деле слишком мало, потому что некоторые места в коде, которые повторно используют прокси-сервер службы (например, выполнение нескольких вызовов в цикле или некоторая обработка данных между вызовами), теперь получают ошибку при попытке вызвать службу после того, как сессия была закрыта. Так что вам придется найти правильный баланс. Но, похоже, лучшая практика - закрывать ваши связи.
Нужно остерегаться использования Dispose
в прокси вашего сервиса. Я всегда пытался набрать .dispo
, чтобы посмотреть, не вызовет ли intellisense метод Dispose
на моем прокси-сервере, и обнаружил, что он не предполагал, что он не реализует IDisposable
и его не нужно закрывать или удалять. Оказывается, он реализует IDisposable
, но делает это явно, поэтому вам нужно будет привести его как IDisposable
для вызова Dispose
. Но ждать! Пока не вставляйте свой прокси в оператор using
. Реализация Dispose
просто вызывает Close
на прокси, который вызывает исключение, если прокси находится в состоянии сбоя (то есть, если вызов службы вызвал исключение). Поэтому вы не можете безопасно делать что-то вроде этого:
using(MyWcfClient proxy = new MyWcfClient())
{
try
{
proxy.Calculate();
}
catch(Exception)
{
}
}
, поскольку, если Calculate
выдает исключение, закрывающая скобка блока using
также выдает исключение при попытке утилизировать ваш прокси. Вместо этого вам просто нужно вызвать Close
после вашего последнего вызова метода сервиса. Очевидно, вы также можете позвонить Abort
в catch
, но я не уверен, действительно ли он связывается с сервером для завершения сеанса.
MyWcfClient proxy = new MyWcfClient
try
{
proxy.Calculate();
proxy.Close();
}
catch(Exception)
{
proxy.Abort();
}
Надеюсь, это поможет кому-то в подобной ситуации!
Добавление
Мы предполагаем, что причина, по которой мы начали испытывать это при перемещении серверов и не испытывали его раньше, заключается в том, что мы раньше использовали продукты Barracuda и теперь используем Oracle, и, возможно, старый балансировщик нагрузки или брандмауэр закрывал для нас открытые соединения.