2016-03-02 21 views
19

Abbiamo SOA per la nostra soluzione. Stiamo usando .net framework 4.5.1, asp.net mvc 4.6, sql server, windows server e thinktecture identity server 3 (per chiamate webapi basate su token.)I metodi asincroni dell'applicazione Mvc sono sospesi

Struttura della soluzione simile;
enter image description here

La nostra applicazione di frontend in mvc parla con la nostra applicazione webapi tramite un wrapper httpClient. Ecco il codice generico del wrapper del client http;

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Net; 
using System.Net.Http; 
using System.Net.Http.Headers; 
using System.Threading.Tasks; 

namespace Cheetah.HttpClientWrapper 
{ 
    public class ResourceServerRestClient : IResourceServerRestClient 
    { 
     private readonly ITokenProvider _tokenProvider; 

     public ResourceServerRestClient(ITokenProvider tokenProvider) 
     { 
      _tokenProvider = tokenProvider; 
     } 

     public string BaseAddress { get; set; } 

     public Task<T> GetAsync<T>(string uri, string clientId) 
     { 
      return CheckAndInvokeAsync(async token => 
      { 
       using (var client = new HttpClient()) 
       { 
        ConfigurateHttpClient(client, token, clientId); 

        HttpResponseMessage response = await client.GetAsync(uri); 

        if (response.IsSuccessStatusCode) 
        { 
         return await response.Content.ReadAsAsync<T>(); 
        } 

        var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}"); 
        exception.Data.Add("StatusCode", response.StatusCode); 
        throw exception; 
       } 
      }); 
     } 

     private void ConfigurateHttpClient(HttpClient client, string bearerToken, string resourceServiceClientName) 
     { 
      if (!string.IsNullOrEmpty(resourceServiceClientName)) 
      { 
       client.DefaultRequestHeaders.Add("CN", resourceServiceClientName); 
      } 

      if (string.IsNullOrEmpty(BaseAddress)) 
      { 
       throw new Exception("BaseAddress is required!"); 
      } 

      client.BaseAddress = new Uri(BaseAddress); 
      client.Timeout = new TimeSpan(0, 0, 0, 10); 
      client.DefaultRequestHeaders.Accept.Clear(); 
      client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", bearerToken); 
     } 

     private async Task<T> CheckAndInvokeAsync<T>(Func<string, Task<T>> method) 
     { 
      try 
      { 
       string token = await _tokenProvider.IsTokenNullOrExpired(); 

       if (!string.IsNullOrEmpty(token)) 
       { 
        return await method(token); 
       } 

       var exception = new Exception(); 
       exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized); 
       throw exception; 
      } 
      catch (Exception ex) 
      { 
       if (ex.Data.Contains("StatusCode") && ((HttpStatusCode)ex.Data["StatusCode"]) == HttpStatusCode.Unauthorized) 
       { 
        string token = await _tokenProvider.GetTokenAsync(); 

        if (!string.IsNullOrEmpty(token)) 
        { 
         return await method(token); 
        } 
       } 

       throw; 
      } 
     } 

     public void ThrowResourceServerException(List<string> messages) 
     { 
      string message = messages.Aggregate((p, q) => q + " - " + p); 

      var exception = new Exception(message); 

      exception.Data.Add("ServiceOperationException", message); 

      throw exception; 
     } 
    } 
} 

Inoltre, a volte questo wrapper client HTTP usando con NitoAsync manager (chiamare i metodi asincrone come sync.), E qualche volta stiamo usando questo metodo generico direttamente con attendono - compito asincrono aspettare simili;

var result = await _resourceServerRestClient.GetAsync<ServiceOperation<DailyAgendaModel>>("dailyAgenda/" + id); 

Così qui è il nostro problema:

