Expérience dans le développement du service Refund Tool avec une API asynchrone sur Kafka

Qu'est-ce qui peut faire qu'une grande entreprise comme Lamoda avec un processus rationalisé et des dizaines de services interconnectés change considérablement l'approche? La motivation peut être complètement différente: du législatif au désir inhérent à tous les programmeurs d'expérimenter.

Mais cela ne signifie pas du tout que l'on ne peut pas compter sur des avantages supplémentaires. Que peut-on gagner exactement si vous implémentez l'API événementielle sur Kafka, dira Sergey Zaika ( fewald ). Il y aura certainement aussi des bosses farcies et des découvertes intéressantes - une expérience ne peut pas s'en passer.



Avertissement: Cet article est basé sur les matériaux du mitap que Sergey a organisé en novembre 2018 sur HighLoad ++. L'expérience en direct de Lamoda avec Kafka a attiré des auditeurs pas moins que d'autres reportages d'horaires. Il nous semble que c'est un excellent exemple du fait qu'il est toujours possible et nécessaire de trouver des personnes partageant les mêmes idées, et les organisateurs de HighLoad ++ continueront à essayer de créer une atmosphère propice à cela.

À propos du processus


Lamoda est une grande plateforme de commerce électronique qui a son propre centre de contact, un service de livraison (et de nombreux affiliés), un studio photo, un immense entrepôt et tout fonctionne sur son logiciel. Il existe des dizaines de modes de paiement, des partenaires B2B qui peuvent utiliser tout ou partie de ces services et souhaitent connaître les dernières informations sur leurs produits. De plus, Lamoda opère dans trois pays en plus de la Fédération de Russie et tout y est un peu différent. Au total, il existe probablement plus d'une centaine de façons de configurer une nouvelle commande, qui doit être traitée à sa manière. Tout cela fonctionne avec l'aide de dizaines de services qui communiquent parfois de manière non évidente. Il existe également un système central dont la principale responsabilité est le statut des commandes. Nous l'appelons BOB, je travaille avec elle.

Outil de remboursement avec API pilotée par les événements


Le mot événementiel est assez galvaudé, un peu plus loin nous définirons plus en détail ce que l'on entend par là. Je vais commencer par le contexte dans lequel nous avons décidé d'essayer l'approche API événementielle de Kafka.



Dans n'importe quel magasin, en plus des commandes pour lesquelles les clients paient, il y a des moments où le magasin est tenu de retourner de l'argent, car le produit ne convenait pas au client. Ce processus relativement court: nous clarifions les informations, s'il y a un tel besoin, et transférons l'argent.

Mais le retour a été compliqué en raison de modifications de la législation, et nous avons dû mettre en place un microservice distinct pour cela.



Notre motivation:

  1. Loi FZ-54 - brièvement, la loi vous oblige à faire rapport au bureau des impôts sur chaque transaction monétaire, que ce soit un remboursement ou un reçu, dans un SLA assez court en quelques minutes. Nous, en tant que e-commerce, effectuons pas mal d'opérations. Techniquement, cela signifie une nouvelle responsabilité (et donc un nouveau service) et des améliorations dans tous les systèmes impliqués.
  2. Division BOB - projet interne de l'entreprise visant à débarrasser BOB d'un grand nombre de responsabilités non essentielles et à réduire sa complexité globale.



Ce diagramme illustre les principaux systèmes Lamoda. Maintenant, la plupart d'entre eux ressemblent davantage à une constellation de 5 à 10 microservices autour d'un monolithe décroissant . Ils grandissent lentement, mais nous essayons de les réduire, car il est effrayant de déployer le fragment mis en évidence au milieu - on ne peut pas le laisser tomber. Tous les échanges (flèches) que nous sommes obligés de réserver et reposent sur le fait que l'un d'eux peut être indisponible.

Il y a aussi pas mal d'échanges en BOB: paiement, livraison, systèmes de notification, etc.

Techniquement, BOB c'est:

  • ~ 150k lignes de code + ~ 100k lignes de tests;
  • php7.2 + Zend 1 & Symfony Components 3;
  • > 100 API et ~ 50 intégrations sortantes;
  • 4 pays avec leur propre logique métier.

Le déploiement de BOB est coûteux et douloureux, la quantité de code et les tâches qu'il résout sont telles que personne ne peut le mettre dans leur tête. En général, il existe de nombreuses raisons de le simplifier.

Processus de retour


Initialement, deux systèmes sont impliqués dans le processus: BOB et Paiement. Maintenant, deux autres apparaissent:

  • Service de fiscalisation, qui s'occupera des problèmes de fiscalisation et de communication avec les services externes.
  • Outil de remboursement, dans lequel de nouveaux échanges sont simplement retirés afin de ne pas gonfler le BOB.

Maintenant, le processus ressemble à ceci:



  1. BOB reçoit une demande de remboursement.
  2. BOB parle de cet outil de remboursement.
  3. L'outil de remboursement indique Paiement: "Récupérez l'argent".
  4. Le paiement renvoie l'argent.
  5. L'outil de remboursement et BOB synchronisent les statuts l'un avec l'autre, car pour le moment, ils en ont tous les deux besoin. Nous ne sommes pas encore prêts à passer entièrement à l'outil de remboursement, car BOB a une interface utilisateur, des rapports pour la comptabilité et généralement beaucoup de données que vous ne pouvez pas facilement transférer. Nous devons nous asseoir sur deux chaises.
  6. La demande de fiscalisation part.

En conséquence, nous avons fait une sorte de bus d'événement sur Kafka - un bus d'événement, sur lequel tout a commencé. Hourra, nous avons maintenant un seul point d'échec (sarcasme).



Les avantages et les inconvénients sont assez évidents. Nous avons fait un bus, alors maintenant tous les services en dépendent. Cela simplifie la conception, mais introduit un point de défaillance unique dans le système. Kafka tombera, le processus augmentera.

Qu'est-ce qu'une API événementielle


Une bonne réponse à cette question se trouve dans le rapport de Martin Fowler (GOTO 2017) "Les nombreuses significations de l'architecture événementielle" .

En bref, ce que nous avons fait:

  1. Enveloppé tous les échanges asynchrones via le stockage des événements . Au lieu d'informer chaque consommateur intéressé du changement d'état via le réseau, nous écrivons un événement de changement d'état dans le référentiel centralisé, et les consommateurs intéressés par le sujet lisent tout ce qui apparaît à partir de là.
  2. Un événement dans ce cas est une notification ( notifications ) que quelque chose a changé quelque part. Par exemple, le statut de la commande a changé. Un consommateur qui est intéressé par une sorte de données accompagnant le changement de statut et qui ne figure pas dans la notification peut découvrir lui-même son statut.
  3. L'option maximale est un sourcing d'événements à part entière, un transfert d'état , dans lequel l'événement contient toutes les informations nécessaires au traitement: d'où et vers quel statut avez-vous changé, comment les données ont-elles changé, etc. La seule question est de savoir si cela vaut la peine et combien d'informations vous pouvez vous permettre de stocker.

Dans le cadre du lancement de l'outil de remboursement, nous avons utilisé la troisième option. Cela a simplifié le traitement des événements, car il n'était pas nécessaire d'obtenir des informations détaillées, et il a également exclu le scénario lorsque chaque nouvel événement génère une vague de clarification des demandes d'obtention des consommateurs.

Le service Refund Tool n'est pas chargé , donc Kafka ressemble plus à un test au stylo qu'à une nécessité. Je ne pense pas que si le service de remboursement devenait un projet à forte charge, l'entreprise serait heureuse.

Échange asynchrone TEL QUEL


Pour les échanges asynchrones, le département PHP utilise généralement RabbitMQ. Nous avons collecté les données de la demande, les avons placées dans la file d'attente et le consommateur du même service les a lues et envoyées (ou ne les a pas envoyées). Pour l'API elle-même, Lamoda utilise activement Swagger. Nous concevons l'API, la décrivons dans Swagger, générons du code client et serveur. Nous utilisons également un JSON RPC 2.0 légèrement avancé.

Ici et là, des bus esb sont utilisés, quelqu'un vit sur activeMQ, mais, en général, RabbitMQ est la norme .

Échange asynchrone À ÊTRE


Lors de la conception d'un échange via un bus d'événements, une analogie est tracée. Nous décrivons également l'échange futur de données à travers des descriptions de structure d'événements. Le format yaml, la génération de code devait être faite par nous-mêmes, le générateur crée le DTO selon les spécifications et enseigne aux clients et aux serveurs comment travailler avec eux. La génération se fait en deux langues - golang et php . Cela maintient les bibliothèques cohérentes. Le générateur est écrit en golang, pour lequel il a reçu le nom de gogi.

Le sourcing d'événements chez Kafka est une chose typique. Il existe une solution à partir de la version d'entreprise principale de Kafka Confluent, il y a le nakadi , une solution de nos "frères" dans la zone du domaine Zalando. Notre motivation pour commencer avec vanilla Kafka est de laisser la solution gratuite jusqu'à ce que nous décidions finalement de l'utiliser ou non partout, et de laisser également une marge de manœuvre et d'améliorations: nous voulons la prise en charge de notre JSON RPC 2.0 , générateurs pour deux langues, et voir quoi d'autre.

Il est ironique que même dans un cas aussi heureux, quand il y a une entreprise similaire à Zalando, qui a pris une décision similaire, nous ne pouvons pas l'utiliser efficacement.

Sur le plan architectural, au démarrage, le modèle est le suivant: lire directement depuis Kafka, mais écrire uniquement via le bus d'événements. Il y a beaucoup de prêt à lire dans Kafka: courtiers, équilibreurs et il est plus ou moins prêt pour la mise à l'échelle horizontale, je voulais le garder. L'enregistrement, nous voulions passer par une passerelle, alias Events-bus, et c'est pourquoi.

Evénements-bus


Ou un bus événementiel. Il s'agit simplement d'une passerelle http sans état qui joue plusieurs rôles importants:

  • Validation de la production - nous vérifions que les événements répondent à nos spécifications.
  • Un système maître d'événements , c'est-à-dire qu'il est le seul et principal système de l'entreprise à répondre à la question de savoir quels événements avec quelles structures sont considérés comme valides. La validation inclut simplement les types de données et les énumérations pour une spécification stricte du contenu.
  • La fonction de hachage pour le partitionnement - la structure du message de Kafka est une valeur-clé, et ici elle est calculée par le hachage de la clé où le mettre.

Pourquoi


Nous travaillons dans une grande entreprise avec un processus rationalisé. Pourquoi changer quelque chose? Il s'agit d'une expérience et nous nous attendons à obtenir plusieurs avantages.

1: n + 1 échanges (un à plusieurs)


Avec Kafka, il est très facile de connecter de nouveaux consommateurs à l'API.

Supposons que vous ayez un répertoire qui doit être tenu à jour dans plusieurs systèmes à la fois (et dans certains nouveaux). Auparavant, nous avions inventé un bundle qui implémentait un set-API, et les adresses des consommateurs étaient signalées au système maître. Maintenant, le système maître envoie des mises à jour sur le sujet et à tous ceux qui souhaitent lire. Un nouveau système est apparu - ils l'ont signé sur le sujet. Oui, également en bundle, mais plus simple.

Dans le cas de l'outil de remboursement, qui est un morceau de BOB, il est pratique pour nous de les garder synchronisés via Kafka. Le paiement dit qu'ils ont rendu l'argent: BOB, RT l'a découvert, a changé de statut, le service de fiscalisation l'a découvert et a fait un chèque.



Nous avons l'intention de créer un service de notifications unique, qui informerait le client des nouvelles de sa commande / retours. Cette responsabilité est désormais répartie entre les systèmes. Il nous suffira d'enseigner au service de notifications comment intercepter et répondre aux informations pertinentes de Kafka (et désactiver ces notifications dans d'autres systèmes). Aucun nouvel échange direct ne sera nécessaire.

Axé sur les données


Les informations entre les systèmes deviennent transparentes - quelle que soit l'entreprise sanglante que vous avez et à quel point votre carnet de commandes est gonflé. Lamoda dispose d'un département Data Analytics qui collecte des données sur les systèmes et les met sous une forme réutilisable, à la fois pour les entreprises et pour les systèmes intelligents. Kafka vous permet de leur fournir rapidement un grand nombre de données et de maintenir ce flux d'informations à jour.

Journal de réplication


Les messages ne disparaissent pas après la lecture, comme dans RabbitMQ. Lorsque l'événement contient suffisamment d'informations pour le traitement, nous avons un historique des modifications récentes apportées à l'objet et, si vous le souhaitez, la possibilité d'appliquer ces modifications.

La période de stockage du journal de réplication dépend de l'intensité de l'écriture sur ce sujet. Kafka vous permet de définir de manière flexible des limites de temps de stockage et de volume de données. Pour les sujets intensifs, il est important que tous les consommateurs aient le temps de lire les informations avant qu'elles ne disparaissent, même en cas d'inopérabilité à court terme. Il s'avère généralement stocker des données pour des unités de jours , ce qui est assez suffisant pour le support.



Puis un peu de récit de la documentation, pour ceux qui ne connaissent pas Kafka (la photo est aussi de la documentation)

Il y a des files d'attente dans AMQP: nous écrivons des messages dans la file d'attente pour le consommateur. En règle générale, une file d'attente est traitée par un système avec la même logique métier. Si vous devez notifier plusieurs systèmes, vous pouvez apprendre à l'application à écrire dans plusieurs files d'attente ou à configurer l'échange avec le mécanisme de fanout, qui les clone lui-même.

Kafka a une abstraction de sujet similaire dans laquelle vous écrivez des messages, mais ils ne disparaissent pas après la lecture. Par défaut, lorsque vous vous connectez à Kafka, vous recevez tous les messages et en même temps, vous avez la possibilité de sauvegarder l'endroit où vous vous étiez arrêté. Autrement dit, vous lisez de manière séquentielle, vous ne pouvez pas marquer le message comme lu, mais enregistrer l'identifiant, à partir duquel ensuite continuer la lecture. L'ID auquel vous vous arrêtez s'appelle offset, et le mécanisme est commit offset.

En conséquence, une logique différente peut être mise en œuvre. Par exemple, nous avons BOB dans 4 cas pour différents pays - Lamoda est en Russie, au Kazakhstan, en Ukraine, en Biélorussie. Puisqu'ils sont déployés séparément, ils ont un peu leurs propres configurations et leur propre logique métier. Dans le message, nous indiquons de quel pays il s'agit. Chaque consommateur BOB dans chaque pays lit avec différents groupId, et si le message ne s'applique pas à lui, sautez-le, c'est-à-dire validez immédiatement l'offset +1. Si le même sujet est lu par notre service de paiement, il le fait avec un groupe distinct et, par conséquent, le décalage ne se chevauche pas.

Exigences de l'événement:

  • Exhaustivité des données. Je souhaite qu'il y ait suffisamment de données dans l'événement pour qu'elles puissent être traitées.

  • Intégrité Nous déléguons le bus d'événements pour vérifier que l'événement est cohérent et qu'il peut le gérer.
  • L'ordre est important. En cas de retour, nous sommes obligés de travailler avec l'histoire. Avec les notifications, la commande n'est pas importante, s'il s'agit de notifications homogènes, l'e-mail sera le même quelle que soit la commande arrivée en premier. Dans le cas d'un retour, il existe un processus clair, si vous modifiez la commande, il y aura des exceptions, le remboursement ne sera pas créé ou traité - nous nous retrouverons dans un statut différent.
  • Cohérence. Nous avons un référentiel, et maintenant au lieu de l'API, nous créons des événements. Nous avons besoin d'un moyen de transférer rapidement et à moindre coût des informations sur de nouveaux événements et des changements sur des événements existants à nos services. Ceci est réalisé en utilisant une spécification commune dans un référentiel git séparé et des générateurs de code. Par conséquent, les clients et les serveurs de différents services sont coordonnés avec nous.

Kafka in Lamoda


Nous avons trois installations Kafka:

  1. Journaux
  2. R&D;
  3. Evénements-bus.

Aujourd'hui, nous ne parlons que du dernier point. Dans les événements-bus, nous n'avons pas de très grandes installations - 3 courtiers (serveurs) et un total de 27 sujets. En règle générale, un sujet est un processus. Mais c'est un moment délicat, et nous allons maintenant y revenir.



Ci-dessus est le graphique rps. Le processus de remboursement est marqué d'une ligne turquoise (oui, celle située sur l'axe X), et le rose est le processus de mise à jour du contenu.

Le catalogue de Lamoda contient des millions de produits et les données sont constamment mises à jour. Certaines collections se démodent, de nouvelles sortent à leur place, de nouveaux modèles apparaissent constamment dans le catalogue. Nous essayons de prédire ce qui sera intéressant pour nos clients demain, alors nous achetons constamment de nouvelles choses, les photographions et mettons à jour la fenêtre.

Les pics roses sont une mise à jour du produit, c'est-à-dire des changements de produits. On peut voir que les gars ont pris des photos, pris des photos, et puis encore! - téléchargé un paquet d'événements.

Cas d'utilisation de Lamoda Events


Nous utilisons l'architecture construite pour de telles opérations:

  • Suivi des statuts de retour : appel à l'action et suivi des statuts de tous les systèmes impliqués. Paiement, statuts, fiscalisation, notifications. Ici, nous avons testé l'approche, créé les outils, collecté tous les bogues, rédigé la documentation et expliqué à nos collègues comment l'utiliser.
  • Mise à jour des fiches produits: configuration, métadonnées, caractéristiques. Un système lit (qui s'affiche) et plusieurs écrivent.
  • Email, push et sms : la commande est collectée, la commande est arrivée, le retour a été accepté, etc., beaucoup d'entre eux.
  • Stock, mise à jour de l'entrepôt - une mise à jour quantitative des articles, juste des chiffres: réception à l'entrepôt, retour. Il est nécessaire que tous les systèmes liés à la réservation de marchandises fonctionnent avec les données les plus pertinentes. Maintenant que le système de mise à niveau du drain est assez compliqué, Kafka le simplifiera.
  • Analyse des données (département R&D), outils ML, analyse, statistiques. Nous voulons que les informations soient transparentes - car ce Kafka est bien adapté.

Maintenant, la partie la plus intéressante concerne les cônes farcis et les découvertes intéressantes qui ont eu lieu pendant six mois.

Problèmes de conception


Supposons que nous voulions faire quelque chose de nouveau - par exemple, transférer l'ensemble du processus de livraison à Kafka. Une partie du processus est maintenant implémentée dans le traitement des commandes dans BOB. Derrière le transfert de la commande au service de livraison, le transfert vers un entrepôt intermédiaire, etc., il y a un modèle de statut. Il y a tout un monolithe, même deux, plus un tas d'API de livraison. Ils en savent beaucoup plus sur la livraison.

Il semble que ce soient des domaines similaires, mais pour le traitement des commandes dans le BOB et pour le système de livraison, les statuts sont différents. Par exemple, certains services de messagerie n'envoient pas de statuts intermédiaires, mais uniquement des statuts définitifs: «livrés» ou «perdus». D'autres, au contraire, rendent compte en détail de la circulation des marchandises. Chacun a ses propres règles de validation: pour quelqu'un, l'e-mail est valide, il sera donc traité; pour d'autres, ce n'est pas valable, mais la commande sera toujours traitée, car il y a un téléphone pour la communication, et quelqu'un dira qu'une telle commande ne sera pas traitée du tout.

Flux de données


Dans le cas de Kafka, se pose la question de l'organisation du flux de données. Cette tâche est liée au choix de la stratégie pour plusieurs points, nous les passerons tous en revue.

Dans un sujet ou dans un autre?


Nous avons une spécification d'événement. Dans BOB, nous écrivons qu'une telle commande doit être livrée et indiquons: le numéro de commande, sa composition, certains SKU et codes à barres, etc. Lorsque les marchandises arrivent à l'entrepôt, la livraison pourra recevoir les statuts, les horodatages et tout ce qui est nécessaire. Mais nous souhaitons en outre recevoir des mises à jour sur ces données dans BOB. Nous sommes confrontés au processus inverse d'obtention des données à la livraison. S'agit-il du même événement? Ou s'agit-il d'un échange séparé qui mérite un sujet distinct?

Très probablement, ils seront très similaires, et la tentation de faire un sujet n'est pas déraisonnable, car un sujet séparé est des consommateurs distincts, des configurations distinctes, une génération distincte de tout cela. Mais pas un fait.

Nouveau domaine ou nouvel événement?


Mais si vous utilisez les mêmes événements, un autre problème se pose. Par exemple, tous les systèmes de livraison ne peuvent pas générer un DTO capable de générer des BOB. Nous leur envoyons un identifiant, mais ils ne les enregistrent pas, car ils n'en ont pas besoin, et du point de vue du démarrage du processus de bus d'événements, ce champ est obligatoire.

Si nous introduisons une règle pour le bus d'événements selon laquelle ce champ est obligatoire, nous sommes obligés de définir des règles de validation supplémentaires dans le BOB ou dans le gestionnaire d'événements de démarrage. La validation commence à se glisser sur le service - ce n'est pas très pratique.

— . , - , , , , . — . — , . JSON .

refunds . -, refund update, type, , update . «» , , type.


Kafka Avro , Confluent. . replication log, «». , , : , . , , , .

partitions


Kafka partitions. , , , .

Kafka . partition, . . , , , . , , , Kafka partition, Kafka — , .

Kafka ? ( JSON) key. -, , partition .

refunds , partition, , . - , partition.

Events vs commands


, . Event — : , - - (something_happened), , item refund. - , «item » refund , « refund» - .

, , — , - . something_happened (item_canceled, refund_refunded), something_should_be_done. , item .

, , . , . , do_something. , - ; , ; , -, - . , do_something, , .



RabbitMQ, , http, response — , . Kafka, , Kafka, , , .

, - , - . , , - . , «item_ready_to_refund», , refund , , «money_refunded». , .

Nuances


: , - , , . , offset , .

, , . , events-bus, , PostgreSQL, MySQL UNSIGNED INT, PostgreSQL INT. , Id . Symfony . , , , , offset, , . , Symfony , offset.

- — , Kafka , . . .

Kafka tooling offset. , — , , redeployments. Kafka tooling offset, .

replication log vs rdkafka.so — . PHP, PHP, , , Kafka rdkafka.so, - . , , , - . , .

partitions, consumers >= topic partitions . , . , partitions. , partition, 20 , , . , , partitions.

Suivi


, , , , .

, , , , , , . Kafka , . , .



, , , events-bus , . , Refund Tool , BOB - ( ).



consumer-group lag. , . , 0, . Kafka , .

Burrow , Kafka. API consumer-group , . Failed warning, , — , . , .



API. bob-live-fifa, partition refund.update.v1, , lag 0 — offset -.



updated_at SLA (stuck) . , , . Cron, , 5 refund ( ), - , . Cron, , 0, .

, , :

  • ;
  • ;
  • .
Il semblerait que l'article ait un sujet très spécifique - l'API asynchrone sur Kafka, mais à ce sujet, je veux recommander immédiatement beaucoup de choses.
Premièrement, le prochain HighLoad ++ ne devrait pas être attendu avant novembre, à Saint-Pétersbourg il y aura sa version, et en juin nous parlerons de charges élevées à Novossibirsk.
Deuxièmement, l'auteur du rapport, Sergey Zaika, est membre du comité de programme de notre nouvelle conférence KnowledgeConf sur la gestion des connaissances . La conférence est d'une journée, elle se tiendra le 26 avril, mais son programme est très mouvementé.
Et en mai, il y aura PHP Russie et RIT ++ (avec DevOpsConf inclus) - vous pouvez toujours suggérer votre propre sujet, parler de votre expérience et vous plaindre de vos bosses farcies.

Source: https://habr.com/ru/post/fr445424/


All Articles