A la Bacon Conference de mai dernier, le Lead Application Developper de chez Bitly, Sean O'Connor, a présenté les principales leçons apprises par les développeurs de chez Bitly lors de la création d'un système distribué permettant de gérer 6 milliards de clics par mois.
Qu'est-ce qu'un système distribué ?
Les trois principales caractéristiques d'un système distribué, selon Sean O'Connor, peuvent être facilement trouvées sur Wikipedia :
- Une concurrence réelle entre les différents noeuds du système, avec le coût et la complexité de coordination entre les noeuds qui vont avec.
- L’absence d'un verrou centralisé qui rend impossible un classement chronologique d'événements qui sont survenus entre différents noeuds.
- L'indépendance des noeuds face aux pannes, qui désigne la capacité du système à perdre un noeud sans que les autres noeuds ne soient impactés.
Créer un système distribué devrait donc consister à mettre en oeuvre ces différentes caractéristiques. Sean O'Connor estime que l'approche qui consiste à masquer la nature distribuée du système derrière une abstraction qui présente le système comme s'il n'était pas distribué est totalement dépassée. Une telle approche augmente, selon lui, le risque d'échouer. Parce qu'il arrivera un moment où ma nature distribuée du système transparaîtra au travers de l'abstraction. Au lieu de cela, les abstractions doivent présenter la nature distribuée du système et permettre ainsi de gérer toutes les spécificités liées à cette nature.
Les services comme composants de base
L'architecture de Bitly s'articule autour du concept de service. Un service est une abstraction qui est spécialisée dans une fonctionnalité donnée et qui est défini à l'aide d'une API. Les services sont aux systèmes distribués ce que les fonctions sont au code, selon Sean O'Connor, ils permettent de raisonner a un niveau plus haut sans se soucier des détails d'implémentation. Cette idée est en décalage avec la manière traditionnelle de voir les choses.
Parmi les avantages de modéliser un système à l'aide de services, Sean O'Connor, cite les suivants :
- Moins de lignes de code, ce qui rend les services plus faciles à comprendre.
- L'indépendance face à la panne, dans la mesure où, si un service tombe en panne, le système a uniquement perdu la fonctionnalité associée, tout en continuant de fonctionner dans son ensemble.
- Une plus grande facilité à identifier quelle partie du système présente des défaillances.
Les messages asynchrones
Un point clé qui permet de garantir qu'un système peut croître est d'utiliser des messages asynchrones, de telle sorte qu'un noeud n'a pas besoin d'attendre la réponse d'un autre noeud, le tout en utilisant des files de messages entre les noeuds pour améliorer l'isolation des noeuds.
Le principal avantage de ce type d'architecture est que si un noeud connaît des défaillances qui l'empêchent temporairement de gérer les messages entrants, ceux-ci sont conservés dans la file afin d'être traités aussi rapidement que possible. De plus, la défaillance d'un noeud n'affecte pas directement les autres noeuds.
Le messaging asynchrone renferme son lot de complexités. Dans de nombreux cas, il est plus naturel de gérer certains types d'opérations de manière synchrone. Afin d’illustrer ceci, Sean O'Connor, a expliqué que la réduction d'URL était implémentée chez Bitly de manière totalement synchrone, due à la nécessité que cette opération soit aussi rapide que possible et cohérente, ce qui implique qu'une même URL raccourcie ne puisse pas être renvoyée à des utilisateurs différents. D'un autre côté, les statistiques présentent d'autres contraintes qui en font un bon candidat pour un traitement totalement asynchrone. Ainsi, lorsque Bitly veut collecter et traiter des métriques liées aux actions utilisateurs autour d'un lien, le système se contente d'ajouter un message à une file en aval, où cette requête sera traitée sans vraiment se soucier du temps nécessaire pour collecter les métriques. Essayer de modéliser une opération intrinsèquement synchrone de manière asynchrone peut s'avérer extrêmement complexe, selon Sean O'Connor, il est donc important de comprendre la nature de l'opération à l'avance.
Sean O'Connor a fait une dernière remarque sur l'interaction entre les noeuds et ce qu'apporte la modélisation de messages sous forme d'événements, par opposition à une modélisation sous forme de commandes qui circulent d'un noeud à un autre. Un événement est une description de quelque chose qui a eu lieu quelque part sans que le noeud qui envoie ait besoin de savoir quoi que ce soit sur le noeud qui reçoit. D'un autre côté, si un message est une commande, alors le noeud qui envoie doit connaître la capacité du noeud qui reçoit à exécuter cette commande. Pour cette raison, concevoir les messages comme des événements contribue considérablement à l'isolation entre les noeuds et se prête naturellement à l'existence de plusieurs consommateurs et à l'ajout et la suppression dynamique de consommateurs, sans que le noeud producteur n'ait besoin de savoir quoi que ce soit.
Penser les messages comme des événements mène à un autre constat : il est préférable, du point de vue du producteur, d'annoter les messages plutôt que de les filtrer. A titre d'exemple, Sean O'Connor évoque les différences de gestion entre les liens publics et privés. Les liens privés pourraient être filtrés par le producteur afin qu'ils n'atteignent pas les parties du système qui ne les concernent pas en aval. Ceci implique que le producteur fasse des hypothèses sur les responsabilités des composants en aval. Au lieu de cela, ce que fait Bitly c'est annoter les liens privés et laisser le message continuer sa route, en faisant confiance aux noeuds suivants qui traiteront le problème de manière appropriée.
Accorder les services entre eux
Bitly s'assure que les services soient bien accordés en faisant en sorte de :
- Utiliser un système de back pressure et permettre ainsi aux noeuds qui exécutent les requêtes de savoir que le service demandé est occupé ou surchargé, afin qu'ils puissent diminuer la bande passante pour les futures requêtes. Ceci peut aider à maintenir le système en état de marche et à prévenir les erreurs en cascade. Pour illustrer ceci, Sean O'Connor a évoqué un service de préchargement du cache : si pendant le préchargement du cache, des requêtes continuent à entrer, il existe un risque de corruption des données.
- Répartir les requêtes en fonction de l'état du service. Bitly utilise sa propre librairie hostpool pour répartir la charge et conserver un état des services défaillants pour prioriser les services en meilleur état de fonctionnement.
Supervision
Sean O'Connor amène le sujet de la supervision en citant Leslie Lamport qui a dit "un système distribué est un système dans lequel la défaillance d'une machine dont vous ne connaissiez même pas l’existence peut rendre votre machine inutilisable". Avec plus de 400 serveurs chez Bitly, la supervision est évidemment une tâche fondamentale, puisqu'il s'agit de la seule manière de savoir que le système ne fonctionne pas comme prévu.
Sean O'Connor donne quelques lignes directrices à propos de la supervision :
- Utilisez Nagios pour connaître le statut des serveurs
- Lancez des validations d'intégrité pour vous assurer que le service retourne des données correctes
- Centralisez les logs, dans la mesure où avoir tous les logs à un unique endroit peut aider à faire un diagnostic
- Faites-en sorte que l'information importante parvienne aux bonnes personnes au bon moment. Bitly utilise sa propre plate-forme de messaging nsq associée avec des interfaces web afin d’observer facilement ce qui se passe durant les déploiements.
Bitly est un service de réduction d'URL qui est destiné aux réseaux sociaux, aux SMS et aux emails. En plus de la réduction d'URLs, Bitly recueille des statistiques sur les liens référencés, ce qui constitue l'élément central de leur modèle économique.