Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast

In hierdie artikel sal ons praat oor hoe en hoekom ons ontwikkel het Interaksiestelsel - 'n meganisme wat inligting oordra tussen kliënttoepassings en 1C: Enterprise-bedieners - van die opstel van 'n taak tot deurdink van die argitektuur en implementeringsbesonderhede.

Die interaksiestelsel (hierna - CB) is 'n verspreide foutverdraagsame boodskapstelsel met gewaarborgde aflewering. CB is ontwerp as 'n hoëladingdiens met hoë skaalbaarheid, beskikbaar as 'n aanlyndiens (verskaf deur 1C) en as 'n massaproduksieproduk wat op sy eie bedienerfasiliteite ontplooi kan word.

SW gebruik verspreide berging haselgiet en soekenjin Elasticsearch. Ons sal ook praat oor Java en hoe ons PostgreSQL horisontaal skaal.
Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast

Probleemstelling

Om dit duidelik te maak hoekom ons die interaksiestelsel gemaak het, sal ek jou 'n bietjie vertel oor hoe die ontwikkeling van besigheidstoepassings in 1C werk.

Eerstens, 'n bietjie oor ons vir diegene wat nog nie weet wat ons doen nie :) Ons ontwikkel die 1C:Enterprise-tegnologieplatform. Die platform sluit 'n besigheidstoepassingsontwikkelingsinstrument in, sowel as 'n looptyd wat besigheidstoepassings in 'n kruisplatform-omgewing laat werk.

Kliënt-bediener ontwikkeling paradigma

Besigheidstoepassings wat op 1C:Enterprise geskep is, werk in 'n drie-vlak kliënt-bediener argitektuur "DBMS - toepassingsbediener - kliënt". Aansoekkode geskryf in ingeboude taal 1C, kan op die toepassingsbediener of op die kliënt loop. Alle werk met toepassingsvoorwerpe (gidse, dokumente, ens.), sowel as die lees en skryf van die databasis, word slegs op die bediener uitgevoer. Vorms en opdragkoppelvlakfunksies word ook op die bediener geïmplementeer. Op die kliënt word vorms ontvang, oopgemaak en vertoon, "kommunikasie" met die gebruiker (waarskuwings, vrae ...), klein berekeninge in vorms wat 'n vinnige reaksie vereis (byvoorbeeld, vermenigvuldig die prys met die hoeveelheid), werk met plaaslike lêers, werk met toerusting.

In die toepassingskode moet die opskrifte van prosedures en funksies uitdruklik aandui waar die kode uitgevoer sal word - deur gebruik te maak van die riglyne &AtClient / &AtServer (&AtClient / &AtServer in die Engelse weergawe van die taal). 1C-ontwikkelaars sal my nou regstel deur te sê dat die riglyne eintlik is beter, maar vir ons is dit nie nou belangrik nie.

Jy kan bedienerkode vanaf kliëntkode oproep, maar jy kan nie kliëntkode vanaf bedienerkode oproep nie. Dit is 'n fundamentele beperking wat om 'n aantal redes deur ons gemaak word. Veral omdat die bedienerkode op so 'n manier geskryf moet word dat dit op dieselfde manier uitgevoer word, maak nie saak waar dit vandaan geroep word nie - vanaf die kliënt of vanaf die bediener. En in die geval van 'n oproep na die bedienerkode vanaf 'n ander bedienerkode, is daar geen kliënt as sodanig nie. En omdat tydens die uitvoering van die bedienerkode, die kliënt wat dit geroep het, kon sluit, die toepassing verlaat, en die bediener sou niemand hê om te bel nie.

Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast
Kode wat 'n knoppie-klik hanteer: die oproep van 'n bedienerprosedure vanaf die kliënt sal werk, die oproep van 'n kliëntprosedure vanaf die bediener sal nie

Dit beteken dat as ons 'n boodskap van die bediener na die kliënttoepassing wil stuur, byvoorbeeld dat die vorming van 'n "langspeel"-verslag geëindig het en die verslag bekyk kan word, ons nie so 'n manier het nie. Jy moet truuks gebruik, byvoorbeeld om die bediener van tyd tot tyd van die kliëntkode af te poll. Maar hierdie benadering laai die stelsel met onnodige oproepe, en oor die algemeen lyk dit nie baie elegant nie.

