Продължение на превода на малка книга:
«Разбиране на брокерите на съобщения»,
автор: Jakub Korab, издателство: O’Reilly Media, Inc., дата на издаване: юни 2017, ISBN: 9781492049296.
Предишната преведена част:
ГЛАВА 3
Kafka
Kafka е разработена в LinkedIn, за да заобиколи определени ограничения на традиционните брокери на съобщения и да избегне необходимостта от настройка на множество брокери за различни взаимодействия „точка-точка“, което е описано в тази книга в раздела „Вертикално и хоризонтално мащабиране“ на страница 28. Използването в LinkedIn основно се основава на еднопосочно усвояване на много големи обеми данни, като кликвания на страници и журнали за достъп, като в същото време позволява на тези данни да се използват от няколко системи, без да влияят на производителността на производителите или другите консуматори. Всъщност, причината за съществуването на Kafka е да осигури такава архитектура на обмен на съобщения, каквато описва Universal Data Pipeline.
С оглед на тази крайна цел естествено възникват и други изисквания. Kafka трябва да:
- Бъде изключително бърза
- Да осигурява голям капацитет при работа със съобщения
- Да поддържа модели „Издател-Абонат“ и „Точка-Точка“
- Да не забавя производителността с добавянето на консуматори. Например, производителността и опашките, и темата в ActiveMQ се влошават с увеличаване на броя на консуматорите на приемника
- Да бъде хоризонтално мащабируема; ако един брокер, който съхранява (persist) съобщения, може да го прави само с максималната скорост на диска, то е разумно да излезе извън един екземпляр на брокера, за да се увеличи производителността
- Да разграничават достъпа до съхранение и повторно извличане на съобщения
За да постигне всичко това, Kafka приема архитектура, която преосмисля ролите и отговорностите на клиентите и брокерите за съобщения. JMS моделът е много ориентиран към брокера, където той отговаря за разпространението на съобщение, а клиентите трябва да се грижат само за изпращането и получаването на съобщения. Kafka, от друга страна, е ориентирана към клиента, като клиентът поема много функции на традиционен брокер, като справедливо разпределение на съответните съобщения сред потребителите, в замяна получавайки изключително бърз и мащабируем брокер. За хората, работили с традиционни системи за обмен на съобщения, работата с Kafka изисква основни промени в начина на мислене.
Тази инженерна насока води до създаването на инфраструктура за обмен на съобщения, която е способна да увеличи пропускната способност многократно в сравнение с обикновен брокер. Както ще видим, този подход е свързан с компромиси, които означават, че Kafka не е подходяща за определени видове натоварвания и установен софтуер.
Унифициран модел на получателя
За да отговори на изискванията, описани по-горе, Kafka съчетава обмена на съобщения от тип 'публикация-подписка' и 'точка-точка' в рамките на един вид получател — топик. Това може да обърка хората, работили с системи за обмен на съобщения, където терминът 'топик' се отнася до широковещателен механизъм, от който (от топика) четенето не е надеждно (недоразвито). Топиците на Kafka трябва да се разглеждат като хибриден тип получател, в съответствие с определението, дадено в уводната част на тази книга.
В оставащата част от тази глава, освен ако не посочим изрично друго, терминът 'топик' ще се отнася до топика на Kafka.
За да разберем напълно как се държат топиците и какви гаранции предлагат, първо трябва да разгледаме как са реализирани в Kafka.
Всеки топик в Kafka има свой собствен журнал.
Продюсерите, изпращащи съобщения в Kafka, записват в този журнал, а консуматорите четат от журнала чрез указатели, които постоянно се движат напред. Периодично Kafka изтрива най-старите части от журнала, независимо дали съобщенията в тези части са били прочетени или не. Централната част на дизайна на Kafka е, че брокерът не се интересува дали съобщенията са били прочетени или не — това е отговорност на клиента.
Термините „журнал“ и „указател“ не се срещат в . Тези добре известни термини се използват тук, за да помогнат за разбирането.
Тази модел напълно се различава от ActiveMQ, където съобщенията от всички опашки се съхраняват в един журнал, а брокерът маркира съобщенията като изтрити, след като са били прочетени.
Нека сега да се задълбочим и да разгледаме журнала на топика по-подробно.
Журналът на Kafka се състои от няколко партиции (). Kafka гарантира строг ред в всяка партиция. Това означава, че съобщенията, записани в партицията в определен ред, ще бъдат прочетени в същия ред. Всяка партиция е реализирана под формата на цикличен (rolling) файл на журнала, който съдържа подмножество (subset) от всички съобщения, изпратени в топика от неговите продюсери. По подразбиране създаденият топик съдържа една партиция. Идеята за партиции е централна идея на Kafka за хоризонтално мащабиране.

Фигура 3-1. Партиции на Kafka
Когато продюсер изпраща съобщение в топика на Kafka, той решава в коя партиция да изпрати съобщението. Ще обсъдим това по-подробно по-късно.
Четене на съобщения
Клиентът, който иска да чете съобщенията, управлява именуван указател, наречен група консуматори (consumer group), който сочи към смещението (offset) на съобщението в партицията. Смещението е позиция с нарастващ номер, която започва от 0 в началото на партицията. Тази група консуматори, на която се позовава в API чрез определяния от потребителя идентификатор group_id, съответства на един логически консуматор или система.
Повечето системи, използващи обмен на съобщения, четат данни от получателя чрез множество инстанции и потоци за паралелна обработка на съобщения. По този начин обикновено ще има много инстанции на консуматори, които съвместно използват една и съща група консуматори.
Проблемът с четенето може да бъде представен по следния начин:
- Топикът има няколко партиции
- Множество групи консуматори могат да използват топика едновременно
- Група консуматори може да има няколко отделни инстанции
Това е нетривиален проблем "много към много". За да разберем как Kafka се справя с отношенията между групите консуматори, инстанциите на консуматорите и партициите, ще разгледаме серия от постепенно усложняващи се сценарии за четене.
Консуматорите и групите консуматори
Нека вземем за отправна точка топик с една партиция ().

Фигура 3-2. Консуматорът чете от партицията
Когато инстанция на консуматора се свърже с този топик с собствен group_id, ѝ се назначава партиция за четене и офсет в тази партиция. Позицията на този офсет се конфигурира в клиента като указател за най-новата позиция (най-новото съобщение) или най-ранната позиция (най-старото съобщение). Консуматорът извършва запитвания (polls) за съобщения от топика, което довежда до тяхното последователно четене от журнала.
Позицията на офсета редовно се комитва обратно в Kafka и се запазва като съобщения във вътрешния топик _consumer_offsets. Прочетените съобщения не се изтриват, за разлика от обикновен брокер, и клиентът може да превърта (rewind) офсета, за да преработи вече видяни съобщения.
Когато втори логически консуматор се свърже, използвайки друг group_id, той управлява втори указател, който не зависи от първия (). По този начин, топикът Kafka функционира като опашка, в която съществува един консуматор, и като обикновен топик за публикуване и абониране (pub-sub), на който са абонирани множество консуматори, с допълнителното предимство, че всички съобщения се запазват и могат да се обработват няколко пъти.

Фигура 3-3. Два консуматора в различни групи консуматори четат от една партиция
Консуматорите в група консуматори
Когато един инстанция на консуматора чете данни от партицията, той напълно контролира указателя и обработва съобщенията, както е описано в предишния раздел.
Ако няколко екземпляра на консумера са свързани с едно и също group_id към тема с една партиция, контрола над указателя ще бъде предаден на последния свързал се екземпляр и от този момент той ще получава всички съобщения ().

Фигура 3-4. Два консумера в една и съща група консумери четат от една партиция
Този режим на обработка, в който броят на екземплярите на консумера надвишава броя на партициите, може да се разглежда като разновидност на монополистичен потребител. Това може да бъде полезно, ако ви е необходима "активно-пасивна" (или "гореща-топла") кластеризация на вашите екземпляри на консумерите, въпреки че паралелната работа на няколко консумера ("активно-активна" или "гореща-гореща") е много по-типична от консумерите в режим на изчакване.
Такова поведение на разпределението на съобщенията, описано по-горе, може да предизвика учудване в сравнение с това как работи обикновената опашка JMS. В тази модел съобщенията, изпратени в опашката, ще бъдат равномерно разпределени между двама консумера.
Най-често, когато създаваме няколко екземпляра на консумери, го правим или за паралелна обработка на съобщения, или за увеличаване на скоростта на четене, или за повишаване на устойчивостта на процеса на четене. Тъй като само един екземпляр на консумера може да чете данни от партицията едновременно, как се постига това в Kafka?
Един от начините да го направите е да използвате един екземпляр на консумера, за да прочете всички съобщения и да ги предаде в пул от нишки. Въпреки че този подход увеличава пропускателната способност на обработката, той увеличава сложността на логиката на консумерите и не прави нищо за повишаване на устойчивостта на системата за четене. Ако един екземпляр на консумера бъде изключен поради аварийно спиране на захранването или подобно събитие, четенето спира.
Каноничният начин за решаване на този проблем в Kafka е да се използваОпо-голям брой партиции.
Партициониране
Партициите са основният механизъм за паралелизиране на четенето и мащабиране на темата извън пропускателната способност на един екземпляр на брокера. За да разберем по-добре това, нека разгледаме ситуация, при която има тема с две партиции и един консумер се абонира за тази тема ().

Фигура 3-5. Един потребител чете от няколко партиции
В този сценарий на потребителя се дава контрол над указателите, съответстващи на неговото group_id в двете партиции, и започва да чете съобщения от двете партиции.
Когато в този топик се добави допълнителен потребител за същото group_id, Kafka преразпределя (reallocate) една от партициите от първия на втория потребител. След това всеки екземпляр на потребителя ще чете от една партиция на топика ().
За да се осигури обработка на съобщения паралелно в 20 потока, ще ви трябват най-малко 20 партиции. Ако партициите са по-малко, ще останат потребители, които нямат с какво да се занимават, както беше описано по-рано в обсъждането на монополните потребители.

Фигура 3-6. Два потребителя в една и съща група потребители четат от различни партиции
Тази схема значително намалява сложността на работата на брокера Kafka в сравнение с разпределението на съобщения, необходимо за поддръжка на опашката JMS. Тук не е нужно да се грижат за следните моменти:
- Кой потребител трябва да получи следващото съобщение, въз основа на цикличното (round-robin) разпределение, текущия капацитет на буферите за предварително извличане или предишните съобщения (както за групи съобщения в JMS).
- Кои съобщения са изпратени на кои потребители и трябва ли да се доставят отново в случай на неизправност.
Всичко, което брокерът Kafka трябва да направи, е последователно да предава съобщения на потребителя, когато последният ги поиска.
Обаче, изискванията за паралелно четене и повторно изпращане на неуспешни съобщения не изчезват никъде - отговорността за тях просто преминава от брокера към клиента. Това означава, че те трябва да бъдат отчетени във вашия код.
Изпращане на съобщения
Отговорността за решението в коя партиция да се изпрати съобщение, се носи от производителя на това съобщение. За да разберете механизма, с помощта на който това се прави, първо трябва да разгледате какво всъщност изпращаме.
Докато в JMS използваме структура на съобщението с метаданни (заглавия и свойства) и тяло, съдържащо полезна информация (payload), в Kafka съобщението е пара от "ключ-стойност". Полезната полезност на съобщението се изпраща като стойност (value). Ключът, от друга страна, се използва основно за партициониране и трябва да съдържа специфичен за бизнес логиката ключ, за да постави свързаните съобщения в същата партиция.
В Глава 2 обсъдихме сценария на онлайн залозите, когато свързаните събития трябва да се обработват по ред от един консуматор:
- Потребителският акаунт е настроен.
- Парите се превеждат по сметката.
- Прави се залог, който изтегля пари от сметката.
Ако всяко събитие представлява съобщение, изпратено в темата, тогава в този случай естественият ключ ще бъде идентификаторът на акаунта.
Когато съобщението се изпраща с използването на Kafka Producer API, то се предава на функцията за партициониране, която, като взема предвид съобщението и текущото състояние на кластера на Kafka, връща идентификатора на партицията, в която трябва да бъде изпратено съобщението. Тази функция е реализирана в Java чрез интерфейса Partitioner.
Този интерфейс изглежда по следния начин:
interface Partitioner {
int partition(String topic,
Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster);
}Имплементацията на Partitioner за определяне на партицията използва по подразбиране алгоритъм за хеширане на ключа (general-purpose hashing algorithm over the key) или цикличен перебор (round-robin), ако ключът не е указан. Тази стойност по подразбиране работи добре в повечето случаи. Въпреки това, в бъдеще може да искате да напишете собствена.
Написване на собствена стратегия за партициониране
Нека разгледаме пример, когато искате да изпратите метаданни заедно с полезната информация на съобщението. Полезната информация в нашия пример е инструкция за депозиране на средства в игровия акаунт. Инструкцията е нещо, което бихме искали да гарантираме, че няма да бъде модифицирано по време на преноса и искаме да сме сигурни, че само доверена система на по-високо ниво може да инициира тази инструкция. В този случай изпращащата и получаващата системи се съгласяват на използването на подпис за проверка на автентичността на съобщението.
В обикновен JMS просто дефинираме свойството „подпис на съобщението“ и го добавяме към съобщението. Въпреки това, Kafka не ни предоставя механизъм за предаване на метаданни — само ключ и стойност.
Тъй като стойността е полезната нагрузка на банковия превод (bank transfer payload), целостта на която искаме да запазим, нямаме друг избор, освен да определим структурата на данните за използване в ключа. Предполагаемо, тъй като ни е необходим идентификатор на сметката за партициониране, тъй като всички съобщения, свързани със сметката, трябва да бъдат обработвани по ред, ние ще измислим следната структура на JSON:
{
"signature": "541661622185851c248b41bf0cea7ad0",
"accountId": "10007865234"
}Тъй като стойността на подписа ще варира в зависимост от полезната нагрузка, стратегята за хеширане на интерфейса Partitioner няма да съчетае надеждно свързаните съобщения. Затова ще трябва да напишем собствена стратегия, която ще анализира този ключ и ще разделя (partition) стойността на accountId.
Kafka включва контролни суми за откриване на повреди в съобщенията в хранилището и разполага с пълен набор от функции за сигурност. Въпреки това, понякога се появяват специфични за индустрията изисквания, като описаното по-горе.
Персонализираната стратегия за партициониране трябва да гарантира, че всички свързани съобщения ще се намират в една партиция. Въпреки че изглежда просто, изискването може да се усложни поради важността на подредбата на свързаните съобщения и колко стабилно е количеството партиции в темата.
Броят на партициите в темата може да се променя с времето, тъй като могат да се добавят, ако трафикът надвишава първоначалните очаквания. Следователно, ключовете на съобщенията могат да бъдат свързани с партицията, в която са били първоначално изпратени, намеквайки за част от състоянието, което трябва да бъде разпределено между екземплярите на производителя.
Друг фактор, който трябва да се има предвид, е равномерността на разпределението на съобщенията между партициите. Обикновено ключовете не се разпределят равномерно между съобщенията, и хеш-функциите не гарантират справедливо разпределение на съобщенията за малък набор от ключове.
Важно е да се отбележи, че, каквото и да решите да разделите съобщенията, самият разделител може да се наложи да се използва повторно.
Нека разгледаме изискването за репликация на данни между Kafka клъстери, разположени на различни географски места. За тази цел, Kafka предоставя инструмента за команден ред, наречен MirrorMaker, който се използва за четене на съобщения от един клъстер и предаването им на друг.
MirrorMaker трябва да разбира ключовете на репликирания топик, за да поддържа относителния ред между съобщенията при репликация между клъстери, тъй като броят на партициите за този топик може да не съвпада в двата клъстера.
Потребителските стратегии за партициониране се срещат относително рядко, тъй като стандартните хеширане или циклично разпределение работят успешно в повечето сценарии. Въпреки това, ако се изискват строги гаранции за подредба или е необходимо извличане на метаданни от полезните натоварвания, партиционирането е нещо, на което трябва да се обърне повече внимание.
Предимствата на мащабируемостта и производителността на Kafka произтичат от прехвърлянето на някои задължения на традиционния брокер към клиента. В този случай се взема решение за разпределяне на потенциално свързани съобщения между различни консуматори, работещи паралелно.
JMS брокерите също трябва да се справят с подобни изисквания. Интересно е, че механизмът за изпращане на свързани съобщения на един и същи консуматор, реализиран чрез JMS Message Groups (разновидност на стратегията за балансировка на товар sticky load balancing (SLB)), също изисква от изпращача да маркира съобщенията като свързани. В случая с JMS, брокерът отговаря за изпращането на тази група свързани съобщения на един консуматор от многото и за прехвърлянето на правата на собственост върху групата, ако консуматорът се провали.
Споразумения по отношение на продюсера
Партиционирането не е единственото, което трябва да се има предвид при изпращането на съобщения. Нека разгледаме методите send () на класа Producer в Java API:
Future send(ProducerRecord record);
Future send(ProducerRecord record, Callback callback);Трябва веднага да отбележим, че и двата метода връщат Future, което показва, че операцията по изпращане не се извършва незабавно. В резултат на това съобщението (ProducerRecord) се записва в буфера за изпращане за всяка активна партиция и се предава на брокера от фонов поток в клиентската библиотека на Kafka. Въпреки че това прави работата невероятно бърза, това означава, че неправилно написано приложение може да загуби съобщения, ако процесът му бъде спрян.
Както винаги, има начин да се направи операцията по изпращане по-надеждна, но за сметка на производителността. Размерът на този буфер може да бъде зададен на 0, а потокът на приложението за изпращане ще бъде принуден да изчака, докато предаването на съобщението на брокера не бъде завършено, както следва:
RecordMetadata metadata = producer.send(record).get();Още веднъж за четене на съобщенията
Четенето на съобщенията има допълнителни сложности, които трябва да се разгледат. За разлика от API JMS, който може да стартира слушател на съобщения (message listener) в отговор на получаване на съобщение, интерфейсът Consumer Kafka просто извършва опрашване (polling). Нека да разгледаме по-подробно метода poll (), използван за тази цел:
ConsumerRecords poll(long timeout);Връщаната стойност на метода е контейнерна структура, съдържаща няколко обекта ConsumerRecord от потенциално няколко партиции. ConsumerRecord Той самият е обект-държател за двойка ключ-стойност с относителния метаданни, като например партицията, от която е получен.
Както беше обсъдено в Глава 2, трябва постоянно да помним какво се случва със съобщенията след успешна или неуспешна обработка, например ако клиентът не може да обработи съобщението или ако спре да работи. В JMS това беше обработвано чрез режима на потвърждение (acknowledgement mode). Брокерът или ще изтрие успешно обработеното съобщение, или ще повторно достави необработеното или проваленото (при условие, че са използвани транзакции).
Kafka работи съвсем различно. Съобщенията не се изтриват в брокера след четене и отговорността за това, което се случва при грешка, лежи на самия код, който чете.
Както вече споменахме, групата от консуматори е свързана със смещението в журнала. Позицията в журнала, свързана с това смещение, отговаря на следващото съобщение, което ще бъде издадено в отговор на poll (). Определящото значение при четене има моментът, когато това преместване се увеличава.
Връщайки се към модела на четене, разглеждан по-рано, обработката на съобщението се състои от три етапа:
- Извлечете съобщение за четене.
- Обработете съобщението.
- Потвърдете съобщението.
Консуматорът на Kafka идва с опция за конфигурация enable.auto.commit. Това е често използвана настройка по подразбиране, както обикновено е с настройките, съдържащи думата „авто“.
Преди Kafka 0.10 клиент, който е използвал този параметър, изпращал е преместеното значение на последното прочетено съобщение при следващото извикване poll () след обработка. Това означаваше, че всякакви съобщения, които вече са били извлечени (fetched), могат да бъдат обработвани повторно, ако клиентът вече ги е обработил, но е бил неочаквано унищожен преди извикването poll (). Тъй като брокерът не запазва никакво състояние относно това, колко пъти съобщението е било прочетено, следващият консуматор, който извлича това съобщение, няма да знае, че се е случило нещо лошо. Това поведение беше псевдо-транзакционно. Преместването се потвърдеше само при успешна обработка на съобщението, но ако клиентът прекъсне работата, брокерът отново изпращаше същото съобщение на друг клиент. Такова поведение отговаряше на гаранцията за доставяне на съобщения „поне веднъж«.
В Kafka 0.10 кодът на клиента беше променен по такъв начин, че потвърдителят започна периодично да се задейства от библиотеката на клиента, в съответствие с настройката auto.commit.interval.ms. Това поведение е някъде между режимите JMS AUTO_ACKNOWLEDGE и DUPS_OK_ACKNOWLEDGE. При използване на авто-потвърждаване, съобщенията можеха да бъдат потвърдени независимо от това дали наистина са били обработени — това можеше да се случи в случай на бавен консуматор. Ако консуматорът прекъснеше работата си, съобщенията се извличаха от следващия консуматор, започвайки от потвърдената позиция, което можеше да доведе до пропускане на съобщение. В този случай Kafka не губеше съобщения, четещият код просто не ги обработваше.
Този режим има същите перспективи, каквито имаше в версия 0.9: съобщенията могат да бъдат обработвани, но в случай на срив, преместеното значение може да не е потвърдено, което потенциално може да доведе до дублиране на доставката. Колкото повече съобщения извлекате при изпълнението poll (), толкова повече този проблем.
Както беше обсъдено в раздела „Четене на съобщения от опашката“ на стр. 21, в системата за обмен на съобщения няма понятие за еднократно доставяне на съобщение, ако се вземат предвид режимите на сбой.
В Kafka има два начина за фиксиране (комитване) на офсета: автоматично и ръчно. В двата случая съобщенията могат да бъдат обработвани няколко пъти, ако съобщението е било обработено, но е настъпил сбой преди комита. Вие също така можете изобщо да не обработвате съобщението, ако комитът е станал във фонов режим и вашият код е приключил преди да е започнал обработката (възможно в Kafka 0.9 и по-ранни версии).
Контролът на процеса по комитване на офсета ръчно може да се осъществи в API на консумирача на Kafka, като се зададе параметърът enable.auto.commit на false и изрично да се извика един от следните методи:
void commitSync();
void commitAsync();Ако се стремите да обработвате съобщение ‘поне веднъж’, трябва ръчно да комитнете офсета с помощта на commitSync (), извършвайки тази команда непосредствено след обработката на съобщенията.
Тези методи не позволяват потвърждаване (acknowledged) на съобщения, преди те да бъдат обработени, но не правят нищо за предотвратяване на потенциално дублирано обработване, същевременно създавайки илюзия за транзакционност. В Kafka липсват транзакции. Клиентът няма възможност да направи следното:
- Автоматично да откатва (roll back) неуспешно съобщение. Консумерите сами трябва да обработват изключенията, произтичащи от проблематични пейлоуди и изключвания на бекенда, тъй като не могат да разчитат на повторна доставка на съобщения от брокера.
- Да изпращат съобщения до няколко топика в рамките на една атомарна операция. Както скоро ще видим, контролът върху различни топици и партиции може да бъде на различни машини в клъстера Kafka, които не координират транзакции при изпращане. Към момента на написване на тази статия е направена определена работа, за да стане това възможно с помощта на KIP-98.
- Да свържат прочитането на едно съобщение от един топик с изпращането на друго съобщение в друг топик. Отново, архитектурата на Kafka зависи от множество независими машини, работещи като една шина и не се правят опити да се скрие това. Например, не съществуват компоненти на API, които да позволят свързването на Консюмер и Продюсер в транзакцията. В JMS това се осигурява от обект Session, от който се създават MessageProducers и MessageConsumers.
Ако не можем да разчитаме на транзакции, как можем да осигурим семантика, която е по-близка до тази, предоставена от традиционните системи за обмен на съобщения?
Ако съществува вероятност консумерът да увеличи офсета си преди съобщението да бъде обработено, например по време на срив на консумера, то консумерът няма начин да разбере дали неговата група консумери е пропуснала съобщения, когато ѝ се назначава партиция. Така една от стратегиите е да се върне (rewind) офсета до предишната позиция. API-то на консумера Kafka предоставя следните методи за това:
void seek(TopicPartition partition, long offset);
void seekToBeginning(Collection partitions); Метод seek () може да се използва с метода
offsetsForTimes (Map timestampsToSearch) за връщане в състояние в определен момент в миналото.
Неправилно, използването на този подход означава, че е много вероятно някои съобщения, които са били обработени преди, да бъдат прочетени и обработени отново. За да избегнем това, можем да използваме идемпотентно четене, както е описано в Глава 4, за да следим преди видените съобщения и да изключим дубликатите.
Като алтернатива, кодът на вашия консумер може да бъде прост, ако е допустимо загуба или дублиране на съобщения. Когато разглеждаме сценарии на ползване, за които обикновено се използва Kafka, например, обработка на събития от логове, метрики, проследяване на кликвания и т.н., разбираме, че загубата на отделни съобщения едва ли ще има съществено въздействие върху околните приложения. В такива случаи стойностите по подразбиране са съвсем приемливи. От друга страна, ако вашето приложение трябва да предава плащания, трябва да се грижите внимателно за всяко отделно съобщение. Всичко се свежда до контекста.
Личните наблюдения показват, че с увеличаването на интензивността на съобщенията, стойността на всяко отделно съобщение намалява. Съобщенията с голям обем обикновено стават ценни, ако се разглеждат в агрегирана форма.
Висока наличност (High Availability)
Подходът на Kafka към висока наличност се различава значително от подхода на ActiveMQ. Kafka е проектирана на базата на хоризонтално мащабируеми клъстери, в които всички инстанции на брокера приемат и разпространяват съобщения едновременно.
Клъстерът на Kafka се състои от няколко инстанции на брокера, работещи на различни сървъри. Kafka е разработена да работи на обикновен самостоятелен хардуер, където всеки възел разполага със собствено резервирано хранилище. Използването на мрежови хранилища (SAN) не се препоръчва, тъй като множество компютърни възли могат да се конкурират за времеви интервали на хранилището и да създават конфликти.Ые интервалите на хранилището и да създават конфликти.
Kafka е постоянно активна система. Много големи потребители на Kafka никога не спират своите клъстери и софтуерът винаги осигурява обновление чрез последователно рестартиране. Това се постига чрез гарантиране на съвместимост с предходната версия за съобщенията и взаимодействията между брокерите.
Брокерите са свързани към клъстера от сървъри , който действа като регистър на конфигурационните данни и се използва за координиране на ролите на всеки брокер. ZooKeeper сам е разпределена система, която осигурява висока наличност посредством репликация на информацията чрез установяване на кворум..
В основния случай, темата се създава в клъстера на Kafka с следните свойства:
- Броят на партициите. Както беше обсъдено по-рано, точното значение, използвано тук, зависи от желаното ниво на паралелно четене.
- Факторът на репликация определя колко инстанции на брокера в клъстера трябва да съдържат журнали за тази партиция.
Използвайки ZooKeeper за координация, Kafka се опитва справедливо да разпредели новите партиции между брокерите в клъстера. Това се осъществява от една инстанция, която изпълнява ролята на Контролер.
В рантайма за всяка партиция на темата Контролер назначава брокера с ролите лидер (лидер, майстор, водещ) последователи (последователи, роби, подчинени). Брокерът, действащ като лидер за съответната партиция, отговаря за приемането на всички съобщения, изпратени му от продуцентите, и за разпространението на съобщенията сред консуматорите. При изпращане на съобщения в партицията на темата те се реплицират на всички възли на брокера, които действат като последователи за тази партиция. Всеки възел, съдържащ журнали за партицията, се нарича реплика. Брокерът може да действа като лидер за някои партиции и като последовател за други.
Последовател, съдържащ всички съобщения, съхранявани от лидера, се нарича синхронизирана реплика (реплика, която е в синхронизирано състояние, in-sync replica). Ако брокерът, действащ като лидер за партицията, се изключи, всеки брокер, който е в актуализирано или синхронизирано състояние за тази партиция, може да поеме ролята на лидер. Това е изключително устойчива дизайн.
Част от конфигурацията на продуцента е параметърът acks, който определя колко реплики трябва да потвърдят (acknowledge) получаването на съобщение, преди приложният поток да продължи да изпраща: 0, 1 или всички. Ако е зададена стойност всички, тогава при получаване на съобщение лидерът ще изпрати потвърждение (confirmation) обратно на продуцента, веднага щом получи потвърждения (acknowledgements) за записа от няколко реплики (включително самия себе си), определени от настройките на темата min.insync.replicas (по подразбиране 1). Ако съобщението не може да бъде успешно репликирано, продуцентът ще предизвика изключение за приложението (NotEnoughReplicas или NotEnoughReplicasAfterAppend).
В типичната конфигурация се създава тема с коефициент на репликация 3 (1 лидер, 2 последователи за всяка партиция) и параметърът min.insync.replicas се задава на стойност 2. В този случай клъстерът ще допуска един от брокерите, управляващи партицията на темата, да се изключи, без да влияе на клиентските приложения.
Това ни връща към познатия компромис между производителност и надеждност. Репликацията става за сметка на допълнителното време за изчакване за потвърждения (acknowledgments) от последователите. Въпреки че, тъй като тя се извършва паралелно, репликацията, поне на три възла, има същата производителност, както и на две (игнорирайки увеличението в използването на пропускната способност на мрежата).
Използвайки тази схема на репликация, Kafka успешно избягва необходимостта да осигури физическо записване на всяко съобщение на диск чрез операцията sync (). Всяко съобщение, изпратено от продуцент, ще бъде записано в журнала на партицията, но както бе обсъдено в Глава 2, записът в файла първоначално се извършва в буфера на операционната система. Ако това съобщение е репликирано на друг екземпляр на Kafka и е в неговата памет, загубата на лидера не означава, че самото съобщение е изгубено — синхронизираната реплика може да поеме.
Отказът от необходимостта да се извършва операция sync () означава, че Kafka може да приема съобщения със скорост, с която може да ги записва в паметта. И обратно, колкото по-дълго може да се избегне изхвърлянето (flushing) на паметта на диск, толкова по-добре. Поради тази причина не е рядкост брокерите на Kafka да получават 64 ГБ памет или повече. Такова използване на паметта означава, че един екземпляр на Kafka може да работи със скорости, които многократно надвишават тези на традиционен брокер на съобщения.
Kafka може също да се настрои за прилагане на операцията sync () в пакетите съобщения. Тъй като всичко в Kafka е насочено към работа с пакети, това наистина работи доста добре за много сценарии на употреба и е полезен инструмент за потребители, които изискват много силни гаранции. По-голямата част от чистата производителност на Kafka е свързана със съобщенията, които се изпращат на брокера под формата на пакети и с факта, че тези съобщения се четат от брокера в последователни блокове с помощта на операции (операции, при които не се извършва задача по копиране на данни от едно пространство на паметта в друго). Последното е голямо предимство от гледна точка на производителността и ресурсите и е възможно само благодарение на използването на основната структура от данни на журнала, която определя схемата на партицията.
В клъстера на Kafka е възможно значително по-висока производителност, отколкото при използването на един брокер на Kafka, тъй като партициите на темата могат да се мащабират хоризонтално на множество отделни машини.
Резюме
В тази глава разгледахме как архитектурата на Kafka пренаписва отношенията между клиентите и брокерите, за да осигури невероятно устойчив конвейер за обмен на съобщения, с пропускна способност много пъти по-висока от обикновен брокер за съобщения. Обсъдихме функционалността, която тя използва за постигане на тази цел, и кратко разгледахме архитектурата на приложенията, които осигуряват тази функционалност. В следващата глава ще разгледаме общите проблеми, с които приложенията за обмен на съобщения трябва да се справят, и ще обсъдим стратегии за тяхното решаване. Ще завършим главата, описвайки как да разсъждаваме за технологиите за обмен на съобщения като цяло, за да можете да оцените тяхната приложимост за вашите сценарии на употреба.
Предишната преведена част:
Преводът е изпълнен:
Продължението следва…
Само регистрирани потребители могат да участват в анкетата. , моля.
Използва ли се Kafka във вашата организация?
Да
Не
По-рано се е използвала, сега не се използва
Планираме да я използваме
Гласували са 38 потребители. Въздържали са се 8 потребители.
Източник: habr.com
