Placement de grain

Orleans s’assure qu’en cas d’appel de grain, une instance de ce grain existe en mémoire sur un serveur du cluster pour traiter la demande. Si le grain n’est pas actif actuellement dans le cluster, Orleans choisit l’un des serveurs sur lequel activer le grain. Cela s’appelle le placement de grains. Le placement est aussi un moyen d’équilibrer la charge : même le placement de grains occupés contribue à répartir la charge de travail à l’échelle du cluster.

Le processus de placement dans Orleans est entièrement configurable : les développeurs peuvent choisir l’une des stratégies de placement prêtes à l’emploi (aléatoire, local, en fonction de la charge) ou configurer une logique personnalisée. Cela offre une flexibilité totale quant au choix de l’emplacement où sont créés les grains. Par exemple, les grains peuvent être placés sur un serveur proche des ressources dont ils ont besoin pour fonctionner ou à proximité des autres grains avec lesquels ils communiquent. Par défaut, Orleans choisit un serveur compatible avec le mode aléatoire.

La stratégie de placement de Orleans peut être configurée globalement ou par classe de grain.

Placement aléatoire

Un serveur est sélectionné de manière aléatoire parmi l’ensemble de serveurs compatibles dans le cluster. Cette stratégie de placement est configurée en ajoutant l’attribut RandomPlacementAttribute à un grain.

Placement local

Si le serveur local est compatible, sélectionnez le serveur local. Sinon, sélectionnez un serveur aléatoire. Cette stratégie de placement est configurée en ajoutant l’attribut PreferLocalPlacementAttribute à un grain.

Positionnement basé sur le hachage

Hachez l’ID de grain en entier non négatif et faites une opération modulo avec le nombre de serveurs compatibles. Sélectionnez le serveur correspondant dans la liste des serveurs compatibles classée par adresse de serveur. Notez que la stabilité n’est pas garantie, car l’appartenance au cluster évolue. En effet, l’ajout, la suppression ou le redémarrage de serveurs peut entraîner la sélection d’un autre serveur pour un ID de grain donné. Sachant que les grains placés à l’aide de cette stratégie sont inscrits dans le répertoire de grains, ce changement de placement à mesure que l’appartenance évolue n’a généralement pas d’effet notable.

Cette stratégie de placement est configurée en ajoutant l’attribut HashBasedPlacementAttribute à un grain.

Placement basé sur le nombre d’activations

Cette stratégie de placement vise à placer de nouvelles activations de grain sur le serveur le moins chargé par rapport au nombre de grains récemment occupés. Elle comporte un mécanisme qui fait publier régulièrement à tous les serveurs le nombre total de leurs activations sur tous les autres serveurs. Le directeur de placement sélectionne alors le serveur qui présente le moins d’activations sur la base de prédictions établies à partir du rapport le plus récent du nombre d’activations. Après quoi, le nombre d’activations actuel est prédit à partir du nombre d’activations récent déterminé par le directeur de placement sur le serveur actuel. Le directeur sélectionne plusieurs serveurs de façon aléatoire au moment d’effectuer cette prédiction dans le but d’éviter que plusieurs serveurs distincts surchargent le même serveur. Par défaut, deux serveurs sont sélectionnés de façon aléatoire, mais cette valeur peut être configurée via ActivationCountBasedPlacementOptions.

Cet algorithme repose sur la thèse de Michael David Mitzenmacher The Power of Two Choices in Randomized Load Balancing, et est également utilisé dans Nginx pour l’équilibrage de charge distribué, comme décrit dans l’article NGINX and the "Power of Two Choices" Load-Balancing Algorithm.

Cette stratégie de placement est configurée en ajoutant l’attribut ActivationCountBasedPlacementAttribute à un grain.

Placement des workers sans état

Il s’agit d’une stratégie de placement spéciale utilisée par les grains de worker sans état. Le fonctionnement de ce placement est presque identique à celui de PreferLocalPlacement sauf que chaque serveur peut avoir plusieurs activations d’un même grain et que le grain n’est pas inscrit dans le répertoire des grains, car cela n’est pas nécessaire.

Cette stratégie de placement est configurée en ajoutant l’attribut StatelessWorkerAttribute à un grain.

Placement en fonction du rôle de silo

Stratégie de placement déterministe qui place les grains sur les silos ayant un rôle spécifique. Cette stratégie de placement est configurée en ajoutant l’attribut SiloRoleBasedPlacementAttribute à un grain.

Choisir une stratégie de placement

Le choix de la stratégie de placement de grains appropriée, outre les valeurs par défaut fournies par Orleans, nécessite un monitoring et une évaluation des développeurs. La stratégie de placement doit être choisie selon plusieurs facteurs : la taille et la complexité de l’application, les caractéristiques de la charge de travail et l’environnement de déploiement.

Le placement aléatoire s’appuie sur le principe de la loi des grands nombres (Law of Large Numbers). C’est donc généralement une bonne stratégie par défaut quand il y a une répartition de la charge imprévisible entre un grand nombre de grains (plus de 10 000).

Le placement basé sur le nombre d’activations comporte également un élément aléatoire ; il s’appuie sur le principe « Power of Two Choices », qui est un algorithme couramment utilisé pour l’équilibrage de charge distribué dans les équilibreurs de charge populaires. Les silos publient fréquemment des statistiques d’exécution sur d’autres silos du cluster, notamment celles-ci :

  • Mémoire disponible, mémoire physique totale et utilisation de la mémoire.
  • Utilisation du processeur.
  • Nombre total d’activations et nombre d’activations actives récentes.
    • Fenêtre glissante des activations qui étaient actives quelques secondes auparavant, parfois appelée jeu de travail des activations.

À partir de ces statistiques, seuls les nombres d’activations sont actuellement utilisés pour déterminer la charge sur un silo donné.

Au final, vous devez tester différentes stratégies et surveiller les métriques de performances pour déterminer la stratégie la plus appropriée. En sélectionnant la stratégie de placement de grains correcte, vous pouvez optimiser les performances, la scalabilité et la rentabilité de vos applications Orleans.

Configurer la stratégie de placement par défaut

Orleans utilise le placement aléatoire, sauf si le paramètre par défaut a été écrasé. La stratégie de placement par défaut peut être substituée en inscrivant une implémentation de PlacementStrategy durant la configuration :

siloBuilder.ConfigureServices(services =>
    services.AddSingleton<PlacementStrategy, MyPlacementStrategy>());

Configurer la stratégie de placement pour un grain

La stratégie de placement pour un type de grain est configurée en ajoutant l’attribut approprié dans la classe de grain. Les attributs pertinents sont spécifiés dans les sections de stratégies de placement.

Exemple de stratégie de placement personnalisé

Pour commencer, définissez une classe qui implémente l’interface IPlacementDirector, ce qui nécessite une seule méthode. Dans cet exemple, nous supposons que vous avez défini une fonction GetSiloNumber qui retournera un nombre de silos donné en fonction du Guid du grain qui va être créé.

public class SamplePlacementStrategyFixedSiloDirector : IPlacementDirector
{
    public Task<SiloAddress> OnAddActivation(
        PlacementStrategy strategy,
        PlacementTarget target,
        IPlacementContext context)
    {
        var silos = context.GetCompatibleSilos(target).OrderBy(s => s).ToArray();
        int silo = GetSiloNumber(target.GrainIdentity.PrimaryKey, silos.Length);

        return Task.FromResult(silos[silo]);
    }
}

Vous devez ensuite définir deux classes pour permettre l’affectation de classes de grain à la stratégie :

[Serializable]
public sealed class SamplePlacementStrategy : PlacementStrategy
{
}

[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class SamplePlacementStrategyAttribute : PlacementAttribute
{
    public SamplePlacementStrategyAttribute() :
        base(new SamplePlacementStrategy())
    {
    }
}

Après quoi, étiquetez les classes de grain que cette stratégie doit utiliser avec l’attribut suivant :

[SamplePlacementStrategy]
public class MyGrain : Grain, IMyGrain
{
    // ...
}

Et enfin, inscrivez la stratégie au moment de générer SiloHost :

private static async Task<ISiloHost> StartSilo()
{
    var builder = new HostBuilder(c =>
    {
        // normal configuration methods omitted for brevity
        c.ConfigureServices(ConfigureServices);
    });

    var host = builder.Build();
    await host.StartAsync();

    return host;
}

private static void ConfigureServices(IServiceCollection services)
{
    services.AddSingletonNamedService<
        PlacementStrategy, SamplePlacementStrategy>(
            nameof(SamplePlacementStrategy));

    services.AddSingletonKeyedService<
        Type, IPlacementDirector, SamplePlacementStrategyFixedSiloDirector>(
            typeof(SamplePlacementStrategy));
}

Pour un deuxième exemple simple montrant une autre utilisation du contexte de placement, consultez PreferLocalPlacementDirector dans le référentiel sourceOrleans