【问题标题】:Azure Service Fabric InvokeWithRetryAsync huge overheadAzure Service Fabric InvokeWithRetryAsync 巨大的开销
【发布时间】:2017-06-07 01:07:16
【问题描述】:

我目前正在开发一个需要高吞吐量的 Service Fabric 微服务。

我想知道为什么我无法在我的工作站上使用环回实现每秒超过 500 条 1KB 消息。

我删除了所有业务逻辑并附加了一个性能分析器,只是为了衡量端到端的性能。

似乎大约 96% 的时间用于解决客户端问题,而只有大约 2% 的时间用于处理实际的 Http 请求。

我正在一个紧密的循环中调用“发送”以进行测试:

private HttpCommunicationClientFactory factory = new HttpCommunicationClientFactory();

public async Task Send()
{
    var client = new ServicePartitionClient<HttpCommunicationClient>(
         factory,
         new Uri("fabric:/MyApp/MyService"));

    await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + "/test"));
}

对此有什么想法吗?根据文档,我调用服务的方式似乎是 Service Fabric 最佳实践。

更新:缓存 ServicePartioningClient 确实提高了性能,但是使用分区服务,我无法缓存客户端,因为我不知道给定 PartitionKey 的分区。

更新 2:很抱歉,我在最初的问题中没有包含完整的详细信息。 在最初实现基于套接字的通信时,我们注意到 InvokeWithRetry 的巨大开销。

如果您使用 http 请求,您不会注意到太多。一个 http 请求已经花费了大约 1 毫秒,因此为 InvokeWithRetry 添加 0.5 毫秒并不是那么明显。

但是,如果您使用原始套接字,在我们的例子中大约需要 0.005 毫秒,为 InvokeWithRetry 增加 0.5 毫秒的开销是巨大的!

这是一个 http 示例,使用 InvokeAndRetry 需要 3 倍的时间:

public async Task RunTest()
{
    var factory = new HttpCommunicationClientFactory();
    var uri = new Uri("fabric:/MyApp/MyService");
    var count = 10000;

    // Example 1: ~6000ms
    for (var i = 0; i < count; i++)
    {
        var pClient1 = new ServicePartitionClient<HttpCommunicationClient>(factory, uri, new ServicePartitionKey(1));
        await pClient1.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url));
    }

    // Example 2: ~1800ms
    var pClient2 = new ServicePartitionClient<HttpCommunicationClient>(factory, uri, new ServicePartitionKey(1));
    HttpCommunicationClient resolvedClient = null;
    await pClient2.InvokeWithRetryAsync(
        c =>
        {
            resolvedClient = c;
            return Task.FromResult(true);
        });

    for (var i = 0; i < count; i++)
    {
        await resolvedClient.HttpClient.GetAsync(resolvedClient.Url);
    }
}

我知道 InvokeWithRetry 添加了一些我不想从客户那里错过的好东西。但它是否需要在每次调用时解析分区?

【问题讨论】:

    标签: .net azure azure-service-fabric service-fabric-stateful


    【解决方案1】:

    我认为实际对此进行基准测试并看看实际有什么不同会很好。我创建了一个带有状态服务的基本设置,该服务打开一个 HttpListener 和一个以三种不同方式调用该服务的客户端:

    • 为每个调用创建一个新客户端并按顺序执行所有调用

      for (var i = 0; i < count; i++)
      {
          var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1));
          var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}"));
      }
      
    • 只创建一次客户端,并在每次调用中按顺序重复使用它

      var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1));
      for (var i = 0; i < count; i++)
      {
          var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}"));
      }
      
    • 为每个调用创建一个新客户端并并行运行所有调用

      var tasks = new List<Task>();
      for (var i = 0; i < count; i++)
      {
          tasks.Add(Task.Run(async () =>
          {
              var client = new ServicePartitionClient<HttpCommunicationClient>(_factory, _httpServiceUri, new ServicePartitionKey(1));
              var httpResponseMessage = await client.InvokeWithRetryAsync(c => c.HttpClient.GetAsync(c.Url + $"?index={id}"));
          }));
      }
      Task.WaitAll(tasks.ToArray());
      

    然后我进行了多次计数的测试以获得平均值:

    现在,这应该是什么,而不是在受控环境中进行完整全面的测试,有许多因素会影响这种性能,例如集群大小,被调用的服务实际上做了什么(在这种情况下,什么都没有)以及有效负载的大小和复杂性(在这种情况下是一个非常短的字符串)。

    在这个测试中,我还想看看 Fabric Transport 的表现如何,并且性能与 HTTP 传输相似(老实说,我预计会稍微好一点,但在这种琐碎的场景中可能看不到)。

    值得注意的是,对于 10,000 个调用的并行执行,性能显着下降。这可能是由于服务耗尽了工作内存。这样做的影响可能是某些客户端调用出现故障并在延迟后重试(待验证)。我测量持续时间的方式是直到 所有 调用完成的总时间。同时需要注意的是,测试并没有真正允许服务使用多个节点,因为所有调用都被路由到同一个 Partition。

    总而言之,重用客户端的性能影响是名义上的,对于微不足道的调用,HTTP 执行类似于 Fabric Transport。

    【讨论】:

    • 感谢您的详细测试!他们帮助我更新了最初的问题,并以更好的可解释方式锁定了问题。问题在于 InvokeWithRetryAsync 本身的行为,而不是实例化 ServicePartitionClient。
    • 我认为你是正确的,因为 InvokeWithRetryAsync 是这里的罪魁祸首。查看该代码的作用(使用 dotPeek 或 Reflector),表明它所做的不仅仅是调用 HTTP 端点。问题是,如果您希望您的通信可靠,您可能需要所有这些,或者您可能会实现一些轻量级的东西,以满足您对发球台通信的要求。顺便说一句,我认为它每次尝试解析分区的原因是因为主节点可能已更改为另一个节点上的副本。
    • 恐怕我没有让实现在每个失败情况下都能正常工作,而且我没有时间仔细测试它。在我的情况下,获取端点一次,或者当 FabricClient 报告分区更改时就足够了。当客户端由于与节点的通信失败而引发异常时,重新获取也是合理的。但是每“纳秒”获取太多了。
    • 回答每次解决的问题,没必要。只有当客户端在通信中收到某种错误时,它才应该重新解析。所以模式一般是:1)解决2)连接3)沟通。如果您收到某些错误或连接(如果有)中断,则重新解析并重新开始(传入先前的 ResolvedServicePartition,这会在尝试获取新端点时提示 SF 中的缓存无效/地址)。每次都重新解析是不必要的,因为如果服务没有移动,你只是在访问同一个缓存
    • 好的。所以这似乎是ServicePartitionClient中的一个“错误”?因为 InvokeWithRetryAsync 总是调用 Resolve。而且我无法重用 ServicePartitionClient,因为我不知道分区。
    猜你喜欢
    • 2019-11-30
    • 1970-01-01
    • 1970-01-01
    • 2015-09-17
    • 2017-09-02
    • 2018-11-06
    • 1970-01-01
    • 1970-01-01
    • 2016-09-28
    相关资源
    最近更新 更多