En daar is ook 'n behoefte, byvoorbeeld, wanneer 'n telefoon SIP-bel, stel die kliënttoepassing hieroor in kennis sodat dit dit in die teenparty-databasis kan vind deur die beller se nommer en wys die gebruikerinligting oor die oproepende teenparty. Of, byvoorbeeld, wanneer 'n bestelling by die pakhuis aankom, stel die kliënt se kliënttoepassing hieroor in kennis. Oor die algemeen is daar baie gevalle waar so 'n meganisme nuttig sal wees.

Eintlik instelling

Skep 'n boodskapmeganisme. Vinnig, betroubaar, met gewaarborgde aflewering, met die moontlikheid van buigsame soektog na boodskappe. Gebaseer op die meganisme, implementeer 'n boodskapper (boodskappe, video-oproepe) wat binne 1C-toepassings werk.

Ontwerp die stelsel horisontaal skaalbaar. 'n Toenemende vrag moet gedek word deur 'n toename in die aantal nodusse.

Implementering

Ons het besluit om nie die bedienergedeelte van die CB direk in die 1C:Enterprise-platform in te sluit nie, maar om dit as 'n aparte produk te implementeer, waarvan die API uit die kode van 1C-toepassingsoplossings geroep kan word. Dit is om 'n aantal redes gedoen, waarvan die hoofsaak was om dit moontlik te maak om boodskappe tussen verskillende 1C-toepassings uit te ruil (byvoorbeeld tussen die Departement van Handel en Rekeningkunde). Verskillende 1C-toepassings kan op verskillende weergawes van die 1C:Enterprise-platform loop, op verskillende bedieners geleë wees, ens. Onder sulke omstandighede is die implementering van CB as 'n aparte produk, geleë "aan die kant" van 1C-installasies, die optimale oplossing.

So, ons het besluit om CB as 'n aparte produk te maak. Vir kleiner ondernemings beveel ons aan om die CB-bediener te gebruik wat ons in ons wolk geïnstalleer het (wss://1cdialog.com) om die oorkoste wat verband hou met plaaslike bedienerinstallasie en -konfigurasie te vermy. Groot kliënte mag dit egter raadsaam ag om hul eie CB-bediener by hul fasiliteite te installeer. Ons het 'n soortgelyke benadering in ons wolk SaaS-produk gebruik. 1c vars – dit word vrygestel as 'n produksieproduk vir installasie deur klante, en word ook in ons wolk ontplooi https://1cfresh.com/.

Artikels

Vir vragverspreiding en fouttoleransie sal ons nie een Java-toepassing ontplooi nie, maar verskeie, ons sal 'n lasbalanseerder voor hulle plaas. As jy 'n boodskap van nodus tot nodus moet stuur, gebruik publiseer/teken in Hazelcast.

Kommunikasie tussen die kliënt en die bediener - via websok. Dit is goed geskik vir intydse stelsels.

Verspreide kas

Kies tussen Redis, Hazelcast en Ehcache. Buite in 2015. Redis het pas 'n nuwe groep vrygestel (te nuut, skrikwekkend), daar is 'n Sentinel met baie beperkings. Ehcache weet nie hoe om te groepeer nie (hierdie funksionaliteit het later verskyn). Ons het besluit om met Hazelcast 3.4 te probeer.
Hazelcast is uit die boks saamgevoeg. In die enkelnodusmodus is dit nie baie nuttig nie en kan dit net as 'n kas pas - dit weet nie hoe om data na skyf te stort nie, as die enigste nodus verlore gaan, is die data verlore. Ons ontplooi verskeie Hazelcasts, waartussen ons kritieke data rugsteun. Ons rugsteun nie die kas nie - ons voel nie jammer vir hom nie.

Vir ons is Hazelcast:

  • Berging van gebruikersessies. Dit neem lank om na die databasis te gaan vir 'n sessie, so ons plaas alle sessies in Hazelcast.
  • Kas. Op soek na 'n gebruikersprofiel - kyk in die kas. Het 'n nuwe boodskap geskryf - plaas dit in die kas.
  • Onderwerpe vir kommunikasie van toepassingsgevalle. Die nodus genereer 'n gebeurtenis en plaas dit op 'n Hazelcast-onderwerp. Ander toepassingsnodusse wat op hierdie onderwerp ingeteken is, ontvang en verwerk die geleentheid.
  • trosslotte. Byvoorbeeld, ons skep 'n bespreking deur 'n unieke sleutel (bespreking-enkelton binne die raamwerk van die 1C-basis):

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

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

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

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

Ons het gekyk dat daar geen kanaal is nie. Hulle het die slot gevat, dit weer nagegaan, dit geskep. As jy nie nagaan nadat jy die slot geneem het nie, dan is daar 'n kans dat 'n ander draad ook op daardie oomblik nagegaan het en nou sal probeer om dieselfde bespreking te skep - en dit bestaan ​​reeds. Dit is onmoontlik om 'n slot te maak deur gesinchroniseerde of gewone java Lock. Deur die basis - stadig, en die basis is jammer, deur Hazelcast - wat jy nodig het.

Die keuse van 'n DBMS

Ons het uitgebreide en suksesvolle ervaring met PostgreSQL en samewerking met die ontwikkelaars van hierdie DBMS.

Met 'n cluster is PostgreSQL nie maklik nie - daar is XL, XC, Situs, maar oor die algemeen is dit nie noSQL wat uit die boks skaal nie. NoSQL is nie as die hoofberging beskou nie, dit was genoeg dat ons Hazelcast neem, waarmee ons nie voorheen gewerk het nie.

Aangesien jy 'n relasionele databasis moet skaal, beteken dit skeuring. Soos u weet, verdeel ons die databasis in afsonderlike dele wanneer ons shard, sodat elkeen van hulle op 'n aparte bediener geplaas kan word.

Die eerste weergawe van ons sharding het die vermoë aanvaar om elkeen van die tabelle van ons toepassing na verskillende bedieners in verskillende verhoudings te versprei. Baie boodskappe op bediener A - kom ons skuif asseblief 'n deel van hierdie tabel na bediener B. Hierdie besluit het net geskreeu oor voortydige optimalisering, so ons het besluit om onsself te beperk tot 'n multi-huurder benadering.

Jy kan byvoorbeeld lees oor multi-huurder op die webwerf Citus Data.

In SV is daar konsepte van die toepassing en die intekenaar. 'n Toepassing is 'n spesifieke installasie van 'n besigheidstoepassing, soos ERP of Rekeningkunde, met sy gebruikers en besigheidsdata. 'n Intekenaar is 'n organisasie of 'n individu namens wie die toepassing in die CB-bediener geregistreer is. 'n Intekenaar kan verskeie toepassings laat registreer, en hierdie toepassings kan boodskappe met mekaar uitruil. Die intekenaar het 'n huurder in ons stelsel geword. Boodskappe van verskeie intekenare kan in een fisiese basis geleë wees; as ons sien dat een of ander intekenaar baie verkeer begin genereer het, skuif ons dit na 'n aparte fisiese databasis (of selfs 'n aparte databasisbediener).

Ons het 'n hoofdatabasis waar die roeteringtabel gestoor word met inligting oor die ligging van alle intekenaardatabasisse.

Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast

Om te verhoed dat die hoofdatabasis 'n bottelnek is, hou ons die roeteringtabel (en ander gereeld versoekte data) in die kas.

As die intekenaar se databasis begin verlangsaam, sal ons dit in partisies binne sny. Op ander projekte, om groot tafels te verdeel, gebruik ons pg_padman.

Aangesien dit sleg is om gebruikersboodskappe te verloor, rugsteun ons ons databasisse met replikas. Die kombinasie van sinchrone en asinchrone replikas laat jou toe om te verseker teen die verlies van die hoofdatabasis. Boodskapverlies sal slegs plaasvind in die geval van 'n gelyktydige mislukking van die hoofdatabasis en sy sinchrone replika.

As die sinchrone replika verlore gaan, word die asinchrone replika sinchronies.
As die hoofdatabasis verlore gaan, word die sinchrone replika die hoofdatabasis, die asinchrone replika word 'n sinchrone replika.

Elasticsearch vir soektog

