Lo scenario più comune di servizi pensati per un alto numero di richieste è quello di avere un cluster con annesso sistema di LoadBalance che garantisce lo smistamento delle richieste al nodo meno carico.a
Nonostante si configurino i vari nodi dell’ambiente speculare, quindi stessa configurazione dei servizi, stessa configurazione di AppFabric e stessa configurazione di IIS (binding, app pool, ecc), possono sorgere problemi durante l’utilizzo di Workflow Service che contengono più coppie di activity Recive/Replay correlate tra loro.
Facciamo un semplice esempio: un Workflow Service con una prima coppia Recive/Replay di init (quindi con l’attributo CanCreateInstance a true) e poi una seconda coppia Recive/Replay contenuta in un DoWhile, la cui condizione è posta a TRUE per comodità.
Workflow Service di esempio
Bene, se ora creiamo un client ed aggiungiamo un proxy al servizio tramite il classico “Add Service Reference…” di Visual Studio 2010 (ma anche attraverso svcutils) che punta al cluster, tutto funziona come ci aspetteremmo e viene generato quanto occorre per invocare le varie operation del servizio.
Qual è dunque il problema?
Bene, il problema si verifica quando il nostro client crea decine di thread per invocare Operation1 (ovvero l’activity Recive all’interno del DoWhile) in modo parallelo, ad esempio per effettuare un test di carico o funzionale.
In questo caso può capitare (ed è giusto che sia così) che l’LB smisti la richiesta ad un nodo diverso da quello verso cui inizialmente il proxy è stato creato (passando attraverso il cluster) e il sistema ci presenta un’eccezione tutt’altro che chiara (come sempre!):
There was no channel that could accept the message with action 'http://myservice.mysite.it/Operation1'
visto che il servizio è attivo su entrambi i nodi.
La prima soluzione a cui si pensa è quella di ricreare il proxy all’interno del metodo invocato dal thread ed inglobarlo in uno using, in modo da distruggerlo appena non se ne ha più bisogno:
void MyThread(){
using(var proxy = new ServiceCliend())
{
proxy.Operation1()
}
……
}
Ma il risultato resta “stranamente” lo stesso. Il fatto è che dalla versione 3.5 di dotNet (in realtà timidamente dalla 3.0sp1), è stato adottato un meccanismo di cache most recently uderd (MRU) per il ChannelFactory (accessibile dal proxy attraverso l’omonima property), allo scopo di abbattere i tempi necessari a creare un nuovo channel.
Questo è il nostro problema! Anche se rifaccio la new del proxy, l’engine del framework mi riassocia (se piò e se è valido) lo stesso channel contenuto in cache.
Per aggirare la cosa (perché permettere di settare un attributo di cache enable, come sempre, era chiedere troppo) è convivente creare un helper, ovvero una classe statica, che ha il solo compito di creare il proxy, e prima di ritornarlo effettuare un accesso (una semplice lettura) alla proprietà ChannelFactory, il che ne inibisce la persistenza in cache.
public static ServiceClient GetProxy()
{
ServiceClient proxy = new ServiceClient ();
var prev = proxy.ChannelFactory; //lettura per disattivare la cache del proxy
return proxy;
}
That’s all!
Per approfondimenti: