Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast

Selles artiklis rÀÀgime, kuidas ja miks me arendasime Koostöö sĂŒsteemi – mehhanism, mis edastab teavet kliendirakenduste ja 1C:Enterprise serverite vahel – ĂŒlesande seadmisest arhitektuuri ja rakenduse ĂŒksikasjade vĂ€ljatöötamiseni.

Koostöö sĂŒsteem (edaspidi – KS) on hajutatud tĂ”rketaluv sĂ”numite vahetamise sĂŒsteem tagatud kohaletoimetamisega. KS on loodud kĂ”rgendatud koormuse teenusena, millel on kĂ”rge mastaapsus ning on saadaval nii veebiteenusena (mida pakub 1C) kui ka seeriatootena, mida saab juurutada oma serveriressurssidesse.

KS kasutab hajutatud salvestust Hazelcast ja otsingusĂŒsteemi Elasticsearch. RÀÀgime ka Java'st ja sellest, kuidas me horisontaalselt skaleerime PostgreSQL-i.
Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast

Ülesande seadmine

Et oleks selge, miks me Koostöö sĂŒsteemi tegime, rÀÀgin veidi sellest, kuidas 1C-s Ă€rirakenduste arendamine toimub.

Alustuseks – natuke meie kohta neile, kes veel ei tea, millega me tegeleme:) Me arendame tehnoloogiaplatvormi "1C:EttevĂ”te". Platvorm sisaldab Ă€rirakenduste arendustööriistu ja runtime'i, mis vĂ”imaldab Ă€rirakendustel töötada ristplatvormil.

Kliendiserveri arenduspÔhimÔte

Ärirakendused, mis on loodud "1C:EttevĂ”tte" peal, töötavad kolmekihilise kliendiserveri arhitektuuri "DBMS – rakendusserver – klient". Rakenduslik kood, mis on kirjutatud 1C sisseehitatud keeles, vĂ”ib kĂ€ivituda kas rakendusserveris vĂ”i kliendis. KĂ”ik rakendusobjektidega (nĂ€iteks registrite, dokumentide jne) seotud tehingud, samuti andmebaasi lugemine ja kirjutamine toimub ainult serveris. Vormide ja kĂ€surea funktsionaalsus on samuti rakendatud serveris. Kliendis toimub vormide saamine, avamine ja kuvamine, kasutajaga "suhtlemine" (hoiatuste, kĂŒsimuste jne kaudu), vĂ€iksed arvutused vormides, mis vajavad kiiret reageerimist (nĂ€iteks hinna korrutamine kogusega), kohalike failidega töötamine, seadmetega töötamine.

Rakenduskoodis peavad protseduuride ja funktsioonide pealkirjades olema selgelt mĂ€rgitud, kus kood tĂ€idetakse - kasutades direktiive &ĐĐ°ĐšĐ»ĐžĐ”ĐœŃ‚Đ” / &ĐĐ°ĐĄĐ”Ń€ĐČДрД (&AtClient / &AtServer ingliskeelses keeleversioonis). 1C arendajad parandavad mind nĂŒĂŒd, öeldes, et direktiive tegelikult suurem, kuid meile pole see praegu oluline.

Kliendikoodist saab kutsuda serverikoodi, kuid serverikoodist kliendikoodi kutsuda ei saa. See on meie kehtestatud fundamentaalne piirang mitmel pĂ”hjusel. Esiteks, serverikood peab olema kirjutatud nii, et see tĂ€itub ĂŒhtemoodi, sĂ”ltumata sellest, kust seda kutsutakse - kliendilt vĂ”i serverilt. Ja juhul, kui serverikoodi kutsutakse teise serverikoodi seest, puudub klient tĂ€ielikult. Ja samuti seetĂ”ttu, et serverikoodi tĂ€itmise kĂ€igus vĂ”is klient, kes selle kutsus, sulgeda, rakendusest vĂ€lja minna, ja serveril ei ole enam kedagi, keda kutsuda.

Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast
Nupu vajutamise töötav kood: kliendi serveriprotseduuri kutsumine töötab, serverilt kliendiprotseduuri kutsumine - ei.

See tĂ€hendab, et kui me serverist tahame edastada mingit sĂ”numit kliendirakendusse, nĂ€iteks et "pikaajalise" raporti genereerimine on lĂ”ppenud ja raportit on vĂ”imalik vaadata – sellist vĂ”imalust meil ei ole. Peame kasutama trikke, nĂ€iteks kliendikoodist perioodiliselt serverit kĂŒsima. Kuid selline lĂ€henemine koormab sĂŒsteemi liigsete vĂ€ljakutsetega ja ei tundu eriti Ă©lĂ©gantsena.

Ja on ka vajadus, nĂ€iteks, kui telefonikĂ”ne SIP-teavitada sellest klienditeavet, et see leidu jĂ€tkuvalt helistaja numbri jĂ€rgi personaalsetest andmetest ja nĂ€itaks kasutajale teavet helistava kliendi kohta. VĂ”i nĂ€iteks, kui laos on tellimus, teavitada sellest kliendi rakendust tellijale. ÜhesĂ”naga, olukordi, kus selline mehhanism vĂ”iks kasuks tulla, on palju.

Tegelikult ĂŒlesanne

Loo sÔnumite vahetamise mehhanism. Kiire, usaldusvÀÀrne, garantii kohaletoimetamine, vÔimalus sÔnumite paindlikuks otsimiseks. Mehhanismi pÔhjal rakenda sÔnumi rakendus (sÔnumid, videokÔned), mis töötab 1C rakendustes.

Kujundada horisontaalselt skaleeritav sĂŒsteem. Suurenev koormus peab olema katnud sĂ”lmede arvu suurendamisega.

Rakendus

Otsustasime, et SV serveriosa ei integreerita otse 1C:Enterprise platvormi, vaid rakendatakse eraldi tootena, mille API-d saab kutsuda 1C rakenduste lahenduste koodist. Seda tehti mitmel pĂ”hjusel, millest kĂ”ige olulisem on vĂ”imaldada sĂ”numite vahetust erinevate 1C rakenduste vahel (nĂ€iteks kaubanduse juhtimise ja raamatupidamise vahel). Erinevad 1C rakendused vĂ”ivad töötada erinevate 1C:Enterprise platvormi versioonide peal, asuda erinevates serverites jne. Sellistes tingimustes on SV teostamine eraldi tootena, mis asub 1C installatsioonidest „kĂŒlg“ - optimaalne lahendus.

Nii et, otsustasime, et teeme SV-st eraldi toote. VĂ€ikestele ettevĂ”tetele soovitame kasutada SV-serverit, mille oleme oma pilves ĂŒles seadnud (wss://1cdialog.com), et vĂ€ltida kohaliku installatsiooni ja seadistamisega seotud kulusid. Suuremad kliendid vĂ”ivad aga leida, et nende vĂ”imsustele oma SV-serveri seadmine on Ă”igustatud. Sarnast lĂ€henemist kasutasime ka oma pilvepĂ”hises SaaS-toote puhul. 1cFresh – see on vĂ€lja antud kui versioonitoode klientide installimiseks ning samuti on see meie pilves ĂŒles seatud. https://1cfresh.com/.

Rakendus

Koormuse ja tĂ”rke talumise saavutamiseks seame ĂŒles mitte ĂŒhe, vaid mitu Java-rakendust ja nende ette paneme koormuse tasakaalustaja. Kui sĂ”numit peab node'ilt node'ile edastama, kasutame Hazelcastis publish/subscribe sĂŒsteemi.

Kliendi suhtlemine serveriga toimub websocket'i kaudu. See sobib hĂ€sti reaalajas sĂŒsteemide jaoks.

Jaotatud vahemÀlu

Otsisite Redis, Hazelcasti ja Ehcache'i vahel. Aasta on 2015. Redis just lahkus uuest klastri versioonist (liialt uus, hirmutav), Sentineli kuulub hulk piiranguid. Ehcache ei oska kokku koguneda (see funktsionaalsus tuli hiljem). Otsustasime proovida Hazelcast 3.4.
Hazelcast koguneb klastri «kastist vĂ€lja». Ühe sĂ”lme reĆŸiimis pole see eriti kasulik ja sobib ainult vahemĂ€luna – ei oska andmeid kettale salvestada; kaotasime ainulaadse sĂ”lme – kaotasime andmed. Me paigutame mitu Hazelcasti ja varundame kriitilisi andmeid nende vahel. VahemĂ€lu ei varunda – seda ei ole kahju.

Meie jaoks on Hazelcast:

  • Kasutaja sessioonide salvestus. Iga kord, kui minema baasi sessiooni jĂ€rele minna – vĂ”tab kaua aega, seetĂ”ttu paneme kĂ”ik sessioonid Hazelcasti.
  • VahemĂ€lu. Otsid kasutaja profiili – kontrolli vahemĂ€lus. Kirjutasid uue sĂ”numi – pane vahemĂ€llu.
  • Rakenduse instantside suhtlemise teemad. SĂ”lm genereerib sĂŒndmuse ja asetab selle Hazelcasti teemasse. Teised rakenduse sĂ”lmed, mis sellele teemale on tellinud, saavad ja töötlevad sĂŒndmust.
  • Klastri lukustused. NĂ€iteks loome arutelu unikaalse vĂ”tme alusel (sĂŒntesaator arutelu 1C andmebaasi raames):

conversationKeyChecker.check("BENSOKOLONKA");

      doInClusterLock("BENSOKOLONKA", () -> {

          conversationKeyChecker.check("BENSOKOLONKA");

          createChannel("BENSOKOLONKA");
      });

Kontrollisime, et kanal puudub. VĂ”tsime lukustuse, kontrollisime uuesti, lĂ”ime. Kui lukustust ei kontrollita, on vĂ”imalus, et mĂ”ni teine teema, mis sel ajal samuti kontrollis, proovib luua sama arutelu – ja see juba eksisteerib. Lukustust ei saa teha synchronized vĂ”i tavalise java Lock abil. Andmebaasi kaudu – aeglane, ja andmebaasi ka kahju, Hazelcast lĂ€bi – see on just see, mida vajame.

Valime DBMS-i

Meil on suur ja edukas kogemus PostgreSQL-i ning selle andmebaasi arendajatega koostöö tegemisel.

PostgreSQL-i klustereerimine on keeruline – on XL, XC, Citus, kuid see ei ole iseenesest noSQL, mis on vĂ€lja pakutud mastiks. Me ei pidanud noSQL-i peamise salvestuslahendusena, piisab sellest, et kasutame Hazelcasti, millega me varem pole töötanud.

Kui tuleb skaleerida relatsiooni DB – siis tĂ€hendab see sharding. Nagu te teate, jagame shardinguga andmebaasi eraldi osadeks, nii et igaĂŒht neist saab viia eraldi serverisse.

Meie esimene sharding'i variant eeldas, et saame jagada meie rakenduse iga tabeli erinevatesse serveritesse erinevates proportsioonides. Kui serveris A on palju sĂ”numeid – palun, viige osa sellest tabelist serverisse B. See lahendus oli selgelt enneaegne optimeerimine, seetĂ”ttu otsustasime piirduda multi-tenant lĂ€henemisega.

Multi-tenant'i kohta saate lugeda nÀiteks veebisaidilt Citus Data.

SV-s on mÀÀratlused rakenduse ja tellija kohta. Rakendus on konkreetne Ă€rirakenduse installatsioon, nĂ€iteks ERP vĂ”i Raamatupidamine, koos oma kasutajate ja Ă€riandmetega. Tellija on organisatsioon vĂ”i fĂŒĂŒsiline isik, kelle nimel rakendus registreeritakse SV serveris. Tellijal vĂ”ib olla registreeritud mitu rakendust, ning need rakendused saavad omavahel sĂ”numeid vahetada. Tellija on meie sĂŒsteemis muutunud elanikuks (tenant). Mitme tellija sĂ”numid vĂ”ivad asuda ĂŒhes fĂŒĂŒsilises andmebaasis; kui me nĂ€eme, et mĂ”ni tellija genereerib palju liiklust – viime ta eraldi fĂŒĂŒsilisse andmebaasi (vĂ”i isegi eraldi andmebaasi serverisse).

Meil on peamine andmebaas, kus hoitakse suunamise tabelit kÔigi tellijate andmebaaside asukoha teabega.

Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast

Kuna peamine andmebaas ei tohi olla kitsaskoht, hoiame suunamise tabelit (ja muid sageli kasutatavaid andmeid) vahemÀlus.

Kui tellija andmebaas hakkab aeglustuma, lÔikame selle sees osadeks. Teistes projektides kasutame suurte tabelite partitsioneerimiseks pg_pathman.

Kuna kasutajate sĂ”numite kadumine on halb, toetame meie andmebaase koopiatega. SĂŒnkroonsete ja asĂŒnkroonsete koopiate kombinatsioon vĂ”imaldab meil kindlustada end peamise andmebaasi kadumise korral. SĂ”numi kadu toimub ainult siis, kui peamine andmebaas ja selle sĂŒnkroonne koopia ebaĂ”nnestuvad samal ajal.

Kui sĂŒnkroonne koopia kaob – muutub asĂŒnkroonne koopia sĂŒnkroonseks.
Kui peamine andmebaas kaob – muutub sĂŒnkroonne koopia peamiseks andmebaasiks, asĂŒnkroonne koopia – sĂŒnkroonseks koopiaks.

Elasticsearch otsinguks

Kuna, et SV on lisaks muule ka sĂ”numiteenus, on hĂ€davajalik kiire, mugav ja paindlik otsing, mis arvestab morfoloogiat ja ebakorrektseid vasteid. Otsustasime, et ei hakka ratast leiutama ja kasutame avatud otsingusĂŒsteemi Elasticsearch, mis on loodud Lucene'i raamatukogu pĂ”hjal. Lucene. Elasticsearchi deployime ka klastris (master – data – data), et vĂ€hendada rakenduse sĂ”lmede rikkeprobleemide riski.

Githubis leidsime vene morfoloogia plugin Elasticsearchile ja kasutame seda. Elasticsearchi indeksis hoiame sĂ”nade juured (mida mÀÀrab plugin) ja N-grammid. Kui kasutaja sisestab otsinguteksti, otsime sisestatud teksti N-grammide hulgast. Indeksi salvestamisel sĂ”na „tekstid“ jaguneb jĂ€rgmistesse N-grammidesse:

[te, tek, tekste, tekst, tekstid, ek, eks, ekst, ekstid, ks, kst, kstid, st, stid, tid],

Ning ka salvestatakse sĂ”na „tekst“ juur. Selline lĂ€henemine vĂ”imaldab otsida nii sĂ”na algust, keskel kui ka lĂ”pust.

Üldine pilt

Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast
Kuvab sama pilti artikli algusest, kuid juba seletustega:

  • Tasakaalustaja, mis on Interneti-suunas; meil on nginx, vĂ”ib olla ĂŒkskĂ”ik milline.
  • Java rakenduste instantsid suhtlevad omavahel lĂ€bi Hazelcasti.
  • Veebisoketiga töötamiseks kasutame Netty.
  • Java rakendus on kirjutatud Java 8 keeles, koosneb bundeledest OSGi. Plaanides on ĂŒleminek Java 10-le ja modulaarsele arhitektuurile.

Arendus ja testimine

Arenduse ja testimise protsessis kohtasime mitmeid huvitavaid tooteomadusi, mida kasutasime.

Koormustestimine ja mÀlu leketud

Iga versi vÀljund on koormustestimine. See on edukalt lÀbitud, kui:

  • Test kestis mitu pĂ€eva ja teenusekatkestusi ei esinenud
  • Peamiste toimingute vastuse aeg ei ĂŒletanud mugavat piiri
  • Tootlikuse halvenemine vĂ”rreldes eelmise versiooniga ei ole ĂŒle 10%

Testimisbaasi tĂ€idame andmetega – selleks saame tootmisserverilt teavet kĂ”ige aktiivsema tellija kohta, korrutame tema numbrid 5-ga (sĂ”numite, arutelude, kasutajate arv) ja testime nii.

Koormustestimist sĂŒsteemi interaktsiooni teeme kolmes konfiguratsioonis:

  1. Stressitest
  2. Ainult ĂŒhendused
  3. Tellijate registreerimine

Stressitestil kĂ€ivitame mitu sada voogu, mis pidevalt koormavad sĂŒsteemi: kirjutavad sĂ”numeid, loovad arutelusid, saadavad sĂ”numite nimekirja. Imitseerime tavaliste kasutajate tegevusi (saada oma lugemata sĂ”numite nimekiri, kellelegi kirjutada) ja programmilisi lahendusi (ĂŒle anda pakett erineva konfiguratsiooniga, töödelda teadet).

NÀiteks, nii nÀeb vÀlja osa stressitestist:

  • SĂŒsteemi siseneb kasutaja
    • KĂŒsimus oma lugemata arutelude kohta
    • 50% tĂ”enĂ€osusega loeb sĂ”numeid
    • 50% tĂ”enĂ€osusega kirjutab sĂ”numeid
    • Edasi jĂ€tkab kasutaja:
      • 20% tĂ”enĂ€osusega loob uue arutelu
      • Valib juhuslikult mĂ”ne oma arutelu
      • Siseneb sisse
      • KĂŒsimus sĂ”numeid, kasutajate profiile
      • Loob viis sĂ”numit, mis on suunatud juhuslikele kasutajatele sellest arutelust
      • Lahkub arutelust
      • Kordab 20 korda
      • Lahkub sĂŒsteemist, naaseb stsenaariumi algusesse

    • SĂŒsteemi siseneb chatbot (imiteerib sĂ”numivahetust rakenduste koodist)
      • 50% tĂ”enĂ€osusega loob uue kanali andmevahetuseks (eriline arutelu)
      • 50% tĂ”enĂ€osusega saadab sĂ”numi mĂ”nes olemasolevas kanalisse

Skeem «Ainult ĂŒhendused» ei ilmnenud juhuslikult. On olukordi: kasutajad on sĂŒsteemi lĂŒlitanud, kuid pole veel sisse harjunud. Iga kasutaja lĂŒlitab hommikul kell 09:00 arvuti sisse, loob serveriga ĂŒhenduse ja vaikib. Need tĂŒĂŒbid on ohtlikud, neid on palju – nende paketid sisaldavad ainult PING/PONG, kuid nad hoiavad serveriga ĂŒhendust (nad ei saa seda mitte hoida – Ă€kki tuleb uus sĂ”num). Test simuleerib olukorda, kus sĂŒsteemi pĂŒĂŒab poole tunni jooksul sisse logida suur hulk selliseid kasutajaid. See on sarnane stressitestiga, kuid see keskendub just sellele esimeses sisenemises – et ei toimuks tĂ”rkeid (inimene ei kasuta sĂŒsteemi, kuid see juba ebaĂ”nnestub – raske on midagi hullemat vĂ€lja mĂ”elda).

Kasutajate registreerimise skeem algab esimesest kĂ€ivitamisest. Me tegime stressitesti ja olime kindlad, et kirjavahetuse kĂ€igus sĂŒsteem ei aeglustu. Kuid kasutajad hakkasid sisse logima ja registreerimine hakkas ajapiirangute tĂ”ttu ebaĂ”nnestuma. Registreerimisel kasutasime /dev/random, mis on seotud sĂŒsteemi entropiaga. Server ei jĂ”udnud piisavalt entropiat koguda ning uue SecureRandom'i nĂ”udmisel seiskus see kĂŒmneid sekundeid. Sellest olukorrast on palju vĂ€ljapÀÀse, nĂ€iteks: minna ĂŒle vĂ€hem turvalisele /dev/urandom'ile, paigutada spetsiaalne kaart, mis genereerib entropiat, genereerida juhuslikke numbreid ette ja hoida neid teas. Me lahendasime probleemi ajutiselt puuga, kuid alates sellest ajast juhime eraldi testi uute tellijate registreerimise osas.

Koormuse generaatorina kasutame JMeter. See ei oska töötada veebipÔhiste soketitega, vajalik on plugin. Esimesed otsingutulemustes otsingus "jmeter websocket" on artiklid BlazeMeter'ilt, milles soovitatakse pluginat Maciej Zaleskilt.

. Sellest me ka alustasime.

Peaaegu kohe pÀrast tÔsiste testide alustamist avastasime, et JMeter'is algasid mÀluleked.

Plugin – see on eraldi suur lugu, 176 tĂ€hega on tal 132 fork'i github'is. Autor ei ole sinna 2015. aastast kommenteerinud (me kasutasime seda 2015. aastal, siis see ei tekitanud kahtlusi), mitmeid github'i probleeme seoses mĂ€luleketega, 7 lahendamata pull request'i.
Kui otsustate testida koormust selle pluginaga, pöörake tÀhelepanu jÀrgmistele aruteludele:

  1. Mitme haru sĂŒsteemis kasutati tavalist LinkedList'i, mille tulemusena saadi NPE kĂ€itamisel. Selle saab lahendada, kas liikudes ConcurrentLinkedDeque'i juurde vĂ”i kasutades synchronized-blokke. Otsustasime esimese variandi kasuks (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. MĂ€lu leke, teabe kustutamine ĂŒhenduse katkestamise korral ei toimi (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Streaming-reĆŸiimis (kui veebisoket ei suleta proovi lĂ”pus, vaid kasutatakse edasi) ei tööta Response pattern'id (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

See on ĂŒks neist, mis on github'is. Mida tegime:

  1. VĂ”tsime Elyran Kogani forki (@elyrank) – seal on lahendatud probleemid 1 ja 3
  2. Lahendasime probleemi 2
  3. Uuendasime jetty versioonilt 9.2.14 9.3.12-le
  4. Kapseldasime SimpleDateFormat'i ThreadLocal'i; SimpleDateFormat ei ole lÔimeohutu, mis tÔi kaasa NPE kÀitamisel
  5. Korrigeerisime veel ĂŒhe mĂ€lu leke (ĂŒhendus ei suletud Ă”igesti katkestamise korral)

Kuid siiski voolab!

MĂ€lu hakkas otsa saama mitte ĂŒhe pĂ€eva, vaid kahe pĂ€evaga. Aega ei jÀÀnud, otsustasime kĂ€ivitada vĂ€hem lĂ”ime, kuid neljal agentil. Seda pidi piisama vĂ€hemalt nĂ€dalaks.

On möödunud kaks pĂ€eva


NĂŒĂŒd hakkas Hazelcast-il mĂ€lu otsa saama. Logides oli nĂ€ha, et pĂ€rast paaripĂ€evast testimist hakkab Hazelcast kurtma mĂ€lupuuduse ĂŒle ja pĂ€rast mĂ”nda aega laguneb klaster ning sĂ”lmed jĂ€tkavad ĂŒksikult hukkumist. Me ĂŒhendasime JVisualVM hazelcast-iga ja nĂ€gime "tĂ”usvat saagi" – see kutsus regulaarselt esile GC, kuid ei suutnud kuidagi mĂ€lu puhastada.

Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast

Selgus, et hazelcast 3.4 versioonis ei vabastata mÀlu tÀielikult, kui kustutatakse map / multiMap (map.destroy()):

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

Praegu on viga 3.5 versioonis parandatud, kuid siis oli see probleem. Me loome uusi multiMap-e dĂŒnaamiliste nimedega ja kustutame vastavalt oma loogikale. Kood nĂ€gi vĂ€lja umbes nii:

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

Kutse:

service.join(auth1, "UUED_SÕNUMID_ARUTEL_UUID1");
service.join(auth2, "UUED_SÕNUMID_ARUTEL_UUID1");

multiMap loodi iga tellimuse jaoks ja eemaldati, kui seda ei olnud enam vaja. Otsustasime, et kasutame Map<String,Set>, kus vÔtmena on tellimuse nimi ja vÀÀrtustena seansi identifikaatorid (mille alusel saab vajadusel kasutaja identifikaatoreid saada).

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

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

Graafikud on paranenud.

Kuidas ja miks me kirjutasime suure koormuse talumisega skaleeritava teenuse 1C: EttevÔtted jaoks: Java, PostgreSQL, Hazelcast

Mida veel oleme Ôppinud koormustestimise kohta

  1. JSR223 tuleb kirjutada groovy keeles ja aktiveerida kompilatsiooni vahemĂ€lu – see on oluliselt kiirem. Ling.
  2. Jmeter-Plugins graafikud on lihtsamad mÔista kui standardsed. Ling.

Meie kogemus Hazelcastiga

Hazelcast oli meie jaoks uus toode, alustasime selle kasutamist versioonist 3.4.1, praegu on meie tootmisserveris versioon 3.9.2 (artikli kirjutamise hetkel oli viimane versioon Hazelcast – 3.10).

ID genereerimine

Alustasime tĂ€isarvuliste identifikaatoritega. Kujutlegem, et vajame uue ĂŒksuse jaoks Long tĂŒĂŒpi identifikaatorit. Andmebaasi jĂ€rk ei sobi, tabelid osalevad shardingus – viib see selleni, et on sĂ”num ID=1 andmebaasis DB1 ja sĂ”num ID=1 andmebaasis DB2, Elasticsearchis sellise ID jĂ€rgi ei talletata, Hazelcastis samuti, kuid kĂ”ige hullem on see, kui soovite kahte andmebaasi ĂŒhendada ĂŒhte (nĂ€iteks otsustades, et piisab ĂŒhest andmebaasist nende abonentide jaoks). Hazelcastis saab luua mitu AtomicLong ja hoida loendurit seal, siis on uue ID saamise tulemuslikkus – incrementAndGet pluss aeg Hazelcastisse pĂ€ringu tegemiseks. Kuid Hazelcastis on midagi veel optimaalsemat – FlakeIdGenerator. Iga kliendi jaoks vĂ€ljastatakse ID vahemik, nĂ€iteks esimesena – vahemik 1 kuni 10 000, teisele – vahemik 10 001 kuni 20 000 ja nii edasi. NĂŒĂŒd saab klient uusi identifikaatoreid ise vĂ€lja anda, kuni tema antud vahemik lĂ”peb. Töötab kiiresti, kuid rakenduse (ja Hazelcasti kliendi) taaskĂ€ivitamisel algab uus jĂ€rjestus – sealt tulenevad Ă€rajÀÀnud ID-d jne. Samuti ei saa arendajad hĂ€sti aru, miks ID-d on tĂ€isarvud, kuid on nii palju segmenteeritud. Oleme kĂ”ik kaalunud ja lĂ€inud UUID-de peale.

Muide, neile, kes soovivad olla nagu Twitter, on olemas selline teek nimega Snowcast – see on Snowflake'i teostus Hazelcast'i peal. Vaadata saab siit:

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

Kuid me ei ole selle nii kaugele veel jÔudnud.

TransactionalMap.replace

Veel ĂŒks ĂŒllatus: TransactionalMap.replace ei tööta. Siin on selline 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();
        }        
    });
}

Oodatav: newValue
Tegelik: oldValue

Pidin kirjutama oma replace, kasutades getForUpdate:

protected  boolean replaceInMap(String mapName, K key, V oldValue, V newValue) {
    TransactionalTaskContext context = HazelcastTransactionContextHolder.getContext();
    if (context != null) {
        log.trace("[CACHE] Asendamine vÀÀrtuse a transactioneel kaardil");
        TransactionalMap map = context.getMap(mapName);
        V value = map.getForUpdate(key);
        if (oldValue.equals(value)) {
            map.put(key, newValue);
            return true;
        }

        return false;
    }
    log.trace("[CACHE] Asendamine vÀÀrtuse mitte transactioneel kaardil");
    IMap 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 TransactionalMap mitte.

Uue JAR-i paigaldamine ilma seisakute tekitamiseta

Alguses otsustasime salvestada Hazelcasti oma klasside objekte. NĂ€iteks, meil on klass Application, mida soovime salvestada ja lugeda. Salvestame:

IMap map = hazelcastInstance.getMap("application");
map.set(id, application);

Lugemine:

IMap map = hazelcastInstance.getMap("application");
return map.get(id);

KÔik töötab. SeejÀrel otsustasime luua Hazelcastis indeksi, et selle kaudu otsida:

map.addIndex("subscriberId", false);

Ja kui me registreerisime uue ĂŒksuse, algas ClassNotFoundException. Hazelcast ĂŒritas indeksi tĂ€iendamist, kuid ei teadnud meie klassist midagi ja soovis, et me esitaksime JAR-i koos selle klassiga. Me tegime nii ja kĂ”ik töötas, kuid tekkis uus probleem: kuidas uuendada JAR-i ilma klastrit tĂ€ielikult peatamata? Hazelcast ei paku uut JAR-i pĂ€rast podide vĂ€rskendamist. Sel hetkel otsustasime, et saame ilma indeksiotsinguta ka hakkama. Kui kasutada Hazelcasti vĂ”tme-vÀÀrtuse salvestusena, kas siis kĂ”ik töötab? Mitte pĂ€ris. Siin nĂ€eme jĂ€lle erinevat kĂ€itumist IMap ja TransactionalMap vahel. Seal, kus IMap ei hooli, viskab TransactionalMap vea.

IMap. Salvestame 5000 objekti, loeme vÀlja. KÔik ootuspÀrane.

@Test
void get5000() {
    IMap 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());
    }
}

Aga tehingus see ei tööta, saame ClassNotFoundException:

@Test
void get_transaction() {
    IMap 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 transactionalMap = context.getMap("application_t");
            Application retrievedInside = transactionalMap.get(id);

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

3.8 versioonis ilmus User Class Deployment mehhanism. Saate mÀÀrata ĂŒhe peamise sĂ”lme ja vĂ€rskendada JAR-faili sellel.

NĂŒĂŒd oleme lĂ€henemise tĂ€ielikult muutnud: me serialiseerime JSON-i ja salvestame selle Hazelcasti. Hazelcast ei pea teadma meie klasside struktuuri, mistĂ”ttu saame uuendada ilma seisakuteta. Domeeniobjektide versioneerimist juhib rakendus. Samal ajal vĂ”ivad töötada erinevad rakenduste versioonid, ja vĂ”ib juhtuda, et uus rakendus kirjutab objekte uute vĂ€ljadega, mille kohta vana veel ei tea. Ja samal ajal loeb uus rakendus objekte, mille on salvestanud vana rakendus, kus uutest vĂ€ljadest puuduvad. Sellised olukorrad kĂ€sitletakse rakenduse sees, kuid lihtsuse huvides me ei muuda ega eemalda vĂ€lju, vaid ainult laiendame klasse uute vĂ€ljade lisamisega.

Kuidas me tagame kÔrge jÔudluse

Neli kĂŒlastust Hazelcasti – hea, kaks andmebaasi – halb

Andmete toomine vahemĂ€lust on alati parem kui andmebaasist, kuid me ei taha hoida kasutut teavet. Otsuse selle kohta, mida vahemĂ€llu salvestada, lĂŒkkame arenduse viimasele etapile. Kui uus funktsionaalsus on kodeeritud, aktiveerime PostgreSQLis kĂ”ikide pĂ€ringute logimise (log_min_duration_statement seade 0) ja teeme koormustestimise umbes 20 minutiks. Kogutud logide pĂ”hjal oskavad tööriistad nagu pgFouine ja pgBadger koostada analĂŒĂŒtilisi aruandeid. Aruannetes otsime eelkĂ”ige aeglaseid ja sagedasi pĂ€ringuid. Aeglaste pĂ€ringute jaoks koostame tĂ€itmisplaani (EXPLAIN) ja hindame, kas sellist pĂ€ringut saaks kiirendada. Samade sisendandmete sagedased pĂ€ringud paigutuvad hĂ€sti vahemĂ€llu. PĂ€ringud pĂŒĂŒame hoida "tasapinnaliste" ehk ĂŒhes pĂ€ringus ainult ĂŒhte tabelit.

Töötamine

SV online-teenus kÀivitati 2017. aasta kevadel, eraldi tootena ilmus SV 2017. aasta novembris (toona beetaversioonina).

Rohkem kui aasta kasutusea jooksul ei ole SV online-teenuses olnud tÔsiseid probleeme. Zabbix, kogume ja juurutame lÀbi Bamboo.

SV serveri distributsioon tarnitakse natiivsete paketina: RPM, DEB, MSI. Lisaks pakume Windowsile ĂŒhte installijat, mis on ĂŒks EXE-fail, mis installib serveri, Hazelcasti ja Elasticsearchi ĂŒhele masinale. Esialgu nimetati seda installiversiooni „demonstreerivaks“, kuid nĂŒĂŒd on selge, et see on kĂ”ige populaarsem juurutamisviis.

Allikas: habr.com

Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne veebihosting DDoS kaitsega, VPS VDS serverid | ProHoster