Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast

Šiame straipsnyje mes kalbėsime apie tai, kaip ir kodėl mes sukūrėme Sąveikos sistema – mechanizmas, perduodantis informaciją tarp kliento programų ir 1C:Enterprise serverių – nuo ​​užduoties nustatymo iki architektūros ir įgyvendinimo detalių apmąstymo.

Sąveikos sistema (toliau – SV) yra paskirstyta, gedimams atspari pranešimų sistema su garantuotu pristatymu. SV sukurta kaip didelės apkrovos paslauga su dideliu mastelio keitimu, prieinama ir kaip internetinė paslauga (teikiama 1C), ir kaip masinės gamybos produktas, kurį galima įdiegti savo serveriuose.

SV naudoja paskirstytą saugyklą lazdynas ir paieškos sistema Elasticearch. Taip pat kalbėsime apie „Java“ ir tai, kaip horizontaliai keičiame PostgreSQL.
Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast

Problemos teiginys

Kad būtų aišku, kodėl sukūrėme sąveikos sistemą, šiek tiek papasakosiu apie tai, kaip veikia verslo programų kūrimas 1C.

Pirmiausia šiek tiek apie mus tiems, kurie dar nežino, ką mes darome :) Kuriame 1C:Enterprise technologijų platformą. Platformoje yra verslo programų kūrimo įrankis, taip pat vykdymo laikas, leidžiantis verslo programoms veikti kelių platformų aplinkoje.

Kliento-serverio plėtros paradigma

Verslo programos, sukurtos 1C: Enterprise, veikia trimis lygiais kliento serveris architektūra „DBVS – taikomųjų programų serveris – klientas“. Įrašytas programos kodas įmontuota 1C kalba, gali būti vykdomas programų serveryje arba kliente. Visas darbas su taikomųjų programų objektais (katalogais, dokumentais ir kt.), taip pat duomenų bazės skaitymas ir rašymas atliekamas tik serveryje. Formų ir komandų sąsajos funkcionalumas taip pat įdiegtas serveryje. Klientas atlieka formų priėmimą, atidarymą ir atvaizdavimą, „bendravimą“ su vartotoju (įspėjimai, klausimai...), smulkius skaičiavimus formose, reikalaujančias greito atsakymo (pvz., kainą padaugina iš kiekio), darbą su vietiniais failais, darbas su įranga.

Programos kode procedūrų ir funkcijų antraštėse turi būti aiškiai nurodyta, kur kodas bus vykdomas – naudojant &AtClient / &AtServer direktyvas (&AtClient / &AtServer angliškoje kalbos versijoje). 1C kūrėjai dabar mane pataisys sakydami, kad direktyvos iš tikrųjų yra daugiau nei, bet mums tai dabar nėra svarbu.

Galite iškviesti serverio kodą iš kliento kodo, bet negalite skambinti kliento kodu iš serverio kodo. Tai esminis apribojimas, kurį nustatėme dėl kelių priežasčių. Visų pirma todėl, kad serverio kodas turi būti parašytas taip, kad jis būtų vykdomas vienodai, nesvarbu, kur jis būtų iškviestas – iš kliento ar iš serverio. O jei serverio kodu skambinama iš kito serverio kodo, kliento kaip tokio nėra. Ir todėl, kad serverio kodo vykdymo metu jį iškvietęs klientas galėjo užsidaryti, išeiti iš programos ir serveris nebeturės kam skambinti.

Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast
Kodas, kuris apdoroja mygtuko paspaudimą: serverio procedūros iškvietimas iš kliento veiks, kliento procedūros iškvietimas iš serverio neveiks

Tai reiškia, kad jei norime nusiųsti kokį nors pranešimą iš serverio į kliento programą, pavyzdžiui, kad baigta generuoti „ilgai veikiančią“ ataskaitą ir ataskaitą galima peržiūrėti, tokio būdo neturime. Turite naudoti gudrybes, pavyzdžiui, periodiškai apklausti serverį iš kliento kodo. Tačiau šis metodas apkrauna sistemą nereikalingais skambučiais ir apskritai neatrodo labai elegantiškai.

Taip pat reikia, pavyzdžiui, kai sulaukia telefono skambučio TPP- skambindami praneškite apie tai kliento programai, kad ji, naudodama skambinančiojo numerį, rastų jį sandorio šalies duomenų bazėje ir parodytų vartotojui informaciją apie skambinančią sandorio šalį. Arba, pavyzdžiui, kai užsakymas atkeliauja į sandėlį, informuokite apie tai kliento kliento programą. Apskritai yra daug atvejų, kai toks mechanizmas būtų naudingas.

Pati gamyba

Sukurkite pranešimų siuntimo mechanizmą. Greitai, patikimai, su garantuotu pristatymu, su galimybe lanksčiai ieškoti žinučių. Remdamiesi mechanizmu, įdiekite pasiuntinį (pranešimus, vaizdo skambučius), veikiančią 1C programose.

Suprojektuokite sistemą taip, kad ją būtų galima keisti horizontaliai. Didėjanti apkrova turi būti padengta didinant mazgų skaičių.

Vykdymas

SV serverio dalies nusprendėme neintegruoti tiesiai į 1C:Enterprise platformą, o diegti kaip atskirą produktą, kurio API galima iškviesti iš 1C taikomųjų sprendimų kodo. Tai buvo padaryta dėl kelių priežasčių, iš kurių pagrindinė buvo ta, kad norėjau sudaryti galimybę keistis pranešimais tarp skirtingų 1C programų (pavyzdžiui, tarp prekybos valdymo ir apskaitos). Skirtingos 1C programos gali veikti skirtingose ​​1C:Enterprise platformos versijose, būti skirtinguose serveriuose ir pan. Tokiomis sąlygomis optimalus sprendimas yra SV, kaip atskiro produkto, esančio 1C įrenginių „šone“, įgyvendinimas.

Taigi nusprendėme SV gaminti kaip atskirą produktą. Rekomenduojame mažoms įmonėms naudoti CB serverį, kurį įdiegėme savo debesyje (wss://1cdialog.com), kad išvengtumėte papildomų išlaidų, susijusių su vietiniu serverio diegimu ir konfigūravimu. Dideliems klientams gali būti patartina savo patalpose įdiegti savo CB serverį. Panašų metodą taikėme savo debesies SaaS produkte 1c Šviežia - jis gaminamas kaip masinės gamybos produktas, skirtas montuoti klientų svetainėse, taip pat yra įdiegtas mūsų debesyje https://1cfresh.com/.

Taikymas

Norėdami paskirstyti apkrovą ir gedimų toleranciją, įdiegsime ne vieną „Java“ programą, o kelias su apkrovos balansavimo priemone priešais jas. Jei jums reikia perkelti pranešimą iš mazgo į mazgą, naudokite „Hazelcast“ paskelbti / užsiprenumeruoti.

Ryšys tarp kliento ir serverio vyksta per internetinį lizdą. Jis puikiai tinka realaus laiko sistemoms.

Paskirstyta talpykla

Pasirinkome Redis, Hazelcast ir Ehcache. Tai 2015 m. Redis ką tik išleido naują klasterį (per nauja, baisu), yra Sentinel su daugybe apribojimų. Ehcache nežino, kaip surinkti į klasterį (ši funkcija pasirodė vėliau). Nusprendėme tai išbandyti su Hazelcast 3.4.
„Hazelcast“ iš dėžutės surenkama į grupę. Vieno mazgo režimu jis nėra labai naudingas ir gali būti naudojamas tik kaip talpykla – nemoka išmesti duomenų į diską, jei pametate vienintelį mazgą, prarandate duomenis. Diegiame keletą Hazelcast'ų, tarp kurių sukuriame svarbių duomenų atsargines kopijas. Talpyklos atsarginės kopijos nekuriame – mums tai neprieštarauja.

Mums Hazelcast yra:

  • Vartotojo seansų saugojimas. Kiekvieną kartą pereiti į duomenų bazę seansui užtrunka daug laiko, todėl visus seansus dedame į Hazelcast.
  • Talpykla. Jei ieškote vartotojo profilio, patikrinkite talpyklą. Parašė naują žinutę – įdėkite į talpyklą.
  • Bendravimo tarp taikomųjų programų egzempliorių temos. Mazgas sugeneruoja įvykį ir įdeda jį į Hazelcast temą. Kiti programos mazgai, užsiprenumeravę šią temą, priima ir apdoroja įvykį.
  • Klasteriniai užraktai. Pavyzdžiui, mes kuriame diskusiją naudodami unikalų raktą (viena diskusija 1C duomenų bazėje):

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

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

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

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

Patikrinome, ar nėra kanalo. Paėmėme spyną, dar kartą patikrinome ir sukūrėme. Jei nepatikrinsite užrakto paėmę užraktą, yra tikimybė, kad tą akimirką patikrino kita gija ir dabar bandys sukurti tą pačią diskusiją, bet ji jau egzistuoja. Negalite užrakinti naudojant sinchronizuotą arba įprastą „Java Lock“. Per duomenų bazę - tai lėta, o gaila duomenų bazės per Hazelcast - to jums reikia.

DBVS pasirinkimas

Turime didelę ir sėkmingą patirtį dirbant su PostgreSQL ir bendradarbiaujant su šios DBVS kūrėjais.

Tai nėra lengva naudojant PostgreSQL klasterį – yra XL, XC, Citus, bet apskritai tai nėra NoSQL, kurių mastelį galima pakeisti iš karto. Nelaikėme NoSQL pagrindine saugykla, užteko to, kad paėmėme Hazelcast, su kuriuo anksčiau nedirbome.

Jei reikia padidinti reliacinės duomenų bazės mastelį, tai reiškia skaldymas. Kaip žinote, su sharding mes padaliname duomenų bazę į atskiras dalis, kad kiekvieną iš jų būtų galima patalpinti į atskirą serverį.

Pirmojoje mūsų dalijimosi versijoje buvo numatyta galimybė paskirstyti kiekvieną mūsų programos lentelę skirtinguose serveriuose skirtingomis proporcijomis. Serveryje A yra daug pranešimų – prašau, dalį šios lentelės perkelkime į serverį B. Šis sprendimas tiesiog rėkė apie ankstyvą optimizavimą, todėl nusprendėme apsiriboti kelių nuomininkų metodu.

Pavyzdžiui, apie daugialypį nuomininką galite perskaityti svetainėje Citus duomenys.

SV turi programos ir abonento sąvokas. Programa yra konkretus verslo programos, pvz., ERP arba apskaitos, diegimas su vartotojais ir verslo duomenimis. Abonentas – tai organizacija ar asmuo, kurio vardu paraiška užregistruota SV serveryje. Abonentas gali turėti keletą užregistruotų programų ir šios programos gali keistis pranešimais tarpusavyje. Abonentas tapo mūsų sistemos nuomininku. Kelių abonentų pranešimai gali būti vienoje fizinėje duomenų bazėje; jei matome, kad abonentas pradėjo generuoti daug srauto, perkeliame jį į atskirą fizinę duomenų bazę (ar net atskirą duomenų bazės serverį).

Turime pagrindinę duomenų bazę, kurioje saugoma maršruto parinkimo lentelė su informacija apie visų abonentų duomenų bazių vietą.

Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast

Kad pagrindinė duomenų bazė netaptų kliūtimi, maršruto parinkimo lentelę (ir kitus dažnai reikalingus duomenis) laikome talpykloje.

Jei abonento duomenų bazė pradės lėtėti, viduje suskirstysime ją į skaidinius. Kituose projektuose mes naudojame pg_pathman.

Prarasti vartotojų pranešimus yra blogai, todėl savo duomenų bazes tvarkome su kopijomis. Sinchroninių ir asinchroninių kopijų derinys leidžia apsidrausti pagrindinės duomenų bazės praradimo atveju. Pranešimas bus prarastas tik tuo atveju, jei pirminė duomenų bazė ir jos sinchroninė kopija sugestų vienu metu.

Jei prarandama sinchroninė kopija, asinchroninė kopija tampa sinchronine.
Jei pagrindinė duomenų bazė prarandama, sinchroninė kopija tampa pagrindine, o asinchroninė kopija – sinchronine kopija.

Elasticsearch paieškai

Kadangi, be kita ko, SV taip pat yra pasiuntinys, tai reikalauja greitos, patogios ir lanksčios paieškos, atsižvelgiant į morfologiją, naudojant netikslius atitikmenis. Nusprendėme neišradinėti dviračio iš naujo ir naudoti nemokamą paieškos programą Elasticsearch, sukurtą remiantis biblioteka Liucenė. Mes taip pat diegiame Elasticsearch klasteryje (pagrindinis – duomenys – duomenys), kad pašalintume problemas sugedus programos mazgams.

Github'e radome Rusų morfologijos papildinys Elasticsearch ir naudokite jį. Elasticsearch rodyklėje saugome žodžių šaknis (kurias nustato įskiepis) ir N gramus. Kai vartotojas įveda tekstą ieškoti, ieškome įvesto teksto tarp N gramų. Išsaugomas rodyklėje žodis „tekstai“ bus padalintas į šiuos N gramus:

[tie, tek, tex, tekstas, tekstai, ek, ex, ext, tekstai, ks, kst, ksty, st, sty, jūs],

Taip pat bus išsaugota žodžio „tekstas“ šaknis. Šis metodas leidžia ieškoti žodžio pradžioje, viduryje ir pabaigoje.

Didelė nuotrauka

Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast
Pakartokite paveikslėlį iš straipsnio pradžios, bet su paaiškinimais:

  • Balansuotojas, atskleistas internete; mes turime nginx, jis gali būti bet koks.
  • „Java“ programų egzemplioriai bendrauja tarpusavyje per „Hazelcast“.
  • Norėdami dirbti su žiniatinklio lizdu, mes naudojame Neto.
  • Java programa parašyta Java 8 ir susideda iš paketų OSGi. Planuose yra perėjimas prie „Java 10“ ir perėjimas prie modulių.

Kūrimas ir testavimas

Kurdami ir bandydami SV susidūrėme su daugybe įdomių naudojamų produktų savybių.

Apkrovos testavimas ir atminties nutekėjimas

Kiekvieno SV leidimo išleidimas apima apkrovos testavimą. Tai sėkminga, kai:

  • Bandymas veikė keletą dienų ir nebuvo jokių aptarnavimo gedimų
  • Reagavimo laikas į pagrindines operacijas neviršijo patogios slenksčio
  • Veikimo pablogėjimas, palyginti su ankstesne versija, yra ne daugiau kaip 10%

Bandomąją duomenų bazę užpildome duomenimis – tam iš gamybos serverio gauname informaciją apie aktyviausią abonentą, jo skaičius padauginame iš 5 (pranešimų, diskusijų, vartotojų skaičius) ir tokiu būdu testuojame.

Atliekame trijų konfigūracijų sąveikos sistemos apkrovos testavimą:

  1. streso testas
  2. Tik jungtys
  3. Abonento registracija

Testavimo nepalankiausiomis sąlygomis metu paleidžiame kelis šimtus gijų, jos be perstojo apkrauna sistemą: rašo žinutes, kuria diskusijas, gauna pranešimų sąrašą. Imituojame paprastų vartotojų veiksmus (gauname mano neskaitytų pranešimų sąrašą, kam nors parašome) ir programinius sprendimus (persiunčiame kitokios konfigūracijos paketą, apdorojame įspėjimą).

Pavyzdžiui, taip atrodo testavimo nepalankiausiomis sąlygomis dalis:

  • Vartotojas prisijungia
    • Prašo jūsų neskaitytų diskusijų
    • 50% tikimybė, kad skaitys pranešimus
    • 50 % tikimybė rašyti žinutes
    • Kitas vartotojas:
      • Turi 20% galimybę sukurti naują diskusiją
      • Atsitiktinai pasirenka bet kurią iš savo diskusijų
      • Įeina į vidų
      • Prašo žinučių, vartotojų profilių
      • Sukuria penkis pranešimus, skirtus atsitiktiniams šios diskusijos vartotojams
      • Palieka diskusiją
      • Kartojama 20 kartų
      • Atsijungia, grįžta į scenarijaus pradžią

    • Į sistemą patenka pokalbių robotas (imuliuoja pranešimus iš programos kodo)
      • Turi 50 % galimybę sukurti naują duomenų mainų kanalą (speciali diskusija)
      • 50 % tikimybė parašyti pranešimą bet kuriuo iš esamų kanalų

Scenarijus „Tik ryšiai“ atsirado dėl priežasties. Yra tokia situacija: vartotojai prisijungė prie sistemos, bet dar neįsitraukė. Kiekvienas vartotojas įjungia kompiuterį 09:00 ryte, užmezga ryšį su serveriu ir tyli. Šie vaikinai yra pavojingi, jų yra daug – vieninteliai jų turimi paketai yra PING/PONG, tačiau jie palaiko ryšį su serveriu (jie negali to palaikyti – o jei bus naujas pranešimas). Testas atkartoja situaciją, kai daug tokių vartotojų bando prisijungti prie sistemos per pusvalandį. Tai panašu į testavimą nepalankiausiomis sąlygomis, tačiau jo dėmesys sutelktas būtent į šį pirmąjį įvestį – kad nebūtų gedimų (žmogus nesinaudoja sistema, o ji jau nukrenta – sunku sugalvoti ką nors blogesnio).

Abonento registracijos scenarijus prasideda nuo pirmojo paleidimo. Atlikome testavimą nepalankiausiomis sąlygomis ir buvome tikri, kad susirašinėjimo metu sistema nesulėtėjo. Tačiau atėjo vartotojai ir registracija pradėjo nepavykti dėl skirtojo laiko. Registruodamiesi naudojome / dev / atsitiktinis, kuri yra susijusi su sistemos entropija. Serveris nespėjo sukaupti pakankamai entropijos ir, kai buvo užklaustas naujas SecureRandom, jis užstojo dešimtims sekundžių. Iš šios situacijos yra daug būdų, pavyzdžiui: pereiti prie mažiau saugios /dev/urandom, įdiegti specialią plokštę, kuri generuoja entropiją, iš anksto sugeneruoti atsitiktinius skaičius ir saugoti juos baseine. Laikinai išsprendėme problemą, susijusią su baseinu, tačiau nuo to laiko vykdome atskirą naujų abonentų registravimo testą.

Mes naudojame kaip apkrovos generatorių JMeter. Jis nežino, kaip dirbti su žiniatinklio lizdu, jam reikia papildinio. Pirmieji paieškos rezultatuose pagal užklausą „jmeter websocket“ yra šie: straipsniai iš BlazeMeter, kurie rekomenduoja Maciej Zaleski įskiepis.

Nuo to ir nusprendėme pradėti.

Beveik iš karto pradėję rimtus bandymus sužinojome, kad JMeter pradėjo nutekėti atmintis.

Papildinys yra atskira didelė istorija, turinti 176 žvaigždutes, github'e yra 132 šakės. Pats autorius tam neįsipareigojo nuo 2015 metų (ėmėme 2015 m., tada įtarimų nesukėlė), kelios github problemos dėl atminties nutekėjimo, 7 neuždarytos ištraukimo užklausos.
Jei nuspręsite atlikti apkrovos testavimą naudodami šį papildinį, atkreipkite dėmesį į šias diskusijas:

  1. Kelių gijų aplinkoje buvo naudojamas įprastas LinkedList ir rezultatas buvo toks NPE vykdymo metu. Tai galima išspręsti perjungiant į ConcurrentLinkedDeque arba sinchronizuojant blokus. Mes patys pasirinkome pirmąjį variantą (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Atminties nutekėjimas atsijungiant, ryšio informacija neištrinama (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Srautinio perdavimo režimu (kai žiniatinklio lizdas nėra uždarytas mėginio pabaigoje, bet naudojamas vėliau plane), atsako šablonai neveikia (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Tai vienas iš github. Ką mes padarėme:

  1. Paėmė šakutė Elyran Kogan (@elyrank) – išsprendžia 1 ir 3 problemas
  2. Išspręsta 2 problema
  3. Prieplauka atnaujinta nuo 9.2.14 iki 9.3.12
  4. Wrapped SimpleDateFormat į ThreadLocal; „SimpleDateFormat“ nėra saugus nuo gijų, todėl vykdymo metu atsirado NPE
  5. Ištaisytas kitas atminties nutekėjimas (atjungus jungtis užsidarė netinkamai)

Ir vis dėlto teka!

Atmintis ėmė išsekti ne per dieną, o per dvi. Visiškai nebeliko laiko, todėl nusprendėme paleisti mažiau gijų, bet su keturiais agentais. To turėjo pakakti bent savaitei.

Praėjo dvi dienos...

Dabar Hazelcast baigiasi atmintis. Žurnalai parodė, kad po poros dienų bandymų Hazelcast pradėjo skųstis atminties stoka, o po kurio laiko klasteris subyrėjo, o mazgai vienas po kito miršta. Sujungėme JVisualVM prie „hazelcast“ ir pamatėme „kylantį pjūklą“ – jis reguliariai skambino GC, bet negalėjo išvalyti atminties.

Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast

Paaiškėjo, kad naudojant „hazelcast 3.4“, ištrinant žemėlapį / multiMap (map.destroy()), atmintis nėra visiškai atlaisvinama:

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

Dabar klaida ištaisyta 3.5 versijoje, bet tada tai buvo problema. Sukūrėme naujus multiMaps su dinaminiais pavadinimais ir ištrynėme juos pagal savo logiką. Kodas atrodė maždaug taip:

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

Skambinti:

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

MultiMap buvo sukurta kiekvienai prenumeratai ir ištrinta, kai to nereikėjo. Nusprendėme, kad pradėsime Žemėlapį , raktas bus prenumeratos pavadinimas, o reikšmės bus seanso identifikatoriai (iš kurių, jei reikia, galėsite gauti vartotojo identifikatorius).

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

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

Diagramos pagerėjo.

Kaip ir kodėl sukūrėme didelės apkrovos keičiamo dydžio paslaugą, skirtą 1C: Enterprise: Java, PostgreSQL, Hazelcast

Ką dar sužinojome apie apkrovos testavimą?

  1. JSR223 turi būti parašytas groovy ir įtraukti kompiliavimo talpyklą - tai daug greičiau. Nuoroda.
  2. Jmeter-Plugins grafikus lengviau suprasti nei standartinius. Nuoroda.

Apie mūsų patirtį su Hazelcast

Hazelcast mums buvo naujas produktas, su juo pradėjome dirbti nuo 3.4.1 versijos, dabar mūsų gamybos serveryje veikia 3.9.2 versija (rašymo metu naujausia Hazelcast versija yra 3.10).

ID generavimas

Pradėjome nuo sveikųjų skaičių identifikatorių. Įsivaizduokime, kad mums reikia dar vieno Ilgo naujam subjektui. Seka duomenų bazėje netinka, lentelėse dalyvauja dalijimasis - pasirodo, kad DB1 yra pranešimas ID=1, o DB1 - ID=2, šio ID negalite įdėti nei į Elasticsearch, nei į Hazelcast , bet blogiausia, jei norite sujungti duomenis iš dviejų duomenų bazių į vieną (pavyzdžiui, nuspręsti, kad šiems prenumeratoriams užtenka vienos duomenų bazės). Galite pridėti keletą AtomicLongs prie Hazelcast ir laikyti ten skaitiklį, tada naujo ID gavimo našumas bus incrementAndGet ir užklausos Hazelcast laikas. Tačiau Hazelcast turi kažką optimalesnio – FlakeIdGenerator. Susisiekus su kiekvienu klientu suteikiamas ID diapazonas, pavyzdžiui, pirmas – nuo ​​1 iki 10 000, antrasis – nuo ​​10 001 iki 20 000 ir pan. Dabar klientas gali pats išduoti naujus identifikatorius, kol baigsis jam išduotas diapazonas. Tai veikia greitai, bet kai iš naujo paleidžiate programą (ir Hazelcast klientą), prasideda nauja seka, taigi, praleidimai ir pan. Be to, kūrėjai nelabai supranta, kodėl ID yra sveikieji skaičiai, bet tokie nenuoseklūs. Viską pasvėrėme ir perėjome prie UUID.

Beje, tiems, kurie nori būti kaip „Twitter“, yra tokia „Snowcast“ biblioteka – tai „Snowflake“ įgyvendinimas „Hazelcast“ viršuje. Jį galite peržiūrėti čia:

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

Bet mes to nebesusipratome.

Transakcijos žemėlapis.pakeisti

Dar viena staigmena: „TransactionalMap.replace“ neveikia. Štai testas:

@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

Turėjau parašyti savo pakeitimą naudodamas 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);
}

Išbandykite ne tik įprastas duomenų struktūras, bet ir jų sandorių versijas. Pasitaiko, kad IMap veikia, bet TransactionalMap nebeegzistuoja.

Įdėkite naują JAR be prastovos

Pirmiausia nusprendėme įrašyti mūsų klasių objektus į Hazelcast. Pavyzdžiui, turime klasę Application, norime ją išsaugoti ir perskaityti. Sutaupyti:

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

Mes skaitome:

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

Viskas veikia. Tada nusprendėme sukurti indeksą „Hazelcast“, kad galėtume ieškoti pagal:

map.addIndex("subscriberId", false);

Ir rašydami naują objektą jie pradėjo gauti ClassNotFoundException. Hazelcast bandė įtraukti į indeksą, bet nieko nežinojo apie mūsų klasę ir norėjo, kad jai būtų pateiktas JAR su šia klase. Mes taip ir padarėme, viskas veikė, bet atsirado nauja problema: kaip atnaujinti JAR visiškai nesustabdant klasterio? „Hazelcast“ nepriima naujojo JAR, kai atnaujinamas mazgas po mazgo. Šiuo metu nusprendėme, kad galime gyventi be indekso paieškos. Galų gale, jei naudosite „Hazelcast“ kaip raktų vertės parduotuvę, tada viskas veiks? Ne visai. Čia IMap ir TransactionalMap elgesys vėl skiriasi. Kai IMap nerūpi, TransactionalMap pateikia klaidą.

IMap. Rašome 5000 objektų, juos skaitome. Viskas laukiama.

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

Bet tai neveikia atliekant operaciją, gauname 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();
        }
    });
}

3.8 pasirodė vartotojo klasės diegimo mechanizmas. Galite paskirti vieną pagrindinį mazgą ir atnaujinti jame esantį JAR failą.

Dabar mes visiškai pakeitėme savo požiūrį: patys jį serializuojame į JSON ir išsaugome Hazelcast. „Hazelcast“ nereikia žinoti mūsų klasių struktūros ir mes galime atnaujinti be prastovų. Domeno objektų versijų kūrimą valdo programa. Vienu metu gali veikti skirtingos programos versijos ir galima situacija, kai naujoji programa įrašo objektus su naujais laukais, o senoji apie šiuos laukus dar nežino. Ir tuo pačiu metu naujoji programa nuskaito senosios programos parašytus objektus, kuriuose nėra naujų laukų. Tokias situacijas sprendžiame aplikacijoje, tačiau paprastumo dėlei laukų nekeičiame ir neištriname, tik plečiame klases įtraukdami naujus laukus.

Kaip užtikriname aukštą našumą

Keturios kelionės į Hazelcast – gerai, dvi į duomenų bazę – blogos

Duomenų ieškojimas talpykloje visada yra geriau nei duomenų bazėje, tačiau taip pat nenorite saugoti nepanaudotų įrašų. Sprendimą, ką laikyti talpykloje, paliekame paskutiniam kūrimo etapui. Kai naujos funkcijos yra užkoduotos, įjungiame visų užklausų registravimą sistemoje „PostgreSQL“ (log_min_duration_statement iki 0) ir 20 minučių vykdome apkrovos testavimą. Naudodami surinktus žurnalus, tokios paslaugos kaip pgFouine ir pgBadger gali kurti analitines ataskaitas. Ataskaitose pirmiausia ieškome lėtų ir dažnų užklausų. Lėtoms užklausoms mes sudarome vykdymo planą (EXPLAIN) ir įvertiname, ar tokią užklausą galima paspartinti. Dažnos užklausos dėl tų pačių įvesties duomenų gerai telpa talpykloje. Stengiamės, kad užklausos būtų „plokščias“, po vieną lentelę užklausoje.

Išnaudojimas

SV kaip internetinė paslauga buvo pradėta eksploatuoti 2017 m. pavasarį, o kaip atskiras produktas SV buvo išleistas 2017 m. lapkritį (tuo metu buvo beta versijos būsena).

Per daugiau nei metus veikiančios CB internetinės paslaugos veikimo rimtų problemų nekilo. Mes stebime internetinę paslaugą per Zabbix, surinkti ir dislokuoti iš bambukas.

SV serverio paskirstymas tiekiamas vietinių paketų pavidalu: RPM, DEB, MSI. Be to, „Windows“ siūlome vieną diegimo programą vieno EXE forma, kuri įdiegia serverį, „Hazelcast“ ir „Elasticsearch“ viename kompiuteryje. Iš pradžių šią diegimo versiją vadinome „demo“ versija, tačiau dabar tapo aišku, kad tai yra populiariausias diegimo variantas.

Šaltinis: www.habr.com

Добавить комментарий