Äau, Habr.
Nesen es
MÅ«sdienu lietojumprogrammas darbojas ļoti sarežģītÄ vidÄ. Biznesa loÄ£ika, kas ietÄ«ta modernÄ tehnoloÄ£iju kaudzÄ, darbojas Docker attÄlÄ, ko pÄrvalda orÄ·estrÄtÄjs, piemÄram, Kubernetes vai OpenShift, un sazinÄs ar citÄm lietojumprogrammÄm vai uzÅÄmuma risinÄjumiem, izmantojot fizisko un virtuÄlo marÅ”rutÄtÄju Ä·Ädi. Å ÄdÄ vidÄ vienmÄr kaut kas var salÅ«zt, tÄpÄc notikumu atkÄrtota apstrÄde, ja kÄda no ÄrÄjÄm sistÄmÄm nav pieejama, ir svarÄ«ga mÅ«su biznesa procesu sastÄvdaļa.
KÄ tas bija pirms Kafkas
Projekta sÄkumÄ mÄs izmantojÄm IBM MQ asinhronai ziÅojumu piegÄdei. Ja pakalpojuma darbÄ«bas laikÄ radÄs kļūda, saÅemto ziÅojumu var ievietot miruÅ”o burtu rindÄ (DLQ) turpmÄkai manuÄlai parsÄÅ”anai. DLQ tika izveidots blakus ienÄkoÅ”ajai rindai, ziÅojums tika pÄrsÅ«tÄ«ts IBM MQ iekÅ”pusÄ.
Ja kļūda bija Ä«slaicÄ«ga un mÄs to varÄtu noteikt (piemÄram, ResourceAccessException HTTP zvanam vai MongoTimeoutException MongoDb pieprasÄ«jumam), stÄtos spÄkÄ atkÄrtota mÄÄ£inÄjuma stratÄÄ£ija. NeatkarÄ«gi no lietojumprogrammas sazaroÅ”anas loÄ£ikas, sÄkotnÄjais ziÅojums tika pÄrvietots vai nu uz sistÄmas rindu aizkavÄtai sÅ«tÄ«Å”anai, vai uz atseviŔķu lietojumprogrammu, kas tika izveidota jau sen, lai atkÄrtoti nosÅ«tÄ«tu ziÅojumus. Tas ietver atkÄrtotas nosÅ«tÄ«Å”anas numuru ziÅojuma galvenÄ, kas ir saistÄ«ts ar aizkaves intervÄlu vai lietojumprogrammas lÄ«meÅa stratÄÄ£ijas beigÄm. Ja esam sasnieguÅ”i stratÄÄ£ijas beigas, bet ÄrÄjÄ sistÄma joprojÄm nav pieejama, ziÅojums tiks ievietots DLQ manuÄlai parsÄÅ”anai.
RisinÄjumu meklÄÅ”ana
Neskatoties uz lielo pozitÄ«vo atsauksmju skaitu, man Ŕķiet, ka tas nav pilnÄ«bÄ veiksmÄ«gs. PirmkÄrt, tÄpÄc, ka izstrÄdÄtÄjam papildus biznesa prasÄ«bu ievieÅ”anai bÅ«s jÄpavada daudz laika, lai ieviestu aprakstÄ«to mehÄnismu.
TurklÄt, ja Kafka klasterÄ« ir iespÄjota piekļuves kontrole, jums bÅ«s jÄpavada zinÄms laiks, veidojot tÄmas un nodroÅ”inot tÄm nepiecieÅ”amo piekļuvi. Papildus tam jums bÅ«s jÄatlasa pareizais retention.ms parametrs katrai atkÄrtotÄ mÄÄ£inÄjuma tÄmai, lai ziÅojumiem bÅ«tu laiks atkÄrtoti nosÅ«tÄ«t un tie nepazustu. IevieÅ”ana un piekļuves pieprasÄ«jums bÅ«s jÄatkÄrto katram esoÅ”ajam vai jaunajam pakalpojumam.
Tagad paskatÄ«simies, kÄdus mehÄnismus atspere kopumÄ un jo Ä«paÅ”i atspere-kafka mums nodroÅ”ina ziÅojumu pÄrstrÄdei. Spring-kafka ir pÄrejoÅ”a atkarÄ«ba no atsperes atkÄrtoÅ”anas, kas nodroÅ”ina abstrakcijas dažÄdu BackOffPolicies pÄrvaldÄ«bai. Å is ir diezgan elastÄ«gs rÄ«ks, taÄu tÄ bÅ«tisks trÅ«kums ir ziÅojumu glabÄÅ”ana atkÄrtotai nosÅ«tÄ«Å”anai lietojumprogrammas atmiÅÄ. Tas nozÄ«mÄ, ka, restartÄjot lietojumprogrammu atjauninÄÅ”anas vai darbÄ«bas kļūdas dÄļ, tiks zaudÄti visi ziÅojumi, kas gaida atkÄrtotu apstrÄdi. TÄ kÄ Å”is punkts ir ļoti svarÄ«gs mÅ«su sistÄmai, mÄs to sÄ«kÄk neapskatÄ«jÄm.
Spring-kafka pati nodroÅ”ina, piemÄram, vairÄkas ContainerAwareErrorHandler implementÄcijas
Å Ä« pieeja ļauj atkÄrtoti apstrÄdÄtajiem ziÅojumiem izturÄt lietojumprogrammu restartÄÅ”anu, taÄu joprojÄm nav DLQ mehÄnisma. Å o opciju izvÄlÄjÄmies 2019. gada sÄkumÄ, optimistiski uzskatot, ka DLQ nebÅ«s vajadzÄ«gs (mums paveicÄs un pÄc vairÄku mÄneÅ”u ilgas aplikÄcijas darbÄ«bas ar Å”Ädu pÄrstrÄdes sistÄmu tas faktiski vairs nebija vajadzÄ«gs). Pagaidu kļūdu dÄļ tika aktivizÄts SeekToCurrentErrorHandler. AtlikuÅ”Äs kļūdas tika izdrukÄtas žurnÄlÄ, kÄ rezultÄtÄ tika veikta nobÄ«de, un apstrÄde tika turpinÄta ar nÄkamo ziÅojumu.
Gala lÄmums
IevieÅ”ana, kuras pamatÄ ir SeekToCurrentErrorHandler, pamudinÄja mÅ«s izstrÄdÄt savu mehÄnismu ziÅojumu atkÄrtotai sÅ«tÄ«Å”anai.
PirmkÄrt, vÄlÄjÄmies izmantot esoÅ”o pieredzi un paplaÅ”inÄt to atkarÄ«bÄ no pielietojuma loÄ£ikas. LineÄrÄs loÄ£ikas lietojumprogrammai bÅ«tu optimÄli pÄrtraukt jaunu ziÅojumu lasÄ«Å”anu uz Ä«su laika periodu, ko nosaka atkÄrtoÅ”anas stratÄÄ£ija. CitÄm lietojumprogrammÄm es gribÄju, lai bÅ«tu viens punkts, kas Ä«stenotu atkÄrtoÅ”anas stratÄÄ£iju. TurklÄt Å”im vienam punktam ir jÄbÅ«t DLQ funkcionalitÄtei abÄm pieejÄm.
Pati atkÄrtotÄ mÄÄ£inÄjuma stratÄÄ£ija ir jÄsaglabÄ lietojumprogrammÄ, kas ir atbildÄ«ga par nÄkamÄ intervÄla izgÅ«Å”anu, ja rodas Ä«slaicÄ«ga kļūda.
LineÄrÄs loÄ£ikas lietojumprogrammas patÄrÄtÄja apturÄÅ”ana
StrÄdÄjot ar spring-kafka, patÄrÄtÄja apturÄÅ”anas kods var izskatÄ«ties apmÄram Å”Ädi:
public void pauseListenerContainer(MessageListenerContainer listenerContainer,
Instant retryAt) {
if (nonNull(retryAt) && listenerContainer.isRunning()) {
listenerContainer.stop();
taskScheduler.schedule(() -> listenerContainer.start(), retryAt);
return;
}
// to DLQ
}
PiemÄrÄ retryAt ir laiks, lai restartÄtu MessageListenerContainer, ja tas joprojÄm darbojas. AtkÄrtota palaiÅ”ana notiks atseviÅ”Ä·Ä pavedienÄ, kas palaists TaskScheduler, kura ievieÅ”anu nodroÅ”ina arÄ« pavasaris.
MÄs atrodam retryAt vÄrtÄ«bu Å”ÄdÄ veidÄ:
- Tiek meklÄta atkÄrtotu zvanu skaitÄ«tÄja vÄrtÄ«ba.
- Pamatojoties uz skaitÄ«tÄja vÄrtÄ«bu, tiek meklÄts paÅ”reizÄjais aizkaves intervÄls atkÄrtotÄ mÄÄ£inÄjuma stratÄÄ£ijÄ. StratÄÄ£ija ir deklarÄta paÅ”Ä lietojumprogrammÄ; mÄs izvÄlÄjÄmies JSON formÄtu, lai to saglabÄtu.
- JSON masÄ«vÄ atrastais intervÄls satur sekunžu skaitu, pÄc kura apstrÄde bÅ«s jÄatkÄrto. Å is sekunžu skaits tiek pievienots paÅ”reizÄjam laikam, lai izveidotu vÄrtÄ«bu retryAt.
- Ja intervÄls netiek atrasts, retryAt vÄrtÄ«ba ir nulle un ziÅojums tiks nosÅ«tÄ«ts uz DLQ manuÄlai parsÄÅ”anai.
Izmantojot Å”o pieeju, atliek tikai saglabÄt atkÄrtoto zvanu skaitu katram ziÅojumam, kas paÅ”laik tiek apstrÄdÄts, piemÄram, lietojumprogrammas atmiÅÄ. AtkÄrtoto mÄÄ£inÄjumu skaita saglabÄÅ”ana atmiÅÄ Å”ai pieejai nav bÅ«tiska, jo lineÄrÄs loÄ£ikas lietojumprogramma nevar apstrÄdÄt apstrÄdi kopumÄ. AtŔķirÄ«bÄ no pavasara atkÄrtotas mÄÄ£inÄjuma, lietojumprogrammas restartÄÅ”ana neizraisÄ«s visu pazaudÄto ziÅojumu atkÄrtotu apstrÄdi, bet vienkÄrÅ”i restartÄs stratÄÄ£iju.
Å Ä« pieeja palÄ«dz noÅemt slodzi no ÄrÄjÄs sistÄmas, kas var nebÅ«t pieejama ļoti lielas slodzes dÄļ. Citiem vÄrdiem sakot, papildus atkÄrtotai apstrÄdei mÄs panÄcÄm modeļa ievieÅ”anu
MÅ«su gadÄ«jumÄ kļūdu slieksnis ir tikai 1, un, lai samazinÄtu sistÄmas dÄ«kstÄvi Ä«slaicÄ«gu tÄ«kla pÄrtraukumu dÄļ, mÄs izmantojam ļoti detalizÄtu atkÄrtoÅ”anas stratÄÄ£iju ar maziem latentuma intervÄliem. Tas var nebÅ«t piemÄrots visÄm grupu lietojumprogrammÄm, tÄpÄc attiecÄ«bas starp kļūdu slieksni un intervÄla vÄrtÄ«bu ir jÄizvÄlas, pamatojoties uz sistÄmas Ä«paŔībÄm.
AtseviŔķa lietojumprogramma ziÅojumu apstrÄdei no lietojumprogrammÄm ar nedeterministisku loÄ£iku
Å eit ir koda piemÄrs, kas nosÅ«ta ziÅojumu Å”Ädai lietojumprogrammai (atkÄrtotajam mÄÄ£inÄjumam), kas tiks atkÄrtoti nosÅ«tÄ«ts uz tÄmu DESTINATION, kad bÅ«s sasniegts RETRY_AT laiks:
public <K, V> void retry(ConsumerRecord<K, V> record, String retryToTopic,
Instant retryAt, String counter, String groupId, Exception e) {
Headers headers = ofNullable(record.headers()).orElse(new RecordHeaders());
List<Header> arrayOfHeaders =
new ArrayList<>(Arrays.asList(headers.toArray()));
updateHeader(arrayOfHeaders, GROUP_ID, groupId::getBytes);
updateHeader(arrayOfHeaders, DESTINATION, retryToTopic::getBytes);
updateHeader(arrayOfHeaders, ORIGINAL_PARTITION,
() -> Integer.toString(record.partition()).getBytes());
if (nonNull(retryAt)) {
updateHeader(arrayOfHeaders, COUNTER, counter::getBytes);
updateHeader(arrayOfHeaders, SEND_TO, "retry"::getBytes);
updateHeader(arrayOfHeaders, RETRY_AT, retryAt.toString()::getBytes);
} else {
updateHeader(arrayOfHeaders, REASON,
ExceptionUtils.getStackTrace(e)::getBytes);
updateHeader(arrayOfHeaders, SEND_TO, "backout"::getBytes);
}
ProducerRecord<K, V> messageToSend =
new ProducerRecord<>(retryTopic, null, null, record.key(), record.value(), arrayOfHeaders);
kafkaTemplate.send(messageToSend);
}
PiemÄrÄ redzams, ka liela daļa informÄcijas tiek pÄrsÅ«tÄ«ta galvenÄs. RETRY_AT vÄrtÄ«ba tiek atrasta tÄdÄ paÅ”Ä veidÄ kÄ atkÄrtota mÄÄ£inÄjuma mehÄnismam, izmantojot patÄrÄtÄju pieturu. Papildus DESTINATION un RETRY_AT mÄs izbraucam:
- GROUP_ID, ar kuru mÄs grupÄjam ziÅojumus manuÄlai analÄ«zei un vienkÄrÅ”otai meklÄÅ”anai.
- ORIGINAL_PARTITION, lai mÄÄ£inÄtu saglabÄt to paÅ”u patÄrÄtÄju atkÄrtotai apstrÄdei. Å is parametrs var bÅ«t nulle, un tÄdÄ gadÄ«jumÄ jaunais nodalÄ«jums tiks iegÅ«ts, izmantojot sÄkotnÄjÄ ziÅojuma atslÄgu record.key().
- AtjauninÄta COUNTER vÄrtÄ«ba, lai ievÄrotu atkÄrtotÄ mÄÄ£inÄjuma stratÄÄ£iju.
- SEND_TO ir konstante, kas norÄda, vai ziÅojums tiek nosÅ«tÄ«ts atkÄrtotai apstrÄdei, sasniedzot RETRY_AT, vai ievietots DLQ.
- REASON ā iemesls, kÄpÄc ziÅojumu apstrÄde tika pÄrtraukta.
AtkÄrtoti mÄÄ£inÄt saglabÄt ziÅojumus atkÄrtotai nosÅ«tÄ«Å”anai un manuÄlai parsÄÅ”anai programmÄ PostgreSQL. Taimeris sÄk uzdevumu, kas atrod ziÅojumus ar RETRY_AT un nosÅ«ta tos atpakaļ uz tÄmas MÄRĶA nodalÄ«jumu ORIGINAL_PARTITION ar atslÄgu record.key().
PÄc nosÅ«tÄ«Å”anas ziÅojumi tiek dzÄsti no PostgreSQL. ZiÅojumu manuÄla parsÄÅ”ana notiek vienkÄrÅ”Ä lietotÄja saskarnÄ, kas mijiedarbojas ar atkÄrtotÄju, izmantojot REST API. TÄs galvenÄs funkcijas ir ziÅojumu atkÄrtota nosÅ«tÄ«Å”ana vai dzÄÅ”ana no DLQ, kļūdu informÄcijas skatÄ«Å”ana un ziÅojumu meklÄÅ”ana, piemÄram, pÄc kļūdas nosaukuma.
TÄ kÄ mÅ«su klasteros ir iespÄjota piekļuves kontrole, ir papildus jÄpieprasa piekļuve tÄmai, ko klausÄs Retryer, un jÄļauj atkÄrtotajam mÄÄ£inÄjumam rakstÄ«t tÄmai DESTINATION. Tas ir neÄrti, taÄu atŔķirÄ«bÄ no intervÄla tÄmu pieejas mums ir pilnvÄrtÄ«gs DLQ un lietotÄja interfeiss, lai to pÄrvaldÄ«tu.
Ir gadÄ«jumi, kad ienÄkoÅ”o tÄmu lasa vairÄkas dažÄdas patÄrÄtÄju grupas, kuru lietotnÄs tiek realizÄta atŔķirÄ«ga loÄ£ika. AtkÄrtoti apstrÄdÄjot ziÅojumu, izmantojot atkÄrtotu mÄÄ£inÄjumu vienai no Ŕīm lietojumprogrammÄm, tiks izveidots dublikÄts otrÄ. Lai aizsargÄtos pret to, mÄs izveidojam atseviŔķu tÄmu atkÄrtotai apstrÄdei. IenÄkoÅ”Äs un atkÄrtotÄs tÄmas var lasÄ«t viens un tas pats PatÄrÄtÄjs bez ierobežojumiem.
PÄc noklusÄjuma Ŕī pieeja nenodroÅ”ina Ä·Ädes pÄrtraucÄja funkcionalitÄti, taÄu to var pievienot lietojumprogrammai, izmantojot
secinÄjums
RezultÄtÄ mums ir atseviŔķa lietojumprogramma, kas ļauj atkÄrtot ziÅojumu apstrÄdi, ja kÄda ÄrÄja sistÄma Ä«slaicÄ«gi nav pieejama.
Viena no galvenajÄm lietojumprogrammas priekÅ”rocÄ«bÄm ir tÄ, ka to var izmantot ÄrÄjÄs sistÄmas, kas darbojas tajÄ paÅ”Ä Kafka klasterÄ«, bez bÅ«tiskÄm izmaiÅÄm to pusÄ! Å Ädai lietojumprogrammai vajadzÄs tikai piekļūt atkÄrtotÄ mÄÄ£inÄjuma tÄmai, aizpildÄ«t dažas Kafka galvenes un nosÅ«tÄ«t ziÅojumu atkÄrtoti mÄÄ£inÄtÄjam. Nav nepiecieÅ”ams celt papildu infrastruktÅ«ru. Un, lai samazinÄtu ziÅojumu skaitu, kas tiek pÄrsÅ«tÄ«ti no lietojumprogrammas uz Retryer un atpakaļ, mÄs identificÄjÄm lietojumprogrammas ar lineÄru loÄ£iku un atkÄrtoti apstrÄdÄjÄm tÄs, izmantojot patÄrÄtÄju pieturu.
Avots: www.habr.com