Cosa può spingere un'azienda così grande come Lamoda, con un processo collaudato e decine di servizi interconnessi, a cambiare radicalmente approccio? La motivazione può essere delle più varie: da quella legislativa al desiderio di sperimentare, comune a tutti i programmatori.
Ma questo non significa affatto che non si possa contare su un vantaggio aggiuntivo. In cosa si può guadagnare concretamente implementando un'API event-driven su Kafka, lo racconterà Sergey Zaika (). Parleremo anche degli errori commessi e delle scoperte interessanti, perché un esperimento non può prescindere da essi.

Avvertenza: Questo articolo si basa su materiali del meetup che Sergey ha tenuto nel novembre 2018 durante HighLoad++. L'esperienza reale di Lamoda con Kafka ha attirato l'attenzione del pubblico non meno di altre presentazioni del programma. Ci sembra un ottimo esempio di come si possano sempre trovare e unire forze simili, e gli organizzatori di HighLoad++ continueranno a creare un'atmosfera favorevole a questo.
Sul processo
Lamoda è una grande piattaforma di e-commerce che ha il suo centro di contatto, un servizio di consegna (e numerosi partner), uno studio fotografico, un enorme magazzino, e tutto ciò funziona con il proprio software. Ci sono decine di metodi di pagamento, partner b2b che possono utilizzare parte o tutti questi servizi e vogliono conoscere informazioni aggiornate sui propri prodotti. Inoltre, Lamoda opera in tre paesi oltre alla RF, e lì tutto è un po' diverso. Quindi ci sono probabilmente più di cento modi per configurare un nuovo ordine, che deve essere elaborato in modo unico. Tutto questo funziona grazie a decine di servizi che comunicano a volte in modi non evidenti. C'è anche un sistema centrale, la cui principale responsabilità sono gli stati degli ordini. La chiamiamo BOB, e io lavoro con essa.
Refund Tool con eventi-driven API
La parola eventi-driven è piuttosto abusata, più avanti definiremo nel dettaglio cosa si intende. Comincerò dal contesto in cui abbiamo deciso di testare l'approccio dell'API eventi-driven in Kafka.

In qualsiasi negozio, oltre agli ordini per cui i clienti pagano, ci sono momenti in cui il negozio deve restituire i soldi, perché il cliente non ha trovato il prodotto adatto. Questo è un processo relativamente breve: verifichiamo le informazioni, se necessario, e trasferiamo i soldi.
Ma il rimborso è diventato complicato a causa dei cambiamenti normativi, e abbiamo dovuto realizzare un microservizio dedicato per questo.

La nostra motivazione:
- Legge FZ-54 — in sintesi, la legge richiede di segnalare all'Agenzia delle entrate ogni operazione di denaro, sia un rimborso che una entrata, in un SLA piuttosto breve di pochi minuti. Noi, come e-commerce, gestiamo un numero considerevole di operazioni. Tecnologicamente, questo comporta una nuova responsabilità (e quindi un nuovo servizio) e modifiche in tutti i sistemi coinvolti.
- BOB split — un progetto interno dell'azienda per liberare BOB da un gran numero di responsabilità non core e ridurre la sua complessità generale.

In questo schema sono rappresentati i principali sistemi di Lamoda. Al momento, la maggior parte di essi è più simile a un insieme di 5-10 microservizi attorno a un monolite in via di riduzione. Crescono lentamente, ma cerchiamo di ridurli, perché effettuare il deployment di un frammento dedicato al centro è spaventoso: non possiamo permetterci che cada. Tutti gli scambi (frecce) dobbiamo riservarli e considerare che qualsiasi di essi potrebbe risultare inaccessibile.
In BOB ci sono anche molti scambi: sistemi di pagamento, consegna, notifiche, ecc.
Tecnicamente BOB è:
- ~150k righe di codice + ~100k righe di test;
- php7.2 + Zend 1 & Symfony Components 3;
- >100 API & ~50 integrazioni in uscita;
- 4 paesi con la propria logica commerciale.
Effettuare il deployment di BOB è costoso e doloroso, la quantità di codice e le questioni che gestisce sono tali che nessuno può tenerlo in mente tutto. In generale, ci sono molte ragioni per semplificarlo.
Il processo di rimborso
Inizialmente il processo coinvolge due sistemi: BOB e Payment. Ora se ne aggiungono altri due:
- Fiscalization Service, che si occuperà dei problemi di fiscalizzazione e della comunicazione con servizi esterni.
- Refund Tool, dove vengono semplicemente spostati i nuovi scambi, per non gonfiare BOB.
Ora il processo appare così:

- BOB riceve una richiesta di rimborso.
- BOB comunica al Refund Tool.
- Refund Tool dice a Payment: "Restituisci i soldi".
- Payment restituisce i soldi.
- Refund Tool e BOB sincronizzano tra loro gli stati, perché al momento è necessario per entrambi. Non siamo ancora pronti a passare completamente a Refund Tool, poiché in BOB ci sono UI, report per la contabilità e molte altre informazioni che non possono essere trasferite così semplicemente. Dobbiamo rimanere su due sedie.
- Viene inviata una richiesta per la fiscalizzazione.
Alla fine abbiamo creato su Kafka una sorta di bus degli eventi – event-bus, su cui è tutto basato. Evviva, ora abbiamo un unico punto di guasto (sarcasmo).

I pro e i contro sono abbastanza evidenti. Abbiamo creato un bus, il che significa che tutti i servizi ora dipendono da esso. Questo semplifica la progettazione, ma introduce un unico punto di guasto nel sistema. Se Kafka va giù, il processo si ferma.
Cos'è un API basata sugli eventi
Una buona risposta a questa domanda si trova nella presentazione di Martin Fowler (GOTO 2017) .
In breve, cosa abbiamo fatto:
- Abbiamo incapsulato tutti gli scambi asincroni tramite events storage. Invece di comunicare in rete a ciascun consumatore interessato il cambiamento di stato, scriviamo in uno storage centralizzato un evento che indica il cambiamento di stato, e i consumatori interessati sull'argomento leggono tutto ciò che appare lì.
- Un evento (event) in questo caso è una notifica (notifications) che qualcosa è cambiato da qualche parte. Ad esempio, lo stato di un ordine è cambiato. Un consumatore, che ha bisogno di alcune informazioni accessorie relative al cambiamento di stato e non presenti nella notifica, può autonomamente scoprirne lo stato.
- La variante massima è un completo event sourcing, state transfer, in cui l'evento contiene tutte le informazioni necessarie per l'elaborazione: da dove e in quale stato sono passati, come sono cambiati i dati, ecc. La questione è solo di opportunità e della quantità di informazioni che puoi permetterti di conservare.
Nel contesto del lancio di Refund Tool abbiamo utilizzato la terza variante. Questo ha semplificato l'elaborazione degli eventi, poiché non c'era bisogno di recuperare informazioni dettagliate, e ha anche eliminato lo scenario in cui ogni nuovo evento genera un picco di richieste GET di chiarimento dai consumatori.
Il servizio Refund Tool non è gravoso, quindi Kafka lì è più una prova che una necessità. Non credo che se il servizio di restituzione fondi diventasse un progetto ad alto carico, il business ne sarebbe contento.
Scambio asincrono AS IS
Per gli scambi asincroni, il dipartimento PHP generalmente utilizza RabbitMQ. Raccogliamo i dati per la richiesta, li mettiamo in coda e il consumatore dello stesso servizio li legge e li invia (o non li invia). Per l'API stessa, Lamoda utilizza attivamente Swagger. Progettiamo l'API, la descriviamo in Swagger, generiamo il codice client e server. Utilizziamo anche una versione leggermente estesa di JSON RPC 2.0.
In alcuni casi vengono utilizzati esb-bus, qualcuno vive su activeMQ, ma, in generale, RabbitMQ — standard.
Scambio asincrono TO BE
Progettando lo scambio tramite events-bus, si evidenzia un'analogia. Descriviamo in modo simile i futuri scambi di dati attraverso la descrizione della struttura dell'evento. Il formato yaml, la generazione di codice è stata fatta manualmente, il generatore secondo le specifiche crea DTO e insegna ai client e ai server a lavorare con essi. La generazione avviene in due lingue — golang e php. Questo consente di mantenere le librerie sincronizzate. Il generatore è scritto in golang, per questo ha ricevuto il nome gogi.
L'event sourcing su Kafka è una pratica comune. C'è una soluzione dalla versione enterprise principale di Kafka Confluent, c'è , una soluzione dai nostri "fratelli" nel settore Zalando. La nostra motivazione per iniziare con vanilla Kafka è quella di mantenere la soluzione gratuita, finché non decidiamo definitivamente se utilizzarla su larga scala, e anche di lasciarci spazio per manovre e miglioramenti: vogliamo supportare il nostro JSON RPC 2.0, generatori per due lingue e vedremo cos'altro.
Ironico che anche in un caso così fortunato, quando esiste un business sostanzialmente analogo a Zalando, che ha realizzato una soluzione simile, non possiamo usarla efficacemente.
Architettonicamente, al lancio il pattern è questo: leggiamo direttamente da Kafka, ma scriviamo solo tramite events-bus. Per la lettura in Kafka esistono molte soluzioni pronte: broker, bilanciatori, ed è più o meno pronto per la scalabilità orizzontale, questo è ciò che volevamo mantenere. Per la scrittura, volevamo incapsularla attraverso un Gateway noto anche come Events-bus, ed ecco perché.
Events-bus
O autobus degli eventi. È semplicemente un gateway http stateless, che assume su di sé diversi ruoli importanti:
- Validazione della produzione verifichiamo che gli eventi corrispondano alla nostra specifica.
- Sistema principale per gli eventi, cioè è l'unico e principale sistema dell'azienda che risponde alla domanda su quali eventi con quali strutture siano considerati validi. Nella validazione rientrano semplicemente i tipi di dati e gli enum per una specifica rigorosa del contenuto.
- Funzione hash per lo sharding — la struttura del messaggio Kafka è key-value e in base all'hash dalla chiave si calcola dove collocarlo.
Perché
Lavoriamo in una grande azienda con un processo collaudato. Perché cambiare qualcosa? È un esperimento, e ci aspettiamo di ottenere alcuni vantaggi.
Scambi 1:n+1 (uno a molti)
Con Kafka è molto semplice collegare nuove API dei consumatori.
Supponiamo che tu abbia un database che deve rimanere aggiornato in più sistemi contemporaneamente (e in alcuni nuovi). In passato abbiamo progettato un bundle che implementava il set-API, e alla master system comunicavamo gli indirizzi dei consumatori. Ora la master system invia aggiornamenti a un topic, e tutti quelli interessati li leggono. È emerso un nuovo sistema – lo abbiamo iscritti al topic. Sì, anche questo è un bundle, ma più semplice.
Nel caso del refund-tool, che è un componente di BOB, ci troviamo bene a tenere sincronizzati tramite Kafka. Il Payment comunica che il denaro è stato restituito: BOB e RT ne sono stati informati, hanno aggiornato i loro stati e il Fiscalization Service ha preso atto e ha emesso lo scontrino.

Abbiamo in programma di creare un Notifications Service unico, che informerà il cliente sulle novità relative al suo ordine/ritorno. Attualmente questa responsabilità è diffusa tra i sistemi. Sarà sufficiente insegnare al Notifications Service a pescare informazioni rilevanti da Kafka e a reagire ad esse (e disattivare queste notifiche negli altri sistemi). Non saranno necessari nuovi scambi diretti.
Data-driven
Le informazioni tra i sistemi diventano trasparenti – non importa che tipo di «impegnativo enterprise» tu abbia e quanto sia denominato il tuo backlog. In Lamoda c'è un dipartimento di Data Analytics che raccoglie dati dai sistemi e li rende riutilizzabili, sia per il business che per i sistemi intelligenti. Kafka consente di fornirgli rapidamente molti dati e di mantenere questo flusso informativo aggiornato.
Replication log
I messaggi non scompaiono dopo essere stati letti, come in RabbitMQ. Quando un evento contiene informazioni sufficienti per l'elaborazione, abbiamo una cronologia delle ultime modifiche all'oggetto, e, se lo desideriamo, la possibilità di applicare tali modifiche.
Il periodo di conservazione del replication log dipende dall'intensità della scrittura in questo topic, Kafka consente di impostare in modo flessibile i limiti per il periodo di conservazione e per il volume di dati. Per i topic ad alta intensità è importante che tutti i consumatori riescano a leggere le informazioni prima che scompaiano, anche in caso di breve inattività. Di solito riusciamo a conservare i dati per un numero di giorni, che è più che sufficiente per il supporto.

In seguito un breve riassunto della documentazione, per chi non ha familiarità con Kafka (l'immagine è anch'essa dalla documentazione)
In AMQP ci sono le code: scriviamo messaggi in coda per il consumatore. In generale, una coda è gestita da un sistema con la stessa logica aziendale. Se è necessario informare più sistemi, è possibile insegnare all'applicazione a scrivere in più code o configurare un exchange con un meccanismo fanout, che le clona automaticamente.
In Kafka c'è un'astrazione simile topic, in cui scrivete messaggi, ma non scompaiono dopo la lettura. Di default, quando vi collegate a Kafka, ricevete tutti i messaggi e c'è la possibilità di salvare la posizione in cui vi siete fermati. Cioè, leggete in modo sequenziale, potete non contrassegnare un messaggio come letto, ma salvare l'id da cui continuerete poi la lettura. L'id su cui vi siete fermati si chiama offset, e il meccanismo è il commit offset.
Di conseguenza, si può implementare una logica diversa. Ad esempio, BOB esiste in 4 istanze per diversi paesi: Lamoda è presente in Russia, Kazakistan, Ucraina, Bielorussia. Poiché vengono distribuiti separatamente, hanno configurazioni e logiche aziendali leggermente diverse. Indichiamo nel messaggio a quale paese si riferisce. Ogni consumatore BOB in ogni paese legge con diversi groupId e, se il messaggio non lo riguarda, lo ignora, cioè committa subito l'offset +1. Se lo stesso topic è letto dal nostro Payment Service, allora lo fa con un gruppo separato, e quindi gli offset non si sovrappongono.
Requisiti per gli eventi:
- Completezza dei dati. Vorremmo che nell'evento ci fossero dati sufficienti per poterlo elaborare.
- Integrità. Delegiamo all'Events-bus la verifica che l'evento sia coerente e che possa essere elaborato.
- L'ordine è importante. Nel caso dei resi, siamo costretti a lavorare con la storia. Con le notifiche, l'ordine non è importante, se si tratta di notifiche omogenee, l'email sarà la stessa indipendentemente da quale ordine è arrivato per primo. Nel caso di un reso c'è un processo definito, se si cambia l'ordine, potrebbero sorgere eccezioni, il rimborso non sarà creato o elaborato, e ci troveremo in un altro stato.
- Coerenza. Abbiamo un magazzino e ora stiamo creando eventi invece delle API. Abbiamo bisogno di un modo per trasmettere in modo rapido ed economico informazioni sui nuovi eventi e sulle modifiche a quelli esistenti. Questo viene realizzato tramite una specifica comune in un repository git separato e generatori di codice. Pertanto, i clienti e i server dei diversi servizi sono allineati.
Kafka in Lamoda
Abbiamo tre installazioni di Kafka:
- Logs;
- R&D;
- Events-bus.
Oggi parliamo solo dell'ultimo punto. Nell'events-bus non abbiamo installazioni molto grandi: 3 broker (server) e solo 27 topic. Di norma, un topic corrisponde a un processo. Ma questo è un punto delicato e ora lo toccheremo.

Sopra è il grafico rps. Il processo di rimborsi è segnato dalla linea turchese (sì, sì, quella sull'asse X), mentre quello dell'aggiornamento dei contenuti è segnato in rosa.
Il catalogo di Lamoda contiene milioni di articoli e i dati vengono costantemente aggiornati. Collezioni escono di moda, ne vengono rilasciate di nuove, e nel catalogo appaiono continuamente nuovi modelli. Cerchiamo di prevedere cosa potrebbe interessare ai nostri clienti domani, quindi acquisiamo costantemente nuovi articoli, li fotografiamo e aggiornando la vetrina.
I picchi rosa rappresentano l'aggiornamento del prodotto, ovvero le modifiche agli articoli. Si vede chiaramente che i ragazzi stavano fotografando, fotografando e poi, puff! — hanno caricato un sacco di eventi.
Usi di Lamoda Events
L'architettura costruita la utilizziamo per le seguenti operazioni:
- Monitoraggio degli stati di ritorno: call-to-action e tracciamento degli stati da tutti i sistemi coinvolti. Pagamenti, stati, fiscalizzazione, notifiche. Qui abbiamo testato un approccio, creato strumenti, raccolto bug, scritto documentazione e spiegato ai colleghi come usarlo.
- Aggiornamento delle schede prodotto: configurazione, metadati, caratteristiche. Una sistema legge (quello che visualizza), mentre più sistemi scrivono.
- Email, push e sms: ordine raccolto, ordine arrivato, reso accettato, ecc., ce ne sono molti.
- Stock, aggiornamento del magazzino — aggiornamento quantitativo delle voci, solo numeri: arrivi in magazzino, resi. È necessario affinché tutti i sistemi legati alla prenotazione dei prodotti operino con i dati più aggiornati. Attualmente, il sistema di aggiornamento dello stock è piuttosto complesso; Kafka lo semplificherà.
- Analisi dei dati (R&D-dipartimento), strumenti ML, analisi, statistica. Vogliamo che le informazioni siano trasparenti — per questo Kafka è molto adatto.
Ora passiamo alla parte più interessante riguardo gli errori e le scoperte interessanti avvenute negli ultimi sei mesi.
Problemi di progettazione
Supponiamo di voler creare una nuova cosa — per esempio, trasferire su Kafka l'intero processo di consegna. Attualmente, parte del processo è realizzata nel processamento degli ordini in BOB. Prima di inviare un ordine al servizio di consegna, attraverso lo spostamento in un magazzino intermedio e altro, c'è un modello di stato. Esiste un intero monolite, addirittura due, più una marea di API dedicate alla consegna. Sanno molto di più sulla consegna.
Sembra che queste siano aree simili, ma per il processamento degli ordini in BOB e per il sistema di consegna gli stati sono diversi. Per esempio, alcune compagnie di corriere non inviano stati intermedi, ma solo finali: 'consegnato' o 'perso'. Altre, al contrario, comunicano in modo molto dettagliato il movimento del prodotto. Ognuno ha le proprie regole di validazione: per qualcuno un'email valida significa che verrà elaborata; per altri, un'email non valida, ma l'ordine verrà comunque elaborato perché c'è un telefono per contattare, e qualcuno dirà che quell'ordine non sarà elaborato affatto.
Flusso di dati
Nel caso di Kafka sorgono questioni sull'organizzazione del flusso di dati. Questo compito è legato alla scelta della strategia su diversi punti; esaminiamo tutti questi punti.
In un unico topic o in diversi?
Abbiamo una specifica dell'evento. In BOB scriviamo che un certo ordine deve essere consegnato, e indichiamo: numero dell'ordine, il suo contenuto, alcuni SKU e codici a barre, ecc. Quando il prodotto arriva al magazzino, la consegna può ricevere stati, timestamp e tutto ciò di cui ha bisogno. Ma poi vogliamo ricevere aggiornamenti su questi dati in BOB. Si genera un processo inverso di ricezione dei dati dalla consegna. È lo stesso evento? Oppure è uno scambio separato che merita un topic separato?
Probabilmente saranno molto simili, e la tentazione di creare un unico topic è giustificata, perché un topic separato significa consumer separati, config separati, generazione separata di tutto questo. Ma non è detto.
Nuovo campo o nuovo evento?
Ma se si utilizzano gli stessi eventi, sorge un altro problema. Ad esempio, non tutti i sistemi di consegna possono generare un DTO che possa essere generato da BOB. Inviamo loro l'id, ma non li salvano, perché non ne hanno bisogno, e dal punto di vista dell'inizio del processo event-bus, questo campo è obbligatorio.
Se introduciamo per l'event-bus una regola secondo cui questo campo è obbligatorio, siamo costretti a mettere in BOB o nel gestore dell'evento iniziale ulteriori regole di validazione. La validazione inizia a diffondersi nel servizio — non è molto comodo.
Un altro problema è la tentazione dello sviluppo incrementale. Ci viene detto che dobbiamo aggiungere qualcosa all'evento e, forse, se ci pensiamo bene, sarebbe dovuto essere un evento separato. Ma nella nostra schema, un evento separato è un argomento separato. Un argomento separato è l'intero processo che ho descritto sopra. Il sviluppatore è tentato di aggiungere semplicemente un altro campo nella schema JSON e rigenerarlo.
Nel caso dei rimborsi, siamo arrivati all'evento eventi in sei mesi. Avevamo un evento meta, che si chiama aggiornamento rimborso, in cui c'era un campo tipo, descrivente in cosa consiste questo aggiornamento. Da ciò abbiamo avuto "ottimi" switch con validator che dicevano come dovevamo validare questo evento con quel tipo.
Versionamento degli eventi
Per la validazione dei messaggi in Kafka si può utilizzare , ma era necessario pianificare subito su questo e utilizzare Confluent. Nel nostro caso, con il versionamento, bisogna essere cauti. Non sarà sempre possibile rileggere i messaggi dal replication log, perché il modello "è partito". Fondamentalmente, si deve costruire versioni in modo tale che il modello sia retrocompatibile: per esempio, rendere un campo temporaneamente non obbligatorio. Se le differenze sono troppo forti, iniziamo a scrivere in un nuovo argomento, e i clienti vengono trasferiti quando hanno finito di leggere il vecchio.
Garanzia dell'ordine di lettura delle partizioni
Gli argomenti all'interno di Kafka sono suddivisi in partizioni. Questo non è molto importante mentre progettiamo entità e scambi, ma è importante quando decidiamo come consumare e scalare.
In un caso normale, si scrive un topic in Kafka. Per impostazione predefinita viene utilizzata una sola partizione, e tutti i messaggi di questo topic vi arrivano. Il consumer legge quindi questi messaggi in modo sequenziale. Supponiamo che ora sia necessario espandere la sistema in modo che i messaggi vengano letti da due diversi consumer. Se, ad esempio, invii un SMS, puoi dire a Kafka di creare una partizione aggiuntiva, e Kafka inizierà a distribuire i messaggi su due parti: metà da una parte, metà dall'altra.
Come li divide Kafka? Ogni messaggio ha un corpo (in cui memorizziamo JSON) e una chiave. A questa chiave può essere applicata una funzione hash, che determinerà in quale partizione andrà a finire il messaggio.
Nel nostro caso con i rimborsi, questo è importante; se prendiamo due partizioni, c'è la possibilità che un consumer parallelo elabori il secondo evento prima del primo, e ci saranno problemi. La funzione hash garantisce che i messaggi con la stessa chiave finiscano nella stessa partizione.
Eventi vs comandi
Questo è un altro problema con cui ci siamo confrontati. Un evento è un certo avvenimento: diciamo che qualcosa è accaduto (something_happened), ad esempio, un articolo è stato annullato o è stato effettuato un rimborso. Se questi eventi vengono ascoltati da qualcuno, allora per "articolo annullato" verrà creata un'entità di rimborso, e "è stato effettuato un rimborso" verrà registrato da qualche parte nelle impostazioni.
Ma di solito, quando progetti eventi, non vuoi scriverli senza motivo: ti aspetti che qualcuno li legga. C'è una forte tentazione di non scrivere something_happened (item_canceled, refund_refunded), ma something_should_be_done. Ad esempio, l'articolo è pronto per il reso.
Da un lato, questo suggerisce come sarà utilizzato l'evento. Dall'altro lato, somiglia molto meno a un nome normale per un evento. Inoltre, da qui il passo verso un comando do_something è breve. Ma non hai garanzie che questo evento sia stato letto da qualcuno; e se è stato letto, è stato letto con successo; e se è stato letto con successo, è stata fatta qualcosa, e questa qualcosa è andata a buon fine. Nel momento in cui l'evento diventa do_something, diventa necessaria una retroazione, e questo è un problema.

Nello scambio asincrono in RabbitMQ, quando hai letto un messaggio e sei andato in http, hai una risposta: almeno che il messaggio sia stato ricevuto. Quando hai scritto in Kafka, hai un messaggio che hai scritto in Kafka, ma non sai come è stato elaborato.
Pertanto, nel nostro caso, abbiamo dovuto introdurre un evento di risposta e configurare il monitoraggio affinché, se si verificano un certo numero di eventi, dopo un certo tempo dovrebbero arrivare un numero uguale di eventi di risposta. Se ciò non è avvenuto, sembra che ci sia stato un problema. Ad esempio, se abbiamo inviato l'evento «item_ready_to_refund», ci aspettiamo che venga creato un rimborso, i soldi vengano restituiti al cliente e riceviamo l'evento «money_refunded». Ma non è certo, quindi è necessario un monitoraggio.
Nuanze
C'è un problema abbastanza ovvio: se leggi i messaggi dal topic in modo sequenziale e hai un messaggio problematico, il consumer si blocca e non puoi andare avanti. Devi fermare tutti i consumer, impegnare l'offset oltre, per continuare a leggere.
Ne eravamo a conoscenza, lo avevamo previsto, eppure è successo. E questo è accaduto perché l'evento era valido dal punto di vista dell'events-bus, era valido dal punto di vista del validatore dell'applicazione, ma non era valido dal punto di vista di PostgreSQL, perché in un sistema abbiamo MySQL con UNSIGNED INT, mentre nel nuovo sistema era PostgreSQL con semplicemente INT. Ha una dimensione leggermente più piccola, e l'Id non ci stava. Symfony si è bloccato con un'eccezione. Certamente, abbiamo catturato l'eccezione, perché l'avevamo prevista, e volevamo impegnare questo offset, ma prima volevamo incrementare il contatore dei problemi, dato che il messaggio era stato elaborato in modo non riuscito. Anche i contatori di questo progetto sono memorizzati nel database, e Symfony aveva già chiuso la comunicazione con il database, e la seconda eccezione ha ucciso l'intero processo senza alcuna possibilità di impegnare l'offset.
Il servizio è rimasto inattivo per un certo periodo - per fortuna, con Kafka non è così grave, poiché i messaggi rimangono. Quando il lavoro riprenderà, si potranno leggere i messaggi. È comodo.
Kafka ha la possibilità di impostare un offset arbitrario tramite il tooling. Ma per farlo, è necessario fermare tutti i consumer - nel nostro caso, preparare un rilascio separato in cui non ci saranno consumer, redeployments. Allora, tramite il tooling di Kafka, si può spostare l'offset e il messaggio passerà.
Un'altra nuance - replication log vs rdkafka.so È legato alle specificità del nostro progetto. Abbiamo PHP, e in PHP, di solito, tutte le librerie comunicano con Kafka attraverso il repository rdkafka.so, e poi c'è qualche tipo di wrapper. Forse sono solo le nostre difficoltà personali, ma si è rivelato che semplicemente rileggere un pezzo di ciò che è già stato letto non è poi così semplice. In generale, ci sono stati problemi software.
Tornando alle peculiarità del lavoro con le partizioni, è scritto proprio nella documentazione consumers >= topic partitions. Ma ne ho saputo molto più tardi di quanto avrei voluto. Se volete scalare e avere due consumatori, vi servono almeno due partizioni. Cioè, se avevate una partizione in cui si sono accumulate 20.000 messaggi, e ne avete creato una nuova, il numero di messaggi si bilancerà lentamente. Pertanto, per avere due consumatori paralleli, bisogna capire come gestire le partizioni.
Monitoraggio
Penso che, dal modo in cui monitoriamo, sarà ancora più chiaro quali problemi ci siano nell'approccio esistente.
Ad esempio, contiamo quanti prodotti hanno recentemente cambiato stato nel database e, di conseguenza, a queste modifiche dovrebbero corrispondere eventi, e inviamo questo numero al nostro sistema di monitoraggio. Poi riceviamo da Kafka un secondo numero, che rappresenta quanti eventi sono stati effettivamente registrati. È ovvio che la differenza tra questi due numeri dovrebbe sempre essere zero.

Inoltre, bisogna monitorare come sta andando il produttore, se l'events-bus ha ricevuto i messaggi, e come sta andando il consumatore. Ad esempio, nei grafici qui sotto, il Refund Tool sta funzionando bene, ma BOB ha chiaramente qualche problema (picchi blu).

Ho già menzionato il lag del consumer-group. In sostanza, è il numero di messaggi non letti. In generale i nostri consumatori lavorano velocemente, quindi il lag è di solito 0, ma a volte può esserci un picco temporaneo. Kafka gestisce questa cosa nativamente, ma bisogna impostare un determinato intervallo.
C'è un progetto , che vi darà più informazioni su Kafka. Semplicemente tramite API, per il consumer-group restituisce lo stato di come stanno le cose per quel gruppo. Oltre a OK e Failed, ci sono anche warning, e potrete scoprire che i vostri consumatori non stanno tenendo il passo con il ritmo di produzione - non riescono a elaborare ciò che viene scritto. Il sistema è piuttosto intelligente, è comodo da usare.

Ecco come appare la risposta tramite API. Qui c'è il gruppo bob-live-fifa, partizione refund.update.v1, stato OK, lag 0 - l'offset finale più recente è questo.

Monitoraggio updated_at SLA (stuck) Ho già accennato. Ad esempio, il prodotto è passato allo stato di essere pronto per il reso. Impostiamo un Cron che indica che se dopo 5 minuti questo oggetto non è passato a refund (restituiamo i soldi tramite i sistemi di pagamento molto rapidamente), allora qualcosa è effettivamente andato storto, e questo è sicuramente un caso per il supporto. Quindi prendiamo semplicemente un Cron che legge queste cose, e se sono più di 0, invia un avviso.
In sintesi, è comodo usare eventi quando:
- l'informazione è necessaria a più sistemi;
- non è importante il risultato dell'elaborazione;
- ci sono pochi eventi o gli eventi sono piccoli.
A prima vista, l'articolo ha un tema molto specifico: API asincrono su Kafka, ma in relazione a questo vorrei subito raccomandare molte cose.
In primo luogo, il prossimo bisogna attendere fino a novembre, già ad aprile ci sarà la sua versione a San Pietroburgo, e a giugno parleremo di carichi elevati a Novosibirsk.
In secondo luogo, l'autore della relazione Sergey Zaika fa parte del Comitato di Programma della nostra nuova conferenza sulla gestione della conoscenza . La conferenza è di un giorno, si svolgerà il 26 aprile, ma il programma è molto ricco.
Inoltre, a maggio ci sarà e (con DevOpsConf inclusa) - qui è ancora possibile proporre il proprio tema, raccontare la propria esperienza e lamentarsi dei propri errori.
Fonte: habr.com
