Tutorial DO SOA do HPC Pack III – modo interativo
Em tutorial II discutimos como lidar com um serviço demorado empregando uma sessão durável. No entanto, o modo em lote não é o único modo de computação no mundo do HPC. Alguns cálculos podem ser concluídos em alguns segundos a alguns minutos. O usuário final pode esperar uma resposta quase em tempo real.
Esse modo traz desafios diferentes quando comparado ao modo de lote. O tempo de resposta é mais crítico. Portanto, a sobrecarga de inicialização da sessão não pode ser ignorada. Em um cluster HPC típico, leva alguns segundos para iniciar uma nova sessão. Se houver outros trabalhos em execução no cluster, a sessão recém-criada deverá aguardar até que os recursos estejam disponíveis, o que torna o tempo de inicialização muito mais longo. Felizmente, o HPC Pack fornece uma maneira de lidar com essa situação e reduzir o custo desnecessário.
Consulte o exemplo de código que acompanha para seguir as etapas neste artigo.
Implementar o serviço
Usamos o mesmo serviço de tutorial II – o serviço de fatorização principal. Para atender aos requisitos em tempo real, passaremos números pequenos para o serviço.
Aqui está o contrato de serviço:
[ServiceContract]
public interface IPrimeFactorization
{
[OperationContract]
List<int> Factorize(int n);
}
E aqui está a implementação do serviço:
public List<int> Factorize(int n)
{
List<int> factors = new List<int>();
for (int i = 2; n > 1;)
{
if (n % i == 0)
{
factors.Add(i);
n /= i;
}
else
{
i++;
}
}
return factors;
}
Implementar o cliente
Para economizar tempo de iniciar um novo trabalho, o cliente deve reutilizar a sessão existente em vez de criar uma nova, pois criar uma nova sessão significa iniciar um novo trabalho. Para reutilizar a sessão existente, precisamos criar a sessão da seguinte maneira:
const string headnode = "head.contoso.com";
const string serviceName = "PrimeFactorizationService";
SessionStartInfo info = new SessionStartInfo(headnode, serviceName);
//Enable session pool
info.ShareSession = true;
info.UseSessionPool = true;
Você pode observar que há duas novas propriedades de SessionStartInfo sendo atribuídas aqui.
Definir o ShareSession como true significa que qualquer usuário pode enviar solicitações ao agente, não apenas aquele que cria a sessão.
Definir UseSessionPool como true garante que cada novo cliente use a sessão existente em vez de criar outra. O pool de sessão é mantido no lado do servidor - ele garante que, quando um cliente se conecta ao mesmo serviço com o sinalizador definido como true, ele sempre retorna a mesma sessão, desde que ainda esteja ativo.
Agora podemos criar a sessão. Não queremos usar uma sessão durável porque ela pode afetar o desempenho.
//create an interactive session
using (Session session = Session.CreateSession(info))
{
Console.WriteLine("Session {0} has been created", session.Id);
…
}
Crie um cliente do agente para enviar solicitações e obter respostas.
No caso do código anterior, agora temos uma situação em que pode haver muitos clientes de agente em uma única sessão. Nesse caso, devemos atribuir uma ID exclusiva ao cliente.
//in one session, each broker client should have a unique id
string ClientId = Guid.NewGuid().ToString();
using (BrokerClient<IPrimeFactorization> client = new BrokerClient<IPrimeFactorization>(ClientId, session))
{
Console.WriteLine("BrokerClient {0} has been created", ClientId);
Random random = new Random();
int num = random.Next(1, Int32.MaxValue);
//Send request
FactorizeRequest request = new FactorizeRequest(num);
client.SendRequest<FactorizeRequest>(request, num);
client.EndRequests();
//Get response
foreach (BrokerResponse<FactorizeResponse> response in client.GetResponses<FactorizeResponse>())
{
int number = response.GetUserData<int>();
int[] factors = response.Result.FactorizeResult;
Console.WriteLine("{0} = {1}", number, string.Join<int>(" * ", factors));
}
}
Agora, execute o cliente duas vezes. Você verá que os clientes compartilham a mesma ID de sessão, como resultado do pool de sessão habilitado. Além disso, o primeiro cliente é executado por muito mais tempo do que o segundo, o que indica que o novo cliente reutiliza a sessão criada.
Como GetResponses é uma função síncrona, o cliente será bloqueado e continuará aguardando os resultados. Essa não é uma situação bem-vinda em um sistema em tempo real, então vamos tentar outra maneira de obter respostas.
Podemos definir um de retorno de chamada assíncrono para o cliente usando SetResponseHandler da seguinte maneira:
//use this event sync main thread and callback
AutoResetEvent done = newAutoResetEvent(false);
//set callback function. this handler will be invoke before service replies.
client.SetResponseHandler<FactorizeResponse>((response) =>
{
int number = response.GetUserData<int>();
int[] factors = response.Result.FactorizeResult;
Console.WriteLine("{0} = {1}", number, string.Join<int>(" * ", factors));
//release the lock
done.Set();
});
Assim, depois de normalmente enviar solicitações, o cliente pode continuar com outro trabalho. Quando as respostas estiverem prontas, o manipulador de resposta será chamado para exibir os resultados.
Implantar e testar o serviço
Você pode seguir este tutorial para implantar e testar o caso passo a passo.
Você pode executar vários clientes. A saída será assim:
Observe que todos os clientes compartilham a mesma ID de sessão.
Crescimento automático do cluster e redução automática
Uma situação comum do modo interativo é executar um serviço de execução longa que atende a vários clientes. Para responder a cada solicitação o mais rápido possível, devemos manter a sessão ativa. Mas, por outro lado, ter um trabalho SOA ocupando um grande número de recursos durante os horários de pico é um desperdício.
O HPC Pack tem um recurso para aumentar e reduzir recursos com base no número de solicitações. Se não houver solicitações, reduzirá o número de recursos para o número mínimo especificado pelo trabalho. Ao receber solicitações, ele aumentará automaticamente os recursos para lidar com elas.
Observação: uma sessão atingirá o tempo limite se não houver nenhum cliente se conectando a ela por um período de tempo. Para tornar uma sessão um serviço de execução longa, você pode alterar o SessionIdleTimeout ao iniciar a sessão.