Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В этой статье приведены основные понятия секционирования надежных служб Azure Service Fabric. Секционирование позволяет хранить данные на локальных компьютерах, чтобы данные и вычислительные ресурсы можно масштабировать вместе.
Подсказка
Полный пример кода в этой статье доступен на сайте GitHub.
Секционирование
Секционирование не является уникальным для Service Fabric. На самом деле, это основной шаблон создания масштабируемых служб. В более широком смысле мы можем подумать о секционирования как концепции разделения состояния (данных) и вычислений на меньшие доступные единицы для повышения масштабируемости и производительности. Общеизвестная форма секционирования — это секционирование данных, также называемое сегментированием.
Службы без отслеживания состояния секционирования Service Fabric
Для безстатусных служб можно рассматривать раздел как логическую единицу, содержащую один или несколько экземпляров службы. На рисунке 1 показана бессостоятая служба с пятью экземплярами, распределенными по кластеру с использованием одного раздела.
Существует два типа решений бестранзакционной службы. Первое — это служба, которая сохраняет свое состояние внешне, например в базе данных в Базе данных SQL Azure (например, веб-сайт, в которой хранятся сведения о сеансе и данные). Второй — это службы только для вычислений (например, калькулятор или эскиз изображения), которые не управляют постоянным состоянием.
В любом случае разделение бестейтовой службы является очень редким сценарием—масштабируемость и доступность, как правило, достигаются путем добавления экземпляров. Единственный раз, когда требуется выполнить специальные запросы маршрутизации, необходимо учитывать несколько секций для экземпляров службы без отслеживания состояния.
Например, рассмотрим случай, когда пользователи с идентификаторами в определенном диапазоне должны обслуживаться только конкретным экземпляром службы. Другой пример того, когда можно секционировать бессостояниевую службу, это если у вас есть действительно секционированная серверная часть (например, шардированная база данных в базе данных SQL) и вы хотите управлять тем, какой экземпляр службы должен записывать в шард базы данных, или выполнять другую подготовку в этой бессостояниевой службе, требующую такой же информации о секционировании, как в серверной части. Эти типы сценариев также можно решить различными способами и не обязательно требовать секционирования служб.
Оставшаяся часть этого руководства посвящена государственным службам.
Службы service Fabric с отслеживанием состояния секционирования
Service Fabric упрощает разработку масштабируемых сервисов с сохранением состояния, предлагая лучший способ секционирования состояния (данных). Концептуально можно рассматривать раздел службы с состоянием как единицу масштабирования, которая является высоконадежной благодаря репликам, распределенным и сбалансированным по узлам в кластере.
Разделение в контексте служб Service Fabric с отслеживаемым состоянием относится к процессу определения того, что определенное разделение службы отвечает за часть полного состояния службы. (Как упоминалось ранее, секция — это набор реплик. Особенность Service Fabric заключается в том, что он размещает разделы на различных узлах. Это позволяет им увеличиваться до предела ресурсов узла. По мере роста потребности в данных разделы растут, и служба Service Fabric перебалансирует разделы между узлами. Это обеспечивает непрерывное эффективное использование аппаратных ресурсов.
Чтобы привести пример, предположим, что вы начинаете работу с кластером из 5 узлов и службой, настроенной на 10 разделов и целевым числом в три реплики. В этом случае служба Service Fabric будет балансировать и распределять реплики по кластеру, и в результате получится, что на каждом узле будет по две основные реплики. Если теперь необходимо масштабировать кластер до 10 узлов, Service Fabric перебалансирует основные реплики на всех 10 узлах. Аналогичным образом, если вы сократили до 5 узлов, Service Fabric перебалансирует все реплики по 5 узлам.
На рисунке 2 показано распределение 10 секций до и после масштабирования кластера.
В результате горизонтальное масштабирование достигается, так как запросы от клиентов распределяются между компьютерами, общая производительность приложения улучшается, а также снижается количество запросов на доступ к блокам данных.
Планирование секционирования
Перед реализацией службы следует всегда учитывать стратегию секционирования, которая требуется для горизонтального масштабирования. Существуют разные способы, но все они сосредоточены на том, что приложение должно достичь. В контексте этой статьи рассмотрим некоторые из более важных аспектов.
Хороший подход заключается в том, чтобы сначала подумать о структуре состояния, которое нужно разделить.
Давайте рассмотрим простой пример. Если бы вы создали службу для опроса по всему округу, вы можете создать секцию для каждого города в округе. Затем вы можете сохранить голоса для каждого человека в городе в секции, которая соответствует тому городу. На рисунке 3 показан набор людей и город, в котором они находятся.
По мере того как численность населения городов сильно варьируется, вы можете столкнуться с некоторыми разделами, которые включают много данных (например, Сиэтл), и другими разделами, содержащими очень мало данных (например, Киркленд). Каково влияние наличия разделов с неравномерным распределением состояния?
Если вы снова подумаете о примере, вы легко увидите, что раздел, который содержит голоса за Сиэтл, будет получать больше трафика, чем раздел с голосами за Киркленд. По умолчанию Service Fabric гарантирует, что на каждом узле имеется примерно одно и то же количество первичных и вторичных реплик. Таким образом, в итоге вы можете получить узлы, которые содержат реплики и обслуживают больше трафика, в то время как другие обслуживают меньше. Желательно избегать горячих и холодных участков, таких как в кластере.
Чтобы избежать этого, следует выполнить два действия с точки зрения секционирования:
- Попробуйте секционировать состояние таким образом, чтобы оно равномерно распределялось по всем секциям.
- Отчет о загрузке из каждой реплики для службы. (Сведения о том, как, см. в этой статье о метриках и загрузке). Service Fabric предоставляет возможность сообщать о нагрузке, потребляемой службами, например объем памяти или количество записей. На основе сообщаемых метрик Service Fabric обнаруживает, что некоторые секции обслуживают более высокие нагрузки, чем другие, и перебалансирует кластер путем перемещения реплик в более подходящие узлы, чтобы в целом не перегружать узел.
Иногда нельзя знать, сколько данных будет находиться в заданной секции. Таким образом, общая рекомендация заключается в том, чтобы выполнять обе операции: во-первых, применяя стратегию разделения, которая равномерно распределяет данные по разделам, и во-вторых, путем учета нагрузки. Первый метод предотвращает ситуации, описанные в примере голосования, а второй помогает сгладить временные различия в доступе или загрузке с течением времени.
Другим аспектом планирования разделения является выбор правильного количества разделов, с которых следует начать. С точки зрения Service Fabric, нет ничего, что препятствует началу работы с более высоким числом секций, чем ожидалось для вашего сценария. На самом деле предполагается, что максимальное количество разделов является допустимым подходом.
В редких случаях может потребоваться больше разделов, чем вы изначально выбрали. Так как после завершения количество разделов изменить нельзя, необходимо применить некоторые сложные подходы к разделам, например, создание нового экземпляра той же службы. Кроме того, необходимо реализовать некоторую логику на стороне клиента, которая направляет запросы к правильному экземпляру службы на основе знаний на стороне клиента, которые должен поддерживать код клиента.
Другим аспектом планирования секционирования является доступные ресурсы компьютера. Так как необходимо получить доступ к состоянию и сохранить его, вы обязаны следовать:
- Ограничения пропускной способности сети
- Ограничения системной памяти
- Ограничения дискового хранилища
Так что произойдет, если вы работаете с ограничениями ресурсов в работающем кластере? Ответ заключается в том, что вы можете просто масштабировать кластер для удовлетворения новых требований.
Руководство по планированию емкости предлагает рекомендации по определению количества узлов кластера.
Начало работы с разделением
В этом разделе описывается, как приступить к секционированием службы.
Service Fabric предлагает три схемы секционирования:
- Диапазонное секционирование (иначе называется UniformInt64Partition).
- Именованное секционирование. Приложения, использующие эту модель, обычно имеют данные, которые могут быть сегментированы в ограниченном наборе. Некоторые распространенные примеры полей данных, используемых в качестве именованных ключей разделов, могут быть регионами, почтовыми кодами, группами клиентов или другими бизнес-единицами.
- Одноэлементное секционирование. Одноэлементные разделы обычно используются, если служба не требует дополнительного маршрутизирования. Например, бесстатусные службы используют эту схему секционирования по умолчанию.
Схемы секционирования Named и Singleton — это специальные формы диапазонных разделов. По умолчанию, шаблоны Visual Studio для Service Fabric используют диапазонное секционирование, так как это наиболее распространённый и полезный вариант. Оставшаяся часть этой статьи посвящена схеме распределения по диапазонам.
Схема диапазонного секционирования
Это используется для указания целочисленного диапазона (определяемого низким ключом и высоким ключом) и ряда разделов (n). Он создает n разделов, каждый из которых отвечает за не перекрывающийся поддиапазон общего диапазона ключей разделов. Например, схема секционирования с нижним ключом 0, верхним ключом 99 и количеством 4 создаст четыре раздела, как показано ниже.
Распространенный подход заключается в создании хэша на основе уникального ключа в наборе данных. Некоторые распространенные примеры ключей — идентификационный номер транспортного средства (VIN), идентификатор сотрудника или уникальная строка. Используя этот уникальный ключ, вы создадите хэш-код, модулирует диапазон ключей для использования в качестве ключа. Можно указать верхние и нижние границы допустимого диапазона ключей.
Выбор хэш-алгоритма
Важной частью хэширования является выбор хэш-алгоритма. Следует учитывать, является ли целью группировка аналогичных ключей рядом друг с другом (хэширование с учетом локальности) или распределение действий по всем разделам (хэширование распределения), что является более распространенной практикой.
Характеристики хорошего алгоритма хэширования распределения заключается в том, что легко вычислить, он имеет несколько конфликтов, и он равномерно распределяет ключи. Хорошим примером эффективного хэш-алгоритма является хэш-алгоритм FNV-1 .
Хороший ресурс для выбора общего алгоритма хэш-кода — страница Википедии о хэш-функциях.
Создание службы с отслеживанием состояния с несколькими секциями
Давайте создадим ваш первый надежный сервис с состоянием, который поддерживает несколько разделов. В этом примере вы создадите очень простое приложение, в котором необходимо хранить все фамилии, начинающиеся с одной буквы в одной секции.
Прежде чем писать любой код, необходимо подумать о разделах и ключах секций. Вам нужно 26 секций (по одному для каждой буквы в алфавите), но как насчет низких и высоких ключей? Так как мы буквально хотим иметь одну секцию на букву, мы можем использовать 0 как низкий ключ и 25 в качестве высокого ключа, так как каждая буква является собственным ключом.
Примечание.
Это упрощенный сценарий, так как в действительности распределение будет неравномерно. Фамилии, начинающиеся на буквы "С" или "М", более распространены, чем те, которые начинаются на "Х" или "У".
Откройте Visual Studio>Файл>Создать>Проект.
В диалоговом окне "Создать проект" выберите приложение Service Fabric.
Назовите проект "AlphabetPartitions".
В диалоговом окне "Создание службы" выберите службу с сохраняемым состоянием и назовите ее "Alphabet.Processing".
Задайте количество разделов. Откройте файл ApplicationManifest.xml, расположенный в папке ApplicationPackageRoot проекта AlphabetPartitions, и обновите параметр Processing_PartitionCount до 26, как показано ниже.
<Parameter Name="Processing_PartitionCount" DefaultValue="26" />Также необходимо обновить свойства LowKey и HighKey элемента StatefulService в ApplicationManifest.xml, как показано ниже.
<Service Name="Alphabet.Processing"> <StatefulService ServiceTypeName="Alphabet.ProcessingType" TargetReplicaSetSize="[Processing_TargetReplicaSetSize]" MinReplicaSetSize="[Processing_MinReplicaSetSize]"> <UniformInt64Partition PartitionCount="[Processing_PartitionCount]" LowKey="0" HighKey="25" /> </StatefulService> </Service>Чтобы служба была доступна, откройте конечную точку на порту, добавив элемент конечной точки ServiceManifest.xml (расположен в папке PackageRoot) для службы Alphabet.Processing, как показано ниже:
<Endpoint Name="ProcessingServiceEndpoint" Port="8089" Protocol="http" Type="Internal" />Теперь служба настроена для прослушивания внутренней конечной точки с 26 секциями.
Переопределите метод
CreateServiceReplicaListeners()класса Processing.Примечание.
В этом примере предполагается, что вы используете простой HttpCommunicationListener. Дополнительные сведения о надежном обмене данными о службе см. в статье "Модель обмена данными с надежной службой".
Рекомендуемый шаблон для URL-адреса, прослушиваемого репликой, является следующим форматом:
{scheme}://{nodeIp}:{port}/{partitionid}/{replicaid}/{guid}Поэтому вы хотите настроить прослушиватель связи для прослушивания соответствующих конечных точек с учетом этого шаблона.Несколько реплик этой службы могут размещаться на одном компьютере, поэтому этот адрес должен быть уникальным для реплики. Поэтому идентификатор раздела и идентификатор дубликата находятся в URL-адресе. HttpListener может прослушивать несколько адресов на одном порту, если префикс URL-адреса является уникальным.
Дополнительный GUID существует для расширенного случая, когда вторичные реплики также прослушивают запросы только для чтения. В этом случае необходимо убедиться, что новый уникальный адрес используется при переходе с первичного на вторичный, чтобы принудительно разрешить клиентам повторное разрешение адреса. "+" используется в качестве адреса здесь, чтобы реплика прослушивала все доступные узлы (IP-адрес, полное доменное имя, localhost и т. д.) В приведенном ниже коде показан пример.
protected override IEnumerable<ServiceReplicaListener> CreateServiceReplicaListeners() { return new[] { new ServiceReplicaListener(context => this.CreateInternalListener(context))}; } private ICommunicationListener CreateInternalListener(ServiceContext context) { EndpointResourceDescription internalEndpoint = context.CodePackageActivationContext.GetEndpoint("ProcessingServiceEndpoint"); string uriPrefix = String.Format( "{0}://+:{1}/{2}/{3}-{4}/", internalEndpoint.Protocol, internalEndpoint.Port, context.PartitionId, context.ReplicaOrInstanceId, Guid.NewGuid()); string nodeIP = FabricRuntime.GetNodeContext().IPAddressOrFQDN; string uriPublished = uriPrefix.Replace("+", nodeIP); return new HttpCommunicationListener(uriPrefix, uriPublished, this.ProcessInternalRequest); }Также стоит отметить, что опубликованный URL-адрес немного отличается от префикса ПРОСЛУШИВАющего URL-адреса. URL-адрес для прослушивания предоставляется HttpListener. Опубликованный URL-адрес — это URL-адрес, опубликованный в службе именования Service Fabric, который используется для обнаружения служб. Клиенты будут запрашивать этот адрес через эту службу обнаружения. Адрес, который клиенты получают, должен иметь фактический IP-адрес или полное доменное имя узла для подключения. Поэтому необходимо заменить "+" на IP-адрес узла или полное доменное имя узла, как показано выше.
Последним шагом является добавление логики обработки в службу, как показано ниже.
private async Task ProcessInternalRequest(HttpListenerContext context, CancellationToken cancelRequest) { string output = null; string user = context.Request.QueryString["lastname"].ToString(); try { output = await this.AddUserAsync(user); } catch (Exception ex) { output = ex.Message; } using (HttpListenerResponse response = context.Response) { if (output != null) { byte[] outBytes = Encoding.UTF8.GetBytes(output); response.OutputStream.Write(outBytes, 0, outBytes.Length); } } } private async Task<string> AddUserAsync(string user) { IReliableDictionary<String, String> dictionary = await this.StateManager.GetOrAddAsync<IReliableDictionary<String, String>>("dictionary"); using (ITransaction tx = this.StateManager.CreateTransaction()) { bool addResult = await dictionary.TryAddAsync(tx, user.ToUpperInvariant(), user); await tx.CommitAsync(); return String.Format( "User {0} {1}", user, addResult ? "successfully added" : "already exists"); } }ProcessInternalRequestсчитывает значения параметра строки запроса, используемого для вызова секции и вызововAddUserAsyncдля добавления фамилии в надежный словарьdictionary.Давайте добавим в проект службу без отслеживания состояния, чтобы узнать, как можно вызвать определенную секцию.
Эта служба служит простым веб-интерфейсом, который принимает фамилию в качестве параметра строки запроса, определяет ключ секции и отправляет его в службу Alphabet.Processing для обработки.
В диалоговом окне "Создание службы" выберите службу без отслеживания состояния и вызовите ее "Alphabet.Web", как показано ниже.
.Обновите сведения о конечной точке в ServiceManifest.xml службы Alphabet.WebApi, чтобы открыть порт, как показано ниже.
<Endpoint Name="WebApiServiceEndpoint" Protocol="http" Port="8081"/>Необходимо вернуть коллекцию ServiceInstanceListeners в классе Web. Опять же, вы можете реализовать простой HttpCommunicationListener.
protected override IEnumerable<ServiceInstanceListener> CreateServiceInstanceListeners() { return new[] {new ServiceInstanceListener(context => this.CreateInputListener(context))}; } private ICommunicationListener CreateInputListener(ServiceContext context) { // Service instance's URL is the node's IP & desired port EndpointResourceDescription inputEndpoint = context.CodePackageActivationContext.GetEndpoint("WebApiServiceEndpoint") string uriPrefix = String.Format("{0}://+:{1}/alphabetpartitions/", inputEndpoint.Protocol, inputEndpoint.Port); var uriPublished = uriPrefix.Replace("+", FabricRuntime.GetNodeContext().IPAddressOrFQDN); return new HttpCommunicationListener(uriPrefix, uriPublished, this.ProcessInputRequest); }Теперь необходимо реализовать логику обработки. HttpCommunicationListener вызывает
ProcessInputRequestкогда поступает запрос. Итак, давайте пойдем дальше и добавьте приведенный ниже код.private async Task ProcessInputRequest(HttpListenerContext context, CancellationToken cancelRequest) { String output = null; try { string lastname = context.Request.QueryString["lastname"]; char firstLetterOfLastName = lastname.First(); ServicePartitionKey partitionKey = new ServicePartitionKey(Char.ToUpper(firstLetterOfLastName) - 'A'); ResolvedServicePartition partition = await this.servicePartitionResolver.ResolveAsync(alphabetServiceUri, partitionKey, cancelRequest); ResolvedServiceEndpoint ep = partition.GetEndpoint(); JObject addresses = JObject.Parse(ep.Address); string primaryReplicaAddress = (string)addresses["Endpoints"].First(); UriBuilder primaryReplicaUriBuilder = new UriBuilder(primaryReplicaAddress); primaryReplicaUriBuilder.Query = "lastname=" + lastname; string result = await this.httpClient.GetStringAsync(primaryReplicaUriBuilder.Uri); output = String.Format( "Result: {0}. <p>Partition key: '{1}' generated from the first letter '{2}' of input value '{3}'. <br>Processing service partition ID: {4}. <br>Processing service replica address: {5}", result, partitionKey, firstLetterOfLastName, lastname, partition.Info.Id, primaryReplicaAddress); } catch (Exception ex) { output = ex.Message; } using (var response = context.Response) { if (output != null) { output = output + "added to Partition: " + primaryReplicaAddress; byte[] outBytes = Encoding.UTF8.GetBytes(output); response.OutputStream.Write(outBytes, 0, outBytes.Length); } } }Давайте рассмотрим это шаг за шагом. Код считывает первую букву параметра
lastnameстроки запроса в символ. Затем система определяет ключ раздела для этой буквы, вычитая шестнадцатеричное значениеAиз шестнадцатеричного значения первой буквы фамилии.string lastname = context.Request.QueryString["lastname"]; char firstLetterOfLastName = lastname.First(); ServicePartitionKey partitionKey = new ServicePartitionKey(Char.ToUpper(firstLetterOfLastName) - 'A');Помните, что в этом примере используется 26 разделов с одним ключом раздела на каждый раздел. Затем мы получаем раздел
partitionслужбы для этого ключа с помощью методаResolveAsyncна объектеservicePartitionResolver.servicePartitionResolverопределяется какprivate readonly ServicePartitionResolver servicePartitionResolver = ServicePartitionResolver.GetDefault();Метод
ResolveAsyncпринимает URI службы, ключ секции и маркер отмены в качестве параметров. URI службы обработки соответствуетfabric:/AlphabetPartitions/Processing. Затем мы получаем конечную точку раздела.ResolvedServiceEndpoint ep = partition.GetEndpoint()Наконец, мы создадим URL-адрес конечной точки плюс строку запроса и вызовем службу обработки.
JObject addresses = JObject.Parse(ep.Address); string primaryReplicaAddress = (string)addresses["Endpoints"].First(); UriBuilder primaryReplicaUriBuilder = new UriBuilder(primaryReplicaAddress); primaryReplicaUriBuilder.Query = "lastname=" + lastname; string result = await this.httpClient.GetStringAsync(primaryReplicaUriBuilder.Uri);После завершения обработки мы записываем выходные данные обратно.
Последним шагом является тестирование службы. Visual Studio использует параметры приложения для локального и облачного развертывания. Чтобы протестировать службу с 26 секциями локально, необходимо обновить
Local.xmlфайл в папке ApplicationParameters проекта AlphabetPartitions, как показано ниже:<Parameters> <Parameter Name="Processing_PartitionCount" Value="26" /> <Parameter Name="WebApi_InstanceCount" Value="1" /> </Parameters>После завершения развертывания можно проверить службу и все его разделы в Service Fabric Explorer.
В браузере можно протестировать логику секционирования, введя .
http://localhost:8081/?lastname=somenameВы увидите, что каждая фамилия, начинающаяся с той же буквы, хранится в той же секции.
Полное решение кода, используемого в этой статье, доступно здесь: https://github.com/Azure-Samples/service-fabric-dotnet-getting-started/tree/classic/Services/AlphabetPartitions
Дальнейшие действия
Дополнительные сведения о службах Service Fabric: