Based on our experiences and article written by Boris partitioning seems to be very complex topic. For this reason I decided provide a more information and examples by using of the same sample (Voting Service), which Boris has used.
To setup partition configuration we will have to open configuration of the Stateful Service in application manifest. There we can set type of partition (Singleton, Named and Uniform) and required parameters.
For example in a case of uniform partition we can defined a number of partition keys. Partition key is a value defined by your application. It can be defined by any meaningful rule. Defined number of partition keys is shared across physical partitions. If you define 5 partitions (instances of your statefull service) and number of partition keys is 15, then every instance will host 3 partition keys.
To define number of keys we will use LowKey and HighKey. In this case we will set it on 0 and 10. To do that open ApplicationManifest.xml file.
This is the configuration in manifest file:
<Service Name="VotingService"> <StatefulService ServiceTypeName="VotingServiceType" TargetReplicaSetSize="[VotingService_TargetReplicaSetSize]" MinReplicaSetSize="[VotingService_MinReplicaSetSize]"> <UniformInt64Partition PartitionCount="[VotingService_PartitionCount]" LowKey="0" HighKey="10" /> </StatefulService> </Service> |
To demonstrate using of partitions, I will create a very simple console application, which must run in the SF-cluster. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.ServiceFabric.Services.Client; using System.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System.Fabric; namespace ConsoleApplication { class Program { static void Main(string[] args) { traceAll(); //getPartitionUrlTest(); Console.ReadLine(); } private static void getPartitionUrlTest() { while (true) { ServicePartitionInformation inf; var url = getPartitionUrl(0, out inf); Console.WriteLine(url); Thread.Sleep(2500); } } /// private static void traceAll() { /* Note that APplicationManifest.Xml must have UniformPartition type with keys 0-10! <StatefulService ServiceTypeName="VotingServiceType" TargetReplicaSetSize="[VotingService_TargetReplicaSetSize]" MinReplicaSetSize="[VotingService_MinReplicaSetSize]"> <UniformInt64Partition PartitionCount="[VotingService_PartitionCount]" LowKey="0" HighKey="100" /> </StatefulService> */ for (int i = 0; i < 10; i++) { ServicePartitionInformation inf; var url = getPartitionUrl(i, out inf); Console.WriteLine($"partitionId={inf.Id} Url:{url}"); } } private static string getPartitionUrl(long partitionKey, out ServicePartitionInformation info) { CancellationTokenSource src = new CancellationTokenSource(); var resolver = ServicePartitionResolver.GetDefault(); var partKey = new ServicePartitionKey(partitionKey); var partition = resolver.ResolveAsync(new Uri ("fabric:/Voting/VotingService"), partKey, src.Token).Result; var pEndpoint = partition.GetEndpoint(); var primaryEndpoint = partition.Endpoints.FirstOrDefault(p => p.Role == System.Fabric.ServiceEndpointRole.StatefulPrimary); info = partition.Info; if (primaryEndpoint != null) { JObject addresses = JObject.Parse(primaryEndpoint.Address); var p = addresses["Endpoints"].First(); string primaryReplicaAddress = p.First().Value<string>(); return primaryReplicaAddress; } else return ":("; } } } |
Add following nugget packages:
<package id="Microsoft.ServiceFabric" version="5.0.217" targetFramework="net452" /> <package id="Microsoft.ServiceFabric.Data" version="2.0.217" targetFramework="net461" /> <package id="Microsoft.ServiceFabric.Services" version="2.0.217" targetFramework="net461" /> <package id="Newtonsoft.Json" version="9.0.1-beta1" targetFramework="net461" /> |
If you start application by entering partition key 0-10 in following statement
new ServicePartitionKey(partitionKey);
You will get the URL of the of the service in partition, where this key is hosted. As long the service is running in in on partition all partition-keys will be hosted in that partition. If you execute the test method testAll() you will get following result:
for (int i = 0; i < 10; i++)
{
ServicePartitionInformation inf;
var url = getPartitionUrl(i, out inf);
Console.WriteLine($"partitionId={inf.Id} Url:{url}");
}
If VotingService_PartitionCount is set to 1 all URLs of all keys are same, because all partition keys are assigned to a single existing partition.
To change number of partitions go to file Local.xml under ApplicationParameters
As you see in following code-snippet, number of partition is set to 1.
<Parameters> <Parameter Name="VotingService_PartitionCount" Value="1" /> <Parameter Name="VotingService_MinReplicaSetSize" Value="2" /> <Parameter Name="VotingService_TargetReplicaSetSize" Value="3" /> </Parameters> |
To see physical meaning of partitions open a task manager and see 3 instances of the service.
On left taskmanager on right Service Fabric Explorer Every instance corresponds to one of replicas. According to parameter VotingService_TargetReplicaSetSize, we have 3 replicas, which explains, why we have 3 processes. Note, that only one process is hosted on some URL. Other two replicas are ActiveSecondary replicas, which hold the copy of the state, but do not actively observe requests.
Now, let’s set number of partitions to for example 3 and deploy the application:
<Parameters> <Parameter Name="VotingService_PartitionCount" Value="3" /> <Parameter Name="VotingService_MinReplicaSetSize" Value="2" /> <Parameter Name="VotingService_TargetReplicaSetSize" Value="3" /> </Parameters> |
As next delete the application instance.
Before deploy, please be sure that you are not doing “Update”. Update of running application is not allowed if you change number of partitions.
After successful update you will see following:
Now, VotingService is running in 5 processes (on each node one process). That means, that some of 3*3 = 9 replicas will be assigned to same process on some nodes. You can see these assignments in SF-Explorer:
Finally start the ConsoleApplication and execute method traceAll(), which will list URLs for all 10 partitions. As you see at the picture below, partition keys are shared across all 3 partitions.
That means, the application, which is using VotingService, in this case ConsoleApplication, is responsible to make a decision, which service in this partition will be used. By using of this principal, we can automate load-balancing across partitions based on partition-key. For example, console application could route all requests with names starting with A, B and C to partition with key 1, then names starting with D, E and F to partition 2 etc. Assuming that statistically number of requests (names) starting with some latter are statistically shared across all letters, out system would be symmetrically balanced across all requests.
In this case we used alphabetic letters as a rule, but we could use for example tenant identifies (name) to route all tenant requests to one partition and all other tenant requests to other partition.
As you see, we simply need to define a rule for key sharing based on any business decision and Service Fabric will do the rest.
Last, but not least, If you try to resolve partition with none-existing key (in this case -1), you will get following error:
{"Invalid partition key/ID '{0}' for selector {1}"}
This means that we have used key, which is not defined to be used in partition configuration. Remember we have used LowKey=0 and HighKey=10. Provided key ‘-1’ is obviously not allowed.
Posted
Jun 03 2016, 01:58 PM
by
Damir Dobric