Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast

Selles artiklis rÀÀgime, kuidas ja miks me selle vĂ€lja töötasime Koostöö sĂŒsteemist – mehhanism, mis edastab teavet kliendirakenduste ja 1C:EttevĂ”tte serverite vahel – ĂŒlesande mÀÀratlemisest arhitektuuri ja rakendamise ĂŒksikasjade mĂ”tlemiseni.

Koostöö sĂŒsteem (edaspidi – KS) on hajutatud tĂ”rke- ja koormustaluv sĂŒsteem sĂ”numite vahetamiseks, millele on tagatud kohaletoimetamine. KS on kujundatud kui suure koormusega teenus, millel on kĂ”rge skaleeritavus, ja see on saadaval nii veebiteenusena (mida pakub ettevĂ”te 1C) kui ka seeriatootena, mida saab rakendada oma serverivĂ”imsusel.

KS kasutab hajutatud salvestusruumi Hazelcast ja otsingusĂŒsteemi Elasticsearch. Samuti rÀÀgime Java'st ja sellest, kuidas me PostgreSQL-i horisontaalselt skaleerime.
Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast

Ülesande seadmine

Et aru saada, miks me Koostöö sĂŒsteemi lĂ”ime, rÀÀgin natuke sellest, kuidas 1C-s Ă€rrakenduste arendamine toimub.

Alustuseks – natuke meie kohta neile, kes veel ei tea, millega me tegeleme :) Me arendame tehnoloogilist platvormi „1C:EttevĂ”te“. Platvorm sisaldab Ă€rrakenduste arendamise vahendit ning runtime'i, mis vĂ”imaldab Ă€rrakendustel töötada mitmesugustes keskkondades.

Kliendiserveri arendusparadigma

Ärrakendused, mis on loodud „1C:EttevĂ”ttes“, töötavad kolmetasandilises kliendiserveri arhitektuuris „DBMS – rakenduste server – klient“. Rakenduslik kood, mis on kirjutatud 1C sisekeeles, vĂ”ib töötada rakenduste serveris vĂ”i kliendil. KĂ”ik töö, mis on seotud rakendusobjektide (nĂ€iteks registrid, dokumendid jne), samuti andmebaasi lugemise ja kirjutamisega, toimub ainult serveris. Vormide ja kĂ€suliidese funktsionaalsus on samuti rakendatud serveris. Klient tegeleb vormide saamise, avamise ja kuvamisega, „suhtlemisega“ kasutajaga (hoiatused, kĂŒsimused...), vĂ€ikeste arvutustega vormides, mis vajavad kiiret reageerimist (nĂ€iteks hinnaliste koguste korrutamine), kohalike failide töötlemise ja seadme töötlusega.

Rakendusliku koodi protseduuride ja funktsioonide pĂ€istes tuleb selgelt nĂ€idata, kus kood töötab – kasutada direktiive &ĐĐ°ĐšĐ»ĐžĐ”ĐœŃ‚Đ” / &ĐĐ°ĐĄĐ”Ń€ĐČДрД (&AtClient / &AtServer ingliskeelses versioonis). 1C arendajad parandavad mind, öeldes, et direktiivid on tegelikult ei nĂ”uta enam, kuid see pole meile praegu oluline.

Kliendikoodist saab kutsuda serverikoodi, kuid serverikoodist kliendikoodi kutsuda ei saa. See on meie poolt kehtestatud pĂ”hi piirang mitmetel pĂ”hjustel. EelkĂ”ige seetĂ”ttu, et serverikood peab olema kirjutatud nii, et see toimib ĂŒhtviisi, sĂ”ltumata sellest, kust seda kutsutakse - kliendist vĂ”i serverist. Ja juhul, kui serverikoodi kutsutakse teise serverikoodi kaudu, siis klient enam ei eksisteeri. Samuti seetĂ”ttu, et serverikoodi tĂ€itmise ajal vĂ”ib klient, kes selle kutsus, sulgeda, rakendusest vĂ€lja minna ja serveril ei ole enam kedagi, kellele kutsuda.

Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast
Kood, mis töötleb nupu vajutamist: serveripĂ€ring kliendist töötab, kuid kliendipĂ€ring serverist — ei.

See tĂ€hendab, et kui me serverist soovime edastada mĂ”ne sĂ”numi kliendirakendusele, nĂ€iteks et "pikaks ajaks" kestnud aruande koostamine on lĂ”ppenud ja aruanne on saadaval — meil pole sellist vĂ”imalust. Peame leidma muid lahendusi, nĂ€iteks kĂŒsima serverilt regulaarselt. Kuid selline lĂ€henemine koormab sĂŒsteemi liigsete pĂ€ringutega ja kokkuvĂ”ttes ei nĂ€e see vĂ€ga elegantne vĂ€lja.

Ja on veel vajadus, nĂ€iteks, kui saabub telefonikĂ”ne, SIPteavitada sellest kliendirakendust, et see leidlikult otsiks helistaja numbrit partnerite andmebaasist ja kuvaks kasutajale teavet helistava partneri kohta. VĂ”i nĂ€iteks teavitada kliendirakendust tellijast, kui laost saabub tellimus. ÜhesĂ”naga, selliseid juhtumeid, kus selline mehhanism oleks abiks, on palju.

Asjaolu sÔnastamine.

Luua sÔnumite vahetamise mehhanism. Kiire, usaldusvÀÀrne, tagatud kohaletoimetamisega, vÔimalusega paindlikult sÔnumeid otsida. Mehhanismi baasil ellu viia sÔnumitooja (sÔnumid, videokÔned), mis töötab 1C rakenduste sees.

Kavandada horisontaalselt skaleeritav sĂŒsteem. Kasvav koormus peab olema kaetud sĂ”lmede arvu suurendamisega.

Rakendamine

Otsustasime, et SV serveri osa ei integreerita otse 1C:Enterprise platvormi, vaid rakendatakse eraldi tootena, mille API-d saab kutsuda 1C rakenduste kodeerimisest. See otsus tehti mitmel pÔhjusel, millest peamine oli soov vÔimaldada sÔnumite vahetamist erinevate 1C rakenduste vahel (nÀiteks Kaupade Halduse ja Raamatupidamise vahel). Erinevad 1C rakendused vÔivad töötada erinevate 1C:Enterprise platvormi versioonidega, asuda erinevatel serveritel jne. Sellistes tingimustes on SV rakendamine eraldiseisva tootena, mis asub 1C installatsioonidest

Nii otsustasime, et teeme SV eraldi tootena. VĂ€ikestele ettevĂ”tetele soovitame kasutada SV serverit, mille oleme paigaldanud oma pilve (wss://1cdialog.com), et vĂ€ltida kohapealse paigalduse ja seadistamisega seotud kulusid. Suuremad kliendid vĂ”ivad aga kaaluda enda SV serveri paigaldamist oma ressurssidele. Sarnast lĂ€henemist oleme kasutanud oma pilve pĂ”hises SaaS tootes. 1cFresh – see vĂ€ljastatakse kui seeriatoode klientide paigalduseks ja samuti on see rakendatud meie pilve https://1cfresh.com/.

Rakendus

Koormuse ja tĂ”rketaluvuse jaotamiseks ei paigalda me mitte ĂŒhte, vaid mitut Java-rakendust ning nende ette paigaldame koormuse tasakaalustaja. Kui on vajalik sĂ”numi edastamine node'ilt node'ile – kasutame publish/subscribe funktsiooni Hazelcastis.

Klientide ja serveri vaheline suhtlemine toimub websocketi kaudu. See sobib hĂ€sti reaalajas sĂŒsteemidele.

Jaotatud vahemÀlu

Valisime Redis, Hazelcasti ja Ehcache'i vahel. Aasta on 2015. Redis just kÀivitas uue klastriversiooni (liialt uus, liiga hirmus), Sentinelil on palju piiranguid. Ehcache ei suuda klastrisse kokku minna (see funktsionaalsus ilmus hiljem). Otsustasime proovida Hazelcasti 3.4.
Hazelcast koguneb klastriks "kastist vĂ€lja". Ühe node'i reĆŸiimis ei ole see kuigi kasulik ja vĂ”ib sobida ainult vahemĂ€luks - see ei oska andmeid kettale salvestada, kui kaotame ainus node'i - kaotame andmed. Me kĂ€sitleme mitut Hazelcast'i, mille vahel varundame kriitilisi andmeid. VahemĂ€lu ei varunda – seda ei ole kahju kaotada.

Meie jaoks on Hazelcast:

  • Kasutajasektsioonide salvestamine. Igal korral andmebaasist seanssile minek on aeglane, seetĂ”ttu paneme kĂ”ik seansid Hazelcasti.
  • VahemĂ€lu. Otsid kasutaja profiili – kontrolli vahemĂ€lus. Kirjutasid uue sĂ”numi – salvesta vahemĂ€llu.
  • Rakenduste instantside suhtlemise teemad. SĂ”lm genereerib sĂŒndmuse ja lisab selle Hazelcasti teema. Teised rakenduse sĂ”lmed, mis on sellel teemal tellitud, saavad ja töötlevad sĂŒndmust.
  • Klastri lukustused. NĂ€iteks loome arutelu unikaalse vĂ”tme jĂ€rgi (arutelu-sĂŒntetar, mis on 1C andmebaasis):

conversationKeyChecker.check("BENSIINIAJA");

      doInClusterLock("BENSIINIAJA", () -> {

          conversationKeyChecker.check("BENSIINIAJA");

          createChannel("BENSIINIAJA");
      });

Kontrollisime, et kanali ei ole. VĂ”tsime lukustuse, kontrollisime uuesti, lĂ”ime kanali. Kui lukustuse vĂ”tmisel ei kontrolli, on vĂ”imalus, et teine thread kontrollis samuti ja proovib luua sama arutelu – aga see juba eksisteerib. Lukustamine synchronized vĂ”i tavalise java Lockiga ei ole vĂ”imalik. Andmebaasi kaudu – aeglane, ja andmebaasi on kahju, Hazelcasti kaudu – see on just see, mida vajame.

Valime andmebaasi

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

PostgreSQL-i klastri haldamine on keeruline – there are XL, XC, Citus, kuid see ei ole pĂ”himĂ”tteliselt noSQL, mis skaleeruvad kohe vĂ€lja. NoSQL-i peamise salvestusena ei kaalutud, sest piisavalt on, et vĂ”tame Hazelcasti, millega pole varem töötatud.

Kui on vaja mÔÔta relatiivset andmebaasi – see tĂ€hendab, sharding. Nagu teate, jagame andmebaasi eraldi osadeks, et igaĂŒks neist saaks viia eraldi serverisse.

Meie sharding’i esimene variant vĂ”imaldas iga meie rakenduse tabeli eraldamise erinevatesse serveritesse erinevates proportsioonides. Palju sĂ”numeid serveris A – tulge, viime osa sellest tabelist serverisse B. Selline lahendus karjus varajase optimeerimise jĂ€rele, seega otsustasime piirduda multi-tenant lĂ€henemisega.

Multi-tenant’i kohta saab lugeda nĂ€iteks veebisaidilt Citus Data.

SV-s on rakenduse ja tellija mĂ”isted. 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 rakenduse registreerimine SV-serveris toimub. Tellijal vĂ”ib olla registreeritud mitmeid rakendusi, ja need rakendused saavad omavahel teateid vahetada. Tellija on meie sĂŒsteemis nĂŒĂŒd elanik (tenant). Mitme tellija teated 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 DB-serverisse).

Meil on peamine andmebaas, kus hoitakse marsruutimistabelit, mis sisaldab teavet kÔigi tellijate andmebaaside asukoha kohta.

Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast

Kuna peamine andmebaas ei tohiks olla kitsas koht, hoiame marsruutimistabelit (ja teisi sageli nÔutavaid andmeid) mÀlus.

Kui tellija andmebaas hakkab pidurdama, jagame selle osadeks. Teistes projektides kasutame suurte tabelite partitsioneerimiseks pg_pathman.

Kuna kasutajate sĂ”numite kaotamine on halb, hoiame oma andmebaase koopiatega. SĂŒnkroonsete ja asĂŒnkroonsete koopiate kombinatsioon vĂ”imaldab end kaitsta peamise andmebaasi kaotuse korral. SĂ”numi kaotus 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 lisaks muule on SV ka sĂ”numivahetus, vajatakse siin kiiret, mugavat ja paindlikku otsingut, arvestades morfoloogiat ja ebatĂ€pset vastavust. Otsustasime mitte ratast leiutada ja kasutame avatud allika otsingusĂŒsteemi Elasticsearch, mis on loodud Lucene raamatukogul. Lucene. Elasticsearchi kĂ€itame samuti klustis (master – data – data), et vĂ€ltida probleeme, kui rakenduse sĂ”lmed ebaĂ”nnestuvad.

Githubl leidsime vene morfoloogia plugina Elasticsearchi jaoks ja kasutame seda. Elasticsearchi indeksis hoiame sÔnade juuri (mida mÀÀratleb plugin) ja N-gramme. Kui kasutaja sisestab otsinguteksti, otsime sisestatud teksti N-grammide hulgast. Indeksisse salvestamisel jaguneb sÔna «tekstid» jÀrgnevateks N-grammideks:

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

Samuti salvestatakse sÔna «tekst» juur. Selline lÀhenemine vÔimaldab otsida nii sÔna alguses, keskel kui ka lÔpus.

Ülevaade

Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast
Pilt, mille kordame artikli alguses, kuid nĂŒĂŒd koos selgitustega:

  • Tasakaalustaja, mis on seatud internetti; meil on – nginx, see vĂ”ib olla mis tahes.
  • Java rakenduste instantsid suhtlevad omavahel lĂ€bi Hazelcasti.
  • Veebivoodete haldamiseks kasutame Netty.
  • Java rakendus on kirjutatud Java 8 keeles ja koosneb bĂ€ndlitest OSGi. Plaanis on migreerida Java 10 peale ja liikuda moodulite juurde.

Arendamine ja testimine

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

Koormustestimine ja mÀlu leke

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

  • Test töötas mitmeid ööpĂ€evi ja teeninduses ei olnud katkestusi
  • Peamiste toimingute vastuse aeg ei ĂŒletanud mugavat piiri
  • Tootlikuse halvenemine vĂ”rreldes eelmisel versiooniga ei ĂŒletanud 10%

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

Teeme sĂŒsteemi koormustestimist kolmes konfiguratsioonis:

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

Stressitestis kĂ€ivitame mitusada lĂ”ime, mis pidevalt koormavad sĂŒsteemi: saadavad sĂ”numeid, loovad arutelusid, saavad sĂ”numite nimekirja. Simuleerime tavaliste kasutajate (oma lugemata sĂ”numite nimekirja saamine, kellelegi kirjutamine) ja tarkvaralahenduste tegevust (paketi edastamine teise konfiguratsiooni, teate töötlemine).

NĂ€iteks selline on osa stressitestist:

  • Kasutaja siseneb sĂŒsteemi
    • KĂŒsimus oma lugemata arutelude kohta
    • 50% tĂ”enĂ€osusega loeb sĂ”numeid
    • 50% tĂ”enĂ€osusega kirjutab sĂ”numeid
    • Siiski kasutaja:
      • Loob uue arutelu 20% tĂ”enĂ€osusega
      • Valib juhuslikult mĂ”ne oma arutelu
      • Siseneb sisse
      • KĂŒsimus sĂ”numite, kasutajaprofiilide kohta
      • Loob viis sĂ”numit, mis on suunatud juhuslikele kasutajatele sellest arutelust
      • VĂ€ljub arutelust
      • Kordab 20 korda
      • Logib vĂ€lja, naaseb tagasi stsenaariumi algusesse

    • SĂŒsteemi siseneb vestlusrobot (emuleerib sĂ”numite vahetust rakenduste koodist)
      • Loob 50% tĂ”enĂ€osusega uue kanali andmevahetuseks (eriline arutelu)
      • Kirjutab 50% tĂ”enĂ€osusega sĂ”numi mis tahes olemasolevatesse kanalitesse

Stsenaarium „Ainult ĂŒhendused” ei ilmunud lihtsalt juhuslikult. On olukordi: kasutajad on sĂŒsteemi ĂŒhendatud, kuid pole veel sellesse sisse harjunud. Iga kasutaja kĂ€ivitab hommikul kell 09:00 arvuti, loob ĂŒhenduse serveriga ja vaikib. Need mehed on ohtlikud, neid on palju – paketides on vaid PING/PONG, kuid nad hoidke ĂŒhendust serveriga (nad ei saa mitte hoida – Ă€kki tuleb uus sĂ”num). Test korratakse olukorras, kus kahe tunni jooksul pĂŒĂŒab suure arv kasutajaid sĂŒsteemi autoriseerida. See sarnaneb stressitestiga, kuid keskendub just sellele esimesele sisenemisele – et poleks tĂ”rkeid (inimene ei kasuta sĂŒsteemi, kuid see juba kukub vĂ€lja – raske on vĂ€lja mĂ”elda midagi hullemat).

