РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима

Толеранција грешака и висока доступност су велике теме, па ћемо РаббитМК-у и Кафки посветити засебне чланке. Овај чланак је о РаббитМК, а следећи о Кафки, у поређењу са РаббитМК. Ово је дугачак чланак, па се раскомотите.

Хајде да погледамо стратегије толеранције грешака, доследности и високе доступности (ХА) и компромисе које свака стратегија чини. РаббитМК може да ради на кластеру чворова - и онда је класификован као дистрибуирани систем. Када су у питању дистрибуирани системи, често говоримо о доследности и доступности.

Ови концепти описују како се систем понаша када поквари. Грешка мрежне везе, грешка сервера, квар чврстог диска, привремена недоступност сервера због сакупљања смећа, губитка пакета или успоравања мрежне везе. Све ово може довести до губитка података или сукоба. Испоставило се да је практично немогуће поставити систем који је потпуно конзистентан (без губитка података, нема дивергенције података) и доступан (прихватиће читање и писање) за све сценарије неуспеха.

Видећемо да су конзистентност и доступност на супротним крајевима спектра, а ви морате да изаберете који начин ћете оптимизовати. Добра вест је да је са РаббитМК овај избор могућ. Имате ове врсте „штреберских“ полуга да померите равнотежу ка већој доследности или већој доступности.

Посебно ћемо обратити пажњу на то које конфигурације доводе до губитка података због потврђених записа. Постоји ланац одговорности између издавача, брокера и потрошача. Једном када се порука пренесе брокеру, његов посао је да не изгуби поруку. Када брокер потврди пријем поруке издавачу, не очекујемо да ће она бити изгубљена. Али видећемо да се то заиста може десити у зависности од конфигурације вашег брокера и издавача.

Примитиве отпорности на један чвор

Отпорно чекање/усмеравање

Постоје две врсте редова у РаббитМК-у: трајни и нетрајни. Сви редови се чувају у бази података Мнесиа. Трајни редови се поново оглашавају при покретању чвора и на тај начин преживљавају поновно покретање, пад система или пад сервера (све док се подаци задржавају). То значи да све док декларишете рутирање (размену) и ред чекања отпорним, инфраструктура за чекање/рутирање ће се вратити на мрежу.

Нестални редови и рутирање се уклањају када се чвор поново покрене.

Трајне поруке

Само зато што је ред трајан не значи да ће све његове поруке преживети поновно покретање чвора. Само поруке које је издавач поставио као отпорна (упоран). Сталне поруке стварају додатно оптерећење за брокера, али ако је губитак поруке неприхватљив, онда нема друге опције.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 1. Матрица одрживости

Груписање са пресликавањем редова

Да бисмо преживели губитак брокера, потребан нам је вишак. Можемо комбиновати више РаббитМК чворова у кластер, а затим додати додатну редундантност реплицирањем редова између више чворова. На овај начин, ако један чвор поквари, не губимо податке и остајемо доступни.

Пресликавање реда:

  • један главни ред (мастер), који прима све команде за писање и читање
  • једно или више огледала која примају све поруке и метаподатке из главног реда. Ова огледала нису ту за скалирање, већ искључиво за редундантност.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 2. Пресликавање реда

Пресликавање је подешено одговарајућом политиком. У њему можете одабрати коефицијент репликације, па чак и чворове на којима треба да се налази ред. Примери:

  • ha-mode: all
  • ha-mode: exactly, ha-params: 2 (један мајстор и једно огледало)
  • ha-mode: nodes, ha-params: rabbit@node1, rabbit@node2

Потврда издавача

Да бисте постигли доследно снимање, потребна је потврда издавача. Без њих постоји ризик од губитка порука. Потврда се шаље издавачу након што је порука записана на диск. РаббитМК записује поруке на диск не по пријему, већ на периодичној основи, у региону од неколико стотина милисекунди. Када се ред огледа, потврда се шаље тек након што сви огледали такође запишу своју копију поруке на диск. То значи да коришћење потврда додаје кашњење, али ако је безбедност података важна, онда су оне неопходне.

