Pri transloĝiĝo de Redis al Redis-areto

Pri transloĝiĝo de Redis al Redis-areto

Venante al produkto, kiu disvolviĝas dum pli ol jardeko, tute ne estas surprize trovi malmodernajn teknologiojn en ĝi. Sed kio se en ses monatoj vi devas konservi la ŝarĝon 10 fojojn pli alta, kaj la kosto de faloj pliiĝos centojn da fojoj? En ĉi tiu kazo, vi bezonas bonegan Highload Engineer. Sed en foresto de servistino, ili konfidis al mi solvi la problemon. En la unua parto de la artikolo mi rakontos al vi kiel ni transiris de Redis al Redis-cluster, kaj en la dua parto mi donos konsilojn pri kiel komenci uzi la areton kaj pri kio atenti kiam vi uzas ĝin.

Teknologia Elekto

Ĉu ĝi estas tiel malbona? aparta Redis (memstara redis) en agordo de 1 majstro kaj N sklavoj? Kial mi nomas ĝin malnoviĝinta teknologio?

Ne, Redis ne estas tiom malbona... Tamen estas kelkaj mankoj, kiujn oni ne povas ignori.

  • Unue, Redis ne subtenas katastrofajn reakirmekanismojn post majstra fiasko. Por solvi ĉi tiun problemon, ni uzis agordon kun aŭtomata translokigo de VIP-oj al nova majstro, ŝanĝante la rolon de unu el la sklavoj kaj ŝanĝante la reston. Ĉi tiu mekanismo funkciis, sed ĝi ne povus esti nomata fidinda solvo. Unue, falsaj alarmoj okazis, kaj due, ĝi estis forĵetebla, kaj post operacio manaj agoj estis postulataj por ŝargi la fonton.

  • Due, havi nur unu majstron kondukis al la problemo de sharding. Ni devis krei plurajn sendependajn aretojn "1 majstro kaj N sklavoj", tiam mane distribui la datumbazojn inter ĉi tiuj maŝinoj kaj esperi ke morgaŭ unu el la datumbazoj ne ŝvelus tiom, ke ĝi devus esti movita al aparta kazo.

Kio estas la elektoj?

  • La plej multekosta kaj plej riĉa solvo estas Redis-Enterprise. Ĉi tio estas boksita solvo kun plena teknika subteno. Malgraŭ tio, ke ĝi aspektas ideala el teknika vidpunkto, ĝi ne konvenis al ni pro ideologiaj kialoj.
  • Redis-areto. El la skatolo ekzistas subteno por majstra malsukceso kaj sharding. La interfaco preskaŭ ne diferencas de la regula versio. Ĝi aspektas promesplena, ni parolos pri la faŭltoj poste.
  • Tarantool, Memcache, Aerospike kaj aliaj. Ĉiuj ĉi tiuj iloj faras preskaŭ la samon. Sed ĉiu havas siajn proprajn mankojn. Ni decidis ne meti ĉiujn niajn ovojn en unu korbon. Ni uzas Memcache kaj Tarantool por aliaj taskoj, kaj, rigardante antaŭen, mi diros, ke en nia praktiko estis pli da problemoj kun ili.

Specifaĵoj de uzo

Ni rigardu kiajn problemojn ni historie solvis kun Redis kaj kian funkcion ni uzis:

  • Cache antaŭ petoj al foraj servoj kiel 2GIS | Golang

    GET SET MGET MSET "SELECT DB"

  • Kaŝmemoro antaŭ MYSQL | PHP

    GET SET MGET MSET SCAN "ŜLAVO LAŬ PATRONO" "SELECT DB"

  • La ĉefa stokado por la servo labori kun kunsidoj kaj ŝoforaj koordinatoj | Golang

    GET SET MGET MSET "SELECT DB" "ADD GEO KEY" "GET GEO KEY" SCANADO

Kiel vi povas vidi, ne pli alta matematiko. Kio do estas la malfacilaĵo? Ni rigardu ĉiun metodon aparte.

Metodo
Priskribo
Trajtoj de Redis-cluster
decido

INSTALU
Skribu/legu ŝlosilon

MGET MSET
Skribu/legu plurajn klavojn
La klavoj estos sur malsamaj nodoj. Pretaj bibliotekoj povas fari pluroperaciojn nur ene de unu nodo
Anstataŭigi MGET per dukto de N GET operacioj

ELEKTU DB
Elektu la bazon kun kiu ni laboros
Ne subtenas plurajn datumbazojn
Metu ĉion en unu datumbazon. Aldonu prefiksojn al klavoj

SKANI
Trairu ĉiujn ŝlosilojn en la datumbazo
Ĉar ni havas unu datumbazon, trapasi ĉiujn ŝlosilojn en la areto estas tro multekosta
Konservu senvarian ene de unu ŝlosilo kaj faru HSCAN sur ĉi tiu ŝlosilo. Aŭ rifuzu tute

GEO
Operacioj kun geoŝlosilo
La geoŝlosilo ne estas frakasita

Ŝlosilo laŭ Ŝablono
Serĉante ŝlosilon laŭ ŝablono
Ĉar ni havas unu datumbazon, ni serĉos tra ĉiuj ŝlosiloj en la areto. Tro multekosta
Rifuzi aŭ konservi la senvarian, kiel en la kazo de SCAN

Redis kontraŭ Redis-areto

Kion ni perdas kaj kion ni gajnas kiam ŝanĝas al areto?

  • Malavantaĝoj: ni perdas la funkciecon de pluraj datumbazoj.
    • Se ni volas konservi logike senrilatajn datumojn en unu areto, ni devos fari lambastonojn en formo de prefiksoj.
    • Ni perdas ĉiujn "bazajn" operaciojn, kiel SCAN, DBSIZE, CLEAR DB, ktp.
    • Pluraj operacioj fariĝis multe pli malfacile efektivigi ĉar ĝi povas postuli aliron al pluraj nodoj.
  • Suplementoj:
    • Faŭltoleremo en la formo de majstra malsukceso.
    • Sharding sur la Redis-flanko.
    • Transigi datumojn inter nodoj atome kaj sen malfunkcio.
    • Aldonu kaj redistribuu kapaciton kaj ŝarĝojn sen malfunkcio.

Mi konkludus, ke se vi ne bezonas provizi altan nivelon de toleremo al misfunkciadoj, tiam translokiĝi al areto ne indas, ĉar ĝi povas esti ne-triviala tasko. Sed se vi komence elektas inter aparta versio kaj grapolversio, tiam vi devus elekti areton, ĉar ĝi ne estas pli malbona kaj, krome, malpezigos vin de iuj el la kapdoloroj.

Preparante moviĝi

Ni komencu kun la postuloj por translokiĝi:

  • Ĝi devus esti senjunta. Kompleta halto de servo dum 5 minutoj ne konvenas al ni.
  • Ĝi devus esti kiel eble plej sekura kaj laŭgrada. Mi volas havi iom da kontrolo super la situacio. Ni ne volas forĵeti ĉion samtempe kaj preĝi super la butono de retrotraĵo.
  • Minimuma perdo de datumoj dum moviĝado. Ni komprenas, ke estos tre malfacile moviĝi atome, do ni permesas iun malsinkronigon inter datumoj en regula kaj amasigita Redis.

Areto prizorgado

Ĵus antaŭ la movado, ni devus pensi ĉu ni povas subteni la areton:

  • Leteroj. Ni uzas Prometheus kaj Grafana por grafiki CPU-ŝarĝon, memoruzon, nombron da klientoj, nombron da operacioj GET, SET, AUTH ktp.
  • Sperto. Imagu, ke morgaŭ vi havos grandegan areton sub via respondeco. Se ĝi rompas, neniu krom vi povas ripari ĝin. Se li komencos malrapidiĝi, ĉiuj kuros al vi. Se vi bezonas aldoni rimedojn aŭ redistribui la ŝarĝon, revenu al vi. Por ne griziĝi ĉe 25, estas konsilinde zorgi pri ĉi tiuj kazoj kaj kontroli antaŭe kiel la teknologio kondutos sub iuj agoj. Ni parolu pri tio pli detale en la sekcio "Sperto".
  • Monitorado kaj atentigoj. Kiam areto rompiĝas, vi volas esti la unua, kiu scias pri ĝi. Ĉi tie ni limigis nin al sciigo, ke ĉiuj nodoj resendas la samajn informojn pri la stato de la areto (jes, ĝi okazas malsame). Kaj aliaj problemoj povas esti rimarkitaj pli rapide per atentigoj de Redis-klientservoj.

Ekloĝado

Kiel ni moviĝos:

  • Antaŭ ĉio, vi devas prepari bibliotekon por labori kun la cluster. Ni prenis go-redis kiel la bazon por la Go-versio kaj ŝanĝis ĝin iomete por konveni al ni mem. Ni efektivigis Mult-metodojn per duktoj, kaj ankaŭ iomete korektis la regulojn por ripeti petojn. La PHP-versio havis pli da problemoj, sed ni finfine decidis por php-redis. Ili lastatempe enkondukis grapolsubtenon kaj ĝi aspektas bone laŭ nia opinio.
  • Poste vi devas deploji la areton mem. Ĉi tio estas farita laŭvorte en du komandoj bazitaj sur la agorda dosiero. Ni diskutos la agordon pli detale sube.
  • Por laŭgrada movo ni uzas sekan reĝimon. Ĉar ni havas du versiojn de la biblioteko kun la sama interfaco (unu por la regula versio, la alia por la cluster), ĝi kostas nenion krei envolvaĵon, kiu funkcios kun aparta versio kaj paralele duobligos ĉiujn petojn al la cluster, komparu respondojn kaj skribu diferencojn en la protokoloj (en nia kazo en NewRelic). Tiel, eĉ se la cluster-versio rompas dum lanĉo, nia produktado ne estos tuŝita.
  • Disvolvinte la areton en seka reĝimo, ni povas trankvile rigardi la grafikon de respondaj diferencoj. Se la erarkvanto malrapide sed certe moviĝas al iu malgranda konstanto, tiam ĉio estas en ordo. Kial ankoraŭ ekzistas diferencoj? Ĉar registrado en aparta versio okazas iom pli frue ol en la areto, kaj pro mikrolag, la datumoj povas diverĝi. Restas nur rigardi la malkonformajn protokolojn, kaj se ili ĉiuj estas klarigitaj per la ne-atomiko de la rekordo, tiam ni povas pluiri.
  • Nun vi povas ŝanĝi sekan reĝimon en la kontraŭa direkto. Ni skribos kaj legos el la areto, kaj duobligos ĝin en apartan version. Por kio? Dum la venonta semajno mi ŝatus observi la laboron de la areto. Se subite montriĝas, ke estas problemoj ĉe plej alta ŝarĝo, aŭ ni ne konsideris ion, ni ĉiam havas krizan retrovon al la malnova kodo kaj aktualaj datumoj danke al seka reĝimo.
  • Restas nur malŝalti sekan reĝimon kaj malmunti la apartan version.

Sperteco

Unue, mallonge pri la areto-dezajno.

Antaŭ ĉio, Redis estas ŝlosilvalora vendejo. Arbitraj ŝnuroj estas uzataj kiel ŝlosiloj. Nombroj, ŝnuroj kaj tutaj strukturoj povas esti uzataj kiel valoroj. Estas tre multaj el ĉi-lastaj, sed por kompreni la ĝeneralan strukturon tio ne gravas por ni.
La sekva nivelo de abstraktado post ŝlosiloj estas fendoj (SLOTS). Ĉiu ŝlosilo apartenas al unu el 16 fendoj. Povas esti ajna nombro da ŝlosiloj ene de ĉiu fendo. Tiel, ĉiuj ŝlosiloj estas dividitaj en 383 disajn arojn.
Pri transloĝiĝo de Redis al Redis-areto

Poste, devas esti N majstraj nodoj en la areto. Ĉiu nodo povas esti opiniita kiel aparta Redis-instanco kiu scias ĉion pri aliaj nodoj ene de la areto. Ĉiu majstra nodo enhavas kelkajn fendojn. Ĉiu fendeto apartenas al nur unu majstra nodo. Ĉiuj fendoj devas esti distribuitaj inter nodoj. Se iuj fendoj ne estas asignitaj, tiam la ŝlosiloj konservitaj en ili estos neatingeblaj. Ĝi havas sencon ruli ĉiun majstran nodon sur aparta logika aŭ fizika maŝino. Ankaŭ indas memori, ke ĉiu nodo nur funkcias per unu kerno, kaj se vi volas ruli plurajn Redis-instancojn sur la sama logika maŝino, certigu, ke ili funkcias per malsamaj kernoj (ni ne provis ĉi tion, sed teorie ĝi devus funkcii) . Esence, majstraj nodoj disponigas regulan sharding, kaj pli da majstraj nodoj permesas skribi kaj legi petojn grimpi.

Post kiam ĉiuj ŝlosiloj estas distribuitaj inter la fendoj, kaj la fendoj estas disigitaj inter la majstraj nodoj, arbitra nombro da sklavnodoj povas esti aldonita al ĉiu majstra nodo. Ene de ĉiu tia majstro-sklava ligo, normala reproduktado funkcios. Sklavoj estas necesaj por skali legopetojn kaj por malsukceso en kazo de majstra fiasko.
Pri transloĝiĝo de Redis al Redis-areto

Nun ni parolu pri operacioj, kiujn estus pli bone povi fari.

Ni aliros la sistemon per Redis-CLI. Ĉar Redis ne havas ununuran enirpunkton, vi povas fari la sekvajn operaciojn sur iu el la nodoj. En ĉiu punkto mi aparte atentigas pri la ebleco plenumi la operacion sub ŝarĝo.

  • La unua kaj plej grava afero, kiun ni bezonas, estas la operacio de la grapolnodoj. Ĝi resendas la staton de la areto, montras liston de nodoj, iliajn rolojn, fendodistribuon, ktp. Pli da informoj povas esti akiritaj uzante areto-informojn kaj aretfendojn.
  • Estus bone povi aldoni kaj forigi nodojn. Por ĉi tiu celo estas cluster-renkontiĝo kaj cluster forget operacioj. Bonvolu noti, ke clusterforgeso devas esti aplikita al ĈIU nodo, kaj majstroj kaj kopioj. Kaj amaskunveno nur bezonas esti vokita sur unu nodo. Ĉi tiu diferenco povas esti malkoncerta, do estas plej bone lerni pri ĝi antaŭ ol vi vivi kun via areto. Aldono de nodo estas farita sekure en batalo kaj neniel influas la funkciadon de la areto (kio estas logika). Se vi forigos nodon de la areto, vi devas certigi, ke ne restas fendoj sur ĝi (alie vi riskas perdi aliron al ĉiuj ŝlosiloj sur ĉi tiu nodo). Ankaŭ, ne forigu majstron kiu havas sklavojn, alie nenecesa voĉdono por nova majstro estos farita. Se la nodoj ne plu havas fendojn, tiam ĉi tio estas malgranda problemo, sed kial ni bezonas kromajn elektojn se ni unue povas forigi la sklavojn.
  • Se vi bezonas perforte interŝanĝi la mastro kaj sklavo pozicioj, tiam la cluster-malsukcesa komando faros. Nomante ĝin en batalo, vi devas kompreni, ke la majstro ne estos disponebla dum la operacio. Tipe la ŝaltilo okazas en malpli ol sekundo, sed ne estas atoma. Vi povas atendi, ke iuj petoj al la majstro malsukcesos dum ĉi tiu tempo.
  • Antaŭ ol forigi nodon de la areto, ne restu fendoj sur ĝi. Pli bone estas redistribui ilin per la komando de cluster reshard. Slots estos translokigitaj de unu majstro al alia. La tuta operacio povas daŭri plurajn minutojn, ĝi dependas de la transdono de datumoj, sed la transiga procezo estas sekura kaj neniel influas la funkciadon de la areto. Tiel, ĉiuj datumoj povas esti transdonitaj de unu nodo al alia rekte sub ŝarĝo, kaj sen zorgi pri ĝia havebleco. Tamen, estas ankaŭ subtilecoj. Unue, transdono de datumoj rilatas al certa ŝarĝo sur la ricevanto kaj sendinto nodoj. Se la ricevanto nodo jam estas forte ŝarĝita sur la procesoro, tiam vi ne devus ŝarĝi ĝin kun ricevado de novaj datumoj. Due, tuj kiam ne restas eĉ unu fendeto sur la sendanta majstro, ĉiuj ĝiaj sklavoj tuj iros al la majstro al kiu ĉi tiuj fendoj estis transdonitaj. Kaj la problemo estas, ke ĉiuj ĉi tiuj sklavoj volos sinkronigi datumojn samtempe. Kaj vi estos bonŝanca se ĝi estas parta anstataŭ kompleta sinkronigo. Konsideru ĉi tion kaj kombinu la operaciojn translokigi slotojn kaj malŝalti/transloki sklavojn. Aŭ esperu, ke vi havas sufiĉan marĝenon de sekureco.
  • Kion vi faru se, dum la translokigo, vi trovas, ke vi perdis viajn slotojn ie? Mi esperas, ke ĉi tiu problemo ne influas vin, sed se ĝi faras, estas aranĝo de grapo-riparado. Almenaŭ ŝi disĵetos la fendojn tra la nodoj en hazarda ordo. Mi rekomendas kontroli ĝian funkciadon unue forigante la nodon kun distribuitaj fendoj de la areto. Ĉar datumoj en neasignitaj fendoj jam estas neatingeblaj, estas tro malfrue por zorgi pri problemoj kun la havebleco de ĉi tiuj fendoj. Siavice, la operacio ne influos distribuitajn fendojn.
  • Alia utila operacio estas monitoro. Ĝi permesas vin vidi en reala tempo la tutan liston de petoj irantaj al la nodo. Plie, vi povas grep ĝin kaj ekscii ĉu ekzistas la necesa trafiko.

Indas ankaŭ mencii la majstran malsukcesan proceduron. Resume, ĝi ekzistas, kaj, laŭ mi, ĝi funkcias bonege. Tamen, ne pensu, ke se vi malŝlosas la elektran ŝnuron sur maŝino kun majstra nodo, Redis tuj ŝanĝos kaj klientoj ne rimarkos la perdon. En mia praktiko, la ŝanĝado okazas en kelkaj sekundoj. Dum ĉi tiu tempo, iuj el la datumoj estos neatingeblaj: la nehavebleco de la majstro estas detektita, nodoj voĉdonas por nova, sklavoj estas ŝanĝitaj, datumoj estas sinkronigitaj. La plej bona maniero por certigi mem, ke la skemo funkcias, estas fari lokajn ekzercojn. Levu la areton sur via tekkomputilo, donu al ĝi minimuman ŝarĝon, simulu kraŝon (ekzemple, blokante la havenojn), kaj taksu la ŝanĝrapidecon. Miaopinie, nur post ludado tiamaniere dum unu aŭ du tagoj vi povas esti certa pri la funkciado de la teknologio. Nu, aŭ esperu, ke verŝajne funkcias la programaro, kiun uzas duono de la Interreto.

Agordo

Ofte, la agordo estas la unua afero, kiun vi bezonas por komenci labori kun la ilo.Kaj kiam ĉio funkcias, vi eĉ ne volas tuŝi la agordon. Necesas iom da peno devigi vin reiri al la agordoj kaj zorge trarigardi ilin. En mia memoro, ni havis almenaŭ du seriozajn malsukcesojn pro neatentigo al la agordo. Atentu speciale la sekvajn punktojn:

  • tempodaŭro 0
    Tempo post kiu neaktivaj konektoj estas fermitaj (en sekundoj). 0 - ne fermu
    Ne ĉiu nia biblioteko povis ĝuste fermi ligojn. Malebligante ĉi tiun agordon, ni riskas trafi la limon de la nombro da klientoj. Aliflanke, se ekzistas tia problemo, tiam aŭtomata fino de perditaj konektoj maskos ĝin, kaj ni eble ne rimarkos. Krome, vi ne devus ebligi ĉi tiun agordon kiam vi uzas persistajn konektojn.
  • Konservu xy kaj nur jes
    Konservante RDB-foton.
    Ni diskutos pri RDB/AOF-aferoj detale sube.
  • stop-writes-on-bgsave-eraro ne & slave-serve-stale-data jes
    Se ĝi estas ebligita, se la momentfoto de RDB rompas, la majstro ĉesos akcepti ŝanĝpetojn. Se la konekto al la majstro estas perdita, la sklavo povas daŭrigi respondi al petoj (jes). Aŭ ĉesos respondi (ne)
    Ni ne ĝojas pri la situacio, en kiu Redis iĝas kukurbo.
  • repl-ping-slave-periodo 5
    Post ĉi tiu tempo, ni komencos zorgi, ke la majstro rompiĝis kaj estas tempo efektivigi la malsukcesan proceduron.
    Vi devos permane trovi ekvilibron inter falsaj pozitivoj kaj ekfunkciigo de malsukceso. En nia praktiko ĉi tio estas 5 sekundoj.
  • repl-backlog-size 1024mb & epl-backlog-ttl 0
    Ni povas konservi ĝuste tiom da datumoj en bufro por malsukcesa kopio. Se la bufro finiĝas, vi devos tute sinkronigi.
    Praktiko sugestas, ke estas pli bone agordi pli altan valoron. Estas multaj kialoj, kial kopio povus komenci malfrui. Se ĝi postrestas, tiam plej verŝajne via majstro jam luktas por elteni, kaj plena sinkronigo estos la lasta pajlo.
  • maksimumaj klientoj 10000
    Maksimuma nombro da unufojaj klientoj.
    Laŭ nia sperto, estas pli bone agordi pli altan valoron. Redis bone traktas 10k konektojn. Nur certigu, ke estas sufiĉe da ingoj en la sistemo.
  • maxmemory-politiko volatile-ttl
    La regulo per kiu ŝlosiloj estas forigitaj kiam la disponebla memorlimo estas atingita.
    Gravas ĉi tie ne la regulo mem, sed la kompreno pri kiel tio okazos. Redis povas esti laŭdita pro sia kapablo funkcii normale kiam la memorlimo estas atingita.

Problemoj de RDB kaj AOF

Kvankam Redis mem stokas ĉiujn informojn en RAM, ekzistas ankaŭ mekanismo por konservi datumojn al disko. Pli precize, tri mekanismoj:

  • RDB-snapshot - kompleta momentfoto de ĉiuj datumoj. Agordu per la agordo SAVE XY kaj legas "Konservu plenan foton de ĉiuj datumoj ĉiujn X sekundojn se almenaŭ Y-ŝlosiloj ŝanĝiĝis."
  • Nur almeti dosieron - listo de operacioj en la ordo en kiu ili estas faritaj. Aldonas novajn envenantajn operaciojn al la dosiero ĉiujn X sekundojn aŭ ĉiujn Y operaciojn.
  • RDB kaj AOF estas kombinaĵo de la antaŭaj du.

Ĉiuj metodoj havas siajn avantaĝojn kaj malavantaĝojn, mi ne listigos ilin ĉiujn, mi nur atentigos punktojn, kiuj laŭ mi ne estas evidentaj.

Unue, konservi RDB-foton postulas voki FORK. Se estas multaj datumoj, ĉi tio povas pendigi la tutan Redis dum periodo de kelkaj milisekundoj ĝis sekundo. Krome, la sistemo bezonas asigni memoron por tia momentfoto, kio kondukas al la bezono konservi duoblan provizon de RAM sur la logika maŝino: se 8 GB estas asignitaj por Redis, tiam 16 GB devus esti disponeblaj sur la virtuala maŝino kun ĝi.

Due, estas problemoj kun parta sinkronigo. En AOF-reĝimo, kiam la sklavo estas rekonektita, anstataŭe de parta sinkronigo, plena sinkronigo povas esti farita. Kial tio okazas, mi ne povis kompreni. Sed indas memori ĉi tion.

Ĉi tiuj du punktoj jam igas nin pensi ĉu ni vere bezonas ĉi tiujn datumojn sur la disko, se ĉio estas jam duobligita de sklavoj. Datenoj povas nur esti perditaj se ĉiuj sklavoj malsukcesas, kaj ĉi tio estas "fajro en la DC" nivelproblemo. Kiel kompromiso, vi povas proponi konservi datumojn nur pri sklavoj, sed ĉi-kaze vi devas certigi, ke ĉi tiuj sklavoj neniam fariĝos mastro dum katastrofa reakiro (por tio estas sklava prioritata agordo en ilia agordo). Por ni mem, en ĉiu specifa kazo ni pensas ĉu necesas konservi datumojn al disko, kaj plej ofte la respondo estas "ne".

konkludo

Konklude, mi esperas, ke mi povis doni ĝeneralan ideon pri kiel funkcias redis-cluster por tiuj, kiuj tute ne aŭdis pri ĝi, kaj ankaŭ atentigis kelkajn neevidentajn punktojn por tiuj, kiuj uzis ĝin. Longtempe.
Dankon pro via tempo kaj, kiel ĉiam, komentoj pri la temo estas bonvenaj.

fonto: www.habr.com

Aldoni komenton