Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast

V tomto článku si povieme, ako a prečo sme sa vyvinuli Interakčný systém – mechanizmus, ktorý prenáša informácie medzi klientskymi aplikáciami a servermi 1C:Enterprise – od zadania úlohy až po premýšľanie o architektúre a detailoch implementácie.

Interakčný systém (ďalej len SV) je distribuovaný systém zasielania správ odolný voči chybám s garantovaným doručením. SV je navrhnutá ako služba s vysokým zaťažením s vysokou škálovateľnosťou, ktorá je k dispozícii ako online služba (poskytovaná spoločnosťou 1C) a ako hromadne vyrábaný produkt, ktorý možno nasadiť na vaše vlastné serverové zariadenia.

SV používa distribuované úložisko lieskový oriešok a vyhľadávač ElasticSearch. Povieme si aj o Jave a o tom, ako horizontálne škálujeme PostgreSQL.
Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast

Vyhlásenie o probléme

Aby bolo jasné, prečo sme vytvorili Interaction System, poviem vám niečo o tom, ako funguje vývoj obchodných aplikácií v 1C.

Na začiatok niečo o nás pre tých, ktorí ešte nevedia, čo robíme :) Vytvárame technologickú platformu 1C:Enterprise. Platforma obsahuje nástroj na vývoj podnikových aplikácií, ako aj runtime, ktoré umožňuje podnikovým aplikáciám bežať v prostredí viacerých platforiem.

Paradigma vývoja klient-server

Obchodné aplikácie vytvorené na 1C:Enterprise fungujú na troch úrovniach Klientsky server architektúra „DBMS – aplikačný server – klient“. Kód aplikácie napísaný v vstavaný jazyk 1C, možno spustiť na aplikačnom serveri alebo na klientovi. Všetka práca s aplikačnými objektmi (adresáre, dokumenty atď.), ako aj čítanie a zápis databázy, sa vykonáva iba na serveri. Na serveri je implementovaná aj funkčnosť formulárov a príkazového rozhrania. Klient vykonáva príjem, otváranie a zobrazovanie formulárov, „komunikuje“ s používateľom (upozornenia, otázky...), drobné kalkulácie vo formulároch vyžadujúcich rýchlu odozvu (napríklad vynásobenie ceny množstvom), prácu s lokálnymi súbormi, práca so zariadením.

V kóde aplikácie musia hlavičky procedúr a funkcií explicitne uvádzať, kde sa kód vykoná – pomocou direktív &AtClient / &AtServer (&AtClient / &AtServer v anglickej verzii jazyka). Vývojári 1C ma teraz opravia tým, že smernice sú v skutočnosti viac ako, ale pre nás to teraz nie je dôležité.

Môžete volať kód servera z kódu klienta, ale nemôžete volať kód klienta z kódu servera. Toto je zásadné obmedzenie, ktoré sme urobili z viacerých dôvodov. Najmä preto, že kód servera musí byť napísaný tak, aby sa vykonával rovnakým spôsobom bez ohľadu na to, kde je volaný - z klienta alebo zo servera. A v prípade volania kódu servera z iného kódu servera neexistuje žiadny klient ako taký. A pretože počas vykonávania kódu servera sa klient, ktorý ho volal, mohol zavrieť, ukončiť aplikáciu a server by už nemal komu volať.

Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast
Kód, ktorý spracováva kliknutie na tlačidlo: volanie serverovej procedúry z klienta bude fungovať, volanie klientskej procedúry zo servera nie

To znamená, že ak chceme zo servera poslať do klientskej aplikácie nejakú správu, napríklad, že generovanie „dlhotrvajúceho“ reportu skončilo a report je možné si pozrieť, takú metódu nemáme. Musíte použiť triky, napríklad pravidelne oslovovať server z kódu klienta. Tento prístup však zaťažuje systém zbytočnými hovormi a vo všeobecnosti nevyzerá veľmi elegantne.

A treba aj napríklad, keď príde telefonát SIP- pri telefonovaní na to upozorniť klientsku aplikáciu, aby mohla použiť číslo volajúceho na to, aby ho našla v databáze protistrany a zobrazila používateľovi informácie o volajúcej protistrane. Alebo napríklad, keď objednávka dorazí do skladu, upozornite na to klientsku aplikáciu zákazníka. Vo všeobecnosti existuje veľa prípadov, kedy by bol takýto mechanizmus užitočný.

Samotná výroba

Vytvorte mechanizmus zasielania správ. Rýchly, spoľahlivý, s garantovaným doručením, s možnosťou flexibilného vyhľadávania správ. Na základe tohto mechanizmu implementujte messenger (správy, videohovory), ktorý beží v aplikáciách 1C.

Navrhnite systém tak, aby bol horizontálne škálovateľný. Zvyšujúce sa zaťaženie musí byť pokryté zvýšením počtu uzlov.

Реализация

Serverovú časť SV sme sa rozhodli neintegrovať priamo do platformy 1C:Enterprise, ale implementovať ju ako samostatný produkt, ktorého API je možné volať z kódu aplikačných riešení 1C. Bolo to urobené z niekoľkých dôvodov, z ktorých hlavným bolo, že som chcel umožniť výmenu správ medzi rôznymi aplikáciami 1C (napríklad medzi obchodnou správou a účtovníctvom). Rôzne aplikácie 1C môžu bežať na rôznych verziách platformy 1C:Enterprise, byť umiestnené na rôznych serveroch atď. V takýchto podmienkach je optimálnym riešením implementácia SV ako samostatného produktu umiestneného „na strane“ inštalácií 1C.

Preto sme sa rozhodli vyrobiť SV ako samostatný produkt. Odporúčame, aby malé spoločnosti používali CB server, ktorý sme nainštalovali v našom cloude (wss://1cdialog.com), aby sa vyhli režijným nákladom spojeným s lokálnou inštaláciou a konfiguráciou servera. Veľkí klienti môžu považovať za vhodné nainštalovať ich vlastný CB server vo svojich zariadeniach. Podobný prístup sme použili v našom cloudovom produkte SaaS 1cČerstvé – vyrába sa ako sériovo vyrábaný produkt na inštaláciu u klientov a je nasadený aj v našom cloude https://1cfresh.com/.

Aplikácia

Aby sme rozložili záťaž a odolnosť voči chybám, nasadíme nie jednu Java aplikáciu, ale niekoľko s vyrovnávačom záťaže pred nimi. Ak potrebujete preniesť správu z uzla do uzla, použite publikovať/prihlásiť sa na odber v Hazelcast.

Komunikácia medzi klientom a serverom prebieha cez websocket. Je vhodný pre systémy v reálnom čase.

Distribuovaná vyrovnávacia pamäť

Vyberali sme medzi Redis, Hazelcast a Ehcache. Je rok 2015. Redis práve vydal nový klaster (príliš nový, strašidelný), je tu Sentinel s množstvom obmedzení. Ehcache sa nevie zostaviť do klastra (táto funkcia sa objavila neskôr). Rozhodli sme sa to vyskúšať s Hazelcast 3.4.
Hazelcast je z krabice zostavený do klastra. V režime jedného uzla nie je veľmi užitočný a dá sa použiť iba ako vyrovnávacia pamäť - nevie vypísať dáta na disk, ak stratíte jediný uzol, prídete o dáta. Nasadzujeme niekoľko Hazelcastov, medzi ktorými zálohujeme kritické dáta. Keš nezálohujeme – nevadí nám to.

Pre nás je Hazelcast:

  • Ukladanie používateľských relácií. Vždy trvá dlho prejsť do databázy na reláciu, takže všetky relácie vložíme do Hazelcastu.
  • Cache. Ak hľadáte používateľský profil, skontrolujte vyrovnávaciu pamäť. Napísal novú správu - vložte ju do vyrovnávacej pamäte.
  • Témy pre komunikáciu medzi inštanciami aplikácie. Uzol vygeneruje udalosť a umiestni ju do témy Hazelcast. Ostatné uzly aplikácie prihlásené na odber tejto témy prijímajú a spracúvajú udalosť.
  • Klastrové zámky. Napríklad vytvoríme diskusiu pomocou jedinečného kľúča (jednotná diskusia v databáze 1C):

conversationKeyChecker.check("БЕНЗОКОЛОНКА");

      doInClusterLock("БЕНЗОКОЛОНКА", () -> {

          conversationKeyChecker.check("БЕНЗОКОЛОНКА");

          createChannel("БЕНЗОКОЛОНКА");
      });

Skontrolovali sme, že neexistuje žiadny kanál. Vzali sme zámok, znova ho skontrolovali a vytvorili. Ak po uzamknutí neskontrolujete zámok, je tu šanca, že v tom momente sa skontrolovalo aj iné vlákno a teraz sa pokúsi vytvoriť rovnakú diskusiu - ale už existuje. Nemôžete uzamknúť pomocou synchronizovaného alebo bežného zámku java. Cez databázu - je to pomalé a je to škoda pre databázu; cez Hazelcast - to je to, čo potrebujete.

Výber DBMS

Máme rozsiahle a úspešné skúsenosti s prácou s PostgreSQL a spoluprácou s vývojármi tohto DBMS.

S klastrom PostgreSQL to nie je jednoduché – je XL, XC, Citus, ale vo všeobecnosti to nie sú NoSQL, ktoré sa škálujú hneď po vybalení. NoSQL sme nepovažovali za hlavné úložisko, stačilo, že sme zobrali Hazelcast, s ktorým sme predtým nepracovali.

Ak potrebujete škálovať relačné databázy, znamená to úlomky. Ako viete, pri shardingu rozdeľujeme databázu na samostatné časti, takže každá z nich môže byť umiestnená na samostatnom serveri.

Prvá verzia nášho shardingu predpokladala schopnosť distribuovať každú z tabuliek našej aplikácie na rôzne servery v rôznych pomeroch. Na serveri A je veľa správ – prosím, presuňte časť tejto tabuľky na server B. Toto rozhodnutie jednoducho kričalo o predčasnej optimalizácii, preto sme sa rozhodli obmedziť na prístup viacerých nájomníkov.

O multi-tenantoch sa dočítate napríklad na webe Citusové údaje.

SV má koncepty aplikácie a predplatiteľa. Aplikácia je špecifická inštalácia podnikovej aplikácie, ako je ERP alebo účtovníctvo, s jej používateľmi a obchodnými údajmi. Predplatiteľ je organizácia alebo jednotlivec, v mene ktorého je aplikácia zaregistrovaná na serveri SV. Predplatiteľ môže mať zaregistrovaných niekoľko aplikácií a tieto aplikácie si môžu navzájom vymieňať správy. Predplatiteľ sa stal nájomcom v našom systéme. Správy od viacerých predplatiteľov môžu byť umiestnené v jednej fyzickej databáze; ak vidíme, že predplatiteľ začal generovať veľkú návštevnosť, presunieme ho do samostatnej fyzickej databázy (alebo dokonca do samostatného databázového servera).

Máme hlavnú databázu, kde je uložená smerovacia tabuľka s informáciami o umiestnení všetkých účastníckych databáz.

Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast

Aby hlavná databáza nebola prekážkou, ukladáme smerovaciu tabuľku (a ďalšie často potrebné údaje) do vyrovnávacej pamäte.

Ak sa databáza predplatiteľa začne spomaľovať, interne ju rozdelíme na oddiely. Na iných projektoch, ktoré používame pg_pathman.

Keďže strata používateľských správ je zlá, udržiavame naše databázy s replikami. Kombinácia synchrónnych a asynchrónnych replík umožňuje poistiť sa pre prípad straty hlavnej databázy. K strate správ dôjde iba vtedy, ak primárna databáza a jej synchrónna replika zlyhajú súčasne.

Ak sa synchrónna replika stratí, asynchrónna replika sa stane synchrónnou.
Ak dôjde k strate hlavnej databázy, synchrónna replika sa stane hlavnou databázou a asynchrónna replika sa stane synchrónnou replikou.

Elastické vyhľadávanie pre vyhľadávanie

Keďže SV je okrem iného aj messenger, vyžaduje si rýchle, pohodlné a flexibilné vyhľadávanie s prihliadnutím na morfológiu pomocou nepresných zhôd. Rozhodli sme sa nevynájsť koleso a použiť bezplatný vyhľadávací nástroj Elasticsearch, vytvorený na základe knižnice Lucene. Elasticsearch nasadzujeme aj v klastri (master – data – data), aby sme eliminovali problémy v prípade zlyhania aplikačných uzlov.

Na github sme našli Doplnok ruskej morfológie pre Elasticsearch a použite ho. V indexe Elasticsearch ukladáme korene slov (ktoré plugin určuje) a N-gramy. Keď používateľ zadáva text na vyhľadávanie, hľadáme napísaný text medzi N-gramami. Po uložení do indexu sa slovo „texty“ rozdelí na nasledujúce N-gramy:

[tie, tek, tex, text, texty, ek, ex, ext, texty, ks, kst, ksty, st, sty, vy],

Zachová sa aj koreň slova „text“. Tento prístup vám umožňuje vyhľadávať na začiatku, v strede a na konci slova.

Celkový obraz

Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast
Opakujte obrázok zo začiatku článku, ale s vysvetlivkami:

  • Balancer vystavený na internete; máme nginx, môže to byť akýkoľvek.
  • Inštancie Java aplikácií medzi sebou komunikujú cez Hazelcast.
  • Na prácu s webovou zásuvkou, ktorú používame Netty.
  • Java aplikácia je napísaná v jazyku Java 8 a pozostáva z balíkov OSGi. Plány zahŕňajú migráciu na Java 10 a prechod na moduly.

Vývoj a testovanie

V procese vývoja a testovania SV sme narazili na množstvo zaujímavých vlastností produktov, ktoré používame.

Testovanie záťaže a úniky pamäte

Vydanie každého vydania SV zahŕňa záťažové testovanie. Je úspešná, keď:

  • Test fungoval niekoľko dní a nevyskytli sa žiadne poruchy služby
  • Čas odozvy pre kľúčové operácie neprekročil pohodlný prah
  • Zhoršenie výkonu v porovnaní s predchádzajúcou verziou nie je väčšie ako 10%

Testovaciu databázu naplníme údajmi - za týmto účelom dostaneme z produkčného servera informácie o najaktívnejšom účastníkovi, vynásobíme jeho čísla 5 (počet správ, diskusií, používateľov) a takto to otestujeme.

Záťažové testovanie interakčného systému vykonávame v troch konfiguráciách:

  1. náporový test
  2. Iba pripojenia
  3. Registrácia predplatiteľa

Počas záťažového testu spustíme niekoľko stoviek vlákien a tie načítajú systém bez zastavenia: písanie správ, vytváranie diskusií, prijímanie zoznamu správ. Simulujeme akcie bežných používateľov (získať zoznam mojich neprečítaných správ, napísať niekomu) a softvérové ​​riešenia (preniesť balík inej konfigurácie, spracovať upozornenie).

Takto napríklad vyzerá časť záťažového testu:

  • Používateľ sa prihlási
    • Žiada o vaše neprečítané diskusie
    • 50% pravdepodobnosť, že si prečíta správy
    • Pravdepodobnosť 50% odoslania SMS
    • Ďalší používateľ:
      • Má 20% šancu na vytvorenie novej diskusie
      • Náhodne vyberie ktorúkoľvek zo svojich diskusií
      • Ide dovnútra
      • Žiada správy, užívateľské profily
      • Vytvorí päť správ adresovaných náhodným používateľom z tejto diskusie
      • Opúšťa diskusiu
      • Opakuje sa 20-krát
      • Odhlási sa, vráti sa na začiatok skriptu

    • Chatbot vstúpi do systému (emuluje správy z kódu aplikácie)
      • Má 50% šancu na vytvorenie nového kanála na výmenu údajov (špeciálna diskusia)
      • 50% pravdepodobnosť, že napíše správu na niektorý z existujúcich kanálov

Scenár „Iba pripojenia“ sa objavil z nejakého dôvodu. Existuje situácia: používatelia pripojili systém, ale ešte sa nezapojili. Každý používateľ zapne počítač o 09:00 ráno, nadviaže spojenie so serverom a zostane ticho. Títo chlapíci sú nebezpeční, je ich veľa – jediné balíčky, ktoré majú, sú PING/PONG, no udržiavajú si spojenie so serverom (nedokážu ho udržať – čo ak príde nová správa). Test reprodukuje situáciu, keď sa veľké množstvo takýchto používateľov pokúša prihlásiť do systému za pol hodiny. Podobá sa to záťažovému testu, ale jeho zameranie je práve na tento prvý vstup – aby nedochádzalo k zlyhaniam (človek systém nepoužíva a už mu odpadne – ťažko si vymyslí niečo horšie).

Registračný skript predplatiteľa sa spustí od prvého spustenia. Vykonali sme záťažový test a boli sme si istí, že sa systém počas korešpondencie nespomalil. Používatelia však prišli a registrácia začala zlyhávať z dôvodu časového limitu. Pri registrácii sme použili / Dev / random, čo súvisí s entropiou systému. Server nestihol nahromadiť dostatok entropie a pri požiadavke na nový SecureRandom na desiatky sekúnd zamrzol. Existuje mnoho spôsobov, ako sa z tejto situácie dostať, napríklad: prejsť na menej bezpečný /dev/urandom, nainštalovať špeciálnu dosku, ktorá generuje entropiu, vopred vygenerovať náhodné čísla a uložiť ich do skupiny. Problém s poolom sme dočasne uzavreli, no odvtedy spúšťame samostatný test registrácie nových predplatiteľov.

Používame ako generátor záťaže JMeter. Nevie, ako pracovať s websocket, potrebuje plugin. Prvé vo výsledkoch vyhľadávania pre dopyt „jmeter websocket“ sú: články z BlazeMeter, ktoré odporúčajú plugin od Macieja Zaleského.

Tam sme sa rozhodli začať.

Takmer okamžite po začatí seriózneho testovania sme zistili, že JMeter začala prepúšťať pamäť.

Doplnok je samostatný veľký príbeh; so 176 hviezdičkami má 132 vidličiek na githube. Sám autor sa k tomu nezaviazal od roku 2015 (brali sme to v roku 2015, vtedy to nevyvolávalo podozrenia), niekoľko problémov s githubom ohľadom úniku pamäte, 7 neuzavretých požiadaviek na stiahnutie.
Ak sa rozhodnete vykonať testovanie záťaže pomocou tohto doplnku, venujte pozornosť nasledujúcim diskusiám:

  1. Vo viacvláknovom prostredí sa používal bežný LinkedList a výsledok bol NPE v behu. Dá sa to vyriešiť buď prepnutím na ConcurrentLinkedDeque alebo synchronizovanými blokmi. Pre seba sme si vybrali prvú možnosť (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Únik pamäte; pri odpojení sa informácie o pripojení neodstránia (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. V režime streamovania (keď webová zásuvka nie je zatvorená na konci vzorky, ale je použitá neskôr v pláne), vzory odpovedí nefungujú (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Toto je jeden z tých na github. Čo sme urobili:

  1. Zobral vidlica Elyran Kogan (@elyrank) – opravuje problémy 1 a 3
  2. Vyriešený problém 2
  3. Aktualizované mólo z 9.2.14 na 9.3.12
  4. Zabalený SimpleDateFormat v ThreadLocal; SimpleDateFormat nie je bezpečný pre vlákna, čo viedlo k NPE za behu
  5. Opravený ďalší únik pamäte (pri odpojení bolo pripojenie nesprávne zatvorené)

A predsa tečie!

Pamäť začala dochádzať nie za deň, ale za dva. Nezostával absolútne žiadny čas, tak sme sa rozhodli spustiť menej vlákien, ale na štyroch agentoch. Toto malo stačiť aspoň na týždeň.

Prešli dva dni...

Teraz Hazelcastu dochádza pamäť. Protokoly ukázali, že po niekoľkých dňoch testovania sa Hazelcast začal sťažovať na nedostatok pamäte a po určitom čase sa klaster rozpadol a uzly naďalej umierali jeden po druhom. Pripojili sme JVisualVM k hazelcast a videli sme „stúpajúcu pílu“ - pravidelne volala GC, ale nedokázala vymazať pamäť.

Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast

Ukázalo sa, že v hazelcast 3.4 sa pri odstraňovaní mapy / multiMap (map.destroy()) pamäť úplne neuvoľní:

github.com/hazelcast/hazelcast/issues/6317
github.com/hazelcast/hazelcast/issues/4888

Chyba je teraz opravená vo verzii 3.5, ale vtedy to bol problém. Vytvorili sme nové multiMapy s dynamickými názvami a vymazali sme ich podľa našej logiky. Kód vyzeral asi takto:

public void join(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.put(auth.getUserId(), auth);
}

public void leave(Authentication auth, String sub) {
    MultiMap<UUID, Authentication> sessions = instance.getMultiMap(sub);
    sessions.remove(auth.getUserId(), auth);

    if (sessions.size() == 0) {
        sessions.destroy();
    }
}

Volajte:

service.join(auth1, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");
service.join(auth2, "НОВЫЕ_СООБЩЕНИЯ_В_ОБСУЖДЕНИИ_UUID1");

multiMap bola vytvorená pre každé predplatné a vymazaná, keď to nebolo potrebné. Rozhodli sme sa, že začneme s Mapou , kľúčom bude názov predplatného a hodnotami budú identifikátory relácie (z ktorých potom môžete v prípade potreby získať identifikátory používateľov).

public void join(Authentication auth, String sub) {
    addValueToMap(sub, auth.getSessionId());
}

public void leave(Authentication auth, String sub) { 
    removeValueFromMap(sub, auth.getSessionId());
}

Grafy sa zlepšili.

Ako a prečo sme napísali škálovateľnú službu s vysokým zaťažením pre 1C: Enterprise: Java, PostgreSQL, Hazelcast

Čo sme sa ešte naučili o záťažovom testovaní?

  1. JSR223 musí byť napísaný v groovy a zahrnúť vyrovnávaciu pamäť kompilácie - je to oveľa rýchlejšie. Odkaz.
  2. Grafy Jmeter-Plugins sú zrozumiteľnejšie ako štandardné. Odkaz.

O našich skúsenostiach s Hazelcastom

Hazelcast bol pre nás nový produkt, začali sme s ním pracovať od verzie 3.4.1, teraz na našom produkčnom serveri beží verzia 3.9.2 (v čase písania tohto článku je najnovšia verzia Hazelcast 3.10).

generovanie ID

Začali sme s celočíselnými identifikátormi. Predstavme si, že na novú entitu potrebujeme ďalší Long. Sekvencia v databáze nie je vhodná, tabuľky sú zapojené do shardingu - ukázalo sa, že v DB1 je správa ID=1 a v DB1 je ID=2, toto ID nemôžete vložiť do Elasticsearch, ani do Hazelcast , no najhoršie je, ak chcete spojiť údaje z dvoch databáz do jednej (napr. rozhodnúť, že týmto predplatiteľom stačí jedna databáza). Môžete pridať niekoľko AtomicLongs do Hazelcast a ponechať tam počítadlo, potom sa výkon získania nového ID zvýši AndGet plus čas na požiadavku na Hazelcast. Hazelcast má ale niečo optimálnejšie – FlakeIdGenerator. Pri kontakte s každým klientom je uvedený rozsah ID, napríklad prvý od 1 do 10 000, druhý od 10 001 do 20 000 atď. Teraz môže klient sám vydávať nové identifikátory, kým neskončí rozsah, ktorý mu bol vydaný. Funguje to rýchlo, ale keď reštartujete aplikáciu (a klienta Hazelcast), začne sa nová sekvencia – preto tie preskakovanie atď. Okrem toho vývojári v skutočnosti nerozumejú tomu, prečo sú ID celé čísla, ale sú také nekonzistentné. Všetko sme odvážili a prešli na UUID.

Mimochodom, pre tých, ktorí chcú byť ako Twitter, existuje taká knižnica Snowcast - to je implementácia Snowflake na vrchole Hazelcast. Pozrieť si ho môžete tu:

github.com/noctarius/snowcast
github.com/twitter/snehová vločka

Ale už sme sa k tomu nedostali.

TransactionalMap.replace

Ďalšie prekvapenie: TransactionalMap.replace nefunguje. Tu je test:

@Test
public void replaceInMap_putsAndGetsInsideTransaction() {

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            context.getMap("map").put("key", "oldValue");
            context.getMap("map").replace("key", "oldValue", "newValue");
            
            String value = (String) context.getMap("map").get("key");
            assertEquals("newValue", value);

            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }        
    });
}

Expected : newValue
Actual : oldValue

Musel som napísať vlastnú náhradu pomocou getForUpdate:

protected <K,V> boolean replaceInMap(String mapName, K key, V oldValue, V newValue) {
    TransactionalTaskContext context = HazelcastTransactionContextHolder.getContext();
    if (context != null) {
        log.trace("[CACHE] Replacing value in a transactional map");
        TransactionalMap<K, V> map = context.getMap(mapName);
        V value = map.getForUpdate(key);
        if (oldValue.equals(value)) {
            map.put(key, newValue);
            return true;
        }

        return false;
    }
    log.trace("[CACHE] Replacing value in a not transactional map");
    IMap<K, V> map = hazelcastInstance.getMap(mapName);
    return map.replace(key, oldValue, newValue);
}

Testujte nielen bežné dátové štruktúry, ale aj ich transakčné verzie. Stáva sa, že IMap funguje, ale TransactionalMap už neexistuje.

Vložte nový JAR bez prestojov

Najprv sme sa rozhodli zaznamenať predmety našich tried v Hazelcaste. Napríklad, máme triedu Application, chceme ju uložiť a prečítať. Uložiť:

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
map.set(id, application);

Čítame:

IMap<UUID, Application> map = hazelcastInstance.getMap("application");
return map.get(id);

Všetko funguje. Potom sme sa rozhodli vytvoriť index v Hazelcaste na vyhľadávanie podľa:

map.addIndex("subscriberId", false);

A pri písaní novej entity začali dostávať ClassNotFoundException. Hazelcast sa pokúsil pridať do indexu, ale nevedel nič o našej triede a chcel, aby jej bol dodaný JAR s touto triedou. Urobili sme to, všetko fungovalo, ale objavil sa nový problém: ako aktualizovať JAR bez úplného zastavenia klastra? Hazelcast nevyzdvihne nový JAR počas aktualizácie uzla po uzle. V tomto bode sme sa rozhodli, že môžeme žiť bez indexového vyhľadávania. Koniec koncov, ak používate Hazelcast ako obchod s kľúčmi a hodnotami, všetko bude fungovať? Nie naozaj. Aj tu je správanie IMap a TransactionalMap odlišné. Tam, kde je to IMap jedno, TransactionalMap vyhodí chybu.

IMap. Píšeme 5000 objektov, čítame ich. Všetko sa očakáva.

@Test
void get5000() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application");
    UUID subscriberId = UUID.randomUUID();

    for (int i = 0; i < 5000; i++) {
        UUID id = UUID.randomUUID();
        String title = RandomStringUtils.random(5);
        Application application = new Application(id, title, subscriberId);
        
        map.set(id, application);
        Application retrieved = map.get(id);
        assertEquals(id, retrieved.getId());
    }
}

Ale pri transakcii to nefunguje, dostaneme výnimku ClassNotFoundException:

@Test
void get_transaction() {
    IMap<UUID, Application> map = hazelcastInstance.getMap("application_t");
    UUID subscriberId = UUID.randomUUID();
    UUID id = UUID.randomUUID();

    Application application = new Application(id, "qwer", subscriberId);
    map.set(id, application);
    
    Application retrievedOutside = map.get(id);
    assertEquals(id, retrievedOutside.getId());

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            TransactionalMap<UUID, Application> transactionalMap = context.getMap("application_t");
            Application retrievedInside = transactionalMap.get(id);

            assertEquals(id, retrievedInside.getId());
            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }
    });
}

V 3.8 sa objavil mechanizmus User Class Deployment. Môžete určiť jeden hlavný uzol a aktualizovať na ňom súbor JAR.

Teraz sme úplne zmenili náš prístup: sami ho serializujeme do JSON a ukladáme do Hazelcast. Hazelcast nepotrebuje poznať štruktúru našich tried a môžeme aktualizovať bez prestojov. Verzia doménových objektov je riadená aplikáciou. Súčasne môžu bežať rôzne verzie aplikácie a môže nastať situácia, keď nová aplikácia zapíše objekty s novými poľami, ale stará o týchto poliach ešte nevie. A zároveň nová aplikácia číta objekty napísané starou aplikáciou, ktoré nemajú nové polia. Takéto situácie riešime v rámci aplikácie, no pre jednoduchosť nemeníme ani nemažeme polia, iba rozširujeme triedy pridávaním nových polí.

Ako zabezpečujeme vysoký výkon

Štyri cesty do Hazelcast - dobré, dva do databázy - zlé

Ísť do vyrovnávacej pamäte pre údaje je vždy lepšie ako ísť do databázy, ale nechcete ukladať ani nepoužívané záznamy. Rozhodnutie o tom, čo budeme cacheovať, si necháme až na poslednú fázu vývoja. Keď je nová funkcionalita nakódovaná, zapneme protokolovanie všetkých dopytov v PostgreSQL (log_min_duration_statement na 0) a spustíme záťažové testovanie na 20 minút. Pomocou zhromaždených protokolov môžu nástroje ako pgFouine a pgBadger vytvárať analytické správy. V prehľadoch hľadáme predovšetkým pomalé a časté dopyty. Pre pomalé dotazy zostavíme plán vykonávania (EXPLAIN) a vyhodnotíme, či je možné takýto dotaz urýchliť. Časté požiadavky na rovnaké vstupné údaje dobre zapadajú do vyrovnávacej pamäte. Snažíme sa, aby dopyty boli „ploché“, jedna tabuľka na dopyt.

vykorisťovania

SV ako online služba bola uvedená do prevádzky na jar 2017 a ako samostatný produkt bol SV vydaný v novembri 2017 (vtedy v stave beta verzie).

Za viac ako rok fungovania sa v prevádzke služby CB online nevyskytli žiadne vážnejšie problémy. Online službu monitorujeme cez Zabbix, zbierať a nasadzovať z Bambus.

Serverová distribúcia SV je dodávaná vo forme natívnych balíkov: RPM, DEB, MSI. Plus pre Windows poskytujeme jeden inštalačný program vo forme jedného EXE, ktorý nainštaluje server, Hazelcast a Elasticsearch na jeden počítač. Spočiatku sme túto verziu inštalácie označovali ako „demo“ verziu, ale teraz je jasné, že ide o najobľúbenejšiu možnosť nasadenia.

Zdroj: hab.com

Pridať komentár