J'étudie Apache kafka depuis un mois maintenant. Cependant, je suis bloqué à un moment donné. Mon cas d'utilisation est que j'ai deux processus de consommation ou plus exécutés sur des machines différentes. J'ai exécuté un quelques tests dans lesquels j'ai publié 10 000 messages sur le serveur kafka. Ensuite, pendant le traitement de ces messages, j'ai tué l'un des processus de consommation et l'ai redémarré. Les consommateurs écrivaient les messages traités dans un fichier. Donc, après la fin de la consommation , le fichier affichait plus de 10 000 messages. Certains messages ont donc été dupliqués.
Dans le processus consommateur, j'ai désactivé la validation automatique. Les consommateurs effectuent manuellement des compensations par lot. Ainsi, par exemple, si 100 messages sont écrits dans un fichier, le consommateur valide les décalages. Lorsque le processus d'un seul consommateur est en cours d'exécution et qu'il se bloque et récupère la duplication est évitée de cette manière. Mais lorsque plusieurs consommateurs sont en cours d'exécution et que l'un d'eux se bloque et récupère, il écrit des messages en double dans un fichier.
Existe-t-il une stratégie efficace pour éviter ces messages en double?
La réponse courte est non.
Ce que vous recherchez, c'est un traitement unique. Bien que cela puisse souvent sembler faisable, il ne faut jamais s'y fier car il y a toujours des mises en garde.
Même pour tenter d'empêcher les doublons, vous devez utiliser le simple consommateur. Le fonctionnement de cette approche est pour chaque consommateur, lorsqu'un message est consommé à partir d'une partition, écrivez la partition et le décalage du message consommé sur le disque. Lorsque le consommateur redémarre après un échec, lisez le dernier décalage consommé pour chaque partition à partir du disque.
Mais même avec ce modèle, le consommateur ne peut garantir qu'il ne retraitera pas un message après un échec. Que se passe-t-il si le consommateur consomme un message et échoue avant que le décalage ne soit vidé sur le disque? Si vous écrivez sur le disque avant de traiter le message, que se passe-t-il si vous écrivez l'offset puis échouez avant de réellement traiter le message? Ce même problème existerait même si vous deviez valider des décalages vers ZooKeeper après chaque message.
Il existe cependant des cas où le traitement en une seule fois est plus réalisable, mais uniquement pour certains cas d'utilisation. Cela nécessite simplement que votre décalage soit stocké au même emplacement que la sortie de l'application de l'unité. Par exemple, si vous écrivez un consommateur qui compte les messages, en stockant le dernier décalage compté avec chaque comptage, vous pouvez garantir que le décalage est stocké en même temps que l'état du consommateur. Bien sûr, afin de garantir un traitement en une seule fois, cela nécessiterait que vous consommiez exactement un message et que vous mettiez à jour l'état une seule fois pour chaque message, ce qui est totalement impossible pour la plupart des applications grand public Kafka. Par sa nature Kafka consomme des messages par lots pour des raisons de performances.
Habituellement, votre temps sera mieux utilisé et votre application sera beaucoup plus fiable si vous la concevez simplement pour qu'elle soit idempotente.
C'est ce que Kafka FAQ a à dire au sujet d'une seule fois:
Comment puis-je recevoir des messages en une seule fois de Kafka?
Exactement une fois que la sémantique comporte deux parties: éviter la duplication lors de la production de données et éviter les doublons lors de la consommation de données.
Il existe deux approches pour obtenir une sémantique exacte lors de la production de données:
- Utilisez un seul écrivain par partition et chaque fois que vous obtenez une erreur réseau, vérifiez le dernier message de cette partition pour voir si votre dernière écriture a réussi
- Inclure une clé primaire (UUID ou quelque chose) dans le message et dédupliquer sur le consommateur.
Si vous effectuez l'une de ces opérations, le journal qui héberge Kafka sera exempt de doublons. Cependant, la lecture sans doublons dépend également de la coopération du consommateur. Si le consommateur vérifie périodiquement le point de contrôle sa position puis s'il échoue et redémarre, il redémarrera à partir de la position du point de contrôle. Ainsi, si la sortie de données et le point de contrôle ne sont pas écrits de manière atomique, il sera également possible d'obtenir des doublons ici. Ce problème est particulier à votre système de stockage. Par exemple , si vous utilisez une base de données, vous pouvez les valider ensemble dans une transaction. Le chargeur HDFS Camus que LinkedIn a écrit fait quelque chose comme ça pour les chargements Hadoop. L'autre alternative qui ne nécessite pas de transaction est de stocker le décalage avec les données chargées et dédupliquez en utilisant la combinaison sujet/partition/décalage.
Je pense qu'il y a deux améliorations qui rendraient cela beaucoup plus facile:
- L'idempotence du producteur pourrait se faire automatiquement et beaucoup moins cher en intégrant éventuellement la prise en charge de cela sur le serveur.
- Le consommateur de haut niveau existant n'expose pas beaucoup du contrôle plus fin des décalages (par exemple pour réinitialiser votre position). Nous y travaillerons bientôt
Je suis d'accord avec la déduplication de RaGe du côté des consommateurs. Et nous utilisons Redis pour dédupliquer Kafka message.
Supposons que la classe Message possède un membre appelé "uniqId", qui est rempli par le côté producteur et qui est garanti unique. Nous utilisons une chaîne aléatoire de 12 longueurs. (regexp est '^[A-Za-z0-9]{12}$'
)
Le côté consommateur utilise SETNX de Redis pour dédupliquer et EXPIRE pour purger automatiquement les clés expirées. Exemple de code:
Message msg = ... // eg. ConsumerIterator.next().message().fromJson();
Jedis jedis = ... // eg. JedisPool.getResource();
String key = "SPOUT:" + msg.uniqId; // prefix name at will
String val = Long.toString(System.currentTimeMillis());
long rsps = jedis.setnx(key, val);
if (rsps <= 0) {
log.warn("kafka dup: {}", msg.toJson()); // and other logic
} else {
jedis.expire(key, 7200); // 2 hours is ok for production environment;
}
Le code ci-dessus a détecté plusieurs fois les messages en double lorsque Kafka (version 0.8.x) avait des situations. Avec notre journal d'audit de l'équilibre des entrées/sorties, aucun message n'a été perdu ou dup s'est produit.
Quoi que l'on fasse du côté des producteurs, toujours la meilleure façon, selon nous, de livrer exactement une fois de kafka est de le gérer du côté des consommateurs: