Aller au contenu principal

Vous venez de Redis : pointer votre application .NET vers Zaris

· 6 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Deux chemins de Redis vers Zaris

Si vos services .NET communiquent déjà avec Redis, passer à Zaris n'a pas à être une réécriture. Zaris est un store clé-valeur distribué, en mémoire et répliqué, conçu nativement pour .NET — et depuis la 2.0.0, il parle deux protocoles à la fois : son propre protocole binaire efficace pour les clients .NET de premier ordre, et un front-end RESP2 compatible Redis que n'importe quel client Redis, dans n'importe quel langage, peut viser.

Cela vous offre deux chemins de migration honnêtes, qui ne s'excluent pas mutuellement. Vous pouvez commencer par le chemin RESP sans aucune modification pour mettre Zaris sous charge dès aujourd'hui, puis adopter le client natif là où cela est rentable. Cet article parcourt les deux et vous donne une règle claire pour choisir.

Zaris 0.9.0 est là — et disponible en disponibilité générale

· 5 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Zaris 0.9.0 — Disponibilité générale

Aujourd'hui, nous publions Zaris 0.9.0, et avec lui, Zaris atteint la disponibilité générale. Il est prêt pour la production, multiplateforme, et désormais soutenu par un support commercial.

Si vous ne le connaissez pas encore : Zaris est un store clé-valeur distribué et en mémoire pour .NET. Il est rapide, répliqué, et vous pouvez le piloter depuis un navigateur, PowerShell, ou directement depuis votre application. Cette version est l'aboutissement de ce travail — le moment où un « projet prometteur » devient une « solution que vous pouvez mettre en production ».

Construire des systèmes réactifs avec Clustron Watch

· 6 minutes de lecture

La plupart des systèmes distribués ne commencent pas de façon compliquée.

Ils débutent avec quelque chose de simple — une boucle.

Vous écrivez un petit worker qui recherche du travail :

while (true)
{
var job = await client.GetAsync<Job>("jobs:next");

if (job.IsSuccess)
{
await ProcessAsync(job.Value);
}

await Task.Delay(1000);
}

À ce stade, tout semble raisonnable. La logique est claire, le système se comporte de façon prévisible et la charge cognitive est très faible.

Mais à mesure que le système grandit, ce modèle devient discrètement l'une des parties les plus coûteuses de votre architecture.


Le coût caché du polling dans les systèmes distribués​

Le polling introduit une inefficacité fondamentale : vous interrogez sans cesse le système à propos de changements qui, la plupart du temps, ne se sont pas produits.

À mesure que vous montez en charge :

  • Plusieurs instances commencent à interroger les mêmes données
  • Chaque instance effectue des lectures répétées, dont la plupart ne renvoient aucune information utile
  • Vous introduisez une latence artificielle — les changements ne sont détectés qu'au cycle de polling suivant
  • Le système se met à consommer des ressources simplement pour confirmer que rien n'a changé

Cela aboutit à un système techniquement correct, mais inefficace sur le plan opérationnel.

Plus important encore, le polling vous oblige à faire des arbitrages que vous ne devriez pas avoir à faire :

  • Interroger fréquemment → coût plus élevé, latence plus faible
  • Interroger moins fréquemment → coût plus faible, latence plus élevée

Il n'y a pas de bonne réponse — seulement un compromis.


Un modèle différent : réagir plutôt que demander​

L'alternative n'est pas qu'une simple optimisation — c'est une autre façon de penser les systèmes.

Au lieu de demander :

« Quelque chose a-t-il changé ? »

Vous concevez votre système pour qu'il réagisse à :

« Quelque chose vient de changer. »

C'est le passage d'un modèle pull à un modèle push.

Dans Clustron, cela s'implémente au moyen de Watch.


Modèle de polling vs observation des changements


Présentation de Watch : un flux de changements d'état​

Clustron Watch vous permet de vous abonner aux changements du système et de recevoir les événements au fur et à mesure qu'ils surviennent.

Fondamentalement, Watch transforme vos données en un flux continu de mises à jour.

Observer une clé unique​

var (subscription, initialRecord) = await client.Watch.WatchKeyAsync(
"order:1001",
new WatchOptions { IncludeInitialSnapshot = true },
ev =>
{
Console.WriteLine($"{ev.EventType} → {ev.Key} = {ev.Value}");
});

Cela fait deux choses importantes :

  1. Cela vous donne l'état courant (initialRecord)
  2. Cela vous abonne à tous les changements futurs

Cette combinaison est essentielle. Sans elle, vous devriez récupérer manuellement la valeur courante puis vous abonner — ce qui introduit des conditions de concurrence.


Snapshot + streaming : une garantie de correction​

L'un des aspects subtils mais essentiels de Watch est la possibilité d'inclure un snapshot initial.

Lorsque IncludeInitialSnapshot est activé :

  • Vous recevez la valeur courante au moment de l'abonnement
  • Vous recevez ensuite toutes les mises à jour ultérieures dans l'ordre

Cela garantit que :

  • Vous ne manquez pas l'état existant
  • Vous ne manquez pas les changements futurs
  • Vous n'avez pas besoin de logique de synchronisation supplémentaire

Autrement dit, Watch fournit une vue complète et cohérente de l'évolution de l'état.


Observer des collections : au-delà des clés uniques​

Observer une clé unique est utile, mais la plupart des systèmes réels opèrent sur des groupes de données liées.

Clustron prend cela en charge via l'observation par préfixe :

var subscription = await client.Watch.WatchPrefixAsync(
"jobs:",
new WatchOptions { IncludeInitialSnapshot = false },
ev =>
{
Console.WriteLine($"Job update: {ev.Key}");
});

Cela vous permet d'observer tous les changements au sein d'un groupe logique.

D'un point de vue architectural, c'est là que Watch devient nettement plus puissant :

  • Vous ne réagissez plus à des valeurs individuelles
  • Vous réagissez à des transitions d'état à l'échelle du système

Comprendre le modèle d'événements​

Chaque changement dans le système produit un WatchEvent.

Un événement typique contient :

  • La clé qui a changé
  • Le type de changement (Put ou Delete)
  • La nouvelle valeur (le cas échéant)
  • Une révision représentant la version du changement

Cette révision est particulièrement importante. Elle fournit un ordre et vous permet de raisonner sur la séquence des événements à travers un système distribué.

En pratique, Watch vous donne un journal des transitions d'état, livré en temps réel.


Ce que cela permet en pratique​

Une fois ce modèle adopté, plusieurs problèmes courants deviennent plus simples.

Traitement des tâches​

Au lieu d'interroger pour trouver du nouveau travail, les workers réagissent immédiatement lorsque des tâches sont créées ou mises à jour.

Cela supprime les cycles d'inactivité et réduit la latence.


Invalidation du cache​

Plutôt que de s'appuyer sur un TTL ou un rafraîchissement périodique, les caches peuvent se mettre à jour en réponse aux changements réels.

Cela améliore à la fois la fraîcheur et l'efficacité.


Coordination distribuée​

Les systèmes qui s'appuient sur un état partagé — comme l'élection de leader ou la possession d'un bail — tirent un bénéfice considérable des mises à jour en temps réel.

Les changements se propagent instantanément, permettant une coordination plus rapide et plus fiable.


Présence et supervision​

Suivre les composants actifs devient trivial :

  • Lorsqu'un nœud apparaît → vous recevez un événement
  • Lorsqu'un nœud disparaît → vous recevez un événement

Pas de polling, pas de devinettes.


Considérations opérationnelles​

Watch est conçu pour les charges de travail durables et pilotées par les événements.

Quelques points importants à considérer :

Gardez les handlers légers​

Votre handler d'événements ne devrait pas effectuer directement de travail lourd.

Au lieu de cela :

  • Traitez rapidement
  • Déléguez les tâches plus lourdes à des workers d'arrière-plan

Gérez le cycle de vie des abonnements​

Arrêtez toujours les abonnements lorsqu'ils ne sont plus nécessaires :

await subscription.StopAsync();

Cela garantit que les ressources sont libérées proprement.


Comprenez la portée​

  • Observer une clé → seule cette clé produit des événements
  • Observer un préfixe → seules les clés correspondantes produisent des événements

Les événements sont cadrés et isolés, ce qui évite les interférences non intentionnelles entre composants.


Des boucles de polling aux systèmes réactifs​

Le passage du polling à l'observation ne concerne pas seulement les performances.

Il change la structure de votre application.

Les systèmes de polling sont construits autour de :

Loop → Check → Wait → Repeat

Les systèmes réactifs sont construits autour de :

Event → Handler → Action

Il en résulte des systèmes qui sont :

  • Plus réactifs
  • Plus efficaces
  • Plus faciles à appréhender

En conclusion​

Le polling est souvent le moyen le plus simple de démarrer, et dans les petits systèmes, il fonctionne suffisamment bien.

Mais à mesure que les systèmes grandissent, le coût du polling devient de plus en plus difficile à justifier.

Clustron Watch propose une approche différente — une approche où votre système réagit au changement au lieu de le rechercher.

Stop asking your system if something changed.

Let it tell you when it does.

Pour commencer​

Essayez une observation simple :

await client.Watch.WatchPrefixAsync("orders:", null, ev =>
{
Console.WriteLine($"{ev.Key} updated");
});

À partir de là, commencez à supprimer les boucles de polling — une par une.

Vous constaterez vite que votre système devient non seulement plus rapide, mais fondamentalement plus simple.

Le cache distribué en .NET

· 4 minutes de lecture

Le cache est l'un des moyens les plus efficaces d'améliorer les performances d'une application.
La plupart des applications .NET débutent avec un simple cache en mémoire utilisant MemoryCache ou IMemoryCache. Cela fonctionne parfaitement lorsque l'application s'exécute sur un seul serveur.

Cependant, les applications modernes s'exécutent rarement sur une seule machine.

Lorsque les applications montent en charge horizontalement sur plusieurs instances, le cache en mémoire cesse de fonctionner de façon fiable. Chaque instance maintient son propre cache, ce qui signifie que l'état applicatif devient incohérent à l'échelle du cluster.

C'est ici que le cache distribué devient essentiel.


Cache distribué


Le problème des caches en mémoire

Considérez une application ASP.NET typique déployée derrière un load balancer.

          Load Balancer
|
---------------------------
| | |
Server A Server B Server C

Chaque serveur possède son propre cache en mémoire.

Si une requête met à jour des données en cache sur le serveur A, les caches des serveurs B et C restent périmés.

Cela entraîne plusieurs problèmes :

  • un état applicatif incohérent
  • des entrées de cache périmées
  • des préchauffages de cache dupliqués
  • un comportement imprévisible

Pour résoudre ce problème, le cache doit être partagé entre tous les nœuds.


Qu'est-ce qu'un cache distribué ?

Un cache distribué est un cache centralisé ou en cluster accessible par plusieurs instances applicatives.

Au lieu de stocker les données en cache dans le processus de l'application, les données sont stockées dans un store clé-valeur distribué.

           Application Nodes

App A App B App C
| | |
+---------+---------+
|
Distributed Cache

Tous les nœuds applicatifs lisent et écrivent les entrées de cache depuis le même store distribué.

Cela garantit :

  • des données en cache cohérentes
  • un état applicatif partagé
  • une meilleure évolutivité

Exemple de base d'un cache distribué en .NET

Un cache distribué s'utilise généralement via une abstraction telle que IDistributedCache.

Exemple :

public class ProductService
{
private readonly IDistributedCache _cache;

public ProductService(IDistributedCache cache)
{
_cache = cache;
}

public async Task<string?> GetProductAsync(string id)
{
var cached = await _cache.GetStringAsync(id);

if (cached != null)
return cached;

var product = await LoadProductFromDatabase(id);

await _cache.SetStringAsync(id, product);

return product;
}

private Task<string> LoadProductFromDatabase(string id)
{
return Task.FromResult($"Product {id}");
}
}

L'application vérifie d'abord le cache. Si la valeur est absente, la donnée est chargée depuis la base de données, puis stockée dans le cache.

Ce modèle est appelé cache-aside.


Solutions courantes de cache distribué

Plusieurs technologies offrent des capacités de cache distribué.

Redis​

Redis est l'un des caches distribués les plus utilisés.

Il offre :

  • un stockage clé-valeur
  • de hautes performances
  • des opérations en mémoire

Cependant, Redis nécessite souvent des composants supplémentaires lorsqu'il est utilisé pour la coordination distribuée.


Caches adossés à une base de données​

Certaines applications utilisent des bases de données relationnelles comme caches.

Bien que simple, cette approche introduit :

  • de la contention sur la base de données
  • des performances réduites
  • une évolutivité limitée

Plateformes clé-valeur distribuées​

Les systèmes distribués modernes utilisent souvent des plateformes clé-valeur distribuées qui offrent à la fois des primitives de cache et de coordination.

Ces systèmes prennent en charge :

  • le cache distribué
  • les verrous distribués
  • l'élection de leader
  • les notifications de changement
  • la coordination de cluster

Utiliser Clustron comme cache distribué

Clustron fournit une plateforme clé-valeur distribuée conçue pour les applications .NET.

Parce qu'elle s'exécute en tant que cluster distribué, toutes les instances applicatives interagissent avec le même store de données partagé.

Exemple :

using Clustron.DKV.Client;

var client = await DKVClient.InitializeRemote(
"teststore",
new[]
{
new DkvServerInfo("localhost", 7861)
});

await client.PutAsync("product:1", "Laptop");

var value = await client.GetAsync<string>("product:1");

Console.WriteLine(value);

Dans cet exemple :

  • les nœuds applicatifs se connectent au cluster
  • les données sont stockées dans le store distribué
  • tous les nœuds voient les mêmes données

Cela permet au cache de rester cohérent sur l'ensemble du cluster.


Quand utiliser le cache distribué

Le cache distribué est particulièrement utile dans les systèmes qui :

  • exécutent plusieurs instances applicatives
  • traitent de gros volumes de requêtes
  • s'appuient fortement sur des lectures en base de données
  • requièrent un état applicatif partagé

Les cas d'usage typiques incluent :

  • les applications web ASP.NET
  • les architectures microservices
  • les systèmes de traitement d'arrière-plan
  • les plateformes d'API

Résumé

Le cache en mémoire fonctionne bien pour les applications mono-serveur, mais les systèmes distribués modernes exigent une couche de cache partagée.

Le cache distribué permet à plusieurs instances applicatives de partager les mêmes données en cache, améliorant l'évolutivité et la cohérence.

Des plateformes telles que Clustron fournissent un store clé-valeur distribué qui facilite l'implémentation du cache distribué et de la coordination dans les applications .NET.

L'élection de leader dans les systèmes distribués

· 5 minutes de lecture

Lorsque les applications montent en charge sur plusieurs machines, la coordination devient l'un des problèmes les plus difficiles à résoudre.
Les systèmes distribués exécutent souvent plusieurs nœuds identiques capables tous d'effectuer le même travail. Sans coordination, ces nœuds peuvent tenter la même tâche simultanément, ce qui entraîne du travail en double, des conditions de concurrence ou un état incohérent.

L'un des moyens les plus courants de résoudre ce problème est l'élection de leader.

L'élection de leader garantit qu'un nœud devient temporairement le leader tandis que les autres nœuds restent des suiveurs. Le leader effectue les tâches de coordination pendant que les suiveurs attendent un changement de leadership.

Ce modèle est largement utilisé dans les bases de données distribuées, les ordonnanceurs de tâches, les plateformes d'orchestration de conteneurs et les systèmes de configuration.


L&#39;élection de leader dans les systèmes distribués


Un exemple simple

Imaginez un système distribué de workers comportant trois nœuds :

Worker A Worker B Worker C

Chaque worker est capable d'exécuter des tâches d'arrière-plan.

Si tous les workers exécutent la même tâche planifiée, celle-ci s'exécutera trois fois.
À la place, nous voulons qu'exactement un worker exécute la tâche.

C'est là que l'élection de leader devient utile. Un worker devient le leader, et seul le leader exécute la tâche.


Le modèle d'élection de leader le plus simple

Une approche courante utilise un store de coordination partagé.

L'idée est simple :

  1. Chaque nœud tente d'acquérir le leadership.
  2. Le nœud qui réussit devient le leader.
  3. Le leader renouvelle périodiquement son leadership.
  4. Si le leader défaille, un autre nœud prend le relais.

Cela s'implémente généralement à l'aide d'un bail.

Un bail agit comme un verrou assorti d'une durée d'expiration. Si le nœud détenant le bail disparaît, un autre nœud peut acquérir le leadership en toute sécurité.


Un exemple .NET minimal

L'exemple simplifié suivant illustre l'idée sous-jacente au leadership fondé sur les baux.

public class LeaderElection
{
private readonly ILeaseStore _store;
private readonly string _nodeId;

public LeaderElection(ILeaseStore store, string nodeId)
{
_store = store;
_nodeId = nodeId;
}

public async Task RunAsync()
{
while (true)
{
var acquired = await _store.TryAcquireLeaseAsync("cluster-leader", _nodeId, TimeSpan.FromSeconds(10));

if (acquired)
{
Console.WriteLine($"{_nodeId} is leader");

await PerformLeaderWork();

await _store.RenewLeaseAsync("cluster-leader", _nodeId);
}

await Task.Delay(2000);
}
}

private Task PerformLeaderWork()
{
Console.WriteLine("Running scheduled tasks...");
return Task.CompletedTask;
}
}

Dans cet exemple :

  • Chaque nœud tente d'acquérir le bail "cluster-leader".
  • Le nœud qui réussit devient le leader.
  • Le leader renouvelle périodiquement le bail.
  • Si le leader s'arrête brutalement, le bail expire et un autre nœud prend le relais.

Ce modèle assure un basculement automatique.


Les problèmes de l'élection de leader « faite maison »

Bien que l'exemple ci-dessus paraisse simple, les systèmes distribués réels introduisent des complications :

Les partitions réseau peuvent amener des nœuds à croire qu'ils sont toujours leaders.

La dérive d'horloge peut provoquer des problèmes d'expiration de bail.

Une détection de défaillance lente peut retarder le transfert du leadership.

Construire un mécanisme d'élection de leader robuste exige de gérer soigneusement les cas limites des systèmes distribués.

C'est pourquoi de nombreux systèmes s'appuient sur des plateformes de coordination spécialisées telles que :

  • etcd
  • ZooKeeper
  • Consul

Ces systèmes implémentent des primitives de coordination fiables.


L'élection de leader avec Clustron

Clustron fournit une plateforme de clustering pour les systèmes distribués en .NET.

Au lieu de construire vous-même l'infrastructure de coordination, les applications peuvent utiliser les primitives intégrées de la plateforme.

Exemple utilisant le concept de bail de Clustron :

using Clustron.DKV.Client;

var client = await DKVClient.InitializeRemote(
"teststore",
new[] { new DkvServerInfo("localhost", 7861) });

var lease = await client.GrantLeaseAsync(TimeSpan.FromSeconds(10));

if (lease.Acquired)
{
Console.WriteLine("This node is the leader.");

while (true)
{
await client.RefreshLeaseAsync(lease.Id);
await Task.Delay(3000);
}
}

Ici, le nœud demande un bail au cluster.

Si le bail est accordé, le nœud devient le leader.
Tant que le nœud continue de renouveler le bail, le leadership reste valide.

Si le nœud s'arrête brutalement ou cesse de renouveler le bail, un autre nœud peut acquérir le leadership automatiquement.


Quand l'élection de leader est utile

L'élection de leader apparaît dans de nombreux systèmes distribués réels :

  • Les ordonnanceurs de tâches distribués
  • Les processeurs de tâches d'arrière-plan
  • Les gestionnaires de cluster
  • La coordination de microservices
  • Les contrôleurs de configuration

Chaque fois qu'un système requiert qu'exactement un nœud coordonne l'activité, l'élection de leader devient essentielle.


Résumé

L'élection de leader est une brique fondamentale des systèmes distribués.

Elle permet à des clusters de nœuds de coordonner leur comportement tout en maintenant une haute disponibilité.

Une approche fondée sur les baux fournit une stratégie d'implémentation simple et efficace, et des plateformes distribuées telles que Clustron mettent ces primitives directement à la disposition des applications .NET.

Au lieu de construire la logique de coordination de zéro, les développeurs peuvent se concentrer sur le comportement de l'application pendant que la plateforme de clustering gère le leadership, le basculement et la coordination.

Les verrous distribués expliqués

· 5 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Un guide pratique pour les développeurs .NET

Introduction​

À mesure que les applications montent en charge sur plusieurs machines, la coordination devient l'un des aspects les plus délicats de la conception des systèmes.

Dans une application mono-machine, l'exclusion mutuelle est simple. Les développeurs peuvent s'appuyer sur des primitives du langage telles que lock en C#, Mutex ou Semaphore pour garantir qu'un seul thread accède à une ressource partagée à la fois.

Cependant, ces primitives ne fonctionnent qu'au sein d'un seul processus ou d'une seule machine.

Dès lors que les applications s'exécutent sur plusieurs services ou nœuds, les mécanismes de verrouillage traditionnels ne fonctionnent plus. Deux services s'exécutant sur des machines différentes ne peuvent pas s'appuyer sur une instruction lock locale pour coordonner l'accès à un état partagé.

C'est ici que les verrous distribués deviennent essentiels.

Les verrous distribués permettent aux services répartis sur un cluster de coordonner l'accès aux ressources partagées tout en garantissant qu'un seul participant détient le verrou à un instant donné.


Verrous locaux vs verrous distribués​

Un verrou local typique en C# ressemble à ceci :

private static readonly object _lock = new object();

public void ProcessOrder()
{
lock(_lock)
{
Console.WriteLine("Processing order safely.");
}
}

Cela fonctionne parfaitement à l'intérieur d'un seul processus.

Cependant, si votre application s'exécute sur plusieurs instances de service, chaque instance possède son propre objet _lock. Le verrou ne coordonne plus le travail à l'échelle du système.

Worker A → lock acquired Worker B → lock acquired Worker C → lock acquired

Tous les workers progressent simultanément, ce qui annule l'intérêt du verrouillage.

Les systèmes distribués nécessitent un mécanisme de coordination partagé.


Verrouillage distribué


L'idée derrière les verrous distribués​

Un verrou distribué utilise un système partagé pour représenter la possession du verrou.

Au lieu de verrouiller la mémoire, un nœud enregistre la possession du verrou dans un système de coordination distribué tel que :

  • un store clé-valeur distribué
  • un service de coordination
  • un cache distribué

Concept d'exemple :

Lock Key: order:123 Owner: worker‑1

Si un autre worker tente d'acquérir le même verrou, l'opération échoue jusqu'à ce que le verrou soit libéré ou expire.


Scénario concret : traitement distribué de factures​

Imaginez un cluster de workers traitant des factures.

Worker A Worker B Worker C

Tous les workers recherchent les factures en attente.

Sans verrouillage :

Worker A → processes invoice #101 Worker B → processes invoice #101 Worker C → processes invoice #101

La même facture pourrait être traitée plusieurs fois.

Un verrou distribué garantit qu'un seul worker traite la facture.


Les verrous distribués avec Clustron DKV​

Clustron DKV fournit des primitives intégrées pour la coordination distribuée.

Les verrous sont gérés via l'API Locks, et les opérations sur les clés verrouillées doivent inclure le bon handle de verrou.


Acquérir un verrou​

Un client acquiert d'abord un verrou pour une clé spécifique.

var locks = ((IDkv)client).Locks;

var key = "order:123";

var lockHandle = await locks.AcquireAsync(key, TimeSpan.FromSeconds(10));

if (lockHandle == null)
{
Console.WriteLine("Failed to acquire lock.");
return;
}

Console.WriteLine("Lock acquired.");

Le handle de verrou renvoyé représente la possession du verrou.


Effectuer des opérations sous le verrou​

Une fois un verrou acquis, les opérations doivent inclure le handle de verrou.

var result = await client.PutAsync(
key,
"processed",
Put.WithLock(lockHandle.Handle));

if (result.IsSuccess)
{
Console.WriteLine("Value updated safely.");
}

Seul le propriétaire du verrou peut modifier la valeur.


Lire avec le verrou​

Les lectures peuvent elles aussi respecter la possession du verrou.

var value = await client.GetAsync<string>(
key,
Get.WithLock(lockHandle.Handle));

if (value.IsSuccess)
{
Console.WriteLine($"Value: {value.Value}");
}

Opérations concurrentes sans le verrou​

Si un autre worker tente de modifier la clé sans le verrou, l'opération échoue.

var attempt = await client.PutAsync(key, "new-value");

if (!attempt.IsSuccess)
{
Console.WriteLine($"Operation rejected: {attempt.Status}");
}

Résultat :

KvStatus.Locked

Cela empêche la modification concurrente de la même ressource.


Libérer le verrou​

Une fois le travail terminé, le verrou peut être libéré.

await lockHandle.ReleaseAsync();

Console.WriteLine("Lock released.");

Après la libération du verrou, un autre worker peut l'acquérir.


Exemple de workflow de cluster​

Worker A → Acquire Lock → Success Worker B → Acquire Lock → Failed Worker C → Acquire Lock → Failed

Worker A → Update Key Worker A → Release Lock

Un seul worker effectue l'opération, garantissant la correction.


Gérer les défaillances​

Les verrous dans Clustron sont bornés dans le temps.

Lors de l'acquisition d'un verrou, une durée doit être spécifiée :

await locks.AcquireAsync(key, TimeSpan.FromSeconds(10));

Si le client détenant le verrou s'arrête brutalement ou se déconnecte, le verrou finit par expirer, ce qui permet à un autre worker de l'acquérir.

Cela évite que les verrous ne restent détenus indéfiniment.


Bonnes pratiques​

Lorsque vous implémentez des verrous distribués :

  • Gardez les sections critiques courtes
  • Évitez les verrous globaux lorsque c'est possible
  • Partitionnez les verrous par ressource
  • Concevez toujours en prévoyant les scénarios de défaillance

En conclusion​

Les verrous distribués sont l'une des primitives de coordination les plus fondamentales des systèmes distribués.

Ils permettent aux services s'exécutant sur plusieurs machines de coordonner en toute sécurité l'accès aux ressources partagées tout en préservant la correction du système.

Des plateformes comme Clustron DKV simplifient cela en fournissant des primitives de verrouillage intégrées, des opérations atomiques et de fortes garanties de cohérence.

Dans le prochain article, nous explorerons une autre primitive essentielle des systèmes distribués :

l'élection de leader.

Concevoir des systèmes distribués

· 5 minutes de lecture
Clustron Team
Ingénierie des systèmes distribués

Un guide pratique pour les développeurs .NET

Introduction​

La plupart des systèmes backend commencent leur existence sur une seule machine.

Une application ASP.NET Core traite les requêtes, une base de données stocke les données applicatives et des workers d'arrière-plan gèrent les tâches asynchrones telles que l'envoi d'e-mails ou la génération de rapports. Dans cet environnement, la concurrence reste gérable et la coordination entre les composants est relativement simple.

À mesure que le système grandit, cependant, les choses commencent à changer.

Le trafic augmente, davantage d'instances applicatives sont déployées derrière un load balancer et les services s'exécutent sur plusieurs machines. Ce qui était autrefois une application unique évolue vers un système distribué.

À ce stade, la nature du problème change radicalement.

Les défis ne se limitent plus à l'écriture d'une logique métier correcte. Les développeurs doivent désormais composer avec la coordination entre machines, les pannes réseau, les défaillances partielles et le maintien de la cohérence sur un état distribué.

C'est ici que commence l'ingénierie des systèmes distribués.


Architecture des systèmes distribués


Qu'est-ce qu'un système distribué ?​

Un système distribué est un ensemble d'ordinateurs indépendants qui apparaissent à l'utilisateur comme un système cohérent unique.

Au lieu d'exécuter toute la logique sur une seule machine, les responsabilités sont réparties sur plusieurs nœuds qui communiquent par le réseau.

En voici quelques exemples :

  • Les architectures microservices
  • Les bases de données distribuées
  • Les caches en cluster
  • Les files de messages
  • Les plateformes d'infrastructure cloud

Ces systèmes offrent évolutivité et résilience, mais introduisent de nouvelles complexités.


Pourquoi les systèmes distribués sont difficiles​

Contrairement à une application mono-machine, les systèmes distribués doivent composer avec des réalités telles que :

Les pannes réseau​

Dans les systèmes distribués, la communication passe par un réseau. Les réseaux peuvent être lents, peu fiables ou temporairement indisponibles.

Une requête peut échouer parce que le nœud distant s'est arrêté brutalement, parce que le réseau a perdu des paquets ou parce qu'un timeout s'est produit.

Le système doit gérer ces scénarios avec élégance.


Les défaillances partielles​

Dans un système distribué, les composants peuvent tomber en panne indépendamment les uns des autres.

Un nœud d'un cluster peut s'arrêter brutalement tandis que les autres restent sains. Une instance de service peut redémarrer pendant que d'autres services continuent de fonctionner.

Cela signifie que le système doit fonctionner correctement même lorsqu'une partie seulement de celui-ci est opérationnelle.


Les défis de cohérence​

Lorsque des données sont répliquées sur plusieurs nœuds, les maintenir cohérentes devient difficile.

Par exemple :

  • Deux nœuds peuvent tenter de mettre à jour la même donnée simultanément.
  • Les délais réseau peuvent amener des nœuds à être temporairement en désaccord sur l'état courant.
  • Un nœud peut s'arrêter brutalement après n'avoir exécuté qu'une partie d'une opération.

Concevoir des systèmes qui maintiennent un état cohérent malgré ces scénarios exige une coordination minutieuse.


Briques fondamentales des systèmes distribués​

La plupart des systèmes distribués reposent sur quelques primitives essentielles.

Comprendre ces primitives aide les développeurs à concevoir des architectures fiables.


Le stockage distribué​

Les données doivent souvent être stockées sur plusieurs machines.

Cela permet :

  • la mise à l'échelle horizontale
  • la tolérance aux pannes
  • la haute disponibilité

Les stores clé-valeur distribués sont couramment utilisés à cette fin.


La coordination​

Les nœuds doivent coordonner des actions telles que :

  • l'élection de leader
  • le verrouillage distribué
  • l'appartenance au cluster
  • les mises à jour de configuration

Sans mécanismes de coordination, plusieurs nœuds pourraient tenter des opérations conflictuelles.


Le consensus​

Certains systèmes exigent de fortes garanties sur l'état partagé.

Les algorithmes de consensus tels que Raft ou Paxos permettent aux nœuds de s'accorder sur des décisions même en présence de défaillances.

Ces algorithmes sont fondamentaux pour les bases de données distribuées et les services de coordination.


Le partitionnement et la réplication​

Les données sont généralement :

  • partitionnées entre les nœuds pour répartir la charge
  • répliquées pour se protéger contre les défaillances

Équilibrer performance, disponibilité et cohérence est l'un des défis de conception fondamentaux.


Modèles courants des systèmes distribués​

Plusieurs modèles reviennent régulièrement dans les systèmes réels.

En voici quelques exemples :

  • L'élection de leader pour coordonner les activités du cluster
  • Les verrous distribués pour garantir l'exclusion mutuelle
  • Les files de tâches pour le traitement asynchrone
  • Les limiteurs de débit pour contrôler le volume de requêtes
  • Les protocoles de transaction pour les mises à jour atomiques multi-nœuds

Ces modèles constituent le socle de nombreuses plateformes à grande échelle.


Le rôle des systèmes de coordination​

Pour simplifier le développement distribué, de nombreuses organisations s'appuient sur des systèmes de coordination tels que :

  • etcd
  • ZooKeeper
  • Consul

Ces systèmes fournissent des primitives telles que :

  • l'élection de leader
  • les verrous distribués
  • l'appartenance au cluster
  • le stockage de configuration

Les applications peuvent alors construire des comportements de plus haut niveau par-dessus ces primitives.


Les systèmes distribués dans l'écosystème .NET​

Bien que les outils d'infrastructure distribuée soient souvent associés à d'autres écosystèmes, les applications .NET opèrent de plus en plus dans des environnements distribués.

Les systèmes .NET modernes utilisent fréquemment :

  • Kubernetes
  • des caches distribués
  • des microservices
  • des architectures pilotées par les événements

En conséquence, la coordination et la gestion de l'état distribué deviennent de plus en plus importantes pour les développeurs .NET également.


En conclusion​

Concevoir des systèmes distribués est fondamentalement différent de concevoir des applications mono-machine traditionnelles.

Les défis de la communication réseau, des défaillances partielles et de la cohérence des données introduisent des complexités qui exigent des modèles et une infrastructure spécialisés.

Heureusement, en comprenant les briques fondamentales — stockage, coordination, consensus et réplication — les développeurs peuvent concevoir des systèmes qui montent en charge de façon fiable et restent résilients face aux défaillances.

Dans de prochains articles, nous explorerons certaines de ces primitives plus en détail, notamment l'élection de leader, les modèles de coordination distribuée et les implémentations pratiques en .NET.