Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast

Yn dit artikel sille wy prate oer hoe en wêrom wy ûntwikkele Ynteraksje System - in meganisme dat ynformaasje oerbringt tusken kliïntapplikaasjes en 1C: Enterprise-servers - fan it ynstellen fan in taak oant tinken troch de arsjitektuer en ymplemintaasjedetails.

It ynteraksjesysteem (hjirnei oantsjutten as SV) is in ferdield, fouttolerant berjochtensysteem mei garandearre levering. SV is ûntwurpen as in hege-load tsjinst mei hege scalability, beskikber sawol as in online tsjinst (fersoarge troch 1C) en as in massa-produsearre produkt dat kin wurde ynset op jo eigen server fasiliteiten.

SV brûkt ferdield opslach hazelcast en sykmasine Elastyskesearch. Wy sille ek prate oer Java en hoe't wy PostgreSQL horizontaal skaalje.
Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast

Probleemintwurding

Om dúdlik te meitsjen wêrom't wy it ynteraksjesysteem makke hawwe, sil ik jo in bytsje fertelle oer hoe't de ûntwikkeling fan bedriuwsapplikaasjes yn 1C wurket.

Om te begjinnen, in bytsje oer ús foar dyjingen dy't noch net witte wat wy dogge :) Wy meitsje it 1C: Enterprise technologyplatfoarm. It platfoarm omfettet in ark foar ûntwikkeling fan saaklike applikaasjes, lykas ek in runtime wêrmei saaklike applikaasjes kinne rinne yn in cross-platform-omjouwing.

Client-tsjinner ûntwikkeling paradigma

Bedriuwsapplikaasjes makke op 1C: Enterprise operearje yn in trije-nivo client-tsjinner arsjitektuer "DBMS - applikaasje tsjinner - client". Applikaasjekoade skreaun yn ynboude 1C taal, kin wurde útfierd op de applikaasje tsjinner of op de client. Alle wurk mei applikaasje-objekten (mappen, dokuminten, ensfh.), En ek it lêzen en skriuwen fan de databank, wurdt allinich útfierd op 'e tsjinner. De funksjonaliteit fan formulieren en kommando-ynterface wurdt ek ymplementearre op 'e tsjinner. De klant fiert it ûntfangen, iepenjen en werjaan fan formulieren, "kommunisearjen" mei de brûker (warskôgings, fragen ...), lytse berekkeningen yn formulieren dy't in rappe reaksje fereaskje (bygelyks fermannichfâldigje de priis troch kwantiteit), wurkje mei lokale triemmen, wurkje mei apparatuer.

Yn applikaasjekoade moatte de kopteksten fan prosedueres en funksjes eksplisyt oanjaan wêr't de koade útfierd wurde sil - mei de &AtClient / &AtServer-rjochtlinen (&AtClient / &AtServer yn 'e Ingelske ferzje fan 'e taal). 1C-ûntwikkelders sille my no korrigearje troch te sizzen dat rjochtlinen eins binne mear as, mar foar ús is dit no net wichtich.

Jo kinne belje tsjinner koade út client koade, mar do kinst net neame client koade út tsjinner koade. Dit is in fûnemintele beheining dy't wy makke hawwe foar in oantal redenen. Benammen om't tsjinner koade moat wurde skreaun op sa'n manier dat it útfiert deselde wize nettsjinsteande wêr't it hjit - fan de client of fan de tsjinner. En yn it gefal fan it oproppen fan tsjinnerkoade fan in oare tsjinnerkoade, is d'r gjin kliïnt as sadanich. En om't by it útfieren fan 'e serverkoade de kliïnt dy't it neamde, koe slute, de applikaasje ôfslute, en de tsjinner soe gjinien mear hawwe om te skiljen.

Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast
Koade dy't in knopklik ferwurket: in tsjinnerproseduere oproppe fan 'e kliïnt sil wurkje, in kliïntproseduere oproppe fan 'e tsjinner sil net

Dit betsjut dat as wy wat berjocht fan 'e tsjinner nei de kliïntapplikaasje wolle stjoere, bygelyks dat it generearjen fan in "langrinnende" rapport is klear en it rapport kin wurde besjoen, wy hawwe net sa'n metoade. Jo moatte trúkjes brûke, bygelyks periodyk de tsjinner fan 'e kliïntkoade ôffreegje. Mar dizze oanpak laadt it systeem mei ûnnedige oproppen, en sjocht oer it algemien net heul elegant.

