Hogyan vált Kafka valósággá

Hogyan vált Kafka valósággá

Helló, Habr!

A Tinkoff csapatában dolgozom, amely a saját értesítési központunk fejlesztésével foglalkozik. Legtöbbször Java-n dolgozom a Spring Boot használatával, és különféle technikai problémákat oldok meg a projekt során.

Mikroszolgáltatásaink többsége aszinkron módon kommunikál egymással üzenetközvetítőn keresztül. Korábban az IBM MQ-t használtuk, amely már nem volt képes kezelni a terhelést, de magas szintű szállítási garanciákkal rendelkezett.

Helyette az Apache Kafka lett javasolva, amely magas skálázhatóságot kínál, de sajnos szinte egyedi konfigurálást igényel a különböző forgatókönyvekhez. Ezen kívül a Kafka alapértelmezett at least once delivery mechanizmusa nem tette lehetővé a kívánt szintű konzisztencia fenntartását. A következőkben megosztom tapasztalatainkat Kafka konfigurálásával kapcsolatban, különösen azt, hogyan állítsuk be és éljünk az exactly once delivery mechanizmussal.

Garantált szállítás és még sok más

A következő paraméterek segítenek megakadályozni a problémák egy sorát, amely a default csatlakozási beállításokkal kapcsolatos. De először szeretnék egy paraméterre fókuszálni, amely megkönnyíti a lehetséges hibakeresést.

Ebben segít client.id a Producer és Consumer esetében. Első pillantásra az alkalmazás neve használható értékként, és a legtöbb esetben ez működni fog. Azonban ha az alkalmazásban több Consumer is van és azonos client.id-t adunk meg nekik, akkor a következő figyelmeztetéshez vezet:

org.apache.kafka.common.utils.AppInfoParser — Hiba az AppInfo mbean regisztrálásakor javax.management.InstanceAlreadyExistsException: kafka.consumer:type=app-info,id=kafka.test-0

Ha JMX-et szeretne használni a Kafka alkalmazásban, ez problémát jelenthet. Ebben az esetben a legjobb, ha a client.id értékeként az alkalmazás neve és például a téma neve kombinációját használja. A konfigurációs eredményünket meg lehet nézni a következő parancs kimenetében: kafka-consumer-groups a Confluent eszközeiből:

Hogyan vált Kafka valósággá

Most vizsgáljuk meg a garantált üzenetszállítás forgatókönyvét. A Kafka Producernek van egy acksparamétere, amely lehetővé teszi, hogy beállítsa, hány elismerés után kell a klaszter vezetőjének sikeresen rögzítenie az üzenetet. Ez a paraméter a következő értékeket vehet fel:

  • 0 — az elismerések nem számítanak.
  • 1 — az alapértelmezett paraméter, amelyhez csak 1 replikától szükséges az acknowledge.
  • −1 — az összes szinkronizált replikától szükséges az acknowledge (klaszterbeállítás). min.insync.replicas).

A felsorolt értékekből látható, hogy a −1 értékű acks a legnagyobb garanciát adja arra, hogy az üzenet ne vesszen el.

Ahogy mindannyian tudjuk, a terjesztett rendszerek megbízhatatlanok. Az ideiglenes hibák elleni védelem érdekében a Kafka Producer biztosítja a retriesparamétert, amely lehetővé teszi az újraküldési kísérletek számának megadását a delivery.timeout.mskeretén belül. Mivel a retries paraméter alapértelmezett értéke Integer.MAX_VALUE (2147483647), az üzenet újraküldésének számát csak a delivery.timeout.ms megváltoztatásával lehet szabályozni.

Az exactly once delivery felé haladunk.

A felsorolt beállítások lehetővé teszik, hogy Producerünk nagy megbízhatósággal szállítsa az üzeneteket. Most beszéljünk arról, hogyan lehet garantálni, hogy egy üzenet csak egyszer kerüljön rögzítésre a Kafka-topikban. A legegyszerűbb esetben a Producernek be kell állítani az enable.idempotence paramétert true értékre. Az idempotencia azt garantálja, hogy egy üzenet csak egyszer kerüljön rögzítésre egy topik egy adott partíciójában. Az idempotencia engedélyezésének előfeltételei a következők: acks = all, retry > 0, max.in.flight.requests.per.connection ≤ 5.Ha ezek a paraméterek nincsenek megadva a fejlesztő által, automatikusan az előbb említett értékek lesznek beállítva.

Amikor az idempotencia be van állítva, biztosítani kell, hogy az azonos üzenetek minden alkalommal ugyanabba a partícióba kerüljenek. Ezt el lehet érni a Producer kulcsának és a partitioner.class paraméterének beállításával. Kezdjünk a kulccsal. Minden egyes küldés esetén azonosnak kell lennie. Ezt könnyen elérhetjük, ha az eredeti üzenetből egy üzleti azonosítót használunk. A partitioner.class paraméter alapértelmezett értéke: DefaultPartitioner.Ezen az alapértelmezett partícionálási stratégiánál a következőképpen járunk el:

  • Ha a partíciót kifejezetten megadták az üzenet küldésekor, azt használjuk.
  • Ha a partíció nincs megadva, de a kulcs meg van adva — a kulcs hash-én alapulva választunk partíciót.
  • Ha a partíció és a kulcs sincs megadva — a partíciókat körkörösen választjuk (round-robin).

