2009-12-17 14 views
11

.NET 3.5, VS2008, servizio WCF utilizzando BasicHttpBindingWCF, BasicHttpBinding: Stop nuove connessioni, ma consente connessioni esistenti di continuare

Ho un servizio WCF ospitato in un servizio di Windows. Quando il servizio di Windows si arresta, a causa di aggiornamenti, manutenzione programmata, ecc, ho bisogno di chiudere con grazia il mio servizio WCF. Il servizio WCF ha metodi che possono richiedere diversi secondi per essere completato e il volume tipico è di 2-5 chiamate al secondo al metodo. Devo arrestare il servizio WCF in modo da consentire il completamento di qualsiasi metodo di chiamata precedente, mentre si rifiuta ogni nuova chiamata. In questo modo, posso raggiungere uno stato silenzioso in ~ 5-10 secondi e quindi completare il ciclo di spegnimento del mio servizio Windows.

La chiamata al numero ServiceHost.Close sembra l'approccio corretto, ma chiude immediatamente le connessioni client, senza attendere il completamento dei metodi in corso. Il mio servizio WCF completa il suo metodo, ma non c'è nessuno a cui inviare la risposta, perché il client è già stato disconnesso. Questa è la soluzione suggerita da this question.

Ecco la sequenza di eventi:

  1. client chiama il metodo sul servizio, utilizzando il VS generato classe proxy
  2. Servizio inizia l'esecuzione del metodo del servizio
  3. Service riceve una richiesta di arresto
  4. Servizio chiama ServiceHost.Close (o BeginClose)
  5. Il client viene disconnesso e riceve un System.ServiceModel.CommunicationException
  6. Il servizio completa il metodo di servizio.
  7. Eventualmente il servizio rileva che non ha più lavoro da fare (tramite la logica dell'applicazione) e termina.

Quello che mi serve è che le connessioni client siano mantenute aperte in modo che i client sappiano che i loro metodi di servizio sono stati completati con successo. In questo momento ottengono solo una connessione chiusa e non sanno se il metodo di servizio è stato completato con successo o meno. Prima di usare WCF, stavo usando i socket ed ero in grado di farlo controllando direttamente il socket. (ovvero interrompere il ciclo Accept mentre si sta ancora ricevendo e invia)

È importante che la porta HTTP dell'host sia chiusa in modo che il firewall upstream possa indirizzare il traffico verso un altro sistema host, ma le connessioni esistenti rimangono aperte per consentire l'esistente chiamate di metodo per completare.

C'è un modo per realizzare questo in WCF?

Le cose che ho provato:

  1. ServiceHost.Close() - chiude i clienti subito
  2. ServiceHost.ChannelDispatchers - Call Listener.Close() su ogni - non sembra di fare nulla
  3. ServiceHost.ChannelDispatchers - chiamare CloseInput() su ogni - chiude i clienti subito
  4. Override ServiceHost.OnClosing() - mi permette di ritardare l'chiudere fino a quando decido che è ok per chiudere, ma le nuove connessioni sono consentite durante questo tempo
  5. Rimuovere l'endpoint utilizzando technique described here. Questo cancella tutto.
  6. Esecuzione di uno sniffer di rete per osservare ServiceHost.Close().L'host chiude la connessione, nessuna risposta viene inviata.

Grazie

Modifica: Purtroppo non posso implementare una risposta di consulenza a livello di applicazione che il sistema si sta spegnendo, perché i clienti del settore sono già schierate. (Controllo solo il servizio, non i client)

Modifica: Ho utilizzato il Reflector di Redgate per esaminare l'implementazione Microsoft di ServiceHost.Close. Sfortunatamente, chiama alcune classi di helper internal a cui il mio codice non può accedere.

Modifica: Non ho trovato la soluzione completa che stavo cercando, ma il suggerimento di Benjamin di utilizzare IMessageDispatchInspector per rifiutare le richieste prima di accedere al metodo di servizio è stato il più vicino.

risposta

6

Indovinare:

Hai cercato di afferrare il legame in fase di esecuzione (a partire dai risultati), gettarlo ai BasicHttpBinding e (ri) definire le proprietà lì?

migliori ipotesi da me:

  • OpenTimeout
  • MaxReceivedMessageSize
  • ReaderQuotas

Quelli possono essere impostate in fase di esecuzione in base alla documentazione e sembrano consentire il comportamento desiderato (blocco nuova i clienti). Ciò non aiuterebbe con il "firewall upstream/bilanciamento del carico ha bisogno di reindirizzare" parte però.

Ultima ipotesi: si può (la documentazione dice sì, ma non sono sicuro quali sono le conseguenze) ridefinire l'indirizzo dell'endpoint (s) a un indirizzo localhost su richiesta? questo potrebbe funzionare come una "porta stretta" per l'host firewall pure, se non uccide di tutti i clienti comunque ..

Edit: Giocando con i suggerimenti di cui sopra e di un test limitato ho iniziato a giocare con un messaggio ispettore/combinazione comportamento che sembra essere molto promettente per ora:

public class WCFFilter : IServiceBehavior, IDispatchMessageInspector { 
    private readonly object blockLock = new object(); 
    private bool blockCalls = false; 

    public bool BlockRequests { 
     get { 
      lock (blockLock) { 
       return blockCalls; 
      } 
     } 
     set { 
      lock (blockLock) { 
       blockCalls = !blockCalls; 
      } 
     } 

    } 

    public void Validate(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) {   
    } 

    public void AddBindingParameters(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase, Collection<ServiceEndpoint> endpoints, BindingParameterCollection bindingParameters) {   
    } 

    public void ApplyDispatchBehavior(ServiceDescription serviceDescription, ServiceHostBase serviceHostBase) { 
     foreach (ChannelDispatcher channelDispatcher in serviceHostBase.ChannelDispatchers) { 
      foreach (EndpointDispatcher endpointDispatcher in channelDispatcher.Endpoints) { 
       endpointDispatcher.DispatchRuntime.MessageInspectors.Add(this); 
      } 
     } 
    } 

    public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { 
     lock (blockLock) { 
      if (blockCalls) 
       request.Close(); 
     } 
     return null; 
    } 

    public void BeforeSendReply(ref Message reply, object correlationState) {   
    } 
} 

dimenticare l'utilizzo della serratura scadente ecc, ma utilizzando questo con un test molto semplice WCF (restituendo un numero casuale con un Thread.Sleep all'interno) come questo :