Quando testiamo la nostra applicazione MVC con JMeter (per fare qualche genere-di prova di carico/10 thread per 1 sec), dopo un paio di minuti, mvc l'applicazione smette di funzionare [l'eccezione è l'attività annullata a causa del timeout] (forse solo 1-2 richieste di timeout) su questa riga: HttpResponseMessage response = await client.GetAsync(uri);. Ma dopo quella richiesta, tutte le richieste saranno fallite come se fossero in fila. Quindi l'applicazione mvc è sospesa per 2-15 minuti (a caso) ma in quel momento posso inviare nuove richieste da postino a webapi. Sono ok, voglio dire, webapi sta rispondendo bene. Dopo un paio di minuti, il ritorno all'applicazione in mvc diventa normale.

Nota: Abbiamo il bilanciamento del carico per mvc-ui e webapi. Perché a volte riceviamo 120.000 richieste in un minuto in una giornata impegnativa. Ma dà lo stesso errore se non c'è un sistema di bilanciamento del carico di fronte a un'applicazione webapi o mvc. Quindi non è un problema con LB.

Nota2: Abbiamo provato a utilizzare RestSharp per le comunicazioni mvc-ui e webapi. Abbiamo lo stesso errore qui. Quando un reuqest sta fallendo, tutte le richieste saranno fallite di fila. Sembra che si tratti di un errore di rete, ma non possiamo trovare una prova per questo.

Riesci a vedere qualche errore sul mio wrapper httpClient? o meglio la domanda è;
Nella tua soluzione, in che modo la tua applicazione mvc comunica con l'applicazione webapi? Quali sono le migliori pratiche qui?

Update1: ci siamo trasferiti progetti .net 4.5.1 a 4.6.1. Lo stesso stallo è successo di nuovo. E poi abbiamo spostato temporaneamente tutti i codici sorgente del livello: "Business & Repository" come livello dll. Non ci sono webapi tra il livello di presentazione aziendale & ora. Blocco morto risolto. Stiamo ancora cercando perché i codici httpClientWrapper non funzionino correttamente quando abbiamo chiamato i metodi webapi dai nostri controller di applicazione web.

+0

hai condiviso il codice di richiesta lato client. Ma il problema principale è wep api che non può rispondere normalmente. La tua richiesta sarà bloccata.Il lato Web api deve essere più veloce. È possibile aumentare il timeout. Non lo consiglio Forse puoi dormire la tua richiesta con Thread. Quelli non sono una buona soluzione. Si prega di condividere wep api side. Forse possiamo aumentare performans –

+0

@ SercanTimoçin grazie per la vostra risposta. Howerer webapi è la migliore API che abbiamo scritto, nei nostri test di carico; il tempo di risposta massimo è <500ms. Quindi non pensiamo che sia il faulth del metodo web api. –

+0

La soluzione migliore è eseguire un dump completo della memoria quando l'applicazione si blocca/inizia a scadere. Puoi usare qualcosa come [WinDbg] (https://msdn.microsoft.com/en-us/windows/hardware/hh852365.aspx) per poi eseguire un '! Analize' o [Debug Diag] (https: // www .microsoft.com/it/us/download/details.aspx? id = 49924) per vedere cosa sta succedendo. Potrebbe essere deadlock o risorse non rilasciate o chissà cosa, ma nessuno può dire senza vedere il codice dell'applicazione e osservare come si comporta. – Igor

risposta

0

Io non sono esattamente sicuro WHU, ma inizierò dal refactoring il metodo GetAsync()

public async Task<T> GetAsync<T>(string uri, string clientId) 
    { 
     try 
     { 
     string token = await _tokenProvider.IsTokenNullOrExpired(); 
     if (!string.IsNullOrEmpty(token)) 
      { 
      using (var client = new HttpClient()) 
      { 
       ConfigurateHttpClient(client, token, clientId); 

       HttpResponseMessage response = await client.GetAsync(uri); 

       if (response.IsSuccessStatusCode) 
       { 
        return await response.Content.ReadAsAsync<T>(); 
       } 

       var exception = new Exception($"Resource server returned an error. StatusCode : {response.StatusCode}"); 
       exception.Data.Add("StatusCode", response.StatusCode); 
       throw exception; 
      } 
      } 
      else 
      { 
       var exception = new Exception(); 
       exception.Data.Add("StatusCode", HttpStatusCode.Unauthorized); 
       throw exception; 
      } 
     } 
     catch (Exception ex) 
     { 
      throw; 
     } 
    } 