Tellijate registreerimise stsenaarium algab esimese kĂ€ivitamisega. Me tegime stressitesti ja olime kindlad, et kirjavahetuses sĂŒsteem ei tĂ”rgu. Kuid kasutajad hakkasid tulema ja registreerimine hakkas aeganĂ”udlikult vĂ€lja kukkuma. Registreerimisel kasutasime /dev/random, mis on seotud sĂŒsteemi entropiaga. Server ei jĂ”udnud koguda piisavalt entropiat ja uue SecureRandom'i pĂ€ringu juures hangus see kĂŒmneteks sekunditeks. Sellest olukorrast on palju vĂ€ljapÀÀse, nĂ€iteks: minna vĂ€hem turvaliselt /dev/urandom'i, paigaldada eraldi plaat, mis genereerib entropiat, genereerida juhuslikke numbreid eelnevalt ja hoida neid basseinides. Me sulgesime probleemi ajutiselt basseiniga, kuid alates sellest ajast teeme eraldi testi uute tellijate registreerimiseks.

Koormuse generaatorina kasutame JMeter. Töötada veebisoketiga ei oska, vajame plugina. Esimeste seas otsingutulemustes otsides „jmeter websocket” tulevad artiklid BlazeMeter, milles soovitatakse plugina Maciej Zaleski.

Seda me otsustasime alustada.

Peaaegu kohe pÀrast tÔsise testimise alustamist avastasime, et JMeteris on hakanud esinema mÀlulekkeid.

