我目前正在开发一种需要高吞吐量的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.005ms,为InvokeWithRetry增加0.5ms的开销是巨大的!
这是一个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添加了一些我不想错过的东西。但它是否需要在每次通话时解析分区?
答案 0 :(得分:2)
我认为实际对此进行基准测试并了解其实际差异是很好的。我使用有状态服务创建一个基本设置,打开一个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}"));
}
为每个通话创建一个新客户端,并在parallell中运行所有通话
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次调用的并行执行,性能显着下降。这可能是由于服务耗尽了工作内存。这可能是因为某些客户端调用在延迟后出现故障并重试(待验证)。我测量持续时间的方式是所有调用完成之前的总时间。同时应该注意的是,测试并不真正允许服务使用多个节点,因为所有调用都被路由到同一个分区。
总而言之,重用客户端的性能影响是名义上的,对于简单的调用,HTTP执行类似于Fabric Transport。