NĂ« kĂ«tĂ« artikull do tĂ« flasim se si dhe pĂ«r çfarĂ« e zhvilluam â njĂ« mekanizĂ«m qĂ« transmeton informacionin midis aplikacioneve tĂ« klientit dhe serverĂ«ve tĂ« 1C:NdĂ«rmarrjeve â nga formulimi i detyrave deri te mendimi pĂ«r arkitekturĂ«n dhe detajet e zbatimit.
Sistemi i BashkĂ«punimit (mĂ« poshtĂ« â SB) Ă«shtĂ« njĂ« sistem i shpĂ«rndarĂ« i qĂ«ndrueshĂ«m ndaj dĂ«shtimeve pĂ«r shkĂ«mbimin e mesazheve me dorĂ«zim tĂ« garantuar. SB Ă«shtĂ« projektuar si njĂ« shĂ«rbim me ngarkesĂ« tĂ« lartĂ« me shkallĂ«zim tĂ« lartĂ«, i disponueshĂ«m si njĂ« shĂ«rbim online (ofrohet nga firma 1C) dhe si njĂ« produkt i licencuar qĂ« mund tĂ« implementohet nĂ« kapacitetet tuaja serverike.
SB përdor një depo të shpërndarë dhe një sistem kërkimi . Do të flasim gjithashtu për Java-n dhe se si ne e shkallëzojmë horizontalisht PostgreSQL.
Vendosja e detyrës
Për të kuptuar pse e bëmë Sistemin e Bashkëpunimit, do të flas pak për mënyrën se si zhvillohet aplikacioni biznesor në 1C.
PĂ«r tĂ« filluar â pak pĂ«r ne pĂ«r ata qĂ« ende nuk e dinĂ« se çfarĂ« bĂ«jmĂ« :) Ne krijojmĂ« platformĂ«n teknologjike "1C:Enterprise". Platforma pĂ«rfshin mjetin pĂ«r zhvillimin e aplikacioneve biznesore, si dhe runtime-in qĂ« lejon aplikacionet e biznesit tĂ« funksionojnĂ« nĂ« njĂ« ambient tĂ« shumĂ«-platformĂ«.
Paradigma e zhvillimit klient-server
Aplikacionet e biznesit tĂ« krijuara nĂ« "1C:Enterprise" funksionojnĂ« nĂ« njĂ« arkitekturĂ« tre-niveleshe nĂ« arkitekturĂ«n "DBMS â serveri i aplikacioneve â klienti". Kodi aplikativ, i shkruar nĂ« , mund tĂ« ekzekutohet nĂ« serverin e aplikacioneve ose nĂ« klient. TĂ« gjitha operacionet me objektet aplikative (referenca, dokumente etj.), si dhe leximi dhe shkrimi i bazĂ«s sĂ« tĂ« dhĂ«nave, kryhen vetĂ«m nĂ« server. Funksionaliteti i formave dhe ndĂ«rfaqes sĂ« komandave gjithashtu realizohet nĂ« server. NĂ« klient realizohet marrja, hapja dhe tregimi i formave, "komunikimi" me pĂ«rdoruesin (paralajmĂ«rime, pyetjeâŠ), llogaritje tĂ« vogla nĂ« forma qĂ« kĂ«rkojnĂ« njĂ« reagim tĂ« shpejtĂ« (p.sh., shumĂ«zimi i çmimit me sasionin), punĂ« me skedarĂ«t lokalĂ«, punĂ« me pajisjet.
NĂ« kodin aplikativ, nĂ« titujt e procedurave dhe funksioneve duhet tĂ« tregohet qartĂ« se ku do tĂ« ekzekutohet kodi â pĂ«rmes drejtimeve &NĂ«Klient / &NĂ«Server (&AtClient / &AtServer nĂ« variantin anglisht tĂ« gjuhĂ«s). TĂ« zhvilluesit nĂ« 1C tani do tĂ« mĂ« korrigjojnĂ« duke thĂ«nĂ« se nĂ« tĂ« vĂ«rtetĂ« drejtoritĂ« janĂ« , por pĂ«r ne kjo aktualisht nuk Ă«shtĂ« e rĂ«ndĂ«sishme.
Nga kodi i klientit mund tĂ« thirret kodi i serverit, ndĂ«rsa nga kodi i serverit nuk mund tĂ« thirret kodi i klientit. Kjo Ă«shtĂ« njĂ« kufizim themelor, qĂ« Ă«shtĂ« vendosur nga ne pĂ«r disa arsye. Sidomos sepse kodi i serverit duhet tĂ« shkruhet nĂ« mĂ«nyrĂ« qĂ« tĂ« ekzekutohet njĂ«soj, pavarĂ«sisht se nga ku thirret â nga klienti apo nga serveri. Dhe nĂ« rastin e thirrjes sĂ« kodit tĂ« serverit nga njĂ« kod tjetĂ«r serveri, klienti nuk ekziston si i tillĂ«. Edhe pĂ«r shkak se gjatĂ« ekzekutimit tĂ« kodit tĂ« serverit, klienti qĂ« e thirri atĂ« mund tĂ« ketĂ« mbyllur aplikacionin, dhe serveri nuk do tĂ« ketĂ« askĂ«nd pĂ«r tĂ« thirrur mĂ«.
Kodi qĂ« pĂ«rpunon shtypjen e butonit: thirrja e procedurĂ«s sĂ« serverit nga klienti do tĂ« funksionojĂ«, thirrja e procedurĂ«s sĂ« klientit nga serveri â jo
Kjo do të thotë se nëse duam të dërgojmë një mesazh nga serveri në aplikacionin e klientit, për shembull, se formimi i raporteve "me gjatësi të gjatë" ka përfunduar dhe raporti mund të shikohet - ne nuk kemi një mënyrë të tillë. Duhet të shkojmë në masa të tjera, për shembull, nga kodi i klientit të bëjmë periudhërish pyetje në server. Por një qasje e tillë ngarkon sistemin me thirrje të panevojshme, dhe gjithashtu nuk duket shumë elegante.
Dhe gjithashtu ka nevojë, për shembull, për një telefonatë SIP -të njoftojmë aplikacionin e klientit që është bërë një telefonatë, për të gjetur sipas numrit të telefonit atë në bazën e të dhënave të partnerëve dhe për të treguar përdoruesit informacionin mbi partnerin që po telefonon. Ose, për shembull, kur një porosi arrin në magazinë, për të njoftuar aplikacionin e klientit të porositësit. Në përgjithësi, ka shumë raste ku një mekanizëm i tillë do të ishte i dobishëm.
NĂ« fakt, pohoj
Krijoni një mekanizëm shkëmbimi mesazhe. Të shpejtë, të besueshëm, me garantimin e dorëzimit, me mundësi për kërkimin fleksibël të mesazheve. Në bazë të mekanizmit, realizoni një mesazher (mesazhe, thirrje video) që funksionon brenda aplikacioneve 1C.
Të projektojmë një sistem horizontalisht të shkallëzueshëm. Rritja e shkarkimit duhet të menaxhohet duke rritur numrin e nodave.
Realizimi
Ne vendosĂ«m qĂ« pjesĂ«n serverike tĂ« SV tĂ« mos e integrojmĂ« drejtpĂ«rdrejt nĂ« platformĂ«n 1C:Enterprise, por ta zbatojmĂ« si njĂ« produkt tĂ« veçantĂ«, API-ja e tĂ« cilit mund tĂ« thirret nga kodi i zgjidhjeve aplikative 1C. Kjo u bĂ« pĂ«r njĂ« sĂ«rĂ« arsyesh, e para dhe kryesorja Ă«shtĂ« se donim tĂ« bĂ«nim tĂ« mundur shkĂ«mbimin e mesazheve midis aplikacioneve tĂ« ndryshme 1C (pĂ«r shembull, midis Menaxhimit tĂ« TregtisĂ« dhe Kontabilitetit). Aplikacione tĂ« ndryshme 1C mund tĂ« punojnĂ« nĂ« versione tĂ« ndryshme tĂ« platformĂ«s 1C:Enterprise, tĂ« ndodhen nĂ« servera tĂ« ndryshĂ«m, etj. NĂ« kĂ«to kushte, realizimi i SV si njĂ« produkt i veçantĂ«, qĂ« Ă«shtĂ« "nĂ« anĂ«" tĂ« instalimeve 1C â Ă«shtĂ« zgjidhja optimale.
Prandaj, kemi vendosur tĂ« zhvillojmĂ« SV si njĂ« produkt tĂ« veçantĂ«. PĂ«r kompanitĂ« e vogla, rekomandojmĂ« pĂ«rdorimin e serverit SV, i cili Ă«shtĂ« instaluar nĂ« re (wss://1cdialog.com), pĂ«r tĂ« shmangur kostot e lidhura me instalimin dhe konfigurimin lokal tĂ« serverit. KlientĂ«t mĂ« tĂ« mĂ«dhenj mund tĂ« konsiderojnĂ« si njĂ« mundĂ«si instalimin e serverit tĂ« tyre SV nĂ« kapacitetin e tyre. NjĂ« qasje tĂ« ngjashme kemi pĂ«rdorur nĂ« produktin tonĂ« SaaS nĂ« re. â ai emetohet si njĂ« produkt qĂ« lĂ«shohet pĂ«r instalim te klientĂ«t, dhe gjithashtu Ă«shtĂ« i zhvilluar nĂ« re .
Aplikacioni
PĂ«r tĂ« shpĂ«rndarĂ« ngarkesĂ«n dhe pĂ«r tĂ« siguruar qĂ«ndrueshmĂ«ri, do tĂ« zhvillojmĂ« jo njĂ« aplikacion Java, por disa, duke vendosur njĂ« balancues ngarkese pĂ«rpara tyre. NĂ«se duhet tĂ« kalojmĂ« njĂ« mesazh nga njĂ« nod nĂ« njĂ« tjetĂ«r â do tĂ« pĂ«rdorim publish/subscribe nĂ« Hazelcast.
Biseda mes klientit dhe serverit â pĂ«rmes websocket. Ai Ă«shtĂ« i pĂ«rshtatshĂ«m pĂ«r sistemet nĂ« kohĂ« reale.
Cache i shpërndarë
I zgjedhëm midis Redis, Hazelcast dhe Ehcache. Jemi në vitin 2015. Redis sapo kishte lëshuar një klaster të ri (shumë i ri, frikshëm), ka Sentinel me shumë kufizime. Ehcache nuk mund të grumbullohet në klaster (kjo funksionalitet ka ardhur më vonë). Vendosëm të provojmë me Hazelcast 3.4.
Hazelcast grumbullohet nĂ« klaster "nga kutia". NĂ« mĂ«nyrĂ«n e njĂ« node, ai nuk Ă«shtĂ« shumĂ« i dobishĂ«m dhe mund tĂ« pĂ«rdoret vetĂ«m si njĂ« memorie â nuk mund tĂ« ruajĂ« tĂ« dhĂ«nat nĂ« disk, humbĂ«t noden e vetme â humbĂ«t tĂ« dhĂ«nat. Ne vendosim disa Hazelcast, midis tĂ« cilĂ«ve e backupojmĂ« tĂ« dhĂ«nat kritike. Memoria nuk e backupojmĂ« â nuk Ă«shtĂ« ndonjĂ« gjĂ« pĂ«r tĂ« qenĂ« e shqetĂ«suar.
Për ne, Hazelcast është:
- NjĂ« depo e sesioneve tĂ« pĂ«rdoruesve. Ădo herĂ« qĂ« shkojnĂ« pĂ«r sesionin nĂ« bazĂ«n e tĂ« dhĂ«nave â zgjat shumĂ«, prandaj tĂ« gjitha sesionet i vendosim nĂ« Hazelcast.
- Memoria. Kur kĂ«rkon profilin e pĂ«rdoruesit â kontrollo nĂ« memorie. Shkrove njĂ« mesazh tĂ« ri â vendose nĂ« memorie.
- Temat për komunikimin e instancave të aplikacionit. Noda gjeneron një ngjarje dhe e vendos atë në temën e Hazelcast. Nodat e tjera të aplikacionit, të regjistruar në këtë temë, marrin dhe përpunojnë ngjarjen.
- Bllokimet në klaster. Për shembull, krijojmë një diskutim sipas një çelësi unik (diskutimi-singelton në kuadër të bazës 1C):
conversationKeyChecker.check("Benzokollonka");
doInClusterLock("Benzokollonka", () -> {
conversationKeyChecker.check("Benzokollonka");
createChannel("Benzokollonka");
});Verifikuam se kanali s'ka. MerrĂ«m bllokimin, kontrolluam pĂ«rsĂ«ri, krijuam. NĂ«se pas marrjes sĂ« bllokimit nuk kontrollojmĂ«, ka njĂ« mundĂ«si qĂ« njĂ« thread tjetĂ«r tĂ« ketĂ« kontrolluar nĂ« kĂ«tĂ« moment dhe tani pĂ«rpiqet tĂ« krijojĂ« tĂ« njĂ«jtin diskutim â dhe ai tashmĂ« ekziston. Nuk mund tĂ« bĂ«jmĂ« bllokim pĂ«rmes synchronized ose bllokimit tĂ« zakonshĂ«m tĂ« java-s. NĂ«pĂ«rmjet bazĂ«s â e ngadalshme, dhe gjithashtu baza Ă«shtĂ« e çmuar, pĂ«rmes Hazelcast â kjo Ă«shtĂ« pikĂ«risht ajo qĂ« na nevojitet.
Zgjidhni DBMS
Ne kemi një përvojë të madhe dhe të suksesshme në punën me PostgreSQL dhe bashkëpunimin me zhvilluesit e kësaj DBMS.
Me klasterin PostgreSQL nuk Ă«shtĂ« e thjeshtĂ« â ka , , , por, nĂ« pĂ«rgjithĂ«si, kjo nuk Ă«shtĂ« noSQL, tĂ« cilat shkallĂ«zohen qĂ« nga kutia. Nuk e kemi shqyrtuar NoSQL si njĂ« magazinĂ« kryesore, ishte mjaft e mjaftueshme qĂ« marrim Hazelcast, me tĂ« cilin nuk kishim punuar mĂ« parĂ«.
NĂ«se duhet tĂ« shkallĂ«zojmĂ« njĂ« DB relacional â do tĂ« thotĂ«, . Siç dini, gjatĂ« sharding ne ndajmĂ« bazĂ«n e tĂ« dhĂ«nave nĂ« pjesĂ« tĂ« veçanta nĂ« mĂ«nyrĂ« qĂ« secila prej tyre tĂ« mund tĂ« transportohet nĂ« njĂ« server tĂ« veçantĂ«.
Versioni i parĂ« i sharding tonĂ« parashikonte mundĂ«sinĂ« pĂ«r tĂ« shpĂ«rndarĂ« secilĂ«n nga tabelat e aplikacionit tonĂ« nĂ« serverĂ« tĂ« ndryshĂ«m nĂ« proporcione tĂ« ndryshme. ShumĂ« mesazhe nĂ« serverin A â ju lutemi, le tĂ« kalojmĂ« njĂ« pjesĂ« tĂ« kĂ«saj tabele nĂ« serverin B. Ky vendim thjesht kĂ«rkonte optimizim tĂ« parakohshĂ«m, kĂ«shtu qĂ« vendosĂ«m tĂ« kufizoheshim nĂ« qasjen multi-tenant.
Mund të lexoni për multi-tenant në faqe si .
NĂ« SV ka konceptet e aplikacionit dhe abonentit. Aplikacioni Ă«shtĂ« njĂ« instalim konkret i njĂ« aplikacioni biznesi, pĂ«r shembull, ERP ose Kontabiliteti, me pĂ«rdoruesit dhe tĂ« dhĂ«nat e tij tĂ« biznesit. Abonent Ă«shtĂ« organizata ose individi pĂ«r emrin e tĂ« cilit regjistrohet aplikacioni nĂ« serverin SV. Abonenti mund tĂ« ketĂ« regjistruar disa aplikacione, dhe kĂ«to aplikacione mund tĂ« shkĂ«mbejnĂ« mesazhe midis tyre. Abonenti u bĂ« kĂ«shtu njĂ« banor (tenant) nĂ« sistemin tonĂ«. Mesazhet e disa abonentĂ«ve mund tĂ« qĂ«ndrojnĂ« nĂ« njĂ« bazĂ« tĂ« dhĂ«nash fizike; nĂ«se vĂ«rejmĂ« qĂ« ndonjĂ« abonent po gjeneron shumĂ« trafik â e transferojmĂ« nĂ« njĂ« bazĂ« tĂ« dhĂ«nash fizike tĂ« veçantĂ« (apo madje nĂ« njĂ« server tĂ« veçantĂ« DB).
Kemi një bazë të dhënash kryesore, ku ruhet një tabelë e rrugës me informacion mbi lokacionin e të gjitha bazave të të dhënave abonent.
Për të mos qenë pika e ngushtë, ne e mbajmë tabelën e rrugës (dhe të dhënat e tjera që kërkohen shpesh) në cache.
Nëse baza e të dhënave të abonentëve fillon të ngadalësohet, do të ndjekim ndarjen në pjesë. Në projekte të tjera për ndarjen e tabelave të mëdha përdorim .
Për shkak se humbja e mesazheve të përdoruesit është e keqe, ne mbajmë bazat tona të dhënash me replika. Kombinimi i replikave sinchronike dhe asyncronike na lejon të mbrohemi në rast të humbjes së bazës kryesore të të dhënave. Humbja e një mesazhi do të ndodhë vetëm në rastin e dështimit të papritur të bazës kryesore të të dhënave dhe replikës së saj sinchronike.
NĂ«se humbet replica sinchronike â replica asyncronike bĂ«het sinchronike.
NĂ«se humbet baza kryesore e tĂ« dhĂ«nave â replica sinchronike bĂ«het baza kryesore e tĂ« dhĂ«nave, dhe replica asyncronike bĂ«het replica sinchronike.
Elasticsearch për kërkimin
PĂ«rveç kĂ«saj, SV Ă«shtĂ« edhe njĂ« mesengjer, kĂ«shtu qĂ« ne kemi nevojĂ« pĂ«r njĂ« kĂ«rkim tĂ« shpejtĂ«, tĂ« pĂ«rshtatshĂ«m dhe fleksibĂ«l, duke marrĂ« parasysh morfologjinĂ«, pĂ«r pĂ«rputhjet e pasakta. Ne vendosĂ«m tĂ« mos shpikim rrotĂ«n dhe tĂ« pĂ«rdorim sistemin e kĂ«rkimit tĂ« lirĂ« Elasticsearch, i ndĂ«rtuar mbi bibliotekĂ«n . Elasticsearch ne gjithashtu e zhvillojmĂ« nĂ« njĂ« klaster (master â data â data), pĂ«r tĂ« eliminuar problemet nĂ« rast se ndonjĂ« nyje e aplikacionit dĂ«shton.
Në github gjetëm për Elasticsearch dhe e përdorim atë. Në indeksin Elasticsearch ne ruajmë rrënjët e fjalëve (të cilat i përcakton plugin) dhe N-gramet. Ndërsa përdoruesi shkruan tekstin për kërkim, ne kërkojmë tekstin e shkruar mes N-gramëve. Kur ruhet në indeks, fjala "tekste" do të ndahet në N-gramet e mëposhtme:
[te, tek, tekst, tekste, tekstet, ek, eks, ekst, ekstet, ks, kst, ksty, st, sty, ty],
Dhe gjithashtu do të ruhet rrënja e fjalës "tekst". Ky qasje lejon kërkimin dhe në fillim, dhe në mes, dhe në fund të fjalës.
Pamja e përgjithshme
Ritja e figurës nga fillimi i artikullit, por tashmë me shpjegime:
- Balancuesi, i vendosur nĂ« internet; kemi â nginx, mund tĂ« jetĂ« çfarĂ«do.
- Instancat e aplikacionit Java komunikojnë midis tyre përmes Hazelcast.
- Për punën me websockets përdorim .
- Aplikacioni Java është shkruar në Java 8, përbëhet nga bunde . Në planet është migrimi në Java 10 dhe kalimi në module.
Zhvillimi dhe testimi
Gjatë zhvillimit dhe testimit të SV, hasëm disa karakteristika interesante të produkteve që po përdornim.
Testimi i ngarkesës dhe rrjedhjet e memories
Ădo lĂ«shim i SV Ă«shtĂ« testim ngarkese. Ai kalon me sukses kur:
- Testi ka punuar disa ditë dhe nuk ka pasur ndonjë dështim në shërbim.
- Koha e përgjigjes për operacionet kryesore nuk e kaloi pragun e rehatshëm.
- Përkeqësimi i performancës krahasuar me versionin e mëparshëm nuk është më shumë se 10%.
Baza e testimit mbushet me të dhëna - për këtë marrim nga serveri prodhues informacionin për abonentin më aktiv, e shumzojmë numrin e tij me 5 (numerin e mesazheve, diskutimeve, përdoruesve) dhe kështu e testojmë.
Testimi i ngarkesës së sistemit të ndërveprimit e kryejmë në tri konfiguracione:
- Testi i stresit
- Vetëm lidhjet
- Regjistrimi i abonentëve
Në testin e stresit, ne nisi disa qindra prekje, dhe ato vazhdojnë vazhdimisht të ngarkojnë sistemin: shkruajnë mesazhe, krijojnë diskutime, marrin listën e mesazheve. Imitojmë veprimet e përdoruesve të zakonshëm (të marrin listën e mesazheve të papërmendura, të shkruajnë dikujt) dhe zgjidhjeve programore (të kalojnë një paketë me konfigurim tjetër, të përpunojnë një njoftim).
Për shembull, kështu duket një pjesë e testit të stresit:
- Një përdorues hyn në sistem
- Kërkon diskutimet e tij të papërmendura
- Me 50% mundësi lexon mesazhet
- Me 50% mundësi shkruan mesazhe
- Më pas përdoruesi:
- Me 20% mundësi krijon një diskutim të ri
- Zgjidh rastësisht një nga diskutimet e tij
- Hyn brenda
- Kërkon mesazhet, profilet e përdoruesve
- Krijon pesë mesazhe, të adresuara për përdoruesit rastësisht nga ky diskutim
- Dale nga diskutimi
- E përsërit 20 herë
- Dale nga sistemi, kthehet pas në fillim të skenarit
- Një chat-bot hyn në sistem (emulon shkëmbimin e mesazheve nga kodi i zgjidhjeve aplikative)
- Me 50% mundësi krijon një kanal të ri për shkëmbim të dhënash (diskutim special)
- Me një 50% mundësi dërgon mesazh në ndonjë nga kanalet ekzistuese
Skema "VetĂ«m lidhjet" nuk ka ndodhur rastĂ«sisht. Ka raste kur pĂ«rdoruesit janĂ« lidhur me sistemin, por ende nuk janĂ« angazhuar. Ădo pĂ«rdorues nĂ« mĂ«ngjes nĂ« ora 09:00 ndez kompjuterin, lidh me serverin dhe mllet. KĂ«ta djem janĂ« tĂ« rrezikshĂ«m, janĂ« shumĂ« â nga paketat e tyre kanĂ« vetĂ«m PING/PONG, por e mbajnĂ« lidhjen me serverin (nuk mund ta lirojnĂ« â ndoshta ka njĂ« mesazh tĂ« ri). Testi riprodhon situatĂ«n kur njĂ« numĂ«r i madh i tillĂ« pĂ«rdoruesish pĂ«rpiqen tĂ« autentikohen nĂ« sistem brenda gjysmĂ« ore. Ai duket si njĂ« stres-test, por fokusi i tij Ă«shtĂ« saktĂ«sisht nĂ« kĂ«tĂ« hyrje tĂ« parĂ« â qĂ« tĂ« mos ketĂ« dĂ«shtime (nĂ«se njeriu nuk e pĂ«rdor sistemin, por ai tashmĂ« dĂ«shton â Ă«shtĂ« e vĂ«shtirĂ« tĂ« imagjinosh diçka mĂ« keq).
Skema e regjistrimit të abonentëve fillon nga nisja e parë. Ne zhvilluam një stres-test dhe ishim të sigurt se sistemi nuk ngadalësonte në bisedat. Por përdoruesit e panë dhe regjistrimi filloi të dështonin për shkak të kohës së pritjes. Gjatë regjistrimit përdorëm , i cili është lidhur me entropinë e sistemit. Serveri nuk arrinte të akumulojë mjaftueshëm entropi dhe kur kërkohej një SecureRandom të ri, ngelonte për disa dhjetëra sekonda. Ka shumë mënyra për të zgjidhur këtë situatë, për shembull: të kalojmë në një version më pak të sigurt /dev/urandom, të vendosim një kartë të veçantë që krijon entropi, të gjenerojmë numra rastësorë përpara dhe t'i ruajmë në një rezervuar. Ne përkohësisht e zgjidhëm problemin me një rezervuar, por që atëherë vazhdojmë të bëjmë një test të veçantë për regjistrimin e abonentëve të rinj.
Si gjenerator ngarkese përdorim . Ai nuk di të punojë me websocket, ne na duhet një plug-in. Artikujt e para që shfaqen në rezultatet e kërkimit për "jmeter websocket" janë , në të cilat rekomandojnë .
. Nga ai vendosëm të fillojmë.
Pothuajse menjëherë pas fillimit të testimeve serioze, zbuluam se në JMeter kishin filluar rrjedhjet e memories.
Plug-in-i është një histori e veçantë e madhe, me 176 yje dhe 132 fork në github. Autori vetë nuk ka bërë komitete në të që nga viti 2015 (ne e morëm atë në vitin 2015, atëherë kjo nuk ngjalli dyshime), disa probleme github në lidhje me rrjedhjet e memories, 7 kërkesa të hapura për tërheqje.
Nëse vendosni të kryeni teste ngarkese duke përdorur këtë shtesë, kushtoni vëmendje diskutimeve të mëposhtme:
- NĂ« njĂ« ambient shumĂ«prong, u pĂ«rdor njĂ« LinkedList i zakonshĂ«m, dhe rezultati ishte nĂ« kohĂ«n e ekzekutimit. ĂĂ«shtja zgjidhet ose duke kaluar nĂ« ConcurrentLinkedDeque, ose duke pĂ«rdorur blloqe tĂ« sinkronizuara. Ne zgjodhĂ«m variantin e parĂ« ().
- Shkarkim memorje, informacioni mbi lidhjen nuk fshihet gjatë shkëputjes ().
- Në mënyrën streaming (kur websoketi nuk mbyllet në fund të mostrës, por përdoret më tej në plan), pattern-et e përgjigjes nuk funksionojnë ().
Kjo është nga ato që janë në github. Ajo që bëmë:
- Mora (@elyrank) â nĂ« tĂ« janĂ« korrigjuar problemet 1 dhe 3
- Zgjidhëm problemin 2
- Përditësuam jetty nga 9.2.14 në 9.3.12
- Mbyllëm SimpleDateFormat në ThreadLocal; SimpleDateFormat nuk është e sigurt për shumëprong, gjë që çonte në NPE në kohën e ekzekutimit
- Eliminua një shkarkim tjetër memorjeje (lidhja nuk u mbyll siç duhet gjatë shkëputjes)
Megjithatë, ai vazhdon të rrjedhë!
Memoria përfundoi jo për një ditë, por për dy. Koha nuk mbeti fare, vendosëm të aktivizojmë më pak pronga, por në katër agjentë. Kjo duhet të mjaftonte, së paku, për një javë.
Kaluan dy ditĂ«âŠ
Tani kujtesa po mbaron nĂ« Hazelcast. NĂ« logje u pa se pas disa ditĂ«sh testimi, Hazelcast fillon tĂ« ankohesh pĂ«r mungesĂ« kujtese, dhe pas njĂ« kohe, klasteri shemben dhe nodet vazhdojnĂ« tĂ« vdesin njĂ« nga njĂ«. Ne e lidhĂ«m JVisualVM me hazelcast dhe pamĂ« njĂ« "nose saw" â ai rregullisht thĂ«rriste GC, por nuk mund tĂ« pastronte kujtesĂ«n.
Doli se në hazelcast 3.4 kur fshihej map / multiMap (map.destroy()), kujtesa nuk lirohej plotësisht:
Tani problemi është zgjidhur në 3.5, por atëherë ishte një çështje. Ne krijonim multiMap të reja me emra dinamikë dhe i fshinim sipas logjikës tonë. Kodi dukej më pak si:
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();
}
}Thirrja:
service.join(auth1, "NJOFTIMET_E_REJA_NĂ_DISKUTIM_UUID1");
service.join(auth2, "NJOFTIMET_E_REJA_NĂ_DISKUTIM_UUID1");multiMap u krijua pĂ«r çdo abonim dhe u fshihet kur nuk ishte mĂ« i nevojshĂ«m. VendosĂ«m qĂ« tĂ« krijojmĂ« Map<String,Set>, ku çelĂ«si do tĂ« ishte emri i abonimit dhe si vlera do ishin identifikatorĂ«t e sesioneve (nga tĂ« cilĂ«t mĂ« pas mund tĂ« merret identifikuesi i pĂ«rdoruesve, nĂ«se Ă«shtĂ« e nevojshme).
public void join(Authentication auth, String sub) {
addValueToMap(sub, auth.getSessionId());
}
public void leave(Authentication auth, String sub) {
removeValueFromMap(sub, auth.getSessionId());
}Grafikët u stabilizuan.
ĂfarĂ« tjetĂ«r mĂ«suam rreth testimit tĂ« ngarkesĂ«s
- JSR223 duhet tĂ« shkruhet nĂ« groovy dhe tĂ« pĂ«rfshijĂ« kompilimin e caches â kjo Ă«shtĂ« ndjeshĂ«m mĂ« e shpejtĂ«. .
- Grafikët e Jmeter-Plugins janë më të lehtë për t'u kuptuar se standartët. .
Për përvojën tonë me Hazelcast
Hazelcast për ne ishte një produkt i ri, filluam të punojmë me të që nga versioni 3.4.1, tani në serverin tonë të prodhimit kemi versionin 3.9.2 ( në momentin e shkruarjes së artikullit, versioni më i fundit i Hazelcast ishte 3.10).
Gjenerimi i ID-së
Ne kemi filluar me identifikues tĂ« plotĂ«. Le tĂ« imagjinojmĂ« se na nevojitet njĂ« Long pĂ«r njĂ« entitet tĂ« ri. Sekuenca nĂ« DB nuk funksionon, tabelat pĂ«rfshihen nĂ« sharding â do tĂ« ndodhĂ« qĂ« ka njĂ« mesazh ID=1 nĂ« DB1 dhe njĂ« mesazh ID=1 nĂ« DB2, nĂ« Elasticsearch nuk mund ta vendosĂ«sh njĂ« ID tĂ« tillĂ«, as nĂ« Hazelcast, por mĂ« e keqja Ă«shtĂ« nĂ«se dĂ«shiron tĂ« pĂ«rmbledhĂ«sh tĂ« dhĂ«nat nga dy DB nĂ« njĂ« (pĂ«r shembull, duke vendosur qĂ« njĂ« bazĂ« Ă«shtĂ« e mjaftueshme pĂ«r kĂ«ta abonentĂ«). Mund tĂ« krijosh disa AtomicLong nĂ« Hazelcast dhe tĂ« mbash numĂ«ruesin atje, kĂ«shtu qĂ« performanca e marrjes sĂ« njĂ« ID tĂ« re Ă«shtĂ« â incrementAndGet plus koha pĂ«r njĂ« kĂ«rkesĂ« nĂ« Hazelcast. Por nĂ« Hazelcast ka diçka mĂ« optimizuese â FlakeIdGenerator. Ădo klient, kur kĂ«rkon, merr njĂ« interval ID, pĂ«r shembull, tĂ« parit â nga 1 deri nĂ« 10,000, tĂ« dytit â nga 10,001 deri nĂ« 20,000 dhe kĂ«shtu me radhĂ«. Tani klienti mund tĂ« japĂ« identifikues tĂ« rinj vetĂ«, derisa tĂ« pĂ«rfundojĂ« intervali i dhĂ«nĂ«. Funksionon shpejt, por kur aplikacioni (dhe klienti Hazelcast) rinis, fillon njĂ« sekuencĂ« e re â nga kĂ«tu vijnĂ« humbjet etj. PĂ«r mĂ« tepĂ«r, zhvilluesve nuk u Ă«shtĂ« shumĂ« e qartĂ« pse ID-tĂ« janĂ« tĂ« plotĂ«, por shkojnĂ« kaq shumĂ« nĂ« ngacmime. TĂ« gjithĂ« e vlerĂ«suam dhe kaluam nĂ« UUID.
Mënyra, për ata që duan të jenë si Twitter, ekziston një bibliotekë e tillë e quajtur Snowcast - kjo është një implementim i Snowflake mbi Hazelcast. Mund ta shihni këtu:
Por ne nuk arritëm ta arrijmë atë.
TransactionalMap.replace
Një surprizë tjetër: TransactionalMap.replace nuk funksionon. Ja një test i tillë:
@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();
}
});
}
Pritet: newValue
Aktual: oldValueMë duhej të shkruaja një replace të vetin, duke përdorur getForUpdate:
protected boolean replaceInMap(String mapName, K key, V oldValue, V newValue) {
TransactionalTaskContext context = HazelcastTransactionContextHolder.getContext();
if (context != null) {
log.trace("[CACHE] Po zëvendësojmë vlerën në një hartë tranzaksionale");
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] Po zëvendësojmë vlerën në një hartë që nuk është tranzaksionale");
IMap map = hazelcastInstance.getMap(mapName);
return map.replace(key, oldValue, newValue);
}Testoni jo vetëm strukturat e zakonshme të të dhënave, por edhe versionet e tyre transaksionale. Ndonjëherë, IMap funksionon, por TransactionalMap nuk e bën atë.
Shtoni një JAR të ri pa ndalim shërbimi.
Fillimisht vendosëm të regjistronim në Hazelcast objekte të klasave tona. Për shembull, kemi një klasë Application, duam ta ruajmë dhe ta lexojmë. E ruajmë:
IMap map = hazelcastInstance.getMap("application");
map.set(id, application);E lexojmë:
IMap map = hazelcastInstance.getMap("application");
return map.get(id);Të gjitha funksionon. Më pas vendosëm të ndërtojmë një indeks në Hazelcast, për të kërkuar mbi të:
map.addIndex("subscriberId", false);Dhe kur regjistron njĂ« ent tĂ« ri, filluam tĂ« merrnim ClassNotFoundException. Hazelcast po pĂ«rpiqej tĂ« plotĂ«sonte indeksin, por asgjĂ« s'kishte dĂ«gjuar pĂ«r klasĂ«n tonĂ« dhe dĂ«shironte njĂ« JAR me kĂ«tĂ« klasĂ«. KĂ«shtu e bĂ«mĂ«, gjithçka funksionoi, por u shfaq njĂ« problem i ri: si ta pĂ«rditĂ«sojmĂ« JAR-in pa e ndalur plotĂ«sisht klasterin? Hazelcast nuk e pĂ«rfshin JAR-in e ri gjatĂ« pĂ«rditĂ«simit tĂ« nodit. NĂ« kĂ«tĂ« ĐŒĐŸĐŒĐ”ĐœŃ e vendosĂ«m se mund tĂ« jetonim pa kĂ«rkimin nĂ« indeks. NĂ« fund tĂ« fundit, nĂ«se pĂ«rdorim Hazelcast si njĂ« depo tĂ« tipit çelĂ«s-vlerĂ«, a do tĂ« funksiononte gjithçka? Jo krejtĂ«sisht. KĂ«tu pĂ«rsĂ«ri ka njĂ« sjellje tĂ« ndryshme midis IMap dhe TransactionalMap. Atje ku IMap nuk e ka problem, TransactionalMap jep njĂ« gabim.
IMap. Regjistrojmë 5000 objekte, i lexojmë. Të gjitha s'kanë befasi.
@Test
void get5000() {
IMap map = hazelcastInstance.getMap("application");
UUID subscriberId = UUID.randomUUID();
për (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());
}
}Ndërsa në transaksion nuk funksionon, marrim 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();
}
});
}Në versionin 3.8 u prezantua mekanizmi i Dërgimin të Klases së Përdoruesit. Ju mund të caktoni një nodë kryesore dhe të përditësoni skedarin JAR në të.
Tani ne e kemi ndryshuar plotësisht qasjen: serializojmë vetë në JSON dhe e ruajmë në Hazelcast. Hazelcast-it nuk i nevojitet të dijë strukturën e klasave tona, dhe ne mund të përmirësohemi pa ndonjë kohë të papunë. Menaxhimi i versionimit të objekteve në domen bëhet nga aplikacioni. Në të njëjtën kohë, mund të jenë aktivizuar versione të ndryshme të aplikacionit, dhe mund të ndodhë që aplikacioni i ri të krijojë objekte me fusha të reja, ndërsa i vjetër nuk i njeh ende këto fusha. Dhe në të njëjtën kohë, aplikacioni i ri lexon objekte që janë shkruar nga aplikacioni i vjetër, në të cilat nuk ka fusha të reja. Këto situata i trajtojmë brenda aplikacionit, por për thjeshtësi nuk i ndryshojmë dhe nuk i fshijmë fushat, vetëm i zgjerojmë klasat duke shtuar fusha të reja.
Si e sigurojmë performancën e lartë
KatĂ«r thirrje nĂ« Hazelcast â mirĂ«, dy nĂ« DB â keq
Eshte gjithmonë më mirë të kërkosh të dhënat në keq në vend se në DB, por nuk duam as të ruajmë regjistrimet e padëshiruara. Vendimin për atë që do të keqngjitemi e shtyjmë për fazën e fundit të zhvillimit. Pasi të jetë koduar funksionaliteti i ri, aktivizojmë regjistrimin e të gjitha kërkesave në PostgreSQL (log_min_duration_statement në 0) dhe fillojmë testimin e ngarkesës për rreth 20 minuta. Nga logët e mbledhura, utilitat si pgFouine dhe pgBadger dinë të ndërtojnë raporte analitike. Në raportet, ne fillimisht kërkojmë kërkesat e ngadalta dhe të shpeshta. Për kërkesat e ngadalta, ndërtuam planin e ekzekutimit (EXPLAIN) dhe vlerësojmë nëse një kërkesë e tillë mund të shpejtohet. Kërkesat e shpeshta me të njëjtat të dhëna hyrëse përshtaten mirë në keq. Përpiqemi t'i mbajmë kërkesat "të sheshta", për një tavolinë në një kërkesë.
Ekspluatimi
SV si një shërbim online u lançua në pranverën e vitit 2017, si një produkt të veçantë SV doli në nëntor 2017 (në atë kohë me statusin e beta-versionit).
Më shumë se një vit shfrytëzimi, problme serioze në funksionimin e shërbimit online SV nuk kanë ndodhur. Monitorojmë shërbimin online përmes , mbledhim dhe devlojmë nga .
Distribucioni i serverit SV ofrohet në formë të pakove natyrore: RPM, DEB, MSI. Gjithashtu për Windows ne ofrojmë një instalues të vetëm në formën e një EXE, i cili instalon serverin, Hazelcast dhe Elasticsearch në një makinë. Fillimisht, ne e quajmë këtë version instalimi 'demonstrues', por tani është e qartë se ky është varianti më i njohur i shpërndarjes.
Burimi: habr.com
