Service Fabric Reliable Dictionary 并行读取

Service Fabric Reliable Dictionary parallel reads

我有一个跨 7 个节点的集群分区的 Reliable Dictionary。 [60个分区]。我已经像这样设置了远程侦听器:

var settings = new FabricTransportRemotingListenerSettings
        {
            MaxMessageSize = Common.ServiceFabricGlobalConstants.MaxMessageSize,
            MaxConcurrentCalls = 200
        };

        return new[]
        {
            new ServiceReplicaListener((c) => new FabricTransportServiceRemotingListener(c, this, settings))
        };

我正在尝试进行负载测试以证明 Reliable Dictionary "read" 性能在负载下不会降低。我有一个 "read" 来自这样的字典方法:

using (ITransaction tx = this.StateManager.CreateTransaction())
        {
            IAsyncEnumerable<KeyValuePair<PriceKey, Price>> items;
            IAsyncEnumerator<KeyValuePair<PriceKey, Price>> e;

            items = await priceDictionary.CreateEnumerableAsync(tx,
                (item) => item.Id == id, EnumerationMode.Unordered);                
            e = items.GetAsyncEnumerator();

            while (await e.MoveNextAsync(CancellationToken.None))
            {
                var p = new Price(
                    e.Current.Key.Id,
                    e.Current.Key.Version, e.Current.Key.Id, e.Current.Key.Date,
                    e.Current.Value.Source, e.Current.Value.Price, e.Current.Value.Type,
                    e.Current.Value.Status);

                intermediatePrice.TryAdd(new PriceKey(e.Current.Key.Id, e.Current.Key.Version, id, e.Current.Key.Date), p);
            }
        }
return intermediatePrice;

每个分区大约有 500,000 条记录。字典中每个 "key" 大约 200 字节, "Value" 大约 600 字节。当我直接从浏览器调用此 "read" [调用 REST API 进而调用有状态服务] 时,需要 200 毫秒。 如果我 运行 通过负载测试进行此操作,比方说,16 个并行线程命中 相同的分区 相同的记录 ,它需要每次通话平均耗时 600 毫秒左右。如果我将负载测试并行线程数增加到 24 或 30,则每次调用大约需要 1 秒。 我的问题是,Service Fabric 可靠字典能否处理并行 "read" 操作,就像 SQL 服务器可以处理并行并发读取而不影响吞吐量一样?

根据我看到的代码,您阅读的所有内容都是在 副本上执行的 - 因此您有 7 个节点和 60 个处理请求的服务实例。如果我做对了一切,那么有 60 个处理请求的副本。

你有 7 个节点和 60 个副本 - 因此如果我们想象它们在节点之间或多或少地平均分布,我们有 [=每个节点 24=]8 个副本。

我不确定每个节点的物理配置,但如果我们暂时假设每个节点有 4 个 vCPU,那么您可以想象当您在同一个节点上发出 8 个并发请求时,所有这些请求现在应该是使用 4 个 vCPU 执行。这种情况会导致工作线程争夺 资源 - 保持简单会显着减慢处理速度。

此效果之所以在这里如此明显,是因为您正在 扫描 IReliableDictionary 而不是像这样使用 TryGetValueAsync 按键获取项目应该是。

您可以尝试更改代码以使用 TryGetValueAsync,差异会非常明显。

如果您查看关于 Reliable Dictionary CreateEnumerableAsync Method 的备注,您可以看到它被设计为并发工作,因此并发不是问题。

The returned enumerator is safe to use concurrently with reads and writes to the Reliable Dictionary. It represents a snapshot consistent view

问题是并发并不意味着

当您以这种方式进行查询时,它将:

  1. 必须在开始处理之前拍摄集合的快照,否则您将无法在处理时写入它。
  2. 您必须浏览集合中的所有值才能找到您要查找的项目,并在 return 任何操作之前记下这些值。
  3. 如果还没有在内存中,则从磁盘加载数据,内存中只保留键,值在不需要时保留在磁盘中,可能会被分页以释放内存。
  4. 以下查询可能(我不确定,但我假设)不会重用前一个查询,您的集合可能自上次查询以来已更改。

当您通过这种方式进行大量查询时 运行,许多因素都会发生:

  • 磁盘:加载数据到内存,
  • CPU:比较值和调度线程
  • 内存:存放待处理的快照

使用 Reliable Dictionary 的最佳方式是通过键检索这些值,因为它确切地知道特定键的数据存储在哪里,并且不会增加查找它的额外开销。

如果你真的想这样使用它,我建议你将它设计成一个 Index Table ,你将 id 索引的数据存储在一个字典中,另一个字典中的键是搜索值,而 value 是主词典的关键。这样会快得多。