var sh = new ServiceHost(new WCFTestService(), baseAdresses); 

var filter = new WCFFilter(); 
sh.Description.Behaviors.Add(filter); 

e poi lanciando la proprietà BlockRequests ottengo il seguente comportamento (di nuovo: questo è ovviamente un esempio molto, molto semplificata, ma spero che potrebbe funzionare per voi in ogni caso):

// I Spawn 3 discussioni richiesta di un numero ..
richiesta di un numero ..
richiesta di un numero ..
// log lato server per una richiesta in arrivo
richiesta in ingresso per un numero.
// Il ciclo principale capovolge il "blocco tutto" bool
Blocco accesso da qui in poi.
// 3 più clienti dopo che, per buona misura
Richiesta di un numero ..
Richiesta di un numero ..
Richiesta di un numero ..
// Prima richiesta (con log lato server, vedi sopra) completa con successo Ricevuto 1569129641
// Tutti gli altri messaggi non sono mai arrivati ​​al server e muoiono con un errore
Errore nella richiesta client generata dopo il blocco.
Errore nella richiesta client generata dopo il blocco.
Errore nella richiesta client generata dopo il blocco.
Errore nella richiesta del client prima del blocco.
Errore nella richiesta del client prima del blocco.

+0

La tua idea di ridefinire gli indirizzi degli endpoint è interessante, ma sembra che ci sia solo un ServiceHost .AddServiceEndpoint. La struttura ChannelDispatcher.Endpoints è di tipo get-only. Sai come avresti cambiato gli endpoint del servizio dopo che ServiceHost.Open() è stato chiamato? –

+0

