Подтвердить что ты не робот

Лучшая практика для жизни прокси-сервера WCF - или как часто закрывать прокси-сервер WCF?

Я работал над WPF-приложением, которое использует WCF для доступа к логике и базе данных на стороне сервера.

Я начал с одного прокси-объекта клиента WCF, который я неоднократно использовал для вызова методов на сервере. После некоторого использования прокси-сервера сервер в конечном итоге исключит исключение:   System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://.../Service/BillingService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: Unable to connect to the remote server ---> System.Net.Sockets.SocketException: An operation on a socket could not be performed because the system lacked sufficient buffer space or because a queue was full.

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

После краткого поиска, я решил, что мне нужно периодически закрывать() прокси. Образцы, которые я нашел, вырождены. Этот дал некоторые полезные подсказки, но на самом деле не отвечает на вопрос. Я также видел рекомендации, чтобы избежать шаблона use() (и вместо этого применить try/catch/finally), потому что метод dispose Dispose может вызывать исключение (yuck).

Кажется, что рекомендуемый шаблон формируется следующим образом:

[TestClass]
public class WCFClientUnitTest
{
    BillingServiceClient _service;

    [TestMethod]
    public void TestGetAddressModel()
    {
        List<CustomerModel> customers = null;

        try
        {
            _service = new BillingServiceClient();
            customers = _service.GetCustomers().ToList();
        }
        catch
        {
            _service.Abort();
            _service = null;
            throw;
        }
        finally
        {
            if ((_service != null) &&
                (_service.State == System.ServiceModel.CommunicationState.Opened))
                _service.Close();
            _service = null;
        }

        if (customers != null)
            foreach (CustomerModel customer in customers)
            {
                try
                {
                    _service = new BillingServiceClient();
                    AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID);

                    Assert.IsNotNull(address, "GetAddressModel returned null");
                }
                catch
                {
                    _service.Abort();
                    _service = null;
                    throw;
                }
                finally
                {
                    if ((_service != null) &&
                        (_service.State == System.ServiceModel.CommunicationState.Opened))
                        _service.Close();
                        _service = null;
                }
            }
    }

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

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

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


EDIT 11/3/2010: Это кажется важным, поэтому я добавляю его к вопросу...

В ответ на комментарий/предложение Andrew Shepherd я перезапустил мой unit test с отключением TrendMicro AntiVirus, контролируя вывод netstat -b. Netstat смог зафиксировать значительный рост открытых портов, принадлежащих WebDev.WebServer40.exe. Подавляющее большинство портов находилось в состоянии TIME_WAIT. Microsoft говорит, что порты могут задерживаться в NET_WAIT после того, как клиент закроет соединение...

ПРИМЕЧАНИЕ. Нормально иметь гнездо в состояние TIME_WAIT в течение длительного периода времени времени. Время указано в RFC793 в два раза больше максимального сегмента Время жизни (MSL). MSL определяется как 2 минуты. Таким образом, сокет может находиться в TIME_WAIT до тех пор, пока 4 минут. Некоторые системы реализуют разные значения (менее 2 минут) для MSL.

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

Поэтому мне нужно продолжить один из двух путей: 1) попытка пакетных вызовов службы, чтобы они повторно использовали сокет на стороне сервера 2) измените мой контракт на обслуживание, чтобы я мог вернуть более крупные куски данных с меньшим количеством вызовов.

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

4b9b3361

Ответ 1

Кажется, здесь есть несколько вещей.

Сначала unit test я запускал и отслеживал с помощью netstat -b, указывал, что Cassini (WebDev.WebServer40.exe) был владельцем портов, которые накапливались в состоянии TIME_WAIT. Как отмечается в статье MSFT kb, нормальным является то, что порты будут задерживаться после установления связи FIN, пока приложение ожидает доставки медленных пакетов в сети и очереди сообщений. Конфигурация по умолчанию, равная 2 минутам, объясняет, почему я увидела симуляцию портов после завершения моего unit test. Хотя можно изменить MSL через параметр реестра, он не рекомендуется.

Но, важный момент, который я почти забыл, заключается в том, что служба работает под Cassini. Когда я переключаю конечную точку сервера на работу под IIS7, я не испытываю никакого роста порта вообще! Я не могу объяснить, означает ли это, что клиент повторно использует порты или IIS7 лучше, чем Cassini, при очистке портов после их завершения. Теперь это не полностью отвечает на мой вопрос о том, как часто я должен закрывать прокси-сервер WCF. Это просто означает, что я не имел, чтобы закрыть прокси-сервер часто.

Я вижу, что все еще есть компромисс ресурсов, чтобы поддерживать прокси-сервер открытым в течение длительного периода времени.

Если у вас много (то есть тысяч) клиентов, обращающихся к вашей службе WCF, может иметь смысл освободить ресурсы сервера между вызовами или небольшие партии вызовов. В этом случае обязательно используйте механизм try/catch/finally и не используйте(), потому что хотя прокси-сервер службы реализует IDisposable, метод close() может генерировать исключение, если служба находится в состоянии сбоя.

С другой стороны (как и в моем конкретном случае), если вы только ожидаете, что несколько клиентов получат доступ к вашей службе WCF, вам не потребуется дополнительная сложность часто и явно открывать и закрывать прокси-сервер службы. Поэтому я намерен открыть прокси-сервер один раз, когда мое приложение запустится, и оставьте его открытым до завершения приложения. Я намереваюсь внедрить метод вспомогательного вызова службы (похоже на: Обновление клиента WCF при истечении срока действия SCT?), который будет перерабатывать соединение, на всякий случай, когда он когда- неисправное состояние. Тогда мне не нужно беспокоиться об управлении временем прокси-сервера.

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

Ответ 2

(Проводка совершенно другого второго ответа)

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

Например.

[DataContract]
class CustomerAndAddress
{
    [DataMember]
    CustomerModel Customer;

    [DataMember]
    AddressModel Address;
}

[ServiceContract]
class BillingService
{
   [OperationContract]
   CustomerAndAddress[] GetAllCustomersAndAddresses();
}

или, более вероятно, в реальном мире:

[ServiceContract]
class BillingService
{
   [OperationContract]
   CustomerReportData FetchCustomerReportInfo(CustomerReportParameters parameterSet);
}

Сказав это, мне все еще интересно узнать, можете ли вы снять то, что вы пытаетесь.

Ответ 3

Я написал (ну, нашел и изменил) обертку, чтобы помочь правильно утилизировать службу. Извините за VB. В нем есть некоторые дополнительные вещи, такие как MessageViewerInspector, поэтому я могу перехватить XML, который летает и выходит из службы. Пока просто игнорируйте его.

РЕДАКТИРОВАТЬ. Я знаю, что на самом деле это не отвечает на ваш вопрос о том, как поддерживать всплески запросов, но это, безусловно, сделает использование сервиса немного более чистым, с кодовым названием.

Imports System.ServiceModel

Namespace WCF

    ''' <summary>
    ''' This helper fixes an issue with WCF services where an exception gets thrown
    ''' during the Dispose() call of the client, so it doesn't actually get disposed.
    ''' </summary>
    ''' <typeparam name="TProxy"></typeparam>
    ''' <typeparam name="TChannel"></typeparam>
    ''' <remarks></remarks>
    Public Class ServiceProxyHelper(Of TProxy As {ClientBase(Of TChannel), New}, TChannel As Class)
        Implements IDisposable

        Private _proxy As TProxy
        Private _inspector As MessageViewerInspector

        Public ReadOnly Property ServiceProxy() As TProxy
            Get
                If Not _proxy Is Nothing Then
                    Return _proxy
                Else
                    Throw New ObjectDisposedException("ServiceProxyHelper")
                End If
            End Get
        End Property

        Public ReadOnly Property Inspector() As MessageViewerInspector
            Get
                If _inspector Is Nothing Then
                    _inspector = New MessageViewerInspector()
                End If
                Return _inspector
            End Get
        End Property

        Public Sub New()
            _proxy = New TProxy()
            _proxy.Endpoint.Behaviors.Add(Me.Inspector)
        End Sub

        Public Sub New(ByVal endpointAddress As String)
            Me.New()
            If Not Me._proxy Is Nothing AndAlso Not String.IsNullOrEmpty(endpointAddress) Then
                Me._proxy.Endpoint.Address = New EndpointAddress(endpointAddress)
            End If
        End Sub

        Public Sub Dispose() Implements IDisposable.Dispose

            Try

                If Not _proxy Is Nothing Then
                    If _proxy.State <> CommunicationState.Faulted Then
                        _proxy.Close()
                    Else
                        _proxy.Abort()
                    End If
                End If

            Catch ex As CommunicationException

                _proxy.Abort()

            Catch ex As TimeoutException

                _proxy.Abort()

            Catch ex As Exception

                _proxy.Abort()
                Throw

            Finally

                _proxy = Nothing

            End Try

        End Sub

    End Class

End Namespace

Затем вы можете использовать его следующим образом:

    Using myService As New ServiceProxyHelper(Of MyService.MyServiceClient, MyService.IMyService)
        Try
            ' Do work
            myService.ServiceProxy.DoWork()
        Catch ex As FaultException(Of MyService.MyServiceException)
            ' Log exception
        End Try
    End Using

Ответ 4

Вы поставили свой собственный ответ в вопросе.

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

Это правильно.

Применив это к приведенному вами примеру, вы можете упростить его как:

    try
    {
        _service = new BillingServiceClient();
        customers = _service.GetCustomers().ToList();

    if (customers != null)
        foreach (CustomerModel customer in customers)
        {
                AddressModel address = (AddressModel)_service.GetAddressModel(customer.CustomerID);

                Assert.IsNotNull(address, "GetAddressModel returned null");
        }

    }
    catch
    {
          _service.Abort();
          _service = null;
          throw;
     }
     finally
     {
           if ((_service != null) &&
             (_service.State == System.ServiceModel.CommunicationState.Opened))
              _service.Close();
              _service = null;
     }

Позже Изменить: О, держись, я просто перечитываю свой вопрос. Вы сказали, что это произойдет после нескольких повторных вызовов. Я предполагаю, что приведенный выше код действительно вызывает сбой?