Фаиловер куеуе

Када брокер одустане или се сруши, сви вође реда (мајстори) на том чвору падају заједно са њим. Група затим бира најстарије огледало сваког мастера и промовише га као новог мастера.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 3. Вишеструки пресликани редови и њихове смернице

Брокер 3 пада. Имајте на уму да се огледало Куеуе Ц на Брокеру 2 унапређује у мастер. Такође имајте на уму да је креирано ново огледало за ред Ц на Брокеру 1. РаббитМК увек покушава да одржи фактор репликације наведен у вашим смерницама.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 4. Брокер 3 не успева, што доводи до неуспеха реда Ц

Следећи Брокер 1 пада! Остао нам је само један брокер. Огледало реда Б се унапређује у мастер.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Сл. КСНУМКС

Вратили смо брокера 1. Без обзира на то колико добро подаци преживљавају губитак и опоравак брокера, све поруке у реду чекања се одбацују након поновног покретања. Ово је важно напоменути јер ће бити последица. Ускоро ћемо размотрити ове импликације. Дакле, Брокер 1 је сада поново члан кластера, а кластер покушава да се придржава смерница и стога ствара огледала на Брокеру 1.

У овом случају, губитак Брокера 1 је био потпун, као и подаци, тако да је ред Б без огледала потпуно изгубљен.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 6. Брокер 1 се враћа у службу

Брокер 3 је поново на мрежи, тако да редови А и Б враћају огледала направљена на њему да би задовољили своје ХА смернице. Али сада су сви главни редови на једном чвору! Ово није идеално, боља је равномерна расподела између чворова. Нажалост, овде нема много опција за ребаланс мајстора. Касније ћемо се вратити на овај проблем јер прво треба да погледамо синхронизацију реда.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 7. Брокер 3 се враћа у службу. Сви главни редови на једном чвору!

Дакле, сада бисте требали имати идеју о томе како огледала пружају редундантност и толеранцију грешака. Ово осигурава доступност у случају квара једног чвора и штити од губитка података. Али још нисмо готови, јер је у стварности много компликованије.

Синхронизација

Када креирате ново огледало, све нове поруке ће се увек реплицирати на ово огледало и све друге. Што се тиче постојећих података у главном реду, можемо их реплицирати на ново огледало, које постаје потпуна копија главног реда. Такође можемо изабрати да не реплицирамо постојеће поруке и пустимо да се главни ред и ново огледало конвергирају у времену, при чему нове поруке стижу на реп, а постојеће поруке напуштају главу главног реда.

Ова синхронизација се обавља аутоматски или ручно и њоме се управља помоћу политике реда. Погледајмо пример.

Имамо два огледана реда. Ред А се аутоматски синхронизује, а Ред Б се синхронизује ручно. Оба реда садрже десет порука.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 8. Два реда са различитим режимима синхронизације

Сада губимо Брокера 3.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 9. Брокер 3 је пао

Брокер 3 се враћа у службу. Кластер креира огледало за сваки ред на новом чвору и аутоматски синхронизује нови ред А са главним. Међутим, огледало новог реда Б остаје празно. На овај начин имамо потпуну редундантност на реду А и само једно огледало за постојеће поруке реда Б.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 10. Ново огледало реда А прима све постојеће поруке, али ново огледало реда Б не.

У оба реда стиже још десет порука. Брокер 2 се затим руши и Ред А се враћа на најстарији огледало, који је на Брокеру 1. Нема губитка података када не успе. У реду Б, има двадесет порука у мастеру и само десет у огледалу јер овај ред никада није реплицирао оригиналних десет порука.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 11. Ред А се враћа на Брокера 1 без губитка порука