En der is bygelyks ek ferlet as der in telefoantsje komt SIP- As jo ​​​​in oprop meitsje, ynformearje de kliïntapplikaasje hjiroer, sadat it it nûmer fan 'e beller kin brûke om it te finen yn' e database fan 'e tsjinpartij en de brûkersynformaasje oer de opropende tsjinpartij te sjen. Of, bygelyks, as in bestelling oankomt by it magazijn, melde de klantapplikaasje fan de klant hjiroer. Yn 't algemien binne d'r in protte gefallen wêr't sa'n meganisme nuttich wêze soe.

De produksje sels

Meitsje in messaging meganisme. Fluch, betrouber, mei garandearre levering, mei de mooglikheid om fleksibel te sykjen nei berjochten. Op grûn fan it meganisme implementearje in boadskipper (berjochten, fideoproppen) dy't rint yn 1C-applikaasjes.

Untwerp it systeem om horizontaal skaalber te wêzen. De tanimmende lading moat wurde dekt troch it fergrutsjen fan it oantal knopen.

Ymplemintaasje

Wy besletten om it serverdiel fan SV net direkt te yntegrearjen yn it 1C: Enterprise-platfoarm, mar om it as in apart produkt út te fieren, wêrfan de API kin wurde neamd út 'e koade fan 1C-applikaasje-oplossingen. Dit waard dien foar in oantal redenen, wêrfan de wichtichste wie dat ik it mooglik meitsje woe om berjochten te wikseljen tusken ferskate 1C-applikaasjes (bygelyks tusken Trade Management en Accounting). Ferskillende 1C-applikaasjes kinne rinne op ferskate ferzjes fan it 1C: Enterprise-platfoarm, lizze op ferskate servers, ensfh. Yn sokke omstannichheden is de ymplemintaasje fan SV as in apart produkt lizzend "oan 'e kant" fan 1C-ynstallaasjes de optimale oplossing.

Dat, wy besletten om SV as in apart produkt te meitsjen. Wy riede oan dat lytse bedriuwen de CB-tsjinner brûke dy't wy yn ús wolk ynstalleare (wss://1cdialog.com) om de overheadkosten te foarkommen dy't ferbûn binne mei lokale ynstallaasje en konfiguraasje fan 'e server. Grutte kliïnten kinne it oan te rieden fine om har eigen CB-tsjinner op har foarsjenningen te ynstallearjen. Wy brûkten in ferlykbere oanpak yn ús wolk SaaS-produkt 1c nij - it wurdt produsearre as in massa-produsearre produkt foar ynstallaasje op 'e siden fan kliïnten, en wurdt ek ynset yn ús wolk https://1cfresh.com/.

Applikaasje

Om de lading en fouttolerânsje te fersprieden, sille wy net ien Java-applikaasje ynsette, mar ferskate, mei in loadbalancer foar har. As jo ​​​​in berjocht moatte oerdrage fan knooppunt nei knooppunt, brûk dan publisearje / ynskriuwe yn Hazelcast.

Kommunikaasje tusken de kliïnt en de tsjinner is fia websocket. It is goed geskikt foar real-time systemen.

Ferspraat cache

Wy hawwe keazen tusken Redis, Hazelcast en Ehcache. It is 2015. Redis hat krekt in nij kluster frijlitten (te nij, eng), d'r is Sentinel mei in protte beheiningen. Ehcache wit net hoe te sammeljen yn in kluster (dizze funksjonaliteit ferskynde letter). Wy besletten it te besykjen mei Hazelcast 3.4.
Hazelcast wurdt gearstald yn in kluster út 'e doaze. Yn ienknooppuntmodus is it net heul nuttich en kin allinich brûkt wurde as cache - it wit net hoe't jo gegevens op skiif dumpje kinne, as jo de ienige knooppunt ferlieze, ferlieze jo de gegevens. Wy ynsette ferskate Hazelcasts, tusken dêr't wy reservekopy krityske gegevens. Wy meitsje gjin reservekopy fan it cache - wy hawwe it net slim.

Foar ús is Hazelcast:

  • Opslach fan brûkerssesjes. It duorret in lange tiid om elke kear nei de databank te gean foar in sesje, dus wy sette alle sesjes yn Hazelcast.
  • Cache. As jo ​​​​nei in brûkersprofyl sykje, kontrolearje dan de cache. Skreau in nij berjocht - set it yn it cache.
  • Underwerpen foar kommunikaasje tusken applikaasje eksimplaren. It knooppunt genereart in evenemint en pleatst it yn it Hazelcast-ûnderwerp. Oare tapassingsknooppunten dy't ynskreaun binne op dit ûnderwerp ûntfange en ferwurkje it evenemint.
  • Cluster slûzen. Bygelyks, wy meitsje in diskusje mei in unike kaai (singleton diskusje binnen de 1C databank):

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

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

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

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

Wy hawwe kontrolearre dat der gjin kanaal is. Wy naam it slot, kontrolearre it nochris, en makke it. As jo ​​​​it slot net kontrolearje nei it nimmen fan it slot, dan is d'r in kâns dat in oare thread ek op dat stuit kontrolearre hat en no besykje deselde diskusje te meitsjen - mar it bestiet al. Jo kinne net beskoattelje mei syngronisearre of reguliere java-slot. Troch de databank - it is stadich, en it is spitich foar de databank; fia Hazelcast - dat is wat jo nedich binne.

Kies in DBMS

Wy hawwe wiidweidige en suksesfolle ûnderfining mei it wurkjen mei PostgreSQL en gearwurkjen mei de ûntwikkelders fan dit DBMS.

It is net maklik mei in PostgreSQL-kluster - d'r is XL, XC, Citus, mar yn 't algemien binne dit gjin NoSQL's dy't út it fak skaalje. Wy beskôgen NoSQL net as de wichtichste opslach; it wie genôch dat wy Hazelcast namen, wêrmei't wy net earder hiene wurke.

As jo ​​in relasjonele databank skaalje moatte, betsjut dat sharding. Lykas jo witte, mei sharding ferdiele wy de databank yn aparte dielen, sadat elk fan har op in aparte tsjinner pleatst wurde kin.

De earste ferzje fan ús sharding naam de mooglikheid oan om elk fan 'e tabellen fan ús applikaasje te fersprieden oer ferskate servers yn ferskate proporsjes. D'r binne in protte berjochten op tsjinner A - asjebleaft, lit ús in diel fan dizze tabel ferpleatse nei tsjinner B. Dit beslút raasde gewoan oer foartidige optimalisaasje, sadat wy besletten ússels te beheinen ta in oanpak mei meardere hierders.

Jo kinne bygelyks lêze oer multi-tenant op 'e webside Citus Data.

SV hat de begripen fan tapassing en abonnee. In applikaasje is in spesifike ynstallaasje fan in bedriuwsapplikaasje, lykas ERP of Accounting, mei syn brûkers en saaklike gegevens. In abonnee is in organisaasje of yndividu út waans namme de applikaasje is registrearre yn 'e SV-tsjinner. In abonnee kin hawwe ferskate applikaasjes registrearre, en dizze applikaasjes kinne útwikselje berjochten mei elkoar. De abonnee waard in hierder yn ús systeem. Berjochten fan ferskate abonnees kinne lizze yn ien fysike databank; as wy sjogge dat in abonnee is begûn te generearjen in soad ferkear, wy ferpleatse it nei in aparte fysike databank (of sels in aparte databank tsjinner).

Wy hawwe in haaddatabase wêr't in routingtabel wurdt opslein mei ynformaasje oer de lokaasje fan alle abonneedatabases.

Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast

Om foar te kommen dat de haaddatabank in knelpunt is, hâlde wy de routingtabel (en oare faak nedige gegevens) yn in cache.

As de databank fan de abonnee begjint te fertrage, sille wy it yn partysjes binnen snije. Op oare projekten brûke wy pg_pathman.

Sûnt it ferliezen fan brûkersberjochten min is, ûnderhâlde wy ús databases mei replika's. De kombinaasje fan syngroane en asynchrone replika's kinne jo josels fersekerje yn gefal fan ferlies fan 'e haaddatabase. Berjochtferlies sil allinich foarkomme as de primêre databank en syn syngroane replika tagelyk mislearje.

As in syngroane replika ferlern giet, wurdt de asynchrone replika syngroan.
As de haaddatabank ferlern is, wurdt de syngroane replika de haaddatabank, en de asynchrone replika wurdt in syngroane replika.

Elasticsearch foar sykjen

Sûnt ûnder oare SV is ek in boadskipper, it fereasket in flugge, handige en fleksibele sykjen, rekken hâldend mei morfology, mei help fan ûnprecise wedstriden. Wy besletten it tsjil net opnij út te finen en de fergese sykmasjine Elasticsearch te brûken, makke op basis fan 'e bibleteek Lucene. Wy sette Elasticsearch ek yn in kluster (master - gegevens - gegevens) om problemen te eliminearjen yn gefal fan mislearjen fan applikaasjeknooppunten.

Op github fûnen wy Russyske morfology plugin foar Elasticsearch en brûk it. Yn de Elasticsearch-yndeks bewarje wy wurdwurden (dy't de plugin bepaalt) en N-grammen. As de brûker tekst ynfiert om te sykjen, sykje wy nei de typte tekst tusken N-grammen. As opslein yn 'e yndeks, sil it wurd "teksten" wurde opdield yn de folgjende N-grammen:

[dy, tek, tex, tekst, teksten, ek, ex, ext, teksten, ks, kst, ksty, st, sty, dy],

En de woartel fan it wurd "tekst" sil ek bewarre wurde. Dizze oanpak lit jo sykje oan it begjin, yn 'e midden en oan' e ein fan it wurd.

Algemiene ôfbylding

Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast
Werhelje fan 'e foto fan it begjin fan it artikel, mar mei útlis:

  • Balancer bleatsteld op it ynternet; wy hawwe nginx, it kin elk wêze.
  • Java applikaasje eksimplaren kommunisearje mei elkoar fia Hazelcast.
  • Om te wurkjen mei in web socket wy brûke netty.
  • De Java-applikaasje is skreaun yn Java 8 en bestiet út bondels OSGi. De plannen omfetsje migraasje nei Java 10 en oergong nei modules.

Untwikkeling en testen

Yn it proses fan it ûntwikkeljen en testen fan de SV kamen wy in oantal nijsgjirrige skaaimerken fan 'e produkten dy't wy brûke.

Laad testen en ûnthâld lekken

De frijlitting fan elke SV-útjefte omfettet loadtesten. It is suksesfol as:

  • De test wurke ferskate dagen en d'r wiene gjin tsjinstfalen
  • Responstiid foar kaai operaasjes net boppe in noflike drompel
  • Prestaasje efterútgong yn ferliking mei de foarige ferzje is net mear as 10%

Wy folje de testdatabase mei gegevens - om dit te dwaan, krije wy ynformaasje oer de meast aktive abonnee fan 'e produksjetsjinner, fermannichfâldigje har nûmers mei 5 (it oantal berjochten, diskusjes, brûkers) en testen it op dizze manier.

Wy fiere load testen fan it ynteraksjesysteem yn trije konfiguraasjes:

  1. stress test
  2. Allinnich ferbinings
  3. Subscriber registraasje

Tidens de stresstest lansearje wy ferskate hûnderten triedden, en se laden it systeem sûnder te stopjen: berjochten skriuwe, diskusjes oanmeitsje, in list mei berjochten ûntfange. Wy simulearje de aksjes fan gewoane brûkers (krije in list mei myn net-lêzen berjochten, skriuw nei immen) en software-oplossings (ferstjoere in pakket fan in oare konfiguraasje, ferwurkje in warskôging).

Dit is bygelyks wat diel fan 'e stresstest derút sjocht:

  • Brûker logt yn
    • Fersiket jo net-lêzen diskusjes
    • 50% kâns om berjochten te lêzen
    • 50% kâns op tekst
    • Folgjende brûker:
      • Hat 20% kâns op it meitsjen fan in nije diskusje
      • Selektearret willekeurich ien fan syn diskusjes
      • Giet nei binnen
      • Fersiken berjochten, brûkersprofilen
      • Makket fiif berjochten dy't rjochte binne oan willekeurige brûkers út dizze diskusje
      • Leaves diskusje
      • Werhellet 20 kear
      • Logt út, giet werom nei it begjin fan it skript

    • In chatbot komt it systeem yn (emulearret berjochten fan applikaasjekoade)
      • Hat in kâns fan 50% om in nij kanaal te meitsjen foar gegevensútwikseling (spesjale diskusje)
      • 50% wierskynlik in berjocht te skriuwen nei ien fan 'e besteande kanalen

It senario "Allinich ferbiningen" ferskynde foar in reden. Der is in situaasje: brûkers hawwe it systeem ferbûn, mar binne noch net belutsen. Elke brûker set de kompjûter moarns om 09 oere oan, makket in ferbining mei de tsjinner en bliuwt stil. Dizze jonges binne gefaarlik, d'r binne in protte fan har - de ienige pakketten dy't se hawwe binne PING / PONG, mar se hâlde de ferbining mei de tsjinner (se kinne it net ophâlde - wat as der in nij berjocht is). De test reprodusearret in situaasje dêr't in grut oantal fan sokke brûkers besykje yn te loggen op it systeem yn in heal oere. It is fergelykber mei in stresstest, mar har fokus is krekt op dizze earste ynput - sadat d'r gjin mislearrings binne (in persoan brûkt it systeem net, en it falt al ôf - it is lestich om wat slimmer te tinken).

It abonneeregistraasjeskript begjint fan 'e earste lansearring. Wy hawwe in stresstest útfierd en wiene der wis fan dat it systeem net fertrage tidens korrespondinsje. Mar brûkers kamen en de registraasje begon te mislearjen fanwegen in time-out. By it registrearjen brûkten wy / dev / willekeurich, dat is besibbe oan de entropy fan it systeem. De tsjinner hie gjin tiid om genôch entropy te sammeljen en doe't in nije SecureRandom waard oanfrege, beferzen it foar tsientallen sekonden. Der binne in protte manieren út dizze situaasje, Bygelyks: oerskeakelje nei de minder feilige /dev/urandom, ynstallearje in spesjale boerd dat generearret entropy, generearje willekeurige nûmers foarôf en bewarje se yn in pool. Wy hawwe it probleem mei it swimbad tydlik sluten, mar sûnt hawwe wy in aparte test útfierd foar it registrearjen fan nije abonnees.

Wy brûke as loadgenerator JMeter. It wit net hoe te wurkjen mei websocket; it hat in plugin nedich. De earste yn sykresultaten foar de query "jmeter websocket" binne: artikels út BlazeMeter, dy't oanbefelje plugin troch Maciej Zaleski.

Dêr hawwe wy besletten om te begjinnen.

Hast fuortendaliks nei it begjinnen fan serieuze testen, ûntdutsen wy dat JMeter ûnthâld begon te lekken.

De plugin is in apart grut ferhaal; mei 176 stjerren hat it 132 foarken op github. De skriuwer sels hat him sûnt 2015 net ynset (wy namen it yn 2015, doe wekker it gjin fertochten), ferskate github-problemen oangeande ûnthâldlekken, 7 net-sletten pull-oanfragen.
As jo ​​​​beslute om loadtesten út te fieren mei dizze plugin, jouwe dan omtinken oan de folgjende diskusjes:

  1. Yn in multi-threaded omjouwing waard in reguliere LinkedList brûkt, en it resultaat wie NPE yn runtime. Dit kin oplost wurde troch te wikseljen nei ConcurrentLinkedDeque of troch syngronisearre blokken. Wy hawwe de earste opsje foar ússels keazen (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Unthâldlek; by it loskoppelen wurdt ferbiningynformaasje net wiske (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Yn streamingmodus (as de websocket net oan 'e ein fan' e stekproef is sletten, mar letter yn it plan brûkt wurdt), wurkje antwurdpatroanen net (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Dit is ien fan dy op github. Wat wy diene:

  1. Hawwe nommen foarke Elyran Kogan (@elyrank) - it reparearret problemen 1 en 3
  2. Probleem 2 oplost
  3. Bywurke steiger fan 9.2.14 oant 9.3.12
  4. Ynpakt SimpleDateFormat yn ThreadLocal; SimpleDateFormat is net thread-feilich, wat late ta NPE by runtime
  5. In oar ûnthâldlek reparearre (de ferbining waard ferkeard sluten by it loskoppelen)

En dochs streamt it!

It ûnthâld begon net yn in dei te rinnen, mar yn twa. D'r wie hielendal gjin tiid mear, dus wy besleaten om minder diskusjes te lansearjen, mar op fjouwer aginten. Dit hie op syn minst in wike genôch wêze moatten.

Twa dagen binne foarby...

No rint Hazelcast sûnder ûnthâld. De logs lieten sjen dat Hazelcast nei in pear dagen fan testen begon te klagen oer in gebrek oan ûnthâld, en nei in skoft foel it kluster útinoar, en de knopen bleaunen ien foar ien te stjerren. Wy ferbûn JVisualVM oan hazelcast en seagen in "opkommende seach" - it neamde regelmjittich de GC, mar koe it ûnthâld net wiskje.

Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast

It die bliken dat yn hazelcast 3.4, by it wiskjen fan in kaart / multiMap (map.destroy()), ûnthâld net folslein befrijd is:

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

De brek is no reparearre yn 3.5, mar it wie doe in probleem. Wy makken nije multiMaps mei dynamyske nammen en wiske se neffens ús logika. De koade seach der sa út:

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

Belje:

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

multiMap waard makke foar elk abonnemint en wiske as it net nedich wie. Wy besletten dat wy Map begjinne soene , de kaai sil de namme fan it abonnemint wêze, en de wearden sille sesje-identifiers wêze (wêrfan jo dan brûkersidentifikatoren kinne krije, as nedich).

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

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

De charts binne ferbettere.

Hoe en wêrom hawwe wy in skaalbere tsjinst mei hege lading skreaun foar 1C: Enterprise: Java, PostgreSQL, Hazelcast

Wat oars hawwe wy leard oer load testen?

  1. JSR223 moat yn groovy skreaun wurde en kompilaasjecache omfetsje - it is folle rapper. link.
  2. Jmeter-Plugins-grafiken binne makliker te begripen dan standert. link.

Oer ús ûnderfining mei Hazelcast

Hazelcast wie in nij produkt foar ús, wy begûnen dermei te wurkjen fan ferzje 3.4.1, no rint ús produksjeserver ferzje 3.9.2 (op it stuit fan skriuwen is de lêste ferzje fan Hazelcast 3.10).

ID generaasje

Wy begûnen mei integer identifiers. Litte wy ús foarstelle dat wy in oare Long nedich hawwe foar in nije entiteit. Sequence yn 'e databank is net geskikt, de tabellen binne belutsen by sharding - it docht bliken dat d'r in berjocht ID=1 is yn DB1 en in berjocht ID=1 yn DB2, jo kinne dizze ID net yn Elasticsearch pleatse, noch yn Hazelcast , mar it slimste is as jo de gegevens fan twa databanken yn ien kombinearje wolle (bygelyks beslute dat ien databank genôch is foar dizze abonnees). Jo kinne ferskate AtomicLongs tafoegje oan Hazelcast en de teller dêr hâlde, dan is de prestaasjes fan it krijen fan in nije ID incrementAndGet plus de tiid foar in fersyk nei Hazelcast. Mar Hazelcast hat wat mear optimaal - FlakeIdGenerator. By it kontakt opnimme mei elke kliïnt krije se in ID-berik, bygelyks de earste - fan 1 oant 10, de twadde - fan 000 oant 10, ensfh. No kin de klant op har eigen nije identifiers útjaan oant it berik dat him útjûn is, einiget. It wurket fluch, mar as jo de applikaasje opnij starte (en de Hazelcast-kliïnt), begjint in nije folchoarder - dus de oerslaan, ensfh. Derneist begripe ûntwikkelders net echt wêrom't de ID's integer binne, mar binne sa inkonsistint. Wy woegen alles en stapten oer op UUID's.

Trouwens, foar dyjingen dy't wolle wêze as Twitter, is d'r sa'n Snowcast-bibleteek - dit is in ymplemintaasje fan Snowflake boppe op Hazelcast. Jo kinne it hjir besjen:

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

Mar wy binne der net mear oan ta kommen.

TransactionalMap.replace

In oare ferrassing: TransactionalMap.replace wurket net. Hjir is in test:

@Test
public void replaceInMap_putsAndGetsInsideTransaction() {

    hazelcastInstance.executeTransaction(context -> {
        HazelcastTransactionContextHolder.setContext(context);
        try {
            context.getMap("map").put("key", "oldValue");
            context.getMap("map").replace("key", "oldValue", "newValue");
            
            String value = (String) context.getMap("map").get("key");
            assertEquals("newValue", value);

            return null;
        } finally {
            HazelcastTransactionContextHolder.clearContext();
        }        
    });
}

Expected : newValue
Actual : oldValue

Ik moast myn eigen ferfange skriuwe mei 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);
}

Test net allinich reguliere gegevensstruktueren, mar ek har transaksjeferzjes. It bart dat IMap wurket, mar TransactionalMap bestiet net mear.

Foegje in nij JAR sûnder downtime

Earst hawwe wy besletten om objekten fan ús klassen op te nimmen yn Hazelcast. Wy hawwe bygelyks in Applikaasjeklasse, wy wolle it bewarje en lêze. Rêde:

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

Wy lêze:

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

Alles wurket. Doe besleaten wy in yndeks te bouwen yn Hazelcast om te sykjen troch:

map.addIndex("subscriberId", false);

En by it skriuwen fan in nije entiteit begon se ClassNotFoundException te ûntfangen. Hazelcast besocht te foegjen oan de yndeks, mar wist neat oer ús klasse en woe in JAR mei dizze klasse wurde levere oan it. Wy diene krekt dat, alles wurke, mar in nij probleem ferskynde: hoe kinne jo de JAR bywurkje sûnder it kluster folslein te stopjen? Hazelcast nimt de nije JAR net op tidens in knooppunt-by-knooppunt-update. Op dit punt besletten wy dat wy koenen libje sûnder yndeks sykjen. Ommers, as jo Hazelcast brûke as in winkel foar kaaiwearden, sil alles wurkje? Net wirklik. Hjir is it gedrach fan IMap en TransactionalMap wer oars. Wêr't IMap net skele, smyt TransactionalMap in flater.

IMap. Wy skriuwe 5000 objekten, lês se. Alles wurdt ferwachte.

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

Mar it wurket net yn in transaksje, wy krije in 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();
        }
    });
}

Yn 3.8 ferskynde it meganisme foar ynset fan brûkersklasse. Jo kinne ien masterknooppunt oanwize en it JAR-bestân derop bywurkje.

No hawwe wy ús oanpak folslein feroare: wy serialisearje it sels yn JSON en bewarje it yn Hazelcast. Hazelcast hoecht de struktuer fan ús klassen net te kennen, en wy kinne bywurkje sûnder downtime. Ferzje fan domeinobjekten wurdt regele troch de applikaasje. Ferskillende ferzjes fan 'e applikaasje kinne tagelyk rinne, en in situaasje is mooglik as de nije applikaasje objekten skriuwt mei nije fjilden, mar de âlde wit noch net oer dizze fjilden. En tagelyk lêst de nije applikaasje objekten skreaun troch de âlde applikaasje dy't gjin nije fjilden hawwe. Wy behannelje sokke situaasjes binnen de applikaasje, mar foar de ienfâld feroarje of wiskje wy fjilden net, wy wreidzje de klassen allinich út troch nije fjilden ta te foegjen.

Hoe wy soargje foar hege prestaasjes

Fjouwer reizen nei Hazelcast - goed, twa nei de database - min

Gean nei de cache foar gegevens is altyd better dan nei de databank te gean, mar jo wolle ek net brûkte records opslaan. Wy litte it beslút oer wat te cache oant de lêste faze fan ûntwikkeling. As de nije funksjonaliteit kodearre is, skeakelje wy it oanmelden fan alle queries yn PostgreSQL (log_min_duration_statement nei 0) en rinne load testen foar 20 minuten. Mei help fan de sammele logs kinne nutsbedriuwen lykas pgFouine en pgBadger analytyske rapporten bouwe. Yn rapporten sykje wy yn it foarste plak nei trage en frekwinte fragen. Foar trage queries bouwe wy in útfieringsplan (EXPLAIN) en evaluearje oft sa'n query kin wurde fersneld. Faak oanfragen foar deselde ynfiergegevens passe goed yn 'e cache. Wy besykje te hâlden queries "plat", ien tabel per query.

Operaasje

SV as online tsjinst waard yn 'e maitiid fan 2017 yn gebrûk nommen, en as in apart produkt waard SV yn novimber 2017 frijlitten (op dat stuit yn beta-ferzjestatus).

Yn mear as in jier fan operaasje binne d'r gjin serieuze problemen west yn 'e wurking fan' e CB online tsjinst. Wy kontrolearje de online tsjinst fia Zabbix, sammelje en ynsette fan Bamboe.

De SV-tsjinnerferdieling wurdt levere yn 'e foarm fan native pakketten: RPM, DEB, MSI. Plus foar Windows leverje wy ien ynstallearder yn 'e foarm fan in inkele EXE dy't de server, Hazelcast en Elasticsearch ynstalleart op ien masine. Wy neamden earst dizze ferzje fan 'e ynstallaasje as de "demo" ferzje, mar it is no dúdlik wurden dat dit de populêrste ynsetopsje is.

Boarne: www.habr.com

Add a comment