HPC Pack SOA 教程 III - 交互式模式
在 教程 II 我们讨论了如何使用持久会话来处理耗时的服务。 但是,批处理模式并不是 HPC 世界中唯一的计算模式。 某些计算可以在几秒钟到几分钟内完成。 最终用户可能需要几乎实时响应。
与批处理模式相比,该模式带来了不同的挑战。 响应时间更为关键。 因此,无法忽略会话启动开销。 在典型的 HPC 群集中,启动新会话需要几秒钟时间。 如果群集上运行其他作业,新创建的会话必须等到资源可用,这会使启动时间更长。 幸运的是,HPC Pack 提供了一种方法来处理这种情况并降低不必要的成本。
实现服务
我们使用的服务与 教程 II 中的服务相同, 即主要分解服务。 为了满足实时要求,我们只需将少量数字传递给服务。
下面是服务协定:
[ServiceContract]
public interface IPrimeFactorization
{
[OperationContract]
List<int> Factorize(int n);
}
下面是服务实现:
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;
}
实现客户端
若要节省启动新作业的时间,客户端必须重复使用现有会话,而不是创建新会话,因为创建新会话意味着启动新作业。 若要重复使用现有会话,需要按如下所示创建会话:
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;
你可能会注意到,此处分配了 SessionStartInfo 的两个新属性。
将 ShareSession 设置为 true 意味着任何用户可以将请求发送到代理,而不仅仅是创建会话的请求。
将 UseSessionPool 设置为 true 可确保每个新客户端都使用现有会话,而不是创建另一个会话。 会话池在服务器端维护 - 它保证当客户端连接到同一服务时,标志设置为 true 时,只要它仍然处于活动状态,它始终返回同一会话。
现在,我们可以创建会话。 我们不想使用持久会话,因为它可能会影响性能。
//create an interactive session
using (Session session = Session.CreateSession(info))
{
Console.WriteLine("Session {0} has been created", session.Id);
…
}
创建代理客户端以发送请求并获取响应。
在前面的代码中,我们现在有一种情况:单个会话中可以有多个中转站客户端。 在这种情况下,应向客户端分配唯一 ID。
//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));
}
}
现在运行客户端两次。 你将看到客户端共享同一会话 ID,因为启用了会话池。 此外,第一个客户端运行的时间比第二个客户端长得多,这表示新客户端重复使用创建的会话。
由于 GetResponses 是同步函数,因此客户端将被阻止并一直等待结果。 这不是实时系统中的欢迎情况,因此让我们尝试另一种方法来获取响应。
可以使用 SetResponseHandler 为客户端设置 异步回调,如下所示:
//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();
});
因此,在正常发送请求后,客户端可以继续执行其他工作。 响应准备就绪后,将调用响应处理程序以显示结果。
部署和测试服务
可以运行多个客户端。 输出如下所示:
请注意,所有客户端共享相同的会话 ID。
群集自动增长和自动收缩
交互模式的一种常见情况是运行长时间运行的服务,服务多个客户端。 若要尽快响应每个请求,应使会话保持活动状态。 但是,另一方面,在非高峰期,拥有 SOA 工作占用大量资源是浪费的。
HPC Pack 具有根据请求数增长和收缩资源的功能。 如果没有请求,它将资源数缩小到作业指定的最小数量。 接收请求时,它会自动增加资源来处理这些请求。
注意:如果没有客户端在一段时间内连接到会话,会话将超时。 若要使会话成为长时间运行的服务,可以在启动会话时更改 SessionIdleTimeout。