Ezenkívül a kulcs használata és az idempotens küldés a max.in.flight.requests.per.connection = 1 paraméterrel. rendezett üzenetkezelést biztosít a Consumer számára. Emlékezzünk arra, hogy ha a klaszteredben engedélyezve van a hozzáféréskezelés, akkor jogokra lesz szükséged a vendéglátó tetejére történő idempotens íráshoz.

Ha esetleg hiányzik az idempotens küldés képessége a kulcs mentén, vagy a Producer oldalán a logika megköveteli az adatok konzisztenciájának megőrzését különböző partíciók között, akkor a tranzakciók segíthetnek. Ezenkívül lánc tranzakcióval feltételesen szinkronizálhatod a Kafka írást például egy adatbázis bejegyzésével. A tranzakciós küldés engedélyezéséhez a Producernek rendelkeznie kell idempotenciával, és be kell állítani transactional.id. Ha a Kafka klaszteredben engedélyezve van a hozzáféréskezelés, akkor a tranzakciós íráshoz, akárcsak az idempotens íráshoz, írási jogokra lesz szükséged, amelyeket a transactional.id érték használatával adhatunk meg.

Formálisan bármilyen karakterláncot használhatsz tranzakciós azonosítóként, például az alkalmazás nevét. De ha több példányt indítasz ugyanabból az alkalmazásból ugyanazzal a transactional.id-vel, az első elindított példány leáll egy hibával, mivel a Kafka zombifolyamatnak fogja tekinteni.

org.apache.kafka.common.errors.ProducerFencedException: A Producer régi éra műveletét próbálta végrehajtani. Vagy létezik egy újabb producer ugyanazzal a transactionalId-vel, vagy a producer tranzakcióját az ügynök lejárta miatt megszüntették.

A probléma megoldásához a nevünk végéhez hozzáfűzzük a hosztnevet, amelyet a környezeti változókból nyerünk.

A Producer be van állítva, de a Kafka tranzakciók csak az üzenetek láthatóságát kezelik. A tranzakció státuszától függetlenül az üzenet azonnal bekerül a témába, de további rendszerattribútumokkal rendelkezik.

Ahhoz, hogy ezek az üzenetek ne legyenek korábban feldolgozva a Consumer által, a következő paramétert kell beállítania isolation.level read_committed értékre. Ilyen Consumer képes lesz a nem tranzakciós üzenetek olvasására, mint korábban, míg a tranzakciósakat csak a commit után.
Ha minden korábban említett beállítást elvégeztél, akkor beállítottad az exactly once delivery-t. Gratulálok!

De van egy másik szempont is. A tranzakciós.id, amit korábban beállítottunk, valójában a tranzakció prefixe. A tranzakciós menedzser hozzáfűzi a sorszámot. A kapott azonosítót az alábbiakban kapjuk transactional.id.expiration.ms, amely Kafka klaszteren konfigurálható, és alapértelmezett értéke „7 nap”. Ha ez idő alatt az alkalmazás nem kapott üzeneteket, a következő tranzakciós küldés megkísérlésekor InvalidPidMappingException. Ezt követően a tranzakciós koordinátor új sorszámot ad a következő tranzakcióhoz. Az üzenet elveszhet, ha az InvalidPidMappingException-t nem kezelik megfelelően.

Az összegzések helyett

Amint látható, nem elég csupán üzeneteket küldeni a Kafka-ba. Ki kell választani a paraméterek kombinációját, és készen kell állni a gyors változtatásokra. Ebben a cikkben részletesen bemutattam a pontosan egyszeri kézbesítés beállítását, és leírtam néhány problémát a client.id és a transactional.id konfigurációkkal, amelyekkel találkoztunk. Lent röviden összefoglalom a Producer és Consumer beállításait.

Producer:

  1. acks = all
  2. retries > 0
  3. enable.idempotence = true
  4. max.in.flight.requests.per.connection ≤ 5 (1 a rendezett küldéshez)
  5. transactional.id = ${application-name}-${hostname}

Consumer:

  1. isolation.level = read_committed

A jövőbeli alkalmazásokban a hibák minimalizálása érdekében készítettünk egy burkot a spring konfigurációhoz, ahol már be vannak állítva az előzőleg említett paraméterek értékei.

Itt van néhány anyag az önálló tanulmányozáshoz:

Forrás: habr.com

Megbízható hosting vásárlása weboldalak számára, DDoS védelemmel, VPS VDS szerverek. 🔥 Megbízható webtárhely vásárlása DDoS védelemmel, VPS VDS szerverek | ProHoster