Articles

Sujets Kafka interrogeables avec flux Kafka

Dans les architectures de traitement de données actuelles, Apache Kafka est souvent utilisé au stade de l’entrée. Habituellement, cette étape est utilisée pour enrichir et filtrer les données des messages entrants; cependant, il n’est pas possible d’effectuer des requêtes interactives avant une étape ultérieure du pipeline de traitement des données. En effet, bien que chaque message d’une rubrique Kafka soit conservé par défaut, aucun mécanisme n’est encore disponible pour permettre des recherches rapides d’un message spécifique dans une rubrique.

Néanmoins, pouvoir interroger de nouvelles données à ce stade précoce du pipeline éviterait les retards des pipelines de traitement traditionnels qui incluent généralement des étapes de prétraitement par lots de longue durée et donnerait aux utilisateurs finaux un accès presque instantané aux données entrantes.

Pour créer des applications de traitement de données avec Kafka, la bibliothèque de flux Kafka, qui est maintenue dans le cadre du projet Kafka, est couramment utilisée pour définir des transformations et des analyses de données. Une caractéristique importante des flux Kafka sont les magasins d’état, offrant une abstraction d’un magasin Clé-Valeur local rapide qui peut être lu et écrit lors du traitement des messages avec des flux Kafka. Ces magasins Clé-Valeur peuvent être remplis en permanence de nouveaux messages d’une rubrique Kafka en définissant un processeur de flux approprié, de sorte qu’il est désormais possible de récupérer rapidement les messages de la rubrique sous-jacente.

En complément de cette fonctionnalité de flux Kafka, nous créons une API REST unifiée qui fournit un point de terminaison d’interrogation unique pour une rubrique Kafka donnée.

En résumé, la combinaison de processeurs de flux Kafka avec des magasins d’État et un serveur HTTP peut transformer efficacement n’importe quel sujet Kafka en un magasin clé-valeur en lecture seule rapide.

Kafka Streams est construit comme une bibliothèque qui peut être intégrée dans une application Java ou Scala autonome. Il permet aux développeurs de définir des processeurs de flux qui effectuent des transformations ou des agrégations de données sur les messages Kafka, garantissant que chaque message d’entrée est traité exactement une fois. En utilisant le DSL Kafka Streams, inspiré de l’API Java Stream, les processeurs de flux et les magasins d’état peuvent être enchaînés de manière flexible.

De plus, lors du démarrage de plusieurs instances d’une application basée sur les flux Kafka, les processus forment automatiquement un cluster de traitement hautement disponible à équilibrage de charge sans dépendre de systèmes externes autres que Kafka.

Pour illustrer l’architecture d’une application Kafka Streams qui utilise des magasins d’État, imaginez le scénario suivant : En tant qu’opérateur ferroviaire, chaque fois qu’un client réserve un voyage sur notre site Web, un nouveau message composé de l’identifiant client et d’un horodatage est inséré dans une rubrique Kafka. Plus précisément, un ou plusieurs producteurs de Kafka insèrent ces messages dans un sujet comportant neuf partitions. Comme l’ID client est choisi comme clé pour chaque message, les données appartenant à un client donné seront toujours insérées dans la même partition de la rubrique. Implicitement, les producteurs de Kafka utilisent le DefaultPartitioner pour affecter des messages aux partitions.

Maintenant, supposons que nous ayons une application Kafka Streams qui lit les messages de cette rubrique et les persiste dans un magasin d’état. Étant donné que la clé de chaque message se compose uniquement de l’identifiant client, la valeur correspondante dans le magasin d’état sera toujours l’horodatage de la dernière réservation du client. Afin d’atteindre le degré maximal de traitement parallèle, nous pouvons démarrer jusqu’à neuf instances de l’application Kafka Streams. Dans ce cas, chaque instance d’application se verra attribuer exactement l’une des partitions de sujet. Pour chaque partition d’entrée, Kafka Streams crée un magasin d’état séparé, qui à son tour ne contient que les données des clients appartenant à cette partition.

L’architecture d’application résultante est illustrée dans le diagramme ci-dessous.

Le Kafka Les processeurs de flux responsables des Partitions 4 à 9 sont exclus dans cette illustration. Les flèches en pointillés indiquent que les nouveaux messages d’une partition sont également propagés à des processeurs de flux supplémentaires et à leurs magasins d’état, ce qui permet un basculement rapide en cas de défaillance du processeur principalement affecté.

Il est possible d’utiliser des magasins d’état en mémoire ou persistants dans l’application. Les opérations sur les magasins d’état en mémoire sont encore plus rapides par rapport à la variante persistante, qui utilise en interne un magasin RocksDB. D’autre part, les magasins d’état persistants peuvent être restaurés plus rapidement dans le cas où une application Kafka Streams a échoué et doit redémarrer. De plus, le volume de données par magasin n’est pas limité par la quantité de mémoire principale lors de l’utilisation de magasins d’état persistants.

Dans notre scénario, il n’est pas nécessaire d’avoir une rubrique du journal des modifications qui enregistre les opérations d’écriture dans les magasins d’état: Toutes les données nécessaires pour récupérer un magasin d’état peuvent être obtenues à partir de la rubrique d’entrée d’origine.

Ajout d’un point de terminaison REST aux processeurs de flux

Avec l’architecture présentée jusqu’à présent, nous avons neuf magasins d’état qui peuvent être utilisés pour récupérer la dernière date de réservation des clients appartenant à la partition de sujet d’entrée respective.

Maintenant, afin de rendre ces informations accessibles depuis l’extérieur des processeurs de flux Kafka, nous devons exposer un point de terminaison de service sur chacune des instances d’application du processeur de flux et répondre aux demandes entrantes du magasin d’état interne géré par les flux Kafka.

Comme exigence supplémentaire, nous ne pouvons pas nous attendre à ce que l’application requérante sache quelle instance de Kafka Streams est actuellement responsable du traitement des données d’un client donné. Par conséquent, chaque point de terminaison de service est responsable de rediriger la requête vers l’instance d’application correcte si les données client ne sont pas disponibles localement.

Nous avons choisi d’implémenter le point de terminaison de service comme une API REST, qui a l’avantage d’être accessible depuis n’importe quel client supportant HTTP, et permet d’ajouter très facilement un équilibreur de charge transparent.

L’objet KafkaStreams, disponible dans chaque application Kafka Streams, fournit un accès en lecture seule à tous les magasins d’état locaux et peut également déterminer l’instance d’application responsable d’un identifiant client donné. Lorsque vous utilisez cet objet pour créer notre service REST, l’architecture se présente comme suit :

Un client HTTP peut envoyer des requêtes de recherche à l’un des points de terminaison REST des processeurs de flux. La flèche pointillée indique comment une requête est transmise en interne entre les processeurs de flux, si elle ne peut pas recevoir de réponse à partir d’un magasin d’état local.

En résumé, notre architecture proposée utilise les rubriques Kafka pour stocker de manière fiable les données de message au repos et maintient une deuxième représentation des données dans les magasins d’état pour prendre en charge les requêtes rapides.

Assurer l’évolutivité de l’application

Pour obtenir une application évolutive, nous devons nous assurer que la charge de traitement est également équilibrée sur toutes les instances de l’application Kafka Streams. La charge sur un processeur de flux individuel dépend de la quantité de données et de requêtes qu’il doit gérer.

Plus précisément, il est important de choisir un schéma de partitionnement pour la rubrique Kafka de sorte que la charge des messages et des requêtes entrants soit bien équilibrée sur toutes les partitions et par conséquent également sur tous les processeurs de flux.

Si des magasins d’état en mémoire doivent être utilisés, le nombre de partitions dans la rubrique doit être suffisamment grand pour que chaque processeur de flux puisse conserver le volume de données d’une partition en mémoire principale. Notez qu’en cas de basculement, un processeur de flux peut même avoir besoin de contenir deux partitions en mémoire principale.

Pour tenir compte des défaillances du processeur de flux, le nombre de répliques en veille peut être configuré à l’aide du paramètre num.standby.replicas dans Kafka Streams, qui garantit que des processeurs de flux supplémentaires s’abonnent également aux messages d’une partition de processeur donnée. En cas de panne, ces processeurs peuvent rapidement prendre en charge la réponse aux requêtes, au lieu de commencer à reconstruire le magasin d’état uniquement après qu’une panne s’est déjà produite.

Enfin, le nombre souhaité de processeurs de flux doit s’adapter au matériel disponible. Pour chaque instance d’application de processeur de flux, au moins un cœur de processeur doit être réservé. Sur les systèmes multicœurs, il est possible d’augmenter le nombre de threads de flux par instance d’application, ce qui atténue les frais généraux engendrés par le démarrage d’une application Java distincte par cœur de PROCESSEUR.