1

migliore domanda è; Nella tua soluzione, in che modo la tua applicazione mvc comunica con la tua applicazione webapi? Quali sono le migliori pratiche qui?

Una best practice qui è per il cliente (del browser nel tuo caso) per recuperare i dati direttamente dai controllori Web API e per i controller MVC servire solo vista HTML puri che includono il layout, stili (CSS) , struttura visiva, script (per esempio javascript) ecc. e non i dati.

Browser communicating to MVC and Web API

Image credit: Ode to Code. Tra l'altro l'autore di quel sito non raccomanda il tuo approccio sebbene sia elencato come un'opzione.

  1. Questo server come un buon SOC tra le vostre opinioni ei vostri dati che consente più facilmente di apportare modifiche al dell'una o dell'altra parte.
  2. Consente al client (browser) di recuperare i dati in modo asincrono, creando così una migliore esperienza utente.

Evitando di fare questo e l'aggiunta di un passo richiesta di rete nello stack di chiamate è stato creato un passaggio costoso inutile il flusso di dati (chiamata da MVC Controller (s) per la distribuzione Web API). Più i confini vengono superati durante l'esecuzione, più lenta è l'esecuzione.

La soluzione rapida, come avete già capito, è quella di chiamare la libreria del codice aziendale direttamente dal vostro progetto MVC. Ciò eviterà il passaggio di rete aggiuntivo e non necessario. Non c'è niente di sbagliato nel fare questo e molti altri siti tradizionali servono sia la vista (html) che i dati nella stessa chiamata. Rende un design più strettamente accoppiato ma è migliore di quello che avevi.

La soluzione migliore a lungo termine è quella di modificare le visualizzazioni MVC in modo che chiamino direttamente la distribuzione dell'API Web. Questo può essere fatto utilizzando framework come Angular, React, Backbone, ecc. Se le chiamate al metodo API Web sono limitate e non sono previste in crescita, è possibile utilizzare anche JQuery o javascript puro MA Non vorrei provare a creare un'applicazione complessa su questo, c'è un motivo per cui quadri come Angular sono diventati così popolari.

Per quanto riguarda l'effettivo problema tecnico sottostante in questo caso, non possiamo essere sicuri senza un dump della memoria per vedere quali risorse stanno causando il deadlock. Potrebbe essere qualcosa di semplice come rendere sicuro il vostro MVC I metodi di azione sono anche tornando async Task<ActionResult> (invece di ActionResult che, sto indovinando, è come li avete strutturato ora) in modo che possano chiamare il HttpClient utilizzando un vero e proprio async/await modello. Onestamente, perché è un cattivo design, non vorrei passare il tempo a cercare di farlo funzionare.

0

Si dovrebbe mettere .ConfigureAwait(false) per le sue dichiarazioni in attesa: interiori:

HttpResponseMessage response = await client.GetAsync(uri).ConfigureAwait(false); 

(...) 

return await response.Content.ReadAsAsync<T>().ConfigureAwait(false); 

(...) 

string token = await _tokenProvider.IsTokenNullOrExpired().ConfigureAwait(false); 

(...) 
return await method(token).ConfigureAwait(false);; 

(...) 

string token = await _tokenProvider.GetTokenAsync().ConfigureAwait(false);; 

(...) 

return await method(token).ConfigureAwait(false); 

In questo modo si eviterà di catturare il contesto di sincronizzazione prima che l'attendono è fatto. In caso contrario, la continuazione verrà eseguita in questo contesto, che potrebbe causare un blocco se questo è in uso da altri thread. Così facendo si consentirà il proseguimento nel contesto del compito atteso.