У оба реда стиже још десет порука. Сада се Брокер 1 руши. Ред А лако се пребацује на огледало без губитка порука. Међутим, ред Б има проблема. У овом тренутку можемо оптимизирати или доступност или конзистентност.

Ако желимо да оптимизујемо приступачност, онда политика ха-промовисати-на-неуспех треба инсталирати у увек. Ово је подразумевана вредност, тако да једноставно не можете да наведете смернице уопште. У овом случају, у суштини дозвољавамо кварове у несинхронизованим огледалима. Ово ће довести до губитка порука, али ће ред остати читљив и уписив.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 12. Ред А се враћа на Брокер 3 без губитка порука. Ред Б се враћа на Брокера 3 са десет изгубљених порука

Такође можемо да инсталирамо ha-promote-on-failure у смисао when-synced. У овом случају, уместо враћања на огледало, ред ће чекати док се Брокер 1 са својим подацима не врати у онлајн режим. Након што се врати, главни ред се враћа на Брокер 1 без губитка података. Доступност се жртвује ради безбедности података. Али ово је ризичан режим који чак може довести до потпуног губитка података, што ћемо ускоро погледати.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 13. Ред Б остаје недоступан након губитка Брокера 1

Можда ћете питати: „Да ли је боље никада не користити аутоматску синхронизацију?“ Одговор је да је синхронизација операција блокирања. Током синхронизације, главни ред не може да изврши никакве операције читања или писања!

Погледајмо пример. Сада имамо веома дуге редове. Како могу да нарасту до такве величине? Из више разлога:

  • Редови се не користе активно
  • Ово су редови велике брзине, а тренутно су потрошачи спори
  • Велики су редови, дошло је до квара и потрошачи сустижу корак

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 14. Два велика реда чекања са различитим режимима синхронизације

Сада Брокер 3 пада.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 15. Брокер 3 пада, остављајући по једног мајстора и огледало у сваком реду

Брокер 3 се враћа онлајн и креирају се нова огледала. Главни ред А почиње да реплицира постојеће поруке на ново огледало, а за то време ред је недоступан. Потребно је два сата да се реплицирају подаци, што резултира два сата застоја за овај Ред!

Међутим, ред Б остаје доступан током читавог периода. Жртвовала је мало вишка ради приступачности.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 16. Ред остаје недоступан током синхронизације

Након два сата, ред А такође постаје доступан и може поново да почне да прихвата читање и писање.

Ажурирање

Ово блокирајуће понашање током синхронизације отежава ажурирање кластера са веома великим редовима. У неком тренутку, главни чвор треба поново да се покрене, што значи или прелазак на огледало или онемогућавање реда док се сервер надограђује. Ако одлучимо да пређемо, изгубићемо поруке ако огледала нису синхронизована. Подразумевано, током прекида рада брокера, прелазак на грешку на несинхронизовано огледало се не изводи. То значи да чим се брокер врати, ми не губимо ниједну поруку, једина штета је обичан ред. Правила понашања када је брокер искључен су постављена политиком ha-promote-on-shutdown. Можете подесити једну од две вредности:

  • always= прелазак на несинхронизована огледала је омогућен
  • when-synced= прелазак само на синхронизовано огледало, иначе ред постаје нечитљив и не може се писати. Ред се враћа у службу чим се брокер врати

На овај или онај начин, са великим редовима морате бирати између губитка података и недоступности.

Када доступност побољшава безбедност података

Постоји још једна компликација коју треба размотрити пре него што донесете одлуку. Иако је аутоматска синхронизација боља за редундантност, како утиче на безбедност података? Наравно, са бољом редундантношћу, РаббитМК је мање вероватно да ће изгубити постојеће поруке, али шта је са новим порукама од издавача?

Овде морате узети у обзир следеће:

  • Да ли би издавач једноставно могао да врати грешку и да претходна услуга или корисник покуша поново касније?
  • Може ли издавач да сачува поруку локално или у бази података да би покушао поново касније?

Ако издавач може само да одбаци поруку, у ствари, побољшање приступачности такође побољшава безбедност података.

Дакле, мора се тражити баланс, а решење зависи од конкретне ситуације.

Проблеми са ха-промоте-он-фаилуре=вхен-синцед

Идеја ха-промовисати-на-неуспех= када се синхронизује је да спречавамо прелазак на несинхронизовано огледало и на тај начин избегавамо губитак података. Ред остаје нечитљив или може да се упише. Уместо тога, покушавамо да повратимо срушеног брокера са нетакнутим подацима како би могао да настави да функционише као главни без губитка података.

Али (а ово је велико али) ако је брокер изгубио своје податке, онда имамо велики проблем: ред је изгубљен! Сви подаци су нестали! Чак и ако имате огледала која углавном сустижу главни ред чекања, та огледала се такође одбацују.

Да бисмо поново додали чвор са истим именом, кажемо кластеру да заборави изгубљени чвор (са командом раббитмкцтл заборави_цлустер_ноде) и покрените новог брокера са истим именом хоста. Док кластер памти изгубљени чвор, он памти стари ред чекања и несинхронизована огледала. Када се кластеру каже да заборави напуштени чвор, тај ред се такође заборавља. Сада то треба поново да прогласимо. Изгубили смо све податке, иако смо имали огледала са делимичним скупом података. Било би боље да пређете на несинхронизовано огледало!

Стога, ручна синхронизација (и неуспјех синхронизације) у комбинацији са ha-promote-on-failure=when-synced, по мом мишљењу, прилично ризично. Документи кажу да ова опција постоји за безбедност података, али то је нож са две оштрице.

Мастер ребаланс

Као што смо обећали, враћамо се на проблем акумулације свих мастера на једном или више чворова. Ово се чак може догодити као резултат ажурирања кластера. У кластеру са три чвора, сви главни редови ће се акумулирати на једном или два чвора.

Ребаланс мајстора може бити проблематичан из два разлога:

  • Не постоје добри алати за извођење ребаланса
  • Синхронизација у реду чекања

Постоји трећа страна за ребаланс плугин, што није званично подржано. Што се тиче додатака трећих страна у приручнику РаббитМК рекао: „Додатак пружа неке додатне алате за конфигурацију и извештавање, али га РаббитМК тим не подржава нити верификује. Користите на властиту одговорност."

Постоји још један трик за померање главног реда кроз ХА политике. У приручнику се помиње скрипта за ово. ради овако:

  • Уклања сва огледала помоћу привремене политике која има већи приоритет од постојеће ХА политике.
  • Мења привремену политику ХА да користи режим чвора, наводећи чвор на који треба да се пренесе главни ред.
  • Синхронизује ред за пусх миграцију.
  • Када се миграција заврши, брише привремену политику. Почетна ХА политика ступа на снагу и прави се потребан број огледала.

Лоша страна је што овај приступ можда неће радити ако имате велике редове или строге захтеве за редундантношћу.

Сада да видимо како РаббитМК кластери раде са мрежним партицијама.

Губитак везе

Чворови дистрибуираног система су повезани мрежним везама, а мрежне везе могу и биће искључене. Учесталост прекида зависи од локалне инфраструктуре или поузданости изабраног облака. У сваком случају, дистрибуирани системи морају бити у стању да се носе са њима. Још једном имамо избор између доступности и доследности, а опет добра вест је да РаббитМК пружа обе опције (само не у исто време).

Са РаббитМК имамо две главне опције:

  • Дозволи логичку поделу (раздвојени мозак). Ово осигурава доступност, али може узроковати губитак података.
  • Онемогућите логичко раздвајање. Може довести до краткорочног губитка доступности у зависности од тога како се клијенти повезују са кластером. Такође може довести до потпуне недоступности у кластеру са два чвора.

Али шта је логично раздвајање? Ово је када се кластер подели на два дела због губитка мрежних веза. На свакој страни, огледала се унапређују у мајстора, тако да на крају има неколико мајстора по окрету.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 17. Главни ред и два огледала, свако на посебном чвору. Тада долази до квара на мрежи и једно огледало се одваја. Одвојени чвор види да су друга два отпала и промовише своја огледала господару. Сада имамо два главна реда, и за писање и за читање.

Ако издавачи пошаљу податке оба мастера, на крају имамо две различите копије реда.

РаббитМК-ови различити режими обезбеђују или доступност или доследност.

Режим игнорисања (подразумевано)

Овај режим обезбеђује приступачност. Након губитка везе, долази до логичног раздвајања. Након успостављања везе, администратор мора одлучити којој партицији да да приоритет. Страна која губи биће поново покренута и сви акумулирани подаци на тој страни биће изгубљени.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 18. Три издавача су повезана са три брокера. Интерно, кластер усмерава све захтеве у главни ред на Брокеру 2.

Сада губимо Брокера 3. Он види да су други брокери отпали и промовише своје огледало у господара. Тако долази до логичног раздвајања.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 19. Логичка подела (сплит-браин). Записи иду у два главна реда, а две копије се разилазе.

Повезивање је обновљено, али логично раздвајање остаје. Администратор мора ручно изабрати страну која губи. У случају испод, администратор поново покреће Брокер 3. Све поруке које није успео да пренесе су изгубљене.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 20. Администратор онемогућава Брокера 3.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 21. Администратор покреће Брокер 3 и он се придружује кластеру, губи све поруке које су тамо остављене.

Током губитка везе и након њеног обнављања, кластер и овај ред су били доступни за читање и писање.

Аутохеал моде

Функционише слично режиму Игноре, осим што сам кластер аутоматски бира страну која губи након раздвајања и враћања везе. Страна која губи све се враћа у празан кластер, а ред губи све поруке које су послате само тој страни.

Паузирајте режим мањина

Ако не желимо да дозволимо логичко партиционисање, онда је наша једина опција да одбацимо читање и писање на мањој страни после партиције кластера. Када брокер види да је на мањој страни, обуставља рад, односно затвара све постојеће везе и одбија нове. Једном у секунди проверава да ли је конекција успостављена. Када се веза успостави, наставља са радом и придружује се кластеру.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 22. Три издавача су повезана са три брокера. Интерно, кластер усмерава све захтеве у главни ред на Брокеру 2.

Брокери 1 и 2 се затим одвајају од Брокера 3. Уместо да промовише своје огледало у мастер, Брокер 3 се суспендује и постаје недоступан.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 23. Брокер 3 паузира, искључује све клијенте и одбија захтеве за повезивање.

Када се веза поново успостави, враћа се у кластер.

Погледајмо још један пример где је главни ред на Брокеру 3.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 24. Главни ред на Брокеру 3.

Тада долази до истог губитка везе. Брокер 3 паузира јер је на мањој страни. Са друге стране, чворови виде да је Брокер 3 отпао, тако да се старије огледало из Брокера 1 и 2 унапређује у мастер.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 25. Прелазак на Брокер 2 ако Брокер 3 није доступан.

Када се веза успостави, Брокер 3 ће се придружити кластеру.

РаббитМК против Кафке: Толеранција грешака и висока доступност у кластерима
Пиринач. 26. Кластер се вратио у нормалан рад.

Овде је важно разумети да добијамо доследност, али такође можемо да добијемо доступност, ако Успешно ћемо пребацити клијенте на већи део секције. За већину ситуација, лично бих одабрао режим Паусе Минорити, али то заиста зависи од појединачног случаја.

Да бисте осигурали доступност, важно је осигурати да се клијенти успешно повезују са хостом. Хајде да погледамо наше опције.

Обезбеђивање повезаности купаца

Имамо неколико опција како да усмеримо клијенте на главни део кластера или на радне чворове (након што један чвор откаже) након губитка везе. Прво, подсетимо се да је одређени ред смештен на одређеном чвору, али се рутирање и смернице реплицирају на све чворове. Клијенти се могу повезати са било којим чвором, а интерно рутирање ће их усмерити тамо где треба да иду. Али када је чвор суспендован, он одбија везе, тако да се клијенти морају повезати са другим чвором. Ако чвор отпадне, он уопште мало може да уради.

Наше опције:

  • Кластеру се приступа помоћу балансера оптерећења који једноставно пролази кроз чворове и клијенти поново покушавају да се повежу док не успеју. Ако је чвор искључен или суспендован, покушаји да се повежете са тим чвором неће успети, али наредни покушаји ће ићи на друге сервере (на кружни начин). Ово је погодно за краткорочни губитак везе или оборен сервер који ће се брзо вратити.
  • Приступите кластеру преко балансера оптерећења и уклоните суспендоване/неуспеле чворове са листе чим се открију. Ако ово урадимо брзо и ако клијенти буду у могућности да поново покушају везу, тада ћемо постићи сталну доступност.
  • Дајте сваком клијенту листу свих чворова, а клијент насумично бира један од њих приликом повезивања. Ако добије грешку када покушава да се повеже, прелази на следећи чвор на листи док се не повеже.
  • Уклоните саобраћај са неуспелог/суспендованог чвора користећи ДНС. Ово се ради помоћу малог ТТЛ-а.

Налази

РаббитМК кластеровање има своје предности и мане. Најозбиљнији недостаци су:

  • када се придружују кластеру, чворови одбацују своје податке;
  • блокирање синхронизације доводи до тога да ред постане недоступан.

Све тешке одлуке проистичу из ове две архитектонске карактеристике. Када би РаббитМК могао да сачува податке када се кластер поново придружи, синхронизација би била бржа. Да је способан за неблокирајућу синхронизацију, боље би подржавао велике редове. Решавање ова два проблема би у великој мери побољшало перформансе РаббитМК-а као технологије толерантне на грешке и веома доступне технологије за размену порука. Оклевао бих да препоручим РаббитМК са кластерисањем у следећим ситуацијама:

  • Непоуздана мрежа.
  • Непоуздано складиштење.
  • Веома дуги редови.

Када су у питању подешавања високе доступности, узмите у обзир следеће:

  • ha-promote-on-failure=always
  • ha-sync-mode=manual
  • cluster_partition_handling=ignore (Или autoheal)
  • упорне поруке
  • осигурати да се клијенти повежу на активни чвор када неки чвор откаже

За доследност (безбедност података), размотрите следећа подешавања:

  • Потврда издавача и ручна признања на страни потрошача
  • ha-promote-on-failure=when-synced, ако издавачи могу поново да покушају касније и ако имате веома поуздано складиште! Иначе ставите =always.
  • ha-sync-mode=automatic (али за велике неактивне редове може бити потребан ручни режим; такође размотрите да ли ће недоступност узроковати губитак порука)
  • Паузирајте режим мањина
  • упорне поруке

Још увек нисмо покрили сва питања толеранције грешака и високе доступности; на пример, како да безбедно извршите административне процедуре (као што су ажурирања). Такође морамо да причамо о федерацији и додатку Сховел.

Ако сам још нешто пропустио, јавите ми.

Види и моје брзо, где вршим хаос на РаббитМК кластеру користећи Доцкер и Блоцкаде да тестирам неке од сценарија губитка порука описаних у овом чланку.

Претходни чланци у серији:
бр. 1 - хабр.цом/ру/цомпани/итсумма/блог/416629
бр. 2 - хабр.цом/ру/цомпани/итсумма/блог/418389
бр. 3 - хабр.цом/ру/цомпани/итсумма/блог/437446

Извор: ввв.хабр.цом

Додај коментар