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();
});

因此,在正常发送请求后,客户端可以继续执行其他工作。 响应准备就绪后,将调用响应处理程序以显示结果。

部署和测试服务

可以按照本教程 分步部署和测试用例。

可以运行多个客户端。 输出如下所示:

0624.clip_image008_51125C34

请注意,所有客户端共享相同的会话 ID。

群集自动增长和自动收缩

交互模式的一种常见情况是运行长时间运行的服务,服务多个客户端。 若要尽快响应每个请求,应使会话保持活动状态。 但是,另一方面,在非高峰期,拥有 SOA 工作占用大量资源是浪费的。

HPC Pack 具有根据请求数增长和收缩资源的功能。 如果没有请求,它将资源数缩小到作业指定的最小数量。 接收请求时,它会自动增加资源来处理这些请求。

注意:如果没有客户端在一段时间内连接到会话,会话将超时。 若要使会话成为长时间运行的服务,可以在启动会话时更改 SessionIdleTimeout