Plugin – see on eraldi suur lugu, 176 tĂ€hte on tal 132 fork’i githubis. Autor ei ole sellesse alates 2015. aastast kommenteerinud (me vĂ”tsime selle 2015. aastal, siis see ei tekitanud kahtlusi), mitmed githubi probleemid mĂ€lulekete osas, 7 lahendamata pull requesti.
Kui otsustate seda pluginit koormustestimiseks kasutada, siis pöörake tÀhelepanu jÀrgmistele aruteludele:

  1. Mitme lĂ”ime keskkonnas kasutati tavalist LinkedList’i, mille tulemusena saime NPE kĂ€itusaegsel. Probleem lahendatakse kas ĂŒleminekuga ConcurrentLinkedDeque’ile vĂ”i sĂŒnkroniseeritud plokkidega. Valisime enda jaoks esimese variandi (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. MĂ€luleke, ĂŒhenduse katkestamisel ei kustutata ĂŒhenduse teavet (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Streaming reĆŸiimis (kui websocket ei suletud lĂ”pliku nĂ€itamises, vaid seda kasutatakse edasi plaanis) ei tööta Response pattern’id (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

See on ĂŒks neist, mis on githubis. Mida me tegime:

  1. VĂ”tsime Elyran Kogani forki (@elyrank) – seal on lahendatud probleemid 1 ja 3
  2. Lahendasime probleemi 2
  3. Uuendasime jetty 9.2.14 versioonilt 9.3.12-le
  4. MĂ€hkime SimpleDateFormat’i ThreadLocal’isse; SimpleDateFormat ei ole lĂ”ime ohutu, mis pĂ”hjustas NPE kĂ€itusaegsel
  5. Selle mĂ€lulekke kĂ”rvaldasime (ĂŒhendus ei olnud katkestatud Ă”igesti)

Ja siiski, see lekib!

MĂ€lu hakkas otsa saama mitte pĂ€evaga, vaid kahega. Aega ei jÀÀnud ĂŒldse, otsustasime kĂ€ivitada vĂ€hem lĂ”ime, kuid neljal agendil. Seda peaks piisama vĂ€hemalt nĂ€dalaks.

Kaks pĂ€eva möödus


NĂŒĂŒd hakkas Hazelcast’il mĂ€lu otsa saama. Logides oli nĂ€ha, et paar pĂ€eva testimist jĂ€rel hakkab Hazelcast kurtma mĂ€lu puuduse ĂŒle, ja mĂ”ne aja pĂ€rast kaob klaster ja nodid jĂ€tkavad ĂŒksikute hukkumist. Ühendatud JVisualVM Hazelcast’iga ja nĂ€gime "ĂŒles jĂ”udvat sae" – see kutsus regulaarselt vĂ€lja GC, kuid ei suutnud kuidagi mĂ€lu puhastada.

Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast

Selgus, et hazelcast 3.4 versioonis ei vabastata mÀlumaalt tÀielikult: map / multiMap'i kustutamisel (map.destroy())

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

NĂŒĂŒd on viga parandatud 3.5 versioonis, kuid tol hetkel oli see probleem. Loodime uusi multiMap’e dĂŒnaamiliste nimedega ja kustutasime vastavalt meie loogikale. Kood nĂ€gi vĂ€lja umbes nii:

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

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

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

Kutsumine:

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

multiMap loodi iga tellimuse jaoks ja eemaldati, kui see ei olnud enam vajalik. Otsustasime, et loome Map ning vÔti on tellimuse nimi ja vÀÀrtused on seansi identifikaatorid (mille kaudu saab hiljem vajadusel kasutajate identifikaatoreid kÀtte).

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

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

Graafikud on korrigeeritud.

Kuidas ja miks me kirjutasime kÔrge koormusega skaleeritava teenuse 1C: EttevÔtte jaoks: Java, PostgreSQL, Hazelcast

Mida me veel Ôppisime koormustestimise kohta

  1. JSR223 tuleb kirjutada groovy's ning lisada koostamise vahemÀlu - see on oluliselt kiirem. Link.
  2. Jmeter-Plugins graafikud on lihtsamad mÔista kui standardsed. Link.

Meie kogemus Hazelcast'iga

Hazelcast oli meile uus toode, alustasime selle kasutamist versiooniga 3.4.1, praegu on meie tootmiserveris versioon 3.9.2 (artikli kirjutamise hetkel on viimane versioon Hazelcast 3.10).

ID genereerimine

Alustasime tĂ€isarvuliste identifikaatoritega. Kujutame ette, et me vajame uut Long uut tĂŒĂŒpi entiteedi jaoks. JĂ€rjekord andmebaasis ei sobi, tabelid osalevad jagamises - tuleb vĂ€lja, et andmebaasis 1 on sĂ”num ID=1 ja andmebaasis 2 on sĂ”num ID=1, Elasticsearch'is sellise ID-ga sa ei saa, Hazelcast'is samuti, aga kĂ”ige hullem on, kui soovid andmed kahest andmebaasist ĂŒheks koos viia (nt kui otsustad, et ĂŒks andmebaas piisab nende tellijate jaoks). Hazelcast'is saab luua mitu AtomicLong'i ja hoida loenduri seal, siis on uue ID saamise efektiivsus - incrementAndGet pluss aeg Hazelcast'isse pĂ€ringu tegemiseks. Kuid Hazelcast'is on midagi veel optimaalsemat - FlakeIdGenerator. Iga kliendi juurde, kui ta pĂ€rib, antakse ID-de vahemik, nĂ€iteks esimesena - 1 kuni 10 000, teise jaoks - 10 001 kuni 20 000 ja nii edasi. NĂŒĂŒd vĂ”ib klient ise uusi identifikaatoreid vĂ€ljastada, kuni tema antud vahemik saab otsa. See töötab kiiresti, kuid rakenduse (ja Hazelcast'i kliendi) taaskĂ€ivitamisel algab uus jĂ€rjestus - sealt tulenevad vaheajaks jÀÀvad ja jne. Lisaks ei ole arendajatele kuigi selge, miks ID-d on tĂ€isarvud, kuid nad on nii korralikult valdavalt segaduses. Me kaalusime kĂ”ik ĂŒle ja lĂ€ksime UUID-dele ĂŒle.

Üks asi, mis puudutab neid, kes tahavad olla nagu Twitter, on olemas selline teek, nagu Snowcast - see on Snowflake'i rakendus Hazelcast'i peal. Vaadata saab siit:

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

Aga me pole veel sinna 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();
        }
    });
}

Oodatud: newValue
Tegelik: oldValue

Pidime kirjutama oma replace'i, 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ÀÀrtusele tehingulises kaardis");
        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ÀÀrtusele mitte tehingulises kaardis");
    IMap map = hazelcastInstance.getMap(mapName);
    return map.replace(key, oldValue, newValue);
}

Testige mitte ainult tavalisi andmestruktuure, vaid ka nende tehingulisi versioone. Juhtub, et IMap töötab, aga TransactionalMap ei töötata.

Uue JAR'i allalaadimine ilma katkestuseta

Esialgu otsustasime salvestada Hazelcast'i oma klasside objekte. NĂ€iteks, meil on klass Application, me tahame selle salvestada ja lugeda. Salvestame:

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

Loeme:

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

KÔik töötab. Siis otsustasime luua Hazelcast'is indeksi, et selle kaudu otsida:

map.addIndex("subscriberId", false);

