Лучшие практики для жизни прокси WCF - или как часто закрывать прокси WCF? - PullRequest
20 голосов
/ 29 октября 2010

Я работал над приложением 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.

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

После недолгого поиска я решил, что мне нужно периодически закрывать () прокси. Образцы, которые я обнаружил, очень малы. Этот дал несколько полезных советов, но на самом деле не отвечает на вопрос. Я также видел рекомендации , чтобы избежать использования шаблона () (и вместо этого применить try / catch / finally), потому что метод 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), чтобы ограничить тех, кто может вызывать методы службы. В одном из ответов на в этом посте упоминается, что пересмотр контекста аутентификации и безопасности делает повторное открытие канала для каждого вызова службы бесполезным, но просто рекомендует избегать создания контекста безопасности.


РЕДАКТИРОВАТЬ 03.11.2010: Это кажется важным, поэтому я добавляю его к вопросу ...

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

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

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

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

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

Ответы [ 4 ]

8 голосов
/ 02 ноября 2010

(Опубликовать совершенно другой второй ответ)

Если мы говорим о «наилучшей практике», то реальная лучшая практика с 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 голосов
/ 05 ноября 2010

Я написал (ну, нашел и изменил) оболочку, чтобы помочь правильно утилизировать сервис.Извините за 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
3 голосов
/ 05 ноября 2010

Кажется, что здесь играют несколько вещей.

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

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

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

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

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

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

0 голосов
/ 29 октября 2010

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

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

Это верно.

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

    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;
     }

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...