
Salut tout le monde. Dans cet article, je vais vous expliquer pourquoi chez Avito nous avons choisi Kafka il y a neuf mois et de quoi il s'agit. Je vais partager l'un des cas d'utilisation : un courtier de messages. Et enfin, parlons des avantages que nous avons retirĂ©s de lâutilisation de lâapproche Kafka as a Service.
problĂšme

Tout dâabord, un peu de contexte. Il y a quelque temps, nous avons commencĂ© Ă nous Ă©loigner de l'architecture monolithique, et Avito propose dĂ©jĂ plusieurs centaines de services diffĂ©rents. Ils disposent de leurs propres rĂ©fĂ©rentiels, de leur propre pile technologique et sont responsables de leur part de la logique mĂ©tier.
Lâun des problĂšmes dâun grand nombre de services est la communication. Le service A souhaite souvent connaĂźtre les informations dont dispose le service B. Dans ce cas, le service A accĂšde au service B via une API synchrone. Le service B veut savoir ce qui se passe avec les services D et D, et ils s'intĂ©ressent Ă leur tour aux services A et B. Lorsqu'il existe de nombreux services « curieux », les connexions entre eux se transforment en un enchevĂȘtrement enchevĂȘtrĂ©.
ParallÚlement, le service A peut devenir indisponible à tout moment. Et que doivent faire le service B et tous les autres services qui y sont connectés ? Et si une chaßne d'appels synchrones séquentiels est nécessaire pour mener à bien une opération commerciale, la probabilité d'échec de l'ensemble de l'opération devient encore plus élevée (et plus la chaßne est longue, plus elle est élevée).
Choix technologique

D'accord, les problĂšmes sont clairs. Ils peuvent ĂȘtre Ă©liminĂ©s en crĂ©ant un systĂšme de messagerie centralisĂ© entre les services. DĂ©sormais, chacun des services n'a besoin que de connaĂźtre ce systĂšme de messagerie. De plus, le systĂšme lui-mĂȘme doit ĂȘtre tolĂ©rant aux pannes et Ă©volutif horizontalement, et Ă©galement, en cas d'accident, accumuler un tampon d'accĂšs pour un traitement ultĂ©rieur.
SĂ©lectionnons maintenant la technologie sur laquelle la livraison des messages sera implĂ©mentĂ©e. Pour ce faire, comprenons dâabord ce que nous en attendons :
- les messages entre services ne doivent pas ĂȘtre perdus ;
- les messages peuvent ĂȘtre dupliquĂ©s ;
- les messages peuvent ĂȘtre stockĂ©s et lus sur une profondeur de plusieurs jours (tampon persistant) ;
- les services peuvent s'abonner aux données qui les intéressent ;
- plusieurs services peuvent lire les mĂȘmes donnĂ©es ;
- les messages peuvent contenir une charge utile détaillée et volumineuse (transfert d'état transporté par événement) ;
- Parfois, vous devez garantir lâordre des messages.
Il Ă©tait Ă©galement extrĂȘmement important pour nous de choisir le systĂšme le plus Ă©volutif et le plus fiable avec un dĂ©bit Ă©levĂ© (au moins 100 XNUMX messages de plusieurs kilo-octets par seconde).
à ce stade, nous avons dit au revoir à RabbitMQ (difficile à maintenir stable à des rps élevés), PGQ de SkyTools (pas assez rapide et n'évolue pas bien) et NSQ (pas persistant). Nous utilisons toutes ces technologies dans notre entreprise, mais elles n'étaient pas adaptées au problÚme à résoudre.
Ensuite, nous avons commencé à examiner des technologies qui étaient nouvelles pour nous : Apache Kafka, Apache Pulsar et NATS Streaming.
Pulsar a Ă©tĂ© le premier Ă ĂȘtre abandonnĂ©. Nous avons dĂ©cidĂ© que Kafka et Pulsar sont des solutions assez similaires. Et malgrĂ© le fait que Pulsar a Ă©tĂ© testĂ© par de grandes entreprises, qu'il est plus rĂ©cent et offre une latence plus faible (en thĂ©orie), nous avons dĂ©cidĂ© de laisser Kafka parmi ces deux-lĂ comme standard de facto pour de telles tĂąches. Nous reviendrons probablement Ă Apache Pulsar dans le futur.
Et maintenant, il reste deux candidats : NATS Streaming et Apache Kafka. Nous avons Ă©tudiĂ© les deux solutions en dĂ©tail et toutes deux Ă©taient adaptĂ©es Ă la tĂąche. Mais au final, nous avions peur de la relative jeunesse de NATS Streaming (et du fait que l'un des principaux dĂ©veloppeurs, Tyler Treat, ait dĂ©cidĂ© de quitter le projet et de crĂ©er le sien - Liftbridge). Dans le mĂȘme temps, le mode Clustering de NATS Streaming n'offrait pas la possibilitĂ© d'une forte mise Ă l'Ă©chelle horizontale (ce n'est probablement plus un problĂšme aprĂšs l'ajout du mode de partitionnement en 2017).
Cependant, NATS Streaming est une technologie intĂ©ressante Ă©crite en Go et prise en charge par la Cloud Native Computing Foundation. Contrairement Ă Apache Kafka, il n'a pas besoin de Zookeeper pour fonctionner (peut-ĂȘtre ), puisqu'il implĂ©mente RAFT en interne. Dans le mĂȘme temps, NATS Streaming est plus facile Ă administrer. Nous nâexcluons pas que nous revenions Ă cette technologie Ă lâavenir.
Et pourtant, aujourdâhui notre gagnant est Apache Kafka. Lors de nos tests, il s'est avĂ©rĂ© assez rapide (plus d'un million de messages par seconde en lecture et en Ă©criture avec un volume de messages de 1 kilo-octet), assez fiable, hautement Ă©volutif et Ă©prouvĂ© par l'expĂ©rience de la production de grandes entreprises. De plus, Kafka prend en charge au moins plusieurs grandes entreprises commerciales (nous utilisons par exemple la version Confluent), et Kafka dispose Ă©galement d'un Ă©cosystĂšme dĂ©veloppĂ©.
Kafka en résumé
Avant de commencer, je voudrais immĂ©diatement recommander un excellent livre - "Kafka : le guide dĂ©finitif" (il existe aussi une traduction russe, mais les termes sont un peu ahurissants). Il contient les informations dont vous avez besoin pour acquĂ©rir une comprĂ©hension de base de Kafka et mĂȘme un peu plus. La documentation d'Apache et le blog de Confluent sont Ă©galement bien rĂ©digĂ©s et faciles Ă lire.
Voyons donc Ă vol dâoiseau le fonctionnement de Kafka. La topologie de base de Kafka comprend le producteur, le consommateur, le courtier et le gardien de zoo.
Broker

Le courtier est responsable du stockage de vos données. Toutes les données sont stockées sous forme binaire et le courtier sait peu de choses sur ce qu'elles sont et quelle est leur structure.
Chaque type d'Ă©vĂ©nement logique se trouve gĂ©nĂ©ralement dans sa propre rubrique distincte. Par exemple, l'Ă©vĂ©nement de crĂ©ation d'une annonce peut tomber dans le sujet item.created, et l'Ă©vĂ©nement de sa modification peut tomber dans item.changed. Les sujets peuvent ĂȘtre considĂ©rĂ©s comme des classificateurs d'Ă©vĂ©nements. Au niveau du sujet, vous pouvez dĂ©finir des paramĂštres de configuration tels que :
- la quantité de données stockées et/ou leur ùge (retention.bytes, retention.ms) ;
- facteur de redondance des données (facteur de réplication) ;
- taille maximale d'un message (max.message.bytes) ;
- le nombre minimum de rĂ©pliques cohĂ©rentes auxquelles les donnĂ©es peuvent ĂȘtre Ă©crites dans une rubrique (min.insync.replicas) ;
- la possibilité d'effectuer un basculement sur une réplique en retard non synchrone avec une perte potentielle de données (unclean.leader.election.enable) ;
- et beaucoup plus ().
Ă son tour, chaque sujet est divisĂ© en une ou plusieurs partitions. C'est dans les partis que les Ă©vĂ©nements finissent par tomber. S'il y a plus d'un courtier dans le cluster, les partitions seront rĂ©parties uniformĂ©ment entre tous les courtiers (dans la mesure du possible), ce qui permettra Ă la charge d'Ă©criture et de lecture dans une rubrique d'ĂȘtre rĂ©partie sur plusieurs courtiers Ă la fois.
Sur le disque, les données de chaque partition sont stockées sous forme de fichiers de segments, par défaut égaux à un gigaoctet (contrÎlés via log.segment.bytes). Une caractéristique importante est que les données sont supprimées des partitions (lorsque la rétention est déclenchée) par segments (vous ne pouvez pas supprimer un événement d'une partition, vous ne pouvez supprimer qu'un segment entier et uniquement celui inactif).
Zookeeper
Zookeeper agit en tant que magasin de mĂ©tadonnĂ©es et coordinateur. C'est lui qui est capable de dire si les courtiers sont en vie (vous pouvez regarder cela Ă travers les yeux du gardien de zoo en utilisant zookeeper-shell avec la commande ls /brokers/ids), quel courtier est le contrĂŽleur (get /controller), si les partitions sont synchronisĂ©es avec leurs rĂ©pliques (get /brokers/topics/topic_name/partitions/partition_number/state). Aussi, c'est chez zookeeper que le producteur et le consommateur s'adresseront en premier pour savoir sur quel courtier quels sujets et quelles partitions sont stockĂ©s. Dans les cas oĂč un facteur de rĂ©plication supĂ©rieur Ă 1 est spĂ©cifiĂ© pour un sujet, zookeeper indiquera quelles partitions sont les leaders (elles seront Ă©crites et lues). En cas de panne du courtier, les informations sur les nouvelles partitions leader seront enregistrĂ©es dans zookeeper (Ă partir de la version 1.1.0 de maniĂšre asynchrone, ).
Dans les anciennes versions de Kafka, zookeeper était également responsable du stockage des compensations, mais elles sont désormais stockées dans une rubrique spéciale. __consumer_offsets sur le courtier (bien que vous puissiez toujours utiliser zookeeper à ces fins).
Le moyen le plus simple de transformer vos donnĂ©es en citrouille est de perdre les informations du gardien de zoo. Dans un tel scĂ©nario, il sera trĂšs difficile de comprendre quoi lire et d'oĂč.
Producteur
Producer est le plus souvent un service qui Ă©crit directement des donnĂ©es sur Apache Kafka. Le producteur sĂ©lectionne un sujet dans lequel stocker ses messages de sujet et commence Ă y Ă©crire des informations. Par exemple, le producteur pourrait ĂȘtre un service publicitaire. Dans ce cas, il enverra des Ă©vĂ©nements tels que « annonce créée », « annonce mise Ă jour », « annonce supprimĂ©e », etc. vers des sujets thĂ©matiques. Chaque Ă©vĂ©nement est une paire clĂ©-valeur.
Par défaut, tous les événements sont distribués entre les partitions de sujet à l'aide d'un tourniquet si la clé n'est pas spécifiée (ordre perdu) et via MurmurHash (clé) si la clé est présente (ordre dans une partition).
Il convient de noter d'emblĂ©e que Kafka garantit l'ordre des Ă©vĂ©nements uniquement au sein d'un seul lot. Mais en rĂ©alitĂ©, ce nâest souvent pas un problĂšme. Par exemple, vous pouvez ĂȘtre sĂ»r d'ajouter toutes les modifications apportĂ©es Ă la mĂȘme dĂ©claration dans une seule partition (prĂ©servant ainsi l'ordre de ces modifications dans la dĂ©claration). Vous pouvez Ă©galement envoyer un numĂ©ro de sĂ©quence dans l'un des champs d'Ă©vĂ©nement.
Consommateur

Le consommateur est responsable de la rĂ©cupĂ©ration des donnĂ©es depuis Apache Kafka. Si lâon revient Ă lâexemple ci-dessus, le consommateur pourrait ĂȘtre un service de modĂ©ration. Ce service sera abonnĂ© au sujet du service publicitaire et lorsqu'une nouvelle annonce apparaĂźtra, il la recevra et l'analysera pour vĂ©rifier sa conformitĂ© Ă certaines politiques spĂ©cifiĂ©es.
Apache Kafka se souvient des Ă©vĂ©nements rĂ©cents reçus par le consommateur (une rubrique de service est utilisĂ©e Ă cet effet). __consumer__offsets), garantissant ainsi que si la lecture rĂ©ussit, le consommateur ne recevra pas deux fois le mĂȘme message. Cependant, si vous utilisez l'option activate.auto.commit = true et dĂ©lĂ©guez entiĂšrement le travail de suivi de la position du consommateur dans le sujet Ă Kafka, vous pouvez . Dans le code de production, le plus souvent la position du consommateur est contrĂŽlĂ©e manuellement (le dĂ©veloppeur contrĂŽle le moment oĂč la validation de l'Ă©vĂ©nement de lecture doit avoir lieu).
Dans les cas oĂč un seul consommateur ne suffit pas (par exemple, le flux de nouveaux Ă©vĂ©nements est trĂšs important), vous pouvez ajouter plusieurs consommateurs supplĂ©mentaires en les reliant entre eux dans un groupe de consommateurs. Un groupe de consommateurs est logiquement exactement identique Ă un consommateur, mais avec des donnĂ©es rĂ©parties entre les membres du groupe. Cela permet Ă chaque participant de prendre sa part de messages, augmentant ainsi la vitesse de lecture.
Résultats de test

Je nâĂ©crirai pas ici beaucoup de texte explicatif, je partagerai juste les rĂ©sultats obtenus. Les tests ont Ă©tĂ© effectuĂ©s sur 3 machines physiques (12 CPU, 384 Go de RAM, 15 10 disques SAS, XNUMX Go/s Net), les courtiers et le zookeeper ont Ă©tĂ© dĂ©ployĂ©s dans lxc.
Test de performance
Lors des tests, les résultats suivants ont été obtenus.
- La vitesse d'enregistrement simultané de messages de 1 Ko par 9 producteurs est de 1300000 XNUMX XNUMX événements par seconde.
- La vitesse de lecture simultanée de messages de 1 Ko par 9 consommateurs est de 1500000 XNUMX XNUMX événements par seconde.
Tests de tolérance aux pannes
Lors des tests, les résultats suivants ont été obtenus (3 courtiers, 3 gardiens de zoo).
- L'arrĂȘt anormal de l'un des courtiers n'entraĂźne pas l'arrĂȘt ou l'indisponibilitĂ© du cluster. Le travail se poursuit normalement, mais les courtiers restants ont une lourde charge de travail.
- La terminaison anormale de deux courtiers dans le cas d'un cluster de trois courtiers et min.isr = 2 conduit Ă ce que le cluster soit indisponible en Ă©criture, mais accessible en lecture. Si min.isr = 1, le cluster continue d'ĂȘtre disponible en lecture et en Ă©criture. Cependant, ce mode contredit lâexigence dâune sĂ©curitĂ© Ă©levĂ©e des donnĂ©es.
- Un arrĂȘt anormal de l'un des serveurs Zookeeper n'entraĂźne pas l'arrĂȘt ou l'indisponibilitĂ© du cluster. Les travaux se poursuivent normalement.
- Un arrĂȘt anormal de deux serveurs Zookeeper entraĂźne l'indisponibilitĂ© du cluster jusqu'Ă ce qu'au moins un des serveurs Zookeeper soit restaurĂ©. Cette affirmation est vraie pour un cluster Zookeeper de 3 serveurs. De ce fait, aprĂšs recherches, il a Ă©tĂ© dĂ©cidĂ© d'augmenter le cluster Zookeeper Ă 5 serveurs pour augmenter la tolĂ©rance aux pannes.
Kafka en tant que service

Nous sommes convaincus que Kafka est une excellente technologie qui nous permet de résoudre la tùche qui nous est assignée (implémenter un courtier de messages). Cependant, nous avons décidé d'interdire aux services d'accéder directement à Kafka et de l'ajouter à un service de bus de données. Pourquoi avons-nous fait cela ? En fait, il y a plusieurs raisons.
Data-bus a pris en charge toutes les tùches liées à l'intégration avec Kafka (implémentation et configuration des consommateurs et des producteurs, surveillance, alertes, journalisation, mise à l'échelle, etc.). Ainsi, l'intégration avec le courtier de messages est aussi simple que possible.
Le bus de données nous a permis de nous éloigner d'un langage ou d'une bibliothÚque spécifique pour travailler avec Kafka.
Le bus de donnĂ©es a permis Ă d'autres services d'abstraire la couche de stockage. Peut-ĂȘtre qu'Ă un moment donnĂ©, nous remplacerons Kafka par Pulsar, et personne ne remarquera rien (tous les services ne connaissent que l'API du bus de donnĂ©es).
Data-bus a pris en charge la validation des schémas d'événements.
L'authentification est mise en Ćuvre Ă l'aide d'un bus de donnĂ©es.
Sous le couvert du bus de donnĂ©es, nous pouvons mettre Ă jour tranquillement les versions de Kafka sans temps d'arrĂȘt, gĂ©rer de maniĂšre centralisĂ©e les configurations des producteurs, des consommateurs, des courtiers, etc.
Data-bus nous a permis d'ajouter les fonctionnalités dont nous avions besoin et qui ne sont pas dans Kafka (comme l'audit de sujets, la surveillance des anomalies dans le cluster, la création de DLQ, etc.).
Data-bus vous permet de mettre en Ćuvre le basculement de maniĂšre centralisĂ©e pour tous les services.
Pour le moment, pour commencer Ă envoyer des Ă©vĂ©nements au courtier de messages, il vous suffit de connecter une petite bibliothĂšque Ă votre code de service. C'est tout. Vous avez la possibilitĂ© d'Ă©crire, de lire et de mettre Ă l'Ă©chelle avec une seule ligne de code. LâintĂ©gralitĂ© de lâimplĂ©mentation vous est cachĂ©e, seules quelques poignĂ©es de taille de lot ressortent. Sous le capot, le service de bus de donnĂ©es augmente le nombre requis d'instances productrices et consommatrices dans Kubernetes et leur fournit la configuration nĂ©cessaire, mais tout cela est transparent pour votre service.
Bien entendu, il nâexiste pas de solution miracle et cette approche a ses limites.
- Le bus de donnĂ©es doit ĂȘtre pris en charge en interne, par opposition aux bibliothĂšques tierces.
- Le bus de données augmente le nombre d'interactions entre les services et le courtier de messages, ce qui entraßne des performances inférieures à celles de Kafka seul.
- Tout ne peut pas ĂȘtre cachĂ© aux services aussi facilement ; nous ne voulons pas dupliquer les fonctionnalitĂ©s de KSQL ou de Kafka Streams dans le bus de donnĂ©es, nous devons donc parfois autoriser les services Ă y accĂ©der directement.
Dans notre cas, les avantages l'emportaient sur les inconvĂ©nients et la dĂ©cision de couvrir le courtier de messages avec un service distinct Ă©tait justifiĂ©e. Au cours de lâannĂ©e dâexploitation, nous nâavons eu aucun accident ni problĂšme grave.
PS Merci à ma petite amie, Ekaterina Obalyaeva, pour les superbes photos de cet article. Si vous les avez aimés, il y a d'autres illustrations à venir.
Source: habr.com