Ciao. Scusate le cose sopra erano solo suggerimenti "pensando ad alta voce". Sto provando a creare una configurazione di prova ora e ho limitato (in altre parole: non sono sicuro se puoi usarlo così com'è) successo con un comportamento ora. Aggiornerà il mio post in un secondo. –

+0

Buona idea, ho già avuto un comportamento IDispatchMessageInspector per registrare il traffico, quindi ho aggiunto un controllo per vedere se l'host si sta spegnendo, e in tal caso, chiama Message.Close(). Ciò provoca una FaultException sul client, ma almeno il client riceve le risposte per le chiamate di servizio ricevute prima dell'arresto. La porta in entrata è ancora aperta, sfortunatamente, ma potrebbe non esserci alcun problema. –

0

Non l'ho implementato da solo, quindi YMMV, ma credo che quello che stai cercando di fare è mettere in pausa il servizio prima di fermarlo completamente. La pausa può essere utilizzata per rifiutare nuove connessioni durante il completamento delle richieste esistenti.

In .NET sembra che l'approccio alla sospensione del servizio sia quello di utilizzare ServiceController.

+0

Sì, quello che sto cercando di fare è come una pausa WCF. ServiceController viene utilizzato per interagire con i servizi di Windows. Non si riferisce a WCF. –

+0

ah, mio ​​male (lavorando su un'esperienza WCF molto limitata :-() – STW

0

Questo servizio WCF autentica l'utente in alcun modo? Hai qualche metodo "Handshake"?

+0

L'autenticazione viene eseguita a livello di applicazione. (Fa parte del contratto di servizio) Le credenziali vengono inviate nuovamente ad ogni richiesta. –

2

Esiste una API per il firewall upstream? Il modo in cui lo facciamo nella nostra applicazione è di interrompere le nuove richieste che arrivano al livello di bilanciamento del carico, e quindi quando tutte le richieste hanno terminato l'elaborazione, possiamo riavviare server e servizi.

+0

No, ma stiamo guardando i firewall che in grado di fornire questa funzionalità.buon suggerimento, però. –

+0

+1: questo è il modo in cui anch'io lo so.Prendete un servizio fuori rotazione sul sistema di bilanciamento del carico e poi fate tutto ciò che è necessario fare manutenzione – Alex

0

Penso che potrebbe essere necessario scrivere la propria implementazione con una classe helper che tiene traccia di tutte le richieste in esecuzione, quindi quando viene richiesto uno spegnimento, è possibile scoprire se qualcosa è ancora in esecuzione, ritardare lo spegnimento in base a tale .. . (utilizzando un timer forse?)

Non sei sicuro di bloccare ulteriori richieste in arrivo ... si dovrebbe avere una variabile globale che indica l'applicazione se è stato richiesto un arresto e così si potrebbe negare ulteriori richieste ...

Spero che questo possa aiutarti.

+0

Sì, una classe helper è quello che ho implementato Il trucco è il blocco di nuove richieste –

1

Il mio suggerimento è quello di impostare un EventHandler quando il servizio va in uno "stato di arresto", utilizzare il metodo OnStop. Impostare EventHandler che indica che il servizio sta andando in uno stato di arresto.

Il normale ciclo di servizio dovrebbe controllare se questo evento è impostato, se lo è, restituire un "Servizio sta bloccando il messaggio" al client chiamante e non consentire che entri nella normale routine.

Mentre i processi attivi sono ancora in esecuzione, terminare, prima che il metodo OnStop passi all'uccisione dell'host WCF (ServiceHost.Close).

Un altro modo è tenere traccia delle chiamate attive implementando il proprio contatore di riferimento. saprai quindi quando è possibile interrompere l'Host del servizio, una volta che il contatore di riferimento ha raggiunto lo zero e implementando il controllo precedente per quando è stato avviato l'evento di arresto.

Spero che questo aiuti.

+0

Grazie, ho già implementato la logica per determinare se è ok allo spegnimento.Il problema è che non è mai ok spegnere, perché ci sono sempre nuove richieste in arrivo –

0

Forse si dovrebbe impostare la ServiceBehaviorAttribute

e l'attributo OperationBehavior. Controlla su MSDN

0

In aggiunta alla risposta di Matthew Steeples.

I bilanciatori di carico più importanti come un F5, ecc., Hanno un meccanismo per identificare se un nodo è vivo. Nel tuo caso sembra controllare se una certa porta è aperta. Ma modi alternativi possono essere facilmente configurati.

Quindi potresti esporre per es. due servizi: il vero servizio che serve le richieste e un servizio di monitoraggio "heart beat". Quando si passa alla modalità di manutenzione, è possibile prima prendere il servizio di monitoraggio offline che porterà via il carico dal nodo e spegnerà il servizio reale solo dopo che tutte le richieste sono state completate. Sembra un po 'strano, ma potrebbe aiutare nel tuo scenario ...

Problemi correlati