Aangesien CB onder andere ook 'n boodskapper is, benodig ons hier 'n vinnige, gerieflike en buigsame soektog, met inagneming van morfologie, deur onpresiese passings. Ons het besluit om nie die wiel weer uit te vind nie en die gratis Elasticsearch-soekenjin te gebruik, geskep op grond van die biblioteek Lucene. Ons ontplooi ook Elasticsearch in 'n groepering (meester - data - data) om probleme uit te skakel in die geval van mislukking van toepassingsnodusse.

Op github het ons gevind Russiese morfologie-inprop vir Elasticsearch en gebruik dit. In die Elasticsearch-indeks stoor ons woordwortels (wat die inprop definieer) en N-gramme. Soos die gebruiker teks invoer om te soek, soek ons ​​vir die getikte teks tussen N-gramme. Wanneer dit in die indeks gestoor word, sal die woord "tekste" in die volgende N-gramme verdeel word:

[te, tech, tex, text, texts, ek, eks, ext, exts, ks, kst, ksty, st, sty, jy],

En ook die wortel van die woord "teks" sal gered word. Hierdie benadering laat jou toe om aan die begin, in die middel en aan die einde van die woord te soek.

Die groot prentjie

Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast
Herhaal die prentjie vanaf die begin van die artikel, maar met verduidelikings:

  • Balanseerder blootgestel aan die internet; ons het nginx, dit kan enige wees.
  • Java-toepassingsgevalle kommunikeer met mekaar via Hazelcast.
  • Om met 'n websok te werk, gebruik ons Netty.
  • Java-toepassing geskryf in Java 8, bestaan ​​uit bundels OSGi. Die planne is om na Java 10 te migreer en na modules oor te skakel.

Ontwikkeling en toetsing

Tydens die ontwikkeling en toetsing van die CB het ons 'n aantal interessante kenmerke van die produkte wat ons gebruik teëgekom.

Lastoetsing en geheuelekkasies

Die vrystelling van elke CB-vrystelling is 'n lastoets. Dit het suksesvol geslaag toe:

  • Die toets het vir 'n paar dae gewerk en daar was geen ontkenning van diens nie
  • Reaksietyd vir sleutelbewerkings het nie 'n gemaklike drempel oorskry nie
  • Prestasie agteruitgang in vergelyking met die vorige weergawe is nie meer as 10% nie

Ons vul die toetsdatabasis met data - hiervoor kry ons inligting oor die mees aktiewe intekenaar van die produksiebediener, vermenigvuldig sy getalle met 5 (die aantal boodskappe, besprekings, gebruikers) en so toets ons.

Ons doen lastoetsing van die interaksiestelsel in drie konfigurasies:

  1. stres toets
  2. Slegs verbindings
  3. Intekenaarregistrasie

Tydens 'n strestoets begin ons 'n paar honderd drade, en hulle laai die stelsel sonder ophou: skryf boodskappe, skep besprekings, ontvang 'n lys boodskappe. Ons simuleer die aksies van gewone gebruikers (kry 'n lys van my ongeleesde boodskappe, skryf aan iemand) en programbesluite (dra 'n pakket oor na 'n ander opset, verwerk 'n waarskuwing).

Byvoorbeeld, dit is hoe deel van die strestoets lyk:

  • Gebruiker meld aan
    • Versoek jou ongeleesde drade
    • 50% kans om boodskappe te lees
    • 50% kans om boodskappe te skryf
    • Volgende gebruiker:
      • 20% kans om 'n nuwe draad te skep
      • Kies willekeurig enige van sy besprekings
      • Kom binne
      • Versoek boodskappe, gebruikersprofiele
      • Skep vyf boodskappe gerig aan ewekansige gebruikers uit hierdie draad
      • Buite bespreking
      • Herhaal 20 keer
      • Meld uit, keer terug na die begin van die skrif

    • 'n Chatbot gaan die stelsel binne (emuleer boodskappe vanaf die kode van toegepaste oplossings)
      • 50% kans om 'n nuwe datakanaal te skep (spesiale bespreking)
      • 50% kans om 'n boodskap in enige van die bestaande kanale te skryf

Die scenario "Slegs verbindings" het vir 'n rede verskyn. Daar is 'n situasie: gebruikers het die stelsel gekoppel, maar is nog nie betrokke nie. Elke gebruiker skakel soggens om 09:00 die rekenaar aan, vestig 'n verbinding met die bediener en is stil. Hierdie ouens is gevaarlik, daar is baie van hulle - hulle het net PING / PONG uit die pakkies, maar hulle behou die verbinding met die bediener (hulle kan dit nie hou nie - en skielik 'n nuwe boodskap). Die toets reproduseer die situasie wanneer 'n groot aantal sulke gebruikers binne 'n halfuur by die stelsel probeer aanmeld. Dit lyk soos 'n strestoets, maar die fokus daarvan is juis op hierdie eerste inset - sodat daar geen mislukkings is nie ('n persoon gebruik nie die stelsel nie, maar dit val reeds af - dit is moeilik om met iets ergers vorendag te kom).

