Надежный словарь Service Fabric для параллельного чтения
У меня есть надежный словарь, разделенный на кластер из 7 узлов. [60 разделов]. Я настроил слушателя удаленного взаимодействия, как это.
var settings = new FabricTransportRemotingListenerSettings
{
MaxMessageSize = Common.ServiceFabricGlobalConstants.MaxMessageSize,
MaxConcurrentCalls = 200
};
return new[]
{
new ServiceReplicaListener((c) => new FabricTransportServiceRemotingListener(c, this, settings))
};
Я пытаюсь сделать нагрузочный тест, чтобы доказать, что словарь "чтение" производительность не будет снижаться под нагрузкой. У меня есть "читать" из словаря метод, как это..
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 записей. Каждый "ключ" в словаре составляет около 200 байт, а "значение" - около 600 байт. Когда я вызываю это "чтение" непосредственно из браузера [вызывая REST API, который, в свою очередь, вызывает службу с отслеживанием состояния], это занимает 200 миллисекунд. Если я запускаю тестирование с нагрузкой, скажем, 16 параллельных потоков, работающих на один и тот же раздел и одну и ту же запись, это в среднем занимает около 600 миллисекунд на вызов. Если я увеличу количество параллельных потоков loadtest до 24 или 30, это займет около 1 секунды для каждого вызова. Мой вопрос заключается в том, может ли обслуживаемый матричный словарь обрабатывать параллельные операции "чтения" так же, как SQL-сервер может обрабатывать параллельные параллельные чтения, не влияя на пропускную способность?
2 ответа
Если вы проверите Замечания о надежном словаре метода CreateEnumerableAsync, вы увидите, что он был разработан для одновременной работы, поэтому параллелизм не является проблемой.
Возвращенный перечислитель можно безопасно использовать одновременно с чтением и записью в надежный словарь. Это представляет собой последовательный вид снимка
Проблема в том, что одновременно не означает быстро
Когда вы делаете свой запрос таким образом, он будет:
- необходимо сделать снимок коллекции до того, как он начнет ее обрабатывать, иначе вы не сможете писать в нее во время обработки.
- Вы должны просмотреть все значения в коллекции, чтобы найти искомый элемент и записать эти значения, прежде чем что-либо возвращать.
- Загрузите данные с диска, если еще нет в памяти, в памяти хранятся только ключи, значения сохраняются на диске, когда они не требуются, и могут быть выгружены для освобождения памяти.
- Следующие запросы, вероятно, (я не уверен, но я предполагаю) не будут повторно использовать предыдущий, ваша коллекция могла измениться с момента последнего запроса.
Когда у вас есть огромное количество запросов, выполняемых этими способами, многие факторы будут иметь место:
- Диск: загрузка данных в память,
- CPU: сравнение значений и планирование потоков
- Память: сохранение снимка для обработки
Лучший способ работы с Reliable Dictionary - это получение этих значений с помощью ключей, поскольку он точно знает, где хранятся данные для определенного ключа, и не добавляет эти дополнительные издержки для их поиска.
Если вы действительно хотите использовать его таким образом, я бы порекомендовал вам создать его в виде индексной таблицы, в которой данные, индексируемые по id, хранятся в одном словаре, а в другом словаре ключом является искомое значение, а значением является ключ к главный диктон Это было бы намного быстрее.
Исходя из кода, который я вижу, все, что вы читаете, выполняется на первичных репликах - поэтому у вас есть 7 узлов и 60 экземпляров службы, которые обрабатывают запросы. Если я все сделаю правильно, есть 60 реплик, которые обрабатывают запросы.
У вас есть 7 узлов и 60 реплик - поэтому, если мы представим, что они более или менее равномерно распределены между узлами, у нас будет 8 реплик на узел.
Я не уверен насчет физической конфигурации каждого узла, но если мы на мгновение предположим, что каждый узел имеет 4 vCPU, то вы можете себе представить, что когда вы делаете 8 одновременных запросов на одном узле, все эти запросы теперь должны выполняться с использованием 4 vCPU. Эта ситуация заставляет рабочие потоки бороться за ресурсы - если это просто, это значительно замедляет обработку.
Причина, по которой этот эффект так заметен, заключается в том, что вы сканируете IReliableDictionary
вместо того, чтобы получать элементы по ключу, используя TryGetValueAsync, как это должно быть.
Вы можете попытаться изменить свой код для использования TryGetValueAsync
и разница будет очень заметной.