Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Што можа прымусіць такую ​​вялікую кампанію як Lamoda з адладжаным працэсам і дзясяткамі ўзаемазлучаных сэрвісаў істотна змяняць падыход? Матывацыя можа быць зусім розная: ад заканадаўчай да ўласцівага ўсім праграмістам жадання эксперыментаваць.

Але гэта зусім не значыць, што нельга разлічваць на дадатковую выгаду. У чым канкрэтна можна выйграць, калі ўкараніць events-driven API на Kafka, раскажа Сяргей Заіка (fewald). Пра набітыя гузы і цікавыя адкрыцці таксама абавязкова будзе – не можа эксперымент без іх абысціся.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Disclaimer: Гэты артыкул заснаваны на матэрыялах мітапа, які Сяргей правёў у лістападзе 2018 года на HighLoad++. Жывы досвед Lamoda працы з Kafka прыцягнуў слухачоў не менш, чым на іншыя даклады з раскладу. Нам здаецца, гэта выдатны прыклад таго, што заўсёды можна і трэба знаходзіць аднадумцаў, а арганізатары HighLoad++ і далей будуць імкнуцца ствараць прыхільную да гэтага атмасферу.

Пра працэс

Lamoda - гэта буйная e-commerce платформа, у якой ёсць свой кантакт-цэнтр, служба дастаўкі (і мноства партнёрскіх), фотастудыя, велізарны склад і ўсё гэта працуе на сваім софце. Ёсць дзясяткі спосабаў аплаты, b2b-партнёры, якія могуць карыстацца часткай ці ўсімі гэтымі паслугамі і жадаюць ведаць актуальную інфармацыю па сваіх таварах. Да таго ж, Lamoda працуе ў трох краінах апроч РФ і там усё крыху па-свойму. Разам існуе, мусіць, больш за сотню спосабаў сканфігураваць новую замову, які павінен быць па-свойму апрацаваны. Усё гэта працуе з дапамогай дзясяткаў сэрвісаў, якія маюць зносіны часам невідавочным чынам. Яшчэ ёсць цэнтральная сістэма, чыя галоўная адказнасць - гэта статусы заказаў. Мы называем яе BOB, я працую з ей.

Refund Tool with events-driven API

Слова events-driven даволі заезджанае, крыху далей падрабязней вызначым, што пад гэтым разумеецца. Пачну з кантэксту, у якім мы вырашылі апрабаваць падыход events-driven API у Kafka.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

У любой краме, акрамя заказаў, за якія пакупнікі плацяць, бываюць моманты, калі ад крамы патрабуецца вярнуць грошы, таму што кліенту тавар не падышоў. Гэты параўнальна кароткі працэс: удакладняем інфармацыю, калі ёсць такая неабходнасць, і пераводзім грошы.

Але зварот ускладніўся ў сілу змены заканадаўства, і нам прыйшлося рэалізаваць пад яго асобны мікрасэрвіс.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Наша матывацыя:

  1. Закон ФЗ-54 - Коратка, закон патрабуе паведамляць у падатковую аб кожнай грашовай аперацыі, няхай гэта будзе вяртанне або прыход, у даволі кароткі SLA у некалькі хвілін. Мы, як e-commerce, праводзім даволі шмат аперацый. Тэхнічна гэта значыць новую адказнасць (а значыць новы сэрвіс) і дапрацоўкі ва ўсіх датычных сістэмах.
  2. BOB split - унутраны праект кампаніі па збавенні BOB ад вялікай колькасці няпрофільных адказнасцяў і зніжэнню яго агульнай складанасці.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

На гэтай схеме намаляваны асноўныя сістэмы Lamoda. Цяпер большасць з іх уяўляе сабой хутчэй сузор'е з 5-10 мікрасэрвісаў вакол маналіта, які памяншаецца.. Яны паціху растуць, але мы імкнемся рабіць іх менш, таму што дэплоіць выдзелены ў сярэдзіне фрагмент страшна - нельга дапусціць, каб ён упаў. Усе абмены (стрэлачкі) мы змушаныя рэзерваваць і закладвацца на тое, што любы з іх можа апынуцца недаступным.

У BOB таксама даволі шмат абменаў: сыстэмы аплаты, дастаўкі, натыфікацыі і г.д.

Тэхнічна BOB гэта:

  • ~150k радкоў кода + ~100k радкоў тэстаў;
  • php7.2 + Zend 1 & Symfony Components 3;
  • >100 API & ~50 выходных інтэграцый;
  • 4 краіны са сваёй бізнес-логікай.

Дэплоіць BOB дорага і балюча, колькасць кода і развязальных ім задач такое, што ніхто не можа пакласці яго ў галаву цалкам. Увогуле, шмат нагод яго спрасціць.

Працэс вяртання

Першапачаткова ў працэс уцягнуты дзве сістэмы: BOB і Payment. Цяпер з'яўляюцца яшчэ дзве:

  • Fiscalization Service, які возьме на сябе праблемы з фіскалізацыяй і зносіны з вонкавымі сэрвісамі.
  • Refund Tool, у які проста выносяцца новыя абмены, каб не раздзімаць BOB.

Цяпер працэс выглядае так:

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

  1. Да BOB прыходзіць запыт на вяртанне грошай.
  2. BOB кажа аб гэтым Refund Tool.
  3. Refund Tool кажа Payment: "Вярні грошы".
  4. Payment вяртае грошы.
  5. Refund Tool і BOB сінхранізуюць паміж сабой статуты, таму што пакуль ім абодвум гэта трэба. Мы пакуль не гатовыя цалкам пераключыцца ў Refund Tool, паколькі ў BOB ёсць UI, справаздачы для бухгалтэрыі, і ўвогуле шмат дадзеных, якія так проста не перанясеш. Даводзіцца сядзець на двух крэслах.
  6. Адыходзіць запыт на фіскалізацыю.

У выніку мы зрабілі на Kafka нейкую шыну падзей - event-bus, на якой усё завязалася. Ура, зараз у нас ёсць адзіная кропка адмовы (сарказм).

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Плюсы і мінусы даволі відавочныя. Мы зрабілі шыну, значыць, зараз усе сэрвісы ад яе залежаць. Гэта спрашчае праектаванне, але прыўносіць у сістэму адзіны пункт адмовы. Упадзе kafka, працэс устане.

Што такое events-driven API

Добры адказ на гэтае пытанне ёсць у дакладзе Марціна Фаулера (GOTO 2017) "Малы Meanings of Event-Driven Architecture".

Коратка, што мы зрабілі:

  1. Загарнулі ўсе асінхронныя абмены праз events storage. Замест таго, каб паведамляць па сетцы кожнаму зацікаўленаму спажыўцу аб змене статуту, мы пішам у цэнтралізаванае сховішча падзея аб змене стану, а зацікаўленыя ў топі спажыўцы чытаюць адтуль усё, што з'яўляецца.
  2. Падзея (event) у дадзеным выпадкам - гэта апавяшчэнне (апавяшчэння) аб тым, што нешта дзесьці памянялася. Напрыклад, змяніўся статус замовы. Спажывец, якому важныя нейкія суправаджальныя змены статусу дадзеныя і якіх няма ў апавяшчэнні, можа даведацца пра іх стан сам.
  3. Максімальны варыянт - паўнавартасны event sourcing, state transfer, пры якім event утрымоўвае ўсю інфармацыю, неабходную для апрацоўкі: адкуль і ў які статут перайшлі, як менавіта памяняліся дадзеныя і інш. Пытанне толькі ў мэтазгоднасці і ў аб'ёме інфармацыі які вы можаце сабе дазволіць захоўваць.

У рамках запуску Refund Tool мы выкарыстоўвалі трэці варыянт. Гэта спрасціла апрацоўку падзей, паколькі не трэба здабываць падрабязную інфармацыю, плюс выключыла сцэнар, калі кожная новая падзея спараджае ўсплёск удакладняючых get-запытаў ад спажыўцоў.

Сэрвіс Refund Tool не нагружаны, таму Kafka там хутчэй проба пяра, чым неабходнасць. Не думаю, што, калі б сэрвіс вяртання сродкаў стаў highload-праектам, бізнэс быў бы рады.

Async exchange AS IS

Для асінхронных абменаў, PHP дэпартамент звычайна выкарыстоўвае RabbitMQ. Сабралі дадзеныя для запыту, паклалі ў чаргу, і кансьюмер гэтага ж сэрвісу яго лічыў і адправіў (ці не адправіў). Для самога API Lamoda актыўна выкарыстоўвае Swagger. Праектуем API, апісваем яго ў Swagger, які генеруецца кліенцкі і серверны код. Яшчэ мы выкарыстоўваем крыху пашыраны JSON RPC 2.0.

Дзе-нідзе выкарыстоўваюцца esb-шыны, хтосьці жыве на activeMQ, але, у цэлым, RabbitMQ - стандарт.

Async exchange TO BE

Праектуючы абмен праз events-bus, прасочваецца аналогія. Мы падобнай выявай апісваем будучы абмен дадзенымі праз апісанні структуры event'а. Фармат yaml, кодагенерацыю прыйшлося рабіць самім, генератар па спецыфікацыі стварае DTO і вучыць кліенты і серверы з імі працаваць. Генерацыя ідзе на дзве мовы - golang і php. Гэта дазваляе трымаць бібліятэкі ўзгодненымі. Генератар напісаны на golang, завошта атрымаў імя gogi.

Event-sourcing на Kafka - рэч тыповая. Ёсць рашэнне ад галоўнай enterprise версіі Kafka Confluent, ёсць nakadi, рашэнне ад нашых "братоў" па даменнай вобласці Zalando. Наша матывацыя пачаць з vanilla Kafka - гэта пакінуць рашэнне бясплатным, пакуль канчаткова не вырашылі ці будзем паўсюдна яго выкарыстоўваць, а таксама пакінуць сабе прастору для манеўру і дапрацовак: мы хочам падтрымку свайго JSON RPC 2.0, генератары пад дзве мовы і паглядзім што яшчэ.

Іранічна, што нават у такім шчаслівым выпадку, калі ёсць прыкладна аналагічны бізнэс Zalando, які зрабіў прыкладна падобнае рашэнне, мы не можам яго эфектыўна выкарыстоўваць.

Архітэктурна на запуску патэрн такі: чытэльны напроста з Kafka, але пішам толькі праз events-bus. Для чытання ў Kafka шмат гатовага: брокеры, балансавальнікі і яна больш-менш гатовая пад маштабаванне па-гарызанталі, гэта хацелася захаваць. Запіс жа, мы захацелі загарнуць праз адзін Gateway aka Events-bus, і вось чаму.

Events-bus

Або аўтобус падзей. Гэта проста stateless http gateway, які бярэ на сябе некалькі важных роляў:

  • Валідацыя прадзюсінгу - правяраем, што падзеі адказваюць нашай спецыфікацыі.
  • Майстар-сістэма па падзеях, гэта значыць гэта галоўная і адзіная сістэма ў кампаніі, якая адказвае на пытанне, якія ж events з якімі структурамі лічацца валіднымі. У валідацыю ўваходзяць проста тыпы дадзеных і enums для цвёрдай спецыфікацыі змесціва.
  • Hash-функцыя для шардзіравання - структура паведамлення Kafka гэта key-value і вось па хешу ад key вылічаецца, куды гэта класці.

Чаму

Мы працуем у вялікай кампаніі з адладжаным працэсам. Навошта нешта мяняць? Гэта эксперымент, і мы разлічваем атрымаць некалькі выгод.

1:n+1 абмены (адзін да многіх)

З Kafka вельмі проста падлучаць да API новых спажыўцоў.

Дапусцім у вас ёсць даведнік, які трэба трымаць актуальным у некалькіх сістэмах адразу (і ў якіх-небудзь новых). Раней мы вынаходзілі bundle, які рэалізоўваў set-API, а майстар-сістэме паведамлялі адрасы спажыўцоў. Цяпер майстар-сістэма шле абнаўленні ў топік, а ўсё, каму цікава чытаюць. З'явілася новая сістэма - падпісалі яе на топік. Так, таксама bundle, але прасцей.

У выпадку з refund-tool, які сутнасць кавалачак BOB, нам зручна праз Kafka трымаць іх сінхранізаванымі. Payment кажа, што грошы вярнулі: BOB, RT аб гэтым даведаліся, памянялі сабе статуты, Fiscalization Service пра гэта даведаўся і выбіў чэк.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

У нас ёсць планы зрабіць адзіны Notifications Service, які б апавяшчаў кліента аб навінах па яго замове / зваротам. Цяпер гэтая адказнасць размазана паміж сістэмамі. Нам будзе дастаткова навучыць Notifications Service вылоўліваць з Kafka рэлевантную інфармацыю і рэагаваць на яе (і адключыць у астатніх сістэмах гэтыя апавяшчэнні). Ніякіх новых прамых абменаў не спатрэбіцца.

На аснове дадзеных

Інфармацыя паміж сістэмамі становіцца празрыстай - які б «крывавы enterprise» у вас не стаяў і якім бы пульхным не быў ваш backlog. У Lamoda ёсць аддзел Data Analytics, які збірае дадзеныя па сістэмах і прыводзіць іх у перавыкарыстоўваецца выгляд, як для бізнесу, так і для інтэлектуальных сістэм. Kafka дазваляе хутка даць ім шмат даных і трымаць гэты інфармацыйны паток актуальным.

Replication log

Паведамленні не знікаюць пасля чытання, як у RabbitMQ. Калі event змяшчае дастаткова інфармацыі для апрацоўкі, у нас з'яўляецца гісторыя апошніх змен па аб'екце, і, пры жаданні, магчымасць гэтыя змены прымяніць.

Тэрмін захоўвання replication log залежыць ад інтэнсіўнасці запісу ў гэты топік, Kafka дазваляе гнутка наладзіць межы па часе захоўвання і па аб'ёме дадзеных. Для інтэнсіўных топікаў важна, каб усе спажыўцы паспявалі вычытваць інфармацыю раней, чым яна знікае, нават у выпадку кароткачасовай непрацаздольнасці. Звычайна атрымліваецца захоўваць дадзеныя за адзінкі дзён, што цалкам дастаткова для саппорта.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Далей крыху пераказу дакументацыі, для тых хто з Kafka не знаёмы (карцінка таксама з дакументацыі)

У AMQP ёсць чэргі: пішам паведамленні ў чаргу для кансьюмера. Як правіла, адну чаргу апрацоўвае адна сістэма з адной і той жа бізнэс-логікай. Калі трэба апавясціць некалькі сістэм, можна навучыць прыкладанне пісаць у некалькі чэргаў ці наладзіць exchange з механізмам fanout, які сам іх клануе.

У Kafka ёсць падобная абстракцыя тэма, У якую вы пішыце паведамленні, але яны не знікаюць пасля чытання. Па змаўчанні, пры падлучэнні да Kafka, вы атрымліваеце ўсе паведамленні, і пры гэтым ёсць магчымасць захаваць месца, на якім вы спыніліся. Гэта значыць, вы чытаеце паслядоўна, можаце не адзначыць паведамленне прачытаным, але захаваць id, з якога потым працягнеце чытанне. Id, на якім вы спыніліся, называецца offset (зрушэнне), а механізм - commit offset.

Адпаведна, можна рэалізаваць розную логіку. Напрыклад, у нас BOB існуе ў 4 інстансах для розных краін - Lamoda ёсць у Расіі, Казахстане, Украіне, Беларусі. Паколькі яны дэплояцца асобна, у іх крыху свае канфігі і свая бізнес-логіка. Мы паказваем у паведамленні, да якой краіны яно адносіцца. Кожны кансьюмер BOB у кожнай краіне чытае з рознымі groupId, і, калі паведамленне да яго не адносіцца, прапускаюць яго, г.зн. адразу камітіт offset +1. Калі той жа топік чытае наш Payment Service, то ён робіць гэта з асобным гуртом, і таму offset не перасякаюцца.

Патрабаванні да падзей:

  • Паўната даных. Хацелася б, каб у падзеі было дастаткова звестак, каб яе можна было апрацаваць.

  • Цэласнасць. Мы дэлегуем Events-bus праверку таго, што падзея кансістэнтная і ён можа яе апрацаваць.
  • Парадак важны. У выпадку з вяртаннем мы вымушаны працаваць з гісторыяй. З натыфікацыямі парадак не важны, калі гэта аднастайныя натыфікацыі, email будзе аднолькавы незалежна ад таго, які заказ прыбыў першым. У выпадку звароту ёсць выразны працэс, калі памяняць парадак, то ўзнікнуць выключэнні, refund не створыцца ці не апрацуецца - мы патрапім у іншы статут.
  • Узгодненасць. У нас ёсць сховішча, і зараз мы замест API ствараем events. Нам патрэбен спосаб, хутка і танна перадаваць у нашы сэрвісы інфармацыю аб новых events і аб зменах ва ўжо існуючых. Гэта дасягаецца з дапамогай агульнай спецыфікацыі ў асобным git-рэпазітары і кодагенератараў. Таму кліенты і серверы ў розных сэрвісах у нас узгоднены.

Kafka у Lamoda

У нас ёсць тры інсталяцыі Kafka:

  1. Logs;
  2. R&D;
  3. Events-bus.

Сёння мы гаворым толькі пра апошні пункт. У events-bus у нас не вельмі вялікія інсталяцыі - 3 брокера (сервера) і ўсяго 27 топікаў. Як правіла, адзін топік - гэта адзін працэс. Але гэта тонкі момант, і зараз мы яго кранем.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Вышэй графік rps. Працэс refunds пазначаны бірузовай лініяй (так-так, той, які ляжыць на восі X), а ружовым - працэс абнаўлення кантэнту.

Каталог Lamoda змяшчае мільёны тавараў, прычым дадзеныя ўвесь час абнаўляюцца. Адны калекцыі выходзяць з моды, наўзамен іх выпускаюцца новыя, у каталогу ўвесь час з'яўляюцца новыя мадэлі. Мы імкнемся прадказаць, што будзе цікава нашым кліентам заўтра, таму ўвесь час закупляем новыя рэчы, іх фатаграфуем і абнаўляем вітрыну.

Ружовыя пікі - гэта product update, гэта значыць змены па таварах. Відаць, што хлопцы фатаграфавалі, фатаграфавалі, а потым раз! - загрузілі пачак падзей.

Ламода Events use cases

Пабудаваную архітэктуру мы выкарыстоўваем для такіх аперацый:

  • Адсочванне статусаў зваротаў: call-to-action і трэкінг статусаў ад усіх уцягнутых сістэм. Аплата, статуты, фіскалізацыя, натыфікацыі. Тут мы апрабавалі падыход, зрабілі інструменты, сабралі ўсе багі, напісалі дакументацыю і расказалі калегам, як гэтым карыстацца.
  • Абнаўленне картак тавару: канфігурацыя, мета-дадзеныя, характарыстыкі. Чытае адна сістэма (якая адлюстроўвае), а пішуць некалькі.
  • Email, push і sms: замова сабралася, замова даехала, зварот прыняты і г.д., шмат іх.
  • Сцёк, абнаўленне склада - Колькаснае абнаўленне найменняў, проста лікі: паступленне на склад, вяртанне. Трэба каб усе сістэмы, злучаныя з рэзерваваннем тавара, аперыравалі максімальна актуальнымі дадзенымі. Цяпер сістэма абнаўлення сцёку даволі складаная, Kafka дазволіць яе спрасціць.
  • Аналіз дадзеных (R&D-аддзел), ML-інструменты, аналітыка, статыстыка. Мы хочам, каб інфармацыя была празрыстая - для гэтага Kafka добра падыходзіць.

Цяпер цікавейшая частка пра набітыя гузы і цікавыя адкрыцці, якія адбыліся за паўгода.

Праблемы праектавання

Дапушчальны, мы жадаем зрабіць новую штуку напрыклад, перавесці на Kafka ўвесь працэс дастаўкі. Цяпер частка працэсу рэалізуецца ў Order Processing у BOB. За перадачай замовы ў службу дастаўкі, перасоўваннем на прамежкавы склад і іншым варта статутная мадэль. Ёсць цэлы маналіт, нават два, плюс куча API, прысвечаных дастаўцы. Яны ведаюць аб дастаўцы значна больш.

Здаецца, што гэта падобныя вобласці, але для Order Processing у BOB і для сістэмы дастаўкі статуты адрозніваюцца. Напрыклад, некаторыя кур'ерскія службы не адпраўляюць прамежкавыя статуты, а толькі фінальныя: "даставілі" ці "страцілі". Іншыя, наадварот, вельмі падрабязна паведамляюць аб перамяшчэнні тавара. Ва ўсіх свае правілы валідацыі: для кагосьці email валідны, значыць яго апрацуюць; для іншых - не валідны, але замова ўсё роўна будзе апрацаваны, таму што ёсць тэлефон для сувязі, а хтосьці скажа, што такая замова наогул апрацоўваць не будзе.

Струмень дадзеных

У выпадку з Kafka ўзнікае пытанне арганізацыі патоку даных. Гэтая задача звязана з выбарам стратэгіі па некалькіх пунктах, пройдземся па іх усім.

У адзін топік ці ў розныя?

У нас ёсць спецыфікацыя падзеі. У BOB мы пішам, што такі заказ трэба даставіць, і паказваем: нумар замовы, яго склад, нейкія SKU і бар-коды і г.д. Калі тавар прыбудзе на склад, дастаўка зможа атрымаць статуты, timestamps і ўсё, што трэба. Але далей мы хочам у BOB атрымліваць абнаўленні па гэтых дадзеных. У нас узнікае зваротны працэс атрымання дадзеных з дастаўкі. Гэта тое ж самае падзея? Ці гэта асобны абмен, які заслугоўвае асобнага топіка?

Хутчэй за ўсё, яны будуць моцна падобныя, і спакуса зрабіць адзін топік небеспадстаўны, таму што асобны топік - гэта асобныя кансьюмеры, асобныя канфігі, асобная генерацыя ўсяго гэтага. Але не факт.

Новае поле ці новая падзея?

Але калі выкарыстоўваць тыя ж падзеі, то ўзнікае іншая праблема. Напрыклад, не ўсе сістэмы дастаўкі могуць згенераваць такое DTO, якое зможа генераваць BOB. Мы адпраўляем ім id, а яны іх не захоўваюць, таму што ім яны не патрэбны, а з пункту гледжання старту працэсу event-bus гэтае поле абавязкова.

Калі мы ўводзім для event-bus правіла, што гэтае поле абавязкова, то змушаныя ў BOB або ў апрацоўшчыку стартавай падзеі ставіць дадатковыя правілы валідацыі. Валідацыя пачынае распаўзацца па сэрвісе - гэта не вельмі зручна.

Яшчэ адна праблема - гэта спакуса інкрыментальнай распрацоўкі. Нам кажуць, што трэба нешта дадаць у падзею, і, магчыма, калі добра падумаць, гэта павінна была быць асобная падзея. Але ў нашай схеме асобнае падзея - гэта асобны топік. Асобны топік - гэта ўвесь той працэс, які я апісаў вышэй. У распрацоўніка ўзнікае спакусу проста занесці ў JSON схему яшчэ адно поле і перагенераваць.

У выпадку refunds мы так за паўгода прыехалі да падзеі падзеяў. У нас была адна мета-падзея, якая называецца refund update, у якім было поле type, якія апісваюць, у чым уласна гэты update заключаецца. Ад гэтага ў нас былі "выдатныя" свічы з валідатарамі, якія казалі, як трэба валідаваць гэтую падзею з гэтым type.

Версіянаванне падзей

Для валідацыі паведамленняў у Kafka можна выкарыстоўваць Avro, але трэба было адразу закладваць на гэта і выкарыстоўваць Confluent. У нашым выпадку з версіяваннем даводзіцца быць асцярожным. Не заўсёды будзе магчыма перачытаць паведамленні з replication log, таму што мадэль з'ехала . У асноўным, атрымліваецца будаваць версіі так, каб мадэль была зваротна сумяшчальнай: напрыклад, зрабіць поле часова неабавязковым. Калі адрозненні занадта моцныя, пачынаем пісаць у новы топік, а кліенты перасаджваем, калі яны шануюць стары.

Гарантыя парадку чытання partitions

Топікі ўнутры Kafka разбіты на partitions. Гэта не вельмі важна пакуль мы праектуем сутнасці і абмены, але важна, калі вырашаем, як гэта кансьюміць і маштабаваць.

У звычайным выпадку вы пішыце ў Kafka XNUMX топік. Па змаўчанні выкарыстоўваецца адзін 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, становіцца неабходна зваротная сувязь, і гэта праблема.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

У асінхронным абмене ў RabbitMQ, калі вы прачыталі паведамленне, схадзілі ў http, у вас ёсць response – хаця б, што паведамленне было прынята. Калі вы запісалі ў Kafka, ёсць паведамленне, што вы запісалі ў Kafka, але пра тое, як яно апрацавалася, вы нічога не ведаеце.

Таму ў нашым выпадку прыйшлося ўводзіць зваротную падзею і наладжваць маніторынг на тое, што калі вылецела столькі падзей, праз такі час павінна паступіць столькі ж падзей у адказ. Калі гэтага не адбылося, тое здаецца, нешта пайшло не так. Напрыклад, калі мы адправілі падзею "item_ready_to_refund", мы чакаем, што refund створыцца, кліенту вернуцца грошы, нам вылеціць падзею "money_refunded". Але гэта не дакладна, таму патрэбен маніторынг.

нюансы

Ёсць даволі відавочная праблема: калі вы праглядаеце з топіка паслядоўна, і ў вас нейкае паведамленне дрэннае, кансьюмер падае, і далей вы не сыдзеце. Вам трэба спыніць усе кансьюмеры, Каміціць offset далей, каб працягнуць чытаць.

Мы пра гэта ведалі, на гэта заклаліся, і ўсё роўна гэта адбылося. А адбылося гэта таму, што падзея была валіднай з пункту гледжання events-bus, падзея была валіднай з пункту гледжання валідатара прыкладання, але яна не была валіднай з пункту гледжання PostgreSQL, таму што ў нас у адной сістэме MySQL з UNSIGNED INT, а ў свежанапісанай сістэме быў PostgreSQL проста з INT. У яго памер крыху паменш, і Id не змясціўся. Symfony памёр з выключэннем. Мы, вядома, выключэнне злавілі, таму што заклаліся на яго, і збіраліся камiціць гэты offset, але перад гэтым жадалі інкрыментаваць лічыльнік праблем, раз ужо паведамленне апрацавалася няўдала. Лічыльнікі ў гэтым праекце таксама ляжаць у базе, а Symfony ужо зачыніў зносіны з базай, і другое выключэнне забіла ўвесь працэс без шанцаў камiціць 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.

Маніторынг

Думаю, па тым, як мы маніторым, будзе яшчэ больш зразумела, якія праблемы ёсць у існуючым падыходзе.

Напрыклад, лічым, колькі тавараў у базе нядаўна змянілі статус, і, адпаведна, па гэтых зменах павінны былі здарыцца падзеі, і адпраўляем гэты лік у сваю сістэму маніторынгу. Потым з Kafka мы атрымліваем другую лічбу, колькі насамрэч было прапісана падзей. Відавочна, розніца паміж гэтымі двума лікамі заўсёды павінна быць нулявой.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Акрамя таго, трэба маніторыць, як справы ў прадз'юсара, ці прыняў events-bus паведамленні, і як справы ў кансьюмера. Напрыклад, на графіках ніжэй у Refund Tool усё добра, а ў BOB відавочна нейкія праблемы (сінія пікі).

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Я ўжо ўзгадваў consumer-group lag. Грубіянска кажучы, гэта колькасць непрачытаных паведамленняў. У цэлым кансьюмеры ў нас працуюць хутка, таму лаг звычайна роўны 0, але часам можа быць кароткачасовы пік. Kafka умее гэта са скрынкі, але трэба задаць нейкі інтэрвал.

Ёсць праект нара, Які дасць вам больш інфармацыі па Kafka. Ён проста па API па consumer-group аддае статут, як у гэтай групы справы. Акрамя ОК і Failed там ёсць warning, і вы зможаце даведацца, што вашы кансьюмеры не спраўляюцца з тэмпам прадзюсінгу - не паспяваюць вычытваць тое, што пішацца. Сістэма даволі разумная, яе зручна выкарыстоўваць.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Так выглядае адказ па API. Тут гурт bob-live-fifa, partition refund.update.v1, статус ОК, lag 0 — апошні канчатковы offset вось такой.

Вопыт распрацоўкі сэрвісу Refund Tool з асінхронным API на Kafka

Маніторынг updated_at SLA (stuck) я ўжо згадваў. Напрыклад, тавар перайшоў у статус, што ён гатовы да вяртання. Мы ставім Cron, які кажа, што калі за 5 хвілін гэты аб'ект не перайшоў у refund (мы вяртаем грошы праз плацежныя сістэмы вельмі хутка), то нешта сапраўды пайшло не так, і гэта сапраўды выпадак для саппорта. Таму проста бярэм Cron, які чытае такія штукі, і калі яны больш за 0, то дасылае алерт.

Падводзячы вынік, выкарыстоўваць падзеі зручна, калі:

  • інфармацыя патрэбная некалькім сістэмам;
  • не важны вынік апрацоўкі;
  • падзей трохі ці падзеі маленькія.

Здавалася б, у артыкула суцэль пэўная тэма – асінхронны API на Kafka, але ў сувязі з ёй жадаецца адразу шмат усяго парэкамендаваць.
Па-першае, наступны Высокая нагрузка++ На трэба чакаць да лістапада, ужо ў красавіку будзе яго піцерская версія, а ў чэрвені пагаворым пра высокія нагрузкі ў Новасібірску.
Па-другое, аўтар даклада Сяргей Заіка ўваходзіць у Праграмны камітэт нашай новай канферэнцыі пра кіраванне ведамі. KnowledgeConf. Канферэнцыя аднадзённая, пройдзе 26 красавіка, але праграма ў яе вельмі насычаная.
А яшчэ ў траўні будзе PHP Belarus и РЫТ++ (з DevOpsConf у складзе) – туды яшчэ можна прапанаваць сваю тэму, расказаць пра свой вопыт і паскардзіцца на свае набітыя гузы.

Крыніца: habr.com

Дадаць каментар