Die intekenaarregistrasie-scenario kom van die eerste bekendstelling af. Ons het 'n strestoets gedoen en was seker dat die stelsel nie in korrespondensie verlangsaam nie. Maar gebruikers het gegaan en die registrasie het begin val teen tyd. By registrasie het ons gebruik / Dev / ewekansige, wat gekoppel is aan die entropie van die sisteem. Die bediener het nie tyd gehad om genoeg entropie op te bou nie, en toe 'n nuwe SecureRandom aangevra is, het dit vir tien sekondes gevries. Daar is baie maniere om uit hierdie situasie te kom, byvoorbeeld: skakel oor na 'n minder veilige /dev/urandom, installeer 'n spesiale bord wat entropie genereer, genereer ewekansige getalle vooraf en stoor dit in die swembad. Ons het die probleem met die swembad tydelik gesluit, maar sedertdien het ons 'n aparte toets uitgevoer om nuwe intekenare te registreer.

As 'n lasgenerator gebruik ons jmeter. Dit weet nie hoe om met 'n websocket te werk nie, 'n inprop is nodig. Die eerste in die soekresultate vir die navraag "jmeter websocket" is artikels van BlazeMeterwaarin hulle aanbeveel inprop deur Maciej Zaleski.

Dis waar ons besluit het om te begin.

Byna onmiddellik na die begin van ernstige toetse, het ons ontdek dat geheuelekkasies in JMeter begin het.