Ja kui salvestasime uue ĂŒksuse, hakkasime saama ClassNotFoundException'i. Hazelcast ĂŒritas indeksi tĂ€iendada, kuid ei teadnud meie klassist midagi ja tahtis, et me pakuksime selle klassi JAR'i. Me tegime seda ja kĂ”ik töötas, kuid tekkis uus probleem: kuidas uuendada JAR'i ilma klastrite tĂ€ieliku peatamiseta? Hazelcast ei vĂ”ta uut JAR'i vastu versioonivĂ€rskenduse ajal. Sel hetkel otsustasime, et saame elada ilma indeksi otsimiseta. LĂ”ppude lĂ”puks, kui kasutada Hazelcast'i vĂ”tme-vÀÀrtuse salvestusena, kas kĂ”ik toimib? Mitte pĂ€ris. Siin on jĂ€lle erinev kĂ€itumine IMap'i ja TransactionalMap'i vahel. Seal, kus IMap'il pole vahet, TransactionalMap viskab vea.

IMap. Salvestame 5000 objekti, loeme sisse. KÔik on 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 tehingutes 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 tĂ€ielikult lĂ€henemist muutnud: ise serializeerime JSON-iks ja salvestame Hazelcasti. Hazelcast ei pea teadma meie klasside struktuuri ning me saame vĂ€rskendusi teha ilma seiskamiseta. Domeeniobjektide versioonimist haldab rakendus. Erinevad rakenduse versioonid vĂ”ivad töötada samaaegselt, ja vĂ”ib esineda olukord, kus uus rakendus kirjutab objekte uute vĂ€ljadega, samas kui vana rakendus neid vĂ€lju ei tunne. Ja samal ajal loeb uus rakendus objekte, mille on kirjutanud vana rakendus, millel ei ole uusi vĂ€ljasid. Selliseid olukordi kĂ€sitleme rakenduse sees, kuid lihtsuse huvides ei muuda ega kustuta vĂ€lju, vaid laiendame klasse, lisades uusi vĂ€lju.

Kuidas me tagame kÔrge jÔudluse

Neli pĂ€ringut Hazelcastis – hea, kaks andmebaasis – halb

Andmete hankimine vahemĂ€lust on alati parem kui andmebaasist, kuid ei soovi ka hoida kasutamata kirjeid. Otsuse selle kohta, mida vahemĂ€lus hoida, lĂŒkkame arenduse viimasesse etappi. Kui uus funktsionaalsus on kodeeritud, aktiveerime PostgreSQL-is kĂ”igi pĂ€ringute logimise (log_min_duration_statement 0-le) ja viime lĂ€bi koormustestimise umbes 20 minutiks. Kogutud logide pĂ”hjal suudavad sellised utiliidid nagu pgFouine ja pgBadger koostada analĂŒĂŒtilisi raportite. Raportis otsime esmalt aeglaseid ja sagedasi pĂ€ringuid. Aeglaste pĂ€ringute puhul koostame teostusplaani (EXPLAIN) ja hindame, kas sellist pĂ€ringut on vĂ”imalik kiirendada. Sageli esinevad pĂ€ringud, mis tuginevad samadele sisenditele, sobivad hĂ€sti vahemĂ€lus hoidmiseks. PĂŒĂŒame hoida pĂ€ringud "tasapinnalised", kĂ€sitledes ĂŒhte tabelit ĂŒhes pĂ€ringus.

KĂ€itus

SV kui veebiteenus kÀivitus 2017. aasta kevadel ja kui eraldi toode ilmus SV novembris 2017 (tol hetkel beetaversioonina).

Ühe aasta jooksul ei ole SV veebiteenuse töös tĂ”siseid probleeme olnud. JĂ€lgime veebiteenust lĂ€bi Zabbix, kogume ja paigaldame lĂ€bi Bamboo.

SV serveri installatsioon pakutakse natiivsete paketina: RPM, DEB, MSI. Lisaks pakume Windowsile ĂŒhte installijat, mis on ĂŒhe EXE vormis, mis installeerib serveri, Hazelcasti ja Elasticsearchi ĂŒhele masinale. Alguses nimetasime seda installatsiooni "demonstreerivaks" versiooniks, kuid nĂŒĂŒd on selge, et see on kĂ”ige populaarsem juurutusvariant.

Allikas: habr.com

Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid đŸ”„ Osta usaldusvÀÀrne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster