Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast

Selles artiklis räägime sellest, kuidas ja miks me arenesime Interaktsioonisüsteem - mehhanism, mis edastab teavet klientrakenduste ja 1C: Enterprise serverite vahel - alates ülesande määramisest kuni arhitektuuri ja juurutamise üksikasjade läbimõtlemiseni.

Interaktsioonisüsteem (edaspidi - CB) on hajutatud tõrketaluv sõnumsidesüsteem, mille kohaletoimetamine on garanteeritud. CB on loodud suure mastaapsusega suure koormusega teenusena, mis on saadaval nii võrguteenusena (pakkub 1C) kui ka masstootmistootena, mida saab juurutada oma serveriseadmetes.

SW kasutab hajutatud salvestusruumi sarapuu ja otsingumootor Elasticsearch. Räägime ka Javast ja sellest, kuidas PostgreSQL-i horisontaalselt skaleerime.
Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast

Probleemi avaldus

Et oleks selge, miks me interaktsioonisüsteemi tegime, räägin teile natuke sellest, kuidas ärirakenduste arendamine 1C-s töötab.

Esiteks natuke meist neile, kes veel ei tea, mida me teeme :) Arendame 1C:Enterprise tehnoloogiaplatvormi. Platvorm sisaldab nii ärirakenduste arendustööriista kui ka käitusaega, mis võimaldab ärirakendustel töötada platvormideüleses keskkonnas.

Klient-serveri arendamise paradigma

1C: Enterprise'is loodud ärirakendused töötavad kolmel tasemel klient-server arhitektuur "DBMS - rakendusserver - klient". Sisse kirjutatud rakenduse kood sisseehitatud keel 1C, võib töötada rakendusserveris või kliendis. Kogu töö rakendusobjektidega (kataloogid, dokumendid jne), samuti andmebaasi lugemine ja kirjutamine toimub ainult serveris. Serveris on rakendatud ka vormide ja käsuliidese funktsionaalsus. Kliendil võetakse vorme vastu, avatakse ja kuvatakse, kasutajaga “suhtlemine” (hoiatused, küsimused ...), väikesed arvutused vormides, mis nõuavad kiiret reageerimist (näiteks hinna korrutamine kogusega), töötamine kohalikud failid, töötamine seadmetega.

Rakenduse koodis peavad protseduuride ja funktsioonide päised selgelt näitama, kus kood käivitatakse – kasutades käske &AtClient / &AtServer (keele ingliskeelses versioonis &AtClient / &AtServer). 1C arendajad parandavad mind nüüd, öeldes, et direktiivid on tegelikult sellised parem, aga meie jaoks pole see praegu oluline.

Kliendikoodist saab helistada serveri koodile, kuid serveri koodist kliendi koodile helistada ei saa. See on põhiline piirang, mille oleme seadnud mitmel põhjusel. Eelkõige sellepärast, et serveri kood peab olema kirjutatud nii, et see käiks samamoodi, olenemata sellest, kust seda välja kutsutakse – kas kliendilt või serverist. Ja teisest serverikoodist helistades serveri koodile pole klienti kui sellist. Ja kuna serveri koodi täitmise ajal võib sellele helistanud klient sulgeda, rakendusest väljuda ja serveril poleks kellelegi helistada.

Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast
Kood, mis käsitleb nupuklõpsu: serveriprotseduuri kutsumine kliendilt töötab, kliendiprotseduuri kutsumine serverist mitte

See tähendab, et kui tahame saata serverist kliendirakendusele mõne teate, näiteks selle kohta, et "kauamängiva" aruande moodustamine on lõppenud ja aruannet saab vaadata, siis meil seda võimalust pole. Peate kasutama nippe, näiteks perioodiliselt küsima serverilt kliendikoodist. Kuid selline lähenemine koormab süsteemi tarbetute kõnedega ja üldiselt ei näe see eriti elegantne välja.

Ja on ka vajadus näiteks siis, kui telefon SIP-helista, teavita sellest kliendirakendust, et see leiaks selle helistaja numbri järgi vastaspoole andmebaasist ja näitaks kasutajale infot helistava vastaspoole kohta. Või näiteks tellimuse lattu saabumisel teavita sellest kliendi kliendirakendust. Üldiselt on palju juhtumeid, kus selline mehhanism oleks kasulik.

Tegelikult seadmine

Looge sõnumivahetusmehhanism. Kiire, usaldusväärne, garanteeritud kohaletoimetamisega, sõnumite paindliku otsimise võimalusega. Rakendage mehhanismi alusel sõnumitooja (sõnumid, videokõned), mis töötab 1C rakendustes.

Kujundage süsteem horisontaalselt skaleeritavaks. Kasvav koormus tuleks katta sõlmede arvu suurenemisega.

Реализация

Otsustasime mitte manustada CB serveriosa otse 1C:Enterprise platvormi, vaid juurutada selle eraldi tootena, mille API-d saab välja kutsuda 1C rakenduslahenduste koodist. Seda tehti mitmel põhjusel, millest peamine oli võimaldada sõnumeid vahetada erinevate 1C rakenduste vahel (näiteks kaubanduse ja raamatupidamise osakonna vahel). Erinevad 1C rakendused võivad töötada platvormi 1C: Enterprise erinevates versioonides, asuda erinevates serverites jne. Sellistel tingimustel on optimaalne lahendus CB rakendamine eraldi tootena, mis asub 1C-paigaldiste "küljel".

Niisiis otsustasime teha CB eraldi tootena. Väiksematel ettevõtetel soovitame kasutada CB-serverit, mille installisime oma pilve (wss://1cdialog.com), et vältida kohaliku serveri installimise ja konfigureerimisega seotud kulusid. Suured kliendid võivad aga pidada otstarbekaks paigaldada oma rajatistesse oma CB-server. Kasutasime sarnast lähenemist oma pilve SaaS-i tootes. 1c Värske – see lastakse välja tootmistootena klientidele paigaldamiseks ja juurutatakse ka meie pilves https://1cfresh.com/.

Taotlus

Koormuse jaotuse ja tõrketaluvuse tagamiseks juurutame mitte ühe Java rakenduse, vaid mitu, nende ette paneme koormuse tasakaalustaja. Kui teil on vaja saata sõnum sõlmest sõlme, kasutage Hazelcastis avaldamist/tellimist.

Side kliendi ja serveri vahel – läbi websocketi. See sobib hästi reaalajas süsteemide jaoks.

Hajutatud vahemälu

Valige Redise, Hazelcasti ja Ehcache vahel. Väljas 2015. aastal. Redis andis just välja uue klastri (liiga uus, hirmus), seal on Sentinel, millel on palju piiranguid. Ehcache ei tea, kuidas rühmitada (see funktsioon ilmus hiljem). Otsustasime proovida Hazelcast 3.4-ga.
Hazelcast on karbist välja koondatud. Ühe sõlme režiimis pole see eriti kasulik ja mahub ainult vahemällu - ta ei tea, kuidas andmeid kettale kustutada, kui ainus sõlm läheb kaotsi, lähevad andmed kaotsi. Juurutame mitu Hazelcasti, mille vahel varundame kriitilisi andmeid. Me ei varunda vahemälu – meil pole temast kahju.

Meie jaoks on Hazelcast:

  • Kasutajaseansside salvestamine. Seansi jaoks andmebaasi minemine võtab kaua aega, seega paneme kõik seansid Hazelcasti.
  • Vahemälu. Otsin kasutajaprofiili – kontrollige vahemälu. Kirjutas uue sõnumi – pane see vahemällu.
  • Rakenduse eksemplaride suhtlemise teemad. Sõlm genereerib sündmuse ja asetab selle Hazelcasti teemasse. Teised selle teemaga liitunud rakendussõlmed võtavad vastu ja töötlevad sündmust.
  • klastri lukud. Näiteks loome arutelu unikaalse võtmega (1C baasi raames arutelu-singleton):

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

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

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

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

Kontrollisime, et kanalit pole. Nad võtsid luku, kontrollisid uuesti, lõid selle. Kui pärast luku võtmist ei kontrolli, siis on võimalus, et ka teine ​​lõime kontrollis sel hetkel ja proovib nüüd sama arutelu tekitada - ja see on juba olemas. Sünkroonitud või tavalise Java Locki kaudu pole lukku võimalik teha. Aluse kaudu - aeglaselt ja alusest on kahju, Hazelcasti kaudu - see, mida vajate.

DBMS-i valimine

Meil on ulatuslik ja edukas kogemus PostgreSQL-iga ning koostöö selle DBMS-i arendajatega.

Klastriga pole PostgreSQL lihtne – on XL, XC, Citus, kuid üldiselt pole noSQL see, mis kastist välja mastaap. NoSQL-i põhisalvestuseks ei peetud, piisas sellest, et võtame Hazelcasti, millega me varem töötanud polnud.

Kuna teil on vaja relatsiooniandmebaasi skaleerida, tähendab see killustamine. Nagu teate, jagame jagamisel andmebaasi eraldi osadeks, nii et igaüks neist saab paigutada eraldi serverisse.

Meie jagamise esimene versioon eeldas võimet levitada meie rakenduse iga tabelit erinevatesse serveritesse erinevates proportsioonides. Palju sõnumeid serveris A – palun liigutagem osa sellest tabelist serverisse B. See otsus karjus lihtsalt enneaegse optimeerimise pärast, nii et otsustasime piirduda mitme rentniku lähenemisviisiga.

Veebilehelt saab lugeda näiteks mitme üürniku kohta Cituse andmed.

SV-s on rakenduse ja abonendi mõisted. Rakendus on ärirakenduse (nt ERP või raamatupidamise) konkreetne install koos selle kasutajate ja äriandmetega. Tellija on organisatsioon või üksikisik, kelle nimel rakendus on CB serveris registreeritud. Abonendil võib olla registreeritud mitu rakendust ja need rakendused saavad omavahel sõnumeid vahetada. Tellijast sai meie süsteemis rentnik. Ühes füüsilises baasis võivad asuda mitme abonendi sõnumid; kui näeme, et mõni tellija on hakanud tekitama palju liiklust, viime selle üle eraldi füüsilisse andmebaasi (või isegi eraldi andmebaasiserverisse).

Meil on põhiandmebaas, kus on salvestatud marsruutimistabel koos teabega kõigi abonentide andmebaaside asukoha kohta.

Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast

Et põhiandmebaas ei oleks kitsaskoht, hoiame marsruutimistabelit (ja muid sageli küsitavaid andmeid) vahemälus.

Kui abonendi andmebaas hakkab aeglustuma, lõikame selle sees partitsioonideks. Teiste projektide puhul kasutame suurte tabelite jagamiseks pg_pathman.

Kuna kasutajate sõnumite kaotamine on halb, varundame oma andmebaasid koopiatega. Sünkroonsete ja asünkroonsete koopiate kombinatsioon võimaldab teil kindlustada põhiandmebaasi kadumise. Sõnumi kadumine toimub ainult põhiandmebaasi ja selle sünkroonse koopia samaaegse rikke korral.

Kui sünkroonne koopia kaob, muutub asünkroonne koopia sünkroonseks.
Kui põhiandmebaas kaob, saab sünkroonsest koopiast põhiandmebaas, asünkroonsest koopiast sünkroonne koopia.

Elasticsearch otsinguks

Kuna CB on muuhulgas ka sõnumitooja, siis siin vajame kiiret, mugavat ja paindlikku, morfoloogiat arvestavat otsingut ebatäpsete vastete järgi. Otsustasime jalgratast mitte uuesti leiutada ja kasutada raamatukogu baasil loodud tasuta otsingumootorit Elasticsearch Lucene. Samuti juurutame Elasticsearchi klastris (põhi - andmed - andmed), et kõrvaldada probleemid rakenduse sõlmede rikke korral.

Githubis leidsime Vene morfoloogia plugin Elasticsearchi jaoks ja kasutage seda. Elasticsearchi indeksis salvestame sõnajuured (mille plugin määratleb) ja N-gramme. Kui kasutaja sisestab otsimiseks teksti, otsime trükitud teksti N-grammide hulgast. Indeksisse salvestamisel jagatakse sõna "tekstid" järgmisteks N-grammideks:

[te, tech, tex, tekst, tekstid, ek, eks, ext, exts, ks, kst, ksty, st, sty, you],

Ja ka sõna "tekst" tüvi salvestatakse. See lähenemine võimaldab otsida sõna algusest, keskelt ja lõpust.

Üldine pilt

Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast
Korrates pilti artikli algusest, kuid koos selgitustega:

  • Tasakaalustaja avatud Internetile; meil on nginx, see võib olla ükskõik milline.
  • Java rakenduse eksemplarid suhtlevad omavahel Hazelcasti kaudu.
  • Veebipesaga töötamiseks kasutame Võrkjas.
  • Java 8-s kirjutatud Java-rakendus koosneb kimpudest OSGi. Plaan on üle minna Java 10-le ja minna üle moodulitele.

Arendus ja testimine

CB arendamise ja testimise käigus puutusime kokku mitmete huvitavate omadustega meie kasutatavate toodete juures.

Koormustestimine ja mälulekked

Iga CB vabastuse vabastamine on koormustest. See möödus edukalt, kui:

  • Test töötas mitu päeva ja teenusest keeldumist ei esinenud
  • Võtmetoimingute reageerimisaeg ei ületanud mugavat läve
  • Toimivuse halvenemine võrreldes eelmise versiooniga ei ületa 10%.

Täidame testandmebaasi andmetega - selleks saame tootmisserverist info kõige aktiivsema abonendi kohta, korrutame selle numbrid 5-ga (sõnumite, arutelude, kasutajate arv) ja nii testime.

Teostame interaktsioonisüsteemi koormustesti kolmes konfiguratsioonis:

  1. stressi test
  2. Ainult ühendused
  3. Abonendi registreerimine

Koormustesti käigus käivitame mitusada lõime ja need laadivad süsteemi peatumata: kirjutavad sõnumeid, loovad arutelusid, võtavad vastu sõnumite loendi. Simuleerime tavakasutajate tegevusi (saa oma lugemata kirjade loend, kirjuta kellelegi) ja programmeerime otsuseid (paketi teisaldamine teise konfiguratsiooni, hoiatuse töötlemine).

Näiteks näeb stressitesti osa välja selline:

  • Kasutaja logib sisse
    • Nõuab teie lugemata lõime
    • 50% võimalus sõnumeid lugeda
    • Sõnumite kirjutamise tõenäosus on 50%.
    • Järgmine kasutaja:
      • 20% võimalus luua uus lõim
      • Valib juhuslikult mõne oma arutelu
      • Tuleb sisse
      • Nõuab sõnumeid, kasutajaprofiile
      • Loob sellest lõimest viis juhuslikele kasutajatele adresseeritud sõnumit
      • Arutelu alt väljas
      • Kordub 20 korda
      • Logib välja, naaseb skripti algusesse

    • Süsteemi siseneb vestlusbot (emuleerib sõnumeid rakendatud lahenduste koodist)
      • 50% võimalus luua uus andmekanal (eriline arutelu)
      • 50% võimalus kirjutada sõnum mõnes olemasolevas kanalis

Stsenaarium "Ainult ühendused" ilmus põhjusega. On olukord: kasutajad on süsteemi ühendanud, kuid pole veel kaasatud. Iga kasutaja lülitab hommikul kell 09:00 arvuti sisse, loob ühenduse serveriga ja vaikib. Need tüübid on ohtlikud, neid on palju - neil on pakettidest väljas ainult PING / PONG, kuid nad säilitavad ühenduse serveriga (nad ei saa seda säilitada - ja äkki uus sõnum). Test kordab olukorda, kui suur hulk selliseid kasutajaid üritab poole tunni jooksul süsteemi sisse logida. See näeb välja nagu pingetest, kuid selle fookus on just sellel esimesel sisendil - et ei tekiks tõrkeid (inimene ei kasuta süsteemi, aga see juba kukub ära - raske on midagi hullemat välja mõelda).

Abonendi registreerimise stsenaarium pärineb esimesest käivitamisest. Tegime stressitesti ja olime kindlad, et süsteem ei aeglusta kirjavahetuses. Kuid kasutajad läksid ja registreerimine hakkas ajalõpuks kukkuma. Registreerimisel kasutasime / dev / juhuslik, mis on seotud süsteemi entroopiaga. Serveril ei olnud aega piisavalt entroopiat koguda ja uue SecureRandomi taotlemisel hangus see kümneteks sekunditeks. Sellest olukorrast on mitu väljapääsu, näiteks: lülituge vähem turvalisele /dev/urandom-ile, installige spetsiaalne entroopia genereeriv plaat, genereerige eelnevalt juhuslikud numbrid ja salvestage need basseini. Suletasime basseini probleemi ajutiselt, kuid sellest ajast alates oleme uute tellijate registreerimiseks teinud eraldi testi.

Koormusgeneraatorina kasutame JMeter. Ta ei oska websocketiga töötada, vaja on pluginat. Päringu "jmeter websocket" otsingutulemustes on esimesed artiklid BlazeMeterigamilles nad soovitavad plugin, autor Maciej Zaleski.

Sealt otsustasimegi alustada.

Peaaegu kohe pärast tõsise testimise algust avastasime, et JMeteris algasid mälulekked.

Pistikprogramm on eraldi suur lugu, 176 tärniga on sellel githubis 132 kahvlit. Autor ise pole sellele pühendunud aastast 2015 (võtsime 2015, siis ei äratanud kahtlust), mitu githubi teemat mälulekete kohta, 7 sulgemata tõmbamistaotlust.
Kui otsustate laadida testi selle pistikprogrammiga, võtke arvesse järgmisi arutelusid:

  1. Mitme lõimega keskkonnas kasutati tavalist LinkedListi, mille tulemusena saime NPE tööajal. See lahendatakse kas ConcurrentLinkedDeque'ile üleminekuga või sünkroniseeritud plokkide abil. Valisime enda jaoks esimese variandihttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Mälu leke, ühenduseteavet lahtiühendamisel ei kustutata (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Voogedastusrežiimis (kui veebipesa ei suleta proovi lõpus, vaid seda kasutatakse plaanis edasi) vastusemustrid ei tööta (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

See on üks neist githubis. Mida me tegime:

  1. On võtnud Elyran Kogani kahvel (@elyrank) – parandab probleemid 1 ja 3
  2. Lahendatud probleem 2
  3. Saidi uuendatud 9.2.14-st 9.3.12-ni
  4. ThreadLocalis mähitud SimpleDateFormat; SimpleDateFormat ei ole lõime turvaline, mis viib käitusajal NPE-ni
  5. Parandatud veel üks mäluleke (ühendus katkes lahtiühendamisel valesti)

Ja ometi voolab!

Mälu hakkas lõppema mitte päeva, vaid kahega. Polnud üldse aega, otsustasime ajada vähem niite, kuid nelja agendi peal. Sellest oleks pidanud piisama vähemalt nädalaks.

Kaks päeva on möödunud...

Nüüd hakkab Hazelcasti mälu otsa saama. Logid näitasid, et peale paaripäevast testimist hakkab Hazelcast kurtma mälupuuduse üle ning mõne aja pärast laguneb klaster laiali ning sõlmed surevad ükshaaval edasi. Ühendasime JVisualVM-i hazelcastiga ja nägime ülespoole suunatud saagi – see kutsus regulaarselt GC-d, kuid ei suutnud mälu kuidagi tühjendada.

Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast

Selgus, et hazelcast 3.4-s ei vabane kaardi / multiMapi (map.destroy()) kustutamisel mälu täielikult:

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

Viga on nüüd parandatud versioonis 3.5, kuid see oli toona probleem. Lõime uue dünaamiliste nimedega multiMapi ja kustutasime vastavalt meie loogikale. Kood nägi välja umbes selline:

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();
    }
}

Helistama:

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

MultiMap loodi iga tellimuse jaoks ja eemaldati, kui seda ei vajatud. Otsustasime, et hakkame koostama kaarti , on võtmeks tellimuse nimi ja väärtusteks seansi identifikaatorid (mille abil saate vajaduse korral kasutajatunnuseid hankida).

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

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

Tabelid on paranenud.

Kuidas ja miks me kirjutasime 1C jaoks suure koormusega skaleeritava teenuse: Enterprise: Java, PostgreSQL, Hazelcast

Mida veel oleme koormustestimise kohta õppinud?

  1. JSR223 peab olema kirjutatud groovy ja sisaldama kompileerimisvahemälu - see on palju kiirem. Link.
  2. Jmeter-Pluginsi diagramme on lihtsam mõista kui tavalisi. Link.

Meie kogemusest Hazelcastiga

Hazelcast oli meie jaoks uus toode, alustasime sellega tööd alates versioonist 3.4.1, nüüd on meie tootmisserveris versioon 3.9.2 (selle kirjutamise ajal on Hazelcasti uusim versioon 3.10).

ID genereerimine

Alustasime täisarvuliste identifikaatoritega. Kujutagem ette, et vajame uue üksuse jaoks teist Longi. Jada andmebaasis ei sobi, tabelid on seotud shardinguga - tuleb välja, et DB1-s on sõnum ID=1 ja DB1-s sõnum ID=2, Elasticsearchi ei saa seda ID-d panna, Hazelcastis ka, aga kõige hullem on see, kui tahad kahest andmebaasist andmeid taandada üheks (näiteks otsustades, et nende tellijate jaoks piisab ühest andmebaasist). Teil võib Hazelcastis olla mitu AtomicLongi ja loendur seal hoida, siis uue ID hankimise toimivus on incrementAndGet pluss Hazelcastis päringute tegemiseks kuluv aeg. Kuid Hazelcastil on midagi optimaalsemat - FlakeIdGenerator. Ühenduse võtmisel antakse igale kliendile vahemik ID-sid, näiteks esimene - 1 kuni 10 000, teine ​​- 10 001 kuni 20 000 jne. Nüüd saab klient uusi identifikaatoreid ise väljastada, kuni talle väljastatud vahemik lõpeb. Töötab kiiresti, kuid rakenduse (ja Hazelcasti kliendi) taaskäivitamine käivitab uue jada - sellest tulenevad vahelejätmised jne. Lisaks pole arendajatele väga selge, miks ID-d on täisarvud, kuid need lähevad nii palju vastuollu. Kaalusime kõik läbi ja läksime UUID-dele üle.

Muide, neile, kes tahavad olla nagu Twitter, on selline Snowcasti teek - see on Snowflake'i teostus Hazelcasti peal. Näete siit:

github.com/noctarius/snowcast
github.com/twitter/snowflake

Kuid me pole selleni veel jõudnud.

TransactionMap.replace

Veel üks üllatus: TransactionalMap.replace ei tööta. Siin on 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

Ma pidin kirjutama oma asendamise, kasutades getForUpdate'i:

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);
}

Testige mitte ainult tavalisi andmestruktuure, vaid ka nende tehinguversioone. Juhtub, et IMap töötab, aga TransactionalMapi enam ei eksisteeri.

Kinnitage uus JAR ilma seisakuta

Esiteks otsustasime kirjutada oma klasside objektid Hazelcastile. Näiteks meil on rakendusklass, me tahame seda salvestada ja lugeda. Salvesta:

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

Loe:

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

Kõik töötab. Seejärel otsustasime selle otsimiseks luua Hazelcastis indeksi:

map.addIndex("subscriberId", false);

Ja uue olemi kirjutamisel hakkasid nad saama ClassNotFoundExceptioni. Hazelcast üritas indeksit täiendada, kuid ei teadnud meie klassist midagi ja tahtis selle klassiga JAR-i panna. Tegime just seda, kõik töötas, kuid ilmnes uus probleem: kuidas värskendada JAR-i ilma klastrit täielikult peatamata? Hazelcast ei võta sõlmepõhise värskenduse korral uut JAR-i. Sel hetkel otsustasime, et saame elada ilma indeksiotsinguteta. Lõppude lõpuks, kui kasutate Hazelcasti võtmeväärtuste poena, siis kõik töötab? Mitte päris. Siin on jälle IMap ja TransactionalMap erinev käitumine. Kui IMap ei hooli, annab TransactionalMap veateate.

IMap. Kirjutame üles 5000 objekti, loeme. Kõik on oodatud.

@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());
    }
}

Kuid see tehingu puhul ei tööta, saame ClassNotFoundExceptioni:

@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();
        }
    });
}

Aastal 3.8 ilmus kasutajaklassi juurutamise mehhanism. Saate määrata ühe peasõlme ja värskendada sellel olevat JAR-faili.

Nüüd oleme oma lähenemisviisi täielikult muutnud: me ise serialiseerime JSON-i ja salvestame Hazelcasti. Hazelcast ei pea teadma meie klasside struktuuri ja me saame värskendada ilma seisakuta. Domeeniobjektide versioonimist kontrollib rakendus. Rakenduse erinevaid versioone saab käivitada korraga ning võimalik, et uus rakendus kirjutab uute väljadega objekte, samas kui vana neist väljadest veel ei tea. Ja samal ajal loeb uus rakendus vana rakenduse kirjutatud objekte, millel pole uusi välju. Selliseid olukordi käsitleme rakenduse sees, kuid lihtsuse mõttes me välju ei muuda ega eemalda, vaid laiendame klasse uute väljade lisamisega.

Kuidas me pakume kõrget jõudlust

Neli reisi Hazelcasti on hea, kaks reisi andmebaasi on halb

Vahemällu andmete otsimine on alati parem kui andmebaasis, kuid te ei soovi ka taotlemata kirjeid salvestada. Otsustamine, mida vahemällu salvestada, on jäetud arenduse viimasele etapile. Kui uus funktsioon on kodeeritud, lubame PostgreSQL-is kõigi päringute logimise (log_min_duration_statement kuni 0) ja käivitame 20 minuti jooksul koormustesti.Utiliidid nagu pgFouine ja pgBadger saavad koostada kogutud logide põhjal analüütilisi aruandeid. Aruannetes otsime peamiselt aeglaseid ja sagedasi päringuid. Aeglaste päringute jaoks koostame täitmisplaani (EXPLAIN) ja hindame, kas sellist päringut saab kiirendada. Sagedased sama sisendi päringud mahuvad hästi vahemällu. Püüame hoida päringud tasapinnal, üks tabel päringu kohta.

Ekspluateerimine

CB kui võrguteenus käivitati 2017. aasta kevadel, eraldiseisva CB-tootena ilmus 2017. aasta novembris (tol ajal beeta-staatus).

Rohkem kui aasta tegutsemise jooksul pole CB võrguteenuse töös tõsiseid probleeme esinenud. Jälgime veebiteenust läbi Zabbix, koguda ja kasutusele võtta Bambus.

CB-serveri distributsioon on natiivsete pakettide kujul: RPM, DEB, MSI. Lisaks pakume Windowsi jaoks ühte installijat ühe EXE-vormingus, mis installib serveri, Hazelcasti ja Elasticsearchi ühte masinasse. Alguses nimetasime seda installi versiooni "demoks", kuid nüüd on selgunud, et see on kõige populaarsem juurutamisvõimalus.

Allikas: www.habr.com

Lisa kommentaar