Die inprop is 'n aparte groot storie, met 176 sterre het dit 132 vurke op github. Die skrywer self het hom sedert 2015 nie daartoe verbind nie (ons het dit in 2015 geneem, toe het dit nie agterdog gewek nie), verskeie github-kwessies oor geheuelekkasies, 7 ongeslote trekversoeke.
As jy kies om toets met hierdie inprop te laai, let asseblief op die volgende besprekings:

  1. In 'n multi-threaded omgewing is 'n gereelde LinkedList gebruik, gevolglik het ons gekry NPE tydens looptyd. Dit word opgelos óf deur oor te skakel na ConcurrentLinkedDeque, óf deur gesinchroniseerde blokke. Ons het die eerste opsie vir onsself gekieshttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Geheue lek, verbinding inligting word nie uitgevee wanneer ontkoppel (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. In stroommodus (wanneer die websok nie aan die einde van die monster gesluit is nie, maar verder in die plan gebruik word), werk reaksiepatrone nie (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Dit is een van dié op github. Wat ons gedoen het:

  1. Geneem vurk van Elyran Kogan (@elyrank) - dit maak kwessies 1 en 3 reg
  2. Probleem 2 opgelos
  3. Opgedateerde jetty vanaf 9.2.14 tot 9.3.12
  4. SimpleDateFormat toegedraai in ThreadLocal; SimpleDateFormat nie draad veilig wat lei tot NPE tydens looptyd
  5. Nog 'n geheuelek reggemaak (verbinding verkeerd gesluit met ontkoppeling)

En tog vloei dit!

Geheue het begin eindig nie in 'n dag nie, maar in twee. Daar was glad nie tyd nie, ons het besluit om minder drade te loop, maar op vier agente. Dit moes genoeg gewees het vir ten minste 'n week.

Dis al twee dae...

Nou raak Hazelcast min geheue. Die logs het getoon dat Hazelcast na 'n paar dae van toetsing begin kla oor die gebrek aan geheue, en na 'n rukkie val die groep uitmekaar, en die nodusse gaan een vir een dood. Ons het JVisualVM aan hazelcast gekoppel en die "opwaartse saag" gesien - dit het gereeld die GC genoem, maar kon op geen manier die geheue skoonmaak nie.

Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast

Dit het geblyk dat in hazelcast 3.4, wanneer 'n kaart / multiMap (map.destroy()) uitgevee word, die geheue nie heeltemal bevry is nie:

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

Die fout is nou in 3.5 reggestel, maar dit was destyds 'n probleem. Ons het nuwe multiMap met dinamiese name geskep en volgens ons logika uitgevee. Die kode het iets soos volg gelyk:

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

Bel:

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

multiMap is vir elke intekening geskep en verwyder wanneer dit nie nodig was nie. Ons het besluit dat ons 'n Map sal begin , sal die sleutel die naam van die intekening wees, en die waardes sal sessie-identifiseerders wees (waarmee jy dan gebruikersidentifiseerders kan kry, indien nodig).

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

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

Die kaarte het verbeter.

Hoe en hoekom ons 'n hoogs gelaaide skaalbare diens vir 1C geskryf het: Enterprise: Java, PostgreSQL, Hazelcast

Wat het ons nog oor vragtoetsing geleer

  1. JSR223 moet in groovy geskryf word en kompilasiekas insluit - dit is baie vinniger. Link.
  2. Jmeter-Plugins-kaarte is makliker om te verstaan ​​as standaards. Link.

Oor ons ervaring met Hazelcast

Hazelcast was 'n nuwe produk vir ons, ons het vanaf weergawe 3.4.1 daarmee begin werk, nou het ons weergawe 3.9.2 op ons produksiebediener (ten tyde van hierdie skrywe is die nuutste weergawe van Hazelcast 3.10).

ID generasie

Ons het begin met heelgetal identifiseerders. Kom ons verbeel ons dat ons nog 'n Long nodig het vir 'n nuwe entiteit. Volgorde in die databasis is nie geskik nie, tabelle is betrokke by sharding - dit blyk dat daar 'n boodskap ID=1 in DB1 en 'n boodskap ID=1 in DB2 is, jy kan nie hierdie ID in Elasticsearch plaas nie, in Hazelcast ook, maar die ergste is as jy data van twee databasisse na een wil verminder (byvoorbeeld om te besluit dat een databasis genoeg is vir hierdie intekenare). Jy kan verskeie AtomicLongs in Hazelcast hê en die toonbank daar hou, dan is die prestasie om 'n nuwe ID te kry incrementAndGet plus die tyd om navraag in Hazelcast te doen. Maar Hazelcast het iets meer optimaal - FlakeIdGenerator. Wanneer kontak gemaak word, kry elke kliënt 'n reeks ID's, byvoorbeeld die eerste een - van 1 tot 10 000, die tweede - van 10 001 tot 20 000, ensovoorts. Nou kan die kliënt nuwe identifiseerders op sy eie uitreik totdat die reeks wat aan hom uitgereik is, eindig. Werk vinnig, maar die herbegin van die toepassing (en die Hazelcast-kliënt) begin 'n nuwe reeks - vandaar die oorslaan, ens. Boonop is dit nie baie duidelik vir ontwikkelaars hoekom ID's heelgetalle is nie, maar dit is so baie in stryd. Ons het alles geweeg en na UUID's oorgeskakel.

Terloops, vir diegene wat soos Twitter wil wees, is daar so 'n Snowcast-biblioteek - dit is 'n implementering van Snowflake bo-op Hazelcast. Jy kan hier sien:

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

Maar ons het nog nie daarby uitgekom nie.

TransactionalMap.vervang

Nog 'n verrassing: TransactionalMap.replace werk nie. Hier is 'n toets:

@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

Ek moes my eie vervang skryf met 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);
}

Toets nie net gereelde datastrukture nie, maar ook hul transaksionele weergawes. Dit gebeur dat IMap werk, maar TransactionalMap bestaan ​​nie meer nie.

Heg nuwe JAR aan sonder stilstand

Eerstens het ons besluit om voorwerpe van ons klasse aan Hazelcast te skryf. Ons het byvoorbeeld 'n toepassingsklas, ons wil dit stoor en lees. Stoor:

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

Ons lees:

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

Alles werk. Toe het ons besluit om 'n indeks in Hazelcast te bou om dit te soek:

map.addIndex("subscriberId", false);

En toe hulle 'n nuwe entiteit geskryf het, het hulle 'n ClassNotFoundException begin ontvang. Hazelcast het probeer om by die indeks te voeg, maar het niks van ons klas geweet nie en wou 'n JAR met hierdie klas daarin sit. Ons het net dit gedoen, alles het gewerk, maar 'n nuwe probleem het verskyn: hoe om die JAR op te dateer sonder om die groep heeltemal te stop? Hazelcast haal nie 'n nuwe JAR op 'n per-node-opdatering op nie. Op hierdie stadium het ons besluit dat ons sonder indeksopsoeke kan lewe. As jy Hazelcast as 'n sleutelwaardewinkel gebruik, sal alles dan werk? Nie regtig nie. Hier weer verskillende gedrag van IMap en TransactionalMap. Waar IMap nie omgee nie, gooi TransactionalMap 'n fout.

IMap. Ons skryf 5000 voorwerpe neer, ons lees. Alles word verwag.

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

Maar dit werk nie in 'n transaksie nie, ons kry 'n 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();
        }
    });
}

In 3.8 het die Gebruikersklas-ontplooiingsmeganisme verskyn. Jy kan een hoofnodus aanwys en die JAR-lêer daarop bywerk.

Nou het ons ons benadering heeltemal verander: ons serialiseer self na JSON en stoor na Hazelcast. Hazelcast hoef nie die struktuur van ons klasse te ken nie, en ons kan opdateer sonder stilstand. Weergawe van domeinvoorwerpe word deur die toepassing beheer. Verskillende weergawes van die toepassing kan op dieselfde tyd geloods word, en dit is moontlik dat 'n nuwe toepassing voorwerpe met nuwe velde skryf, terwyl die ou een nog nie van hierdie velde weet nie. En terselfdertyd lees die nuwe toepassing die voorwerpe wat deur die ou toepassing geskryf is wat nie nuwe velde het nie. Ons hanteer sulke situasies binne die toepassing, maar vir eenvoud verander of verwyder ons nie die velde nie, ons brei net die klasse uit deur nuwe velde by te voeg.

Hoe ons hoë werkverrigting lewer

Vier reise na Hazelcast is goed, twee reise na die databasis is sleg

Om data in die kas te soek is altyd beter as in die databasis, maar jy wil ook nie onopgeëiste rekords stoor nie. Om te besluit wat om te kas, word oorgelaat tot die laaste stadium van ontwikkeling. Wanneer die nuwe funksionaliteit gekodeer is, aktiveer ons aanteken van alle navrae in PostgreSQL (log_min_duration_statement na 0) en voer lastoetsing vir 20 minute uit. Hulpprogramme soos pgFouine en pgBadger kan analitiese verslae bou gebaseer op die versamelde logs. In verslae soek ons ​​hoofsaaklik na stadige en gereelde navrae. Vir stadige navrae bou ons 'n uitvoeringsplan (VERDUIDELIK) en evalueer of so 'n navraag versnel kan word. Gereelde versoeke vir dieselfde invoer pas goed in die kas. Ons probeer om navrae "plat" te hou, een tabel per navraag.

Uitbuiting

CB as 'n aanlyndiens is in die lente van 2017 bekendgestel, aangesien 'n aparte CB-produk in November 2017 vrygestel is (destyds in beta-weergawe).

Vir meer as 'n jaar se bedryf was daar geen ernstige probleme in die werking van die CB aanlyn diens nie. Ons monitor die aanlyn diens deur Zabbix, versamel en ontplooi vanaf Bamboes.

Die CB-bedienerverspreiding kom in die vorm van inheemse pakkette: RPM, DEB, MSI. Plus, vir Windows bied ons 'n enkele installeerder in die vorm van 'n enkele EXE wat die bediener, Hazelcast en Elasticsearch op een masjien installeer. Aanvanklik het ons hierdie weergawe van die installasie "demo" genoem, maar nou het dit duidelik geword dat dit die gewildste ontplooiingsopsie is.

Bron: will.com

Voeg 'n opmerking