Kiel Kafka fariĝis realo

Kiel Kafka fariĝis realo

Hej Habr!

Mi laboras en la teamo Tinkoff, kiu disvolvas sian propran sciigan centron. Mi plejparte evoluas en Java uzante Spring boot kaj solvas diversajn teknikajn problemojn, kiuj aperas en projekto.

Plej multaj el niaj mikroservoj komunikas unu kun la alia nesinkrone per mesaĝmakleristo. Antaŭe, ni uzis IBM MQ kiel makleriston, kiu ne plu povis elteni la ŝarĝon, sed samtempe havis altajn liverajn garantiojn.

Kiel anstataŭaĵo, oni proponis al ni Apache Kafka, kiu havas altan skalan potencialon, sed, bedaŭrinde, postulas preskaŭ individuan aliron al agordo por malsamaj scenaroj. Krome, la almenaŭ unufoje livera mekanismo kiu funkcias en Kafka defaŭlte ne permesis konservi la bezonatan nivelon de konsistenco el la skatolo. Poste, mi dividos nian sperton pri Kafka-agordo, precipe, mi rakontos al vi kiel agordi kaj vivi kun ekzakte unufoje livero.

Garantiita livero kaj pli

La agordoj diskutitaj sube helpos malhelpi kelkajn problemojn kun la defaŭltaj konekto-agordoj. Sed unue mi ŝatus atenti unu parametron, kiu faciligos eblan sencimon.

Ĉi tio helpos kliento.id por Produktanto kaj Konsumanto. Unuavide, vi povas uzi la nomon de la aplikaĵo kiel valoron, kaj plejofte ĉi tio funkcios. Kvankam la situacio kiam aplikaĵo uzas plurajn Konsumantojn kaj vi donas al ili la saman client.id, rezultigas la sekvan averton:

org.apache.kafka.common.utils.AppInfoParser — Error registering AppInfo mbean javax.management.InstanceAlreadyExistsException: kafka.consumer:type=app-info,id=kafka.test-0

Se vi volas uzi JMX en aplikaĵo kun Kafka, tiam ĉi tio povus esti problemo. Por ĉi tiu kazo, estas plej bone uzi kombinaĵon de la aplika nomo kaj, ekzemple, la temo nomo kiel la client.id valoro. La rezulto de nia agordo videblas en la komanda eligo kafka-konsumantaj-grupoj de servaĵoj de Confluent:

Kiel Kafka fariĝis realo

Nun ni rigardu la scenaron por garantiita mesaĝo livero. Kafka Producer havas parametron akoj, kiu ebligas al vi agordi post kiom da agnoskoj la clustergvidanto bezonas konsideri la mesaĝon sukcese skribita. Ĉi tiu parametro povas preni la sekvajn valorojn:

  • 0 — agnosko ne estos konsiderata.
  • 1 estas la defaŭlta parametro, nur 1 kopio estas postulata por agnoski.
  • −1 — agnosko de ĉiuj sinkronigitaj kopioj estas bezonata (grupo-agordo min.insync.replicas).

El la listigitaj valoroj estas klare, ke akoj egalaj al −1 donas la plej fortan garantion, ke la mesaĝo ne perdiĝos.

Kiel ni ĉiuj scias, distribuitaj sistemoj estas nefidindaj. Por protekti kontraŭ pasemaj faŭltoj, Kafka Producer disponigas la opcion reprovoj, kiu ebligas al vi agordi la nombron da resendprovoj ene livero.timeout.ms. Ĉar la parametro de reprovoj havas defaŭltan valoron de Entjero.MAX_VALUE (2147483647), la nombro da mesaĝprovoj povas esti ĝustigita ŝanĝante nur delivery.timeout.ms.

Ni iras al ekzakte unufoje livero

La listigitaj agordoj permesas al nia Produktanto liveri mesaĝojn kun alta garantio. Ni nun parolu pri kiel certigi, ke nur unu kopio de mesaĝo estas skribita al Kafka temo? En la plej simpla kazo, por fari tion, vi devas agordi la parametron sur Produktanto ebligi.idempotenco al vera. Idempotency garantias ke nur unu mesaĝo estas skribita al specifa sekcio de unu temo. La antaŭkondiĉo por ebligi idempotencon estas la valoroj acks = ĉiuj, reprovi > 0, max.in.flight.requests.per.connection ≤ 5. Se ĉi tiuj parametroj ne estas specifitaj de la programisto, la supraj valoroj estos aŭtomate fiksitaj.

Kiam idempotenco estas agordita, necesas certigi, ke la samaj mesaĝoj finiĝas en la samaj sekcioj ĉiufoje. Ĉi tio povas esti farita per agordo de la klavo kaj parametro partitioner.class al Produktanto. Ni komencu per la ŝlosilo. Ĝi devas esti la sama por ĉiu submetaĵo. Ĉi tio povas esti facile atingita uzante iun ajn el la komercaj identigiloj de la originala afiŝo. La parametro partitioner.class havas defaŭltan valoron − Defaŭlta Partitioner. Kun ĉi tiu partiga strategio, defaŭlte ni agas tiel:

  • Se la subdisko estas eksplicite specifita dum sendado de la mesaĝo, tiam ni uzas ĝin.
  • Se la sekcio ne estas specifita, sed la ŝlosilo estas specifita, elektu la subdiskon per la hash de la ŝlosilo.
  • Se la subdisko kaj ŝlosilo ne estas specifitaj, elektu la sekciojn unu post la alia (ĉirkaŭklavo).

Ankaŭ, uzante ŝlosilon kaj idempotent sendado kun parametro max.in.flight.requests.per.connection = 1 donas al vi simpligitan mesaĝan prilaboradon ĉe la Konsumanto. Ankaŭ indas memori, ke se alirkontrolo estas agordita sur via areto, tiam vi bezonos rajtojn por idepotente skribi al temo.

Se subite mankas al vi la kapabloj de idempotenta sendo per ŝlosilo aŭ la logiko sur la Produktanto-flanko postulas konservi datuman konsistencon inter malsamaj sekcioj, tiam transakcioj venos al la rekupero. Krome, uzante ĉenan transakcion, vi povas kondiĉe sinkronigi rekordon en Kafka, ekzemple, kun rekordo en la datumbazo. Por ebligi transakcian sendon al la Produktanto, ĝi devas esti idempotenta kaj aldone agordita transakcia.id. Se via Kafka-grupo havas alirkontrolon agordita, tiam transakcia rekordo, kiel idempotenta rekordo, bezonos skribpermesojn, kiuj povas esti donitaj per masko uzante la valoron stokita en transactional.id.

Formale, ajna ĉeno, kiel la aplika nomo, povas esti uzata kiel transakcia identigilo. Sed se vi lanĉas plurajn okazojn de la sama aplikaĵo kun la sama transakcia.id, tiam la unua lanĉita kazo estos ĉesigita kun eraro, ĉar Kafka konsideros ĝin zombia procezo.

org.apache.kafka.common.errors.ProducerFencedException: Producer attempted an operation with an old epoch. Either there is a newer producer with the same transactionalId, or the producer's transaction has been expired by the broker.

Por solvi ĉi tiun problemon, ni aldonas sufikson al la aplika nomo en la formo de la gastiga nomo, kiun ni akiras de mediovariabloj.

La produktanto estas agordita, sed transakcioj sur Kafka nur kontrolas la amplekson de la mesaĝo. Sendepende de la transakcia stato, la mesaĝo tuj iras al la temo, sed havas pliajn sistemajn atributojn.

Por malhelpi tiajn mesaĝojn esti legitaj de la Konsumanto antaŭtempe, ĝi devas agordi la parametron izoleco.nivelo to read_committed value. Tia Konsumanto povos legi ne-transakciajn mesaĝojn kiel antaŭe, kaj transakciajn mesaĝojn nur post transdono.
Se vi starigis ĉiujn agordojn listigitajn antaŭe, tiam vi agordis ĝuste unufoje liveron. Gratulon!

Sed estas unu plia nuanco. Transactional.id, kiun ni agordis supre, estas fakte la transakcia prefikso. Sur la transakcia administranto, sekvencnumero estas aldonita al ĝi. La ricevita identigilo estas elsendita al transakcia.id.expiration.ms, kiu estas agordita sur Kafka areto kaj havas defaŭltan valoron de "7 tagoj". Se dum ĉi tiu tempo la aplikaĵo ne ricevis mesaĝojn, tiam kiam vi provos la sekvan transakcian sendon, vi ricevos NevalidPidMappingException. La transakcia kunordiganto tiam eldonos novan sinsekvon por la sekva transakcio. Tamen, la mesaĝo povas esti perdita se la InvalidPidMappingException ne estas traktita ĝuste.

Anstataŭ rezultoj

Kiel vi povas vidi, ne sufiĉas simple sendi mesaĝojn al Kafka. Vi devas elekti kombinaĵon de parametroj kaj esti preta fari rapidajn ŝanĝojn. En ĉi tiu artikolo, mi provis montri detale la ekzakte unufoje liveran aranĝon kaj priskribis plurajn problemojn kun la agordoj client.id kaj transactional.id, kiujn ni renkontis. Malsupre estas resumo de la agordoj de Produktanto kaj Konsumanto.

Produktanto:

  1. acks = all
  2. reprovoj > 0
  3. enable.idempotence = vera
  4. max.in.flight.requests.per.connection ≤ 5 (1 por orda sendado)
  5. transactional.id = ${aplikaĵo-nomo}-${gastigantonomo}

Konsumanto:

  1. isolation.level = legi_farita

Por minimumigi erarojn en estontaj aplikoj, ni faris nian propran envolvaĵon super la printempa agordo, kie valoroj por iuj el la listigitaj parametroj jam estas fiksitaj.

Jen kelkaj materialoj por memlernado:

fonto: www.habr.com

Aldoni komenton