Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast

Tässä artikkelissa puhumme siitä, miten ja miksi kehitimme Vuorovaikutusjärjestelmä – mekanismi, joka siirtää tietoa asiakassovellusten ja 1C:Enterprise-palvelimien välillä – tehtävän asettamisesta arkkitehtuurin ja toteutustietojen läpi miettimiseen.

Interaction System (jäljempänä SV) on hajautettu, vikasietoinen viestintäjärjestelmä, jonka toimitus on taattu. SV on suunniteltu korkean kuormituksen ja korkean skaalautuvuuden palveluksi, joka on saatavilla sekä online-palveluna (1C:n tarjoama) että massatuotantotuotteena, joka voidaan ottaa käyttöön omissa palvelintiloissasi.

SV käyttää hajautettua tallennustilaa Hazelcast ja hakukone Elasticsearch. Puhumme myös Javasta ja PostgreSQL:n vaakaskaalauksesta.
Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast

Ongelma

Tehdäkseni selväksi, miksi loimme vuorovaikutusjärjestelmän, kerron sinulle hieman siitä, kuinka yrityssovellusten kehittäminen 1C:ssä toimii.

Aluksi vähän meistä niille, jotka eivät vielä tiedä mitä teemme :) Olemme tekemässä 1C:Enterprise-teknologiaalustaa. Alusta sisältää yrityssovellusten kehitystyökalun sekä ajonajan, jonka avulla yrityssovellukset voivat toimia useissa alustoissa.

Asiakas-palvelin kehitysparadigma

1C:Enterprisellä luodut yrityssovellukset toimivat kolmella tasolla asiakas-palvelin arkkitehtuuri “DBMS – sovelluspalvelin – asiakas”. Sovelluskoodi kirjoitettuna sisäänrakennettu 1C-kieli, voidaan suorittaa sovelluspalvelimella tai asiakaskoneella. Kaikki työ sovellusobjektien (hakemistot, asiakirjat jne.) kanssa sekä tietokannan lukeminen ja kirjoittaminen suoritetaan vain palvelimella. Palvelimelle on toteutettu myös lomakkeiden ja komentorajapinnan toiminnallisuus. Asiakas suorittaa lomakkeiden vastaanottamisen, avaamisen ja näyttämisen, "kommunikoinnin" käyttäjän kanssa (varoitukset, kysymykset...), pieniä laskelmia nopeaa vastausta vaativissa lomakkeissa (esim. kertomalla hinnan määrällä), työskentelyä paikallisten tiedostojen kanssa, laitteiden kanssa työskenteleminen.

Sovelluskoodissa proseduurien ja funktioiden otsikoiden tulee osoittaa selvästi, missä koodi suoritetaan - käyttäen &AtClient / &AtServer-käskyjä (&AtClient / &AtServer kielen englanninkielisessä versiossa). 1C-kehittäjät korjaavat nyt minut sanomalla, että direktiivit ovat itse asiassa больше, mutta se ei ole meille nyt tärkeää.

Voit kutsua palvelinkoodia asiakaskoodista, mutta et voi kutsua asiakaskoodia palvelinkoodista. Tämä on perustavanlaatuinen rajoitus, jonka teimme useista syistä. Erityisesti siksi, että palvelinkoodi on kirjoitettava siten, että se suoritetaan samalla tavalla riippumatta siitä, missä sitä kutsutaan - asiakkaalta tai palvelimelta. Ja jos palvelinkoodia kutsutaan toisesta palvelinkoodista, asiakasta sellaisenaan ei ole. Ja koska palvelinkoodin suorittamisen aikana sitä kutsunut asiakas voisi sulkea, poistua sovelluksesta, eikä palvelimella olisi enää kenellekään soittaa.

Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast
Koodi, joka käsittelee painikkeen napsautuksen: palvelinproseduurin kutsuminen asiakkaalta toimii, asiakasproseduurin kutsuminen palvelimelta ei

Tämä tarkoittaa, että jos haluamme lähettää jonkin viestin palvelimelta asiakassovellukselle, esimerkiksi että "pitkän ajan" raportin luominen on päättynyt ja raporttia voi tarkastella, meillä ei ole sellaista menetelmää. Sinun on käytettävä temppuja, esimerkiksi pollattava ajoittain palvelimelta asiakaskoodia. Mutta tämä lähestymistapa kuormittaa järjestelmää tarpeettomilla puheluilla, eikä se yleensä näytä kovin tyylikkäältä.

Ja tarvetta on myös esimerkiksi puhelun saapuessa SIP- soitaessasi ilmoita tästä asiakassovellukselle, jotta se löytää soittajan numeron avulla sen vastapuolitietokannasta ja näyttää käyttäjälle soittavan vastapuolen tiedot. Tai esimerkiksi tilauksen saapuessa varastoon, ilmoita tästä asiakkaan asiakassovellukselle. Yleensä on monia tapauksia, joissa tällainen mekanismi olisi hyödyllinen.

Itse tuotanto

Luo viestimekanismi. Nopea, luotettava, taattu toimitus, mahdollisuus etsiä viestejä joustavasti. Toteuta mekanismin perusteella 1C-sovelluksissa toimiva messenger (viestit, videopuhelut).

Suunnittele järjestelmä vaakasuunnassa skaalautuvaksi. Kasvava kuormitus on katettava lisäämällä solmujen määrää.

Реализация

Päätimme olla integroimatta SV:n palvelinosaa suoraan 1C:Enterprise-alustaan, vaan toteuttaa sen erillisenä tuotteena, jonka API voidaan kutsua 1C-sovellusratkaisujen koodista. Tämä tehtiin useista syistä, joista tärkein oli se, että halusin mahdollistaa viestien vaihdon eri 1C-sovellusten välillä (esimerkiksi kaupanhallinnan ja kirjanpidon välillä). Eri 1C-sovellukset voivat toimia 1C:Enterprise-alustan eri versioissa, sijaita eri palvelimilla jne. Tällaisissa olosuhteissa SV:n toteuttaminen erillisenä tuotteena, joka sijaitsee 1C-asennusten "sivulla", on optimaalinen ratkaisu.

Joten päätimme tehdä SV:n erillisenä tuotteena. Suosittelemme, että pienet yritykset käyttävät CB-palvelinta, jonka olemme asentaneet pilveen (wss://1cdialog.com), jotta vältytään palvelimen paikalliseen asennukseen ja määritykseen liittyviltä yleiskustannuksilta. Suuret asiakkaat saattavat pitää suositeltavaa asentaa oma CB-palvelin tiloihinsa. Käytimme samanlaista lähestymistapaa pilvi SaaS-tuotteessamme 1cFresh – se valmistetaan massatuotantotuotteena asiakkaiden toimipisteille asennettavaksi, ja se otetaan käyttöön myös pilvessämme https://1cfresh.com/.

Sovellus

Kuorman ja vikasietoisuuden jakamiseksi emme ota käyttöön yhtä Java-sovellusta, vaan useita, ja niiden edessä on kuormituksen tasapainotin. Jos sinun on siirrettävä viesti solmusta toiseen, käytä julkaisu/tilaus -toimintoa Hazelcastissa.

Viestintä asiakkaan ja palvelimen välillä tapahtuu websocketin kautta. Se sopii hyvin reaaliaikaisiin järjestelmiin.

Hajautettu välimuisti

Valitsimme Redisin, Hazelcastin ja Ehcachen välillä. On vuosi 2015. Redis julkaisi juuri uuden klusterin (liian uusi, pelottava), siellä on Sentinel, jossa on paljon rajoituksia. Ehcache ei osaa koota klusteriksi (tämä toiminto ilmestyi myöhemmin). Päätimme kokeilla sitä Hazelcast 3.4:n kanssa.
Hazelcast kootaan klusteriksi pakkauksesta otettuna. Yksisolmun tilassa se ei ole kovin hyödyllinen ja sitä voidaan käyttää vain välimuistina - se ei osaa tyhjentää tietoja levylle, jos menetät ainoan solmun, menetät tiedot. Käytämme useita Hazelcasteja, joiden välillä varmuuskopioimme tärkeitä tietoja. Emme varmuuskopioi välimuistia – emme välitä siitä.

Meille Hazelcast on:

  • Käyttäjäistuntojen tallennus. Kestää kauan mennä tietokantaan istuntoa varten joka kerta, joten laitamme kaikki istunnot Hazelcastiin.
  • Kätkö. Jos etsit käyttäjäprofiilia, tarkista välimuisti. Kirjoitit uuden viestin - laita se välimuistiin.
  • Aiheet sovellusinstanssien väliseen viestintään. Solmu luo tapahtuman ja sijoittaa sen Hazelcast-aiheeseen. Muut tähän aiheeseen tilatut sovellussolmut vastaanottavat ja käsittelevät tapahtuman.
  • Klusterin lukot. Luomme esimerkiksi keskustelun käyttämällä ainutlaatuista avainta (yksittäinen keskustelu 1C-tietokannassa):

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

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

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

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

Tarkistimme, ettei kanavaa ole. Otimme lukon, tarkistimme sen uudelleen ja loimme sen. Jos et tarkista lukkoa lukon ottamisen jälkeen, on mahdollista, että toinenkin säiettä tarkisti sillä hetkellä ja yrittää nyt luoda saman keskustelun - mutta se on jo olemassa. Et voi lukita synkronoidun tai tavallisen java-lukon avulla. Tietokannan kautta - se on hidasta, ja se on sääli tietokannasta; Hazelcastin kautta - sitä tarvitset.

DBMS:n valinta

Meillä on laaja ja menestyksekäs kokemus työskentelystä PostgreSQL:n kanssa ja yhteistyöstä tämän DBMS:n kehittäjien kanssa.

Se ei ole helppoa PostgreSQL-klusterin kanssa - on XL, XC, Citus, mutta yleensä nämä eivät ole NoSQL:itä, jotka skaalautuvat laatikosta pois. Emme pitäneet NoSQL:ää päämuistina, riitti, että otimme Hazelcastin, jonka kanssa emme olleet aiemmin työskennelleet.

Jos haluat skaalata relaatiotietokantaa, se tarkoittaa sirpalointia. Kuten tiedät, shardingilla jaamme tietokannan erillisiin osiin, jotta jokainen niistä voidaan sijoittaa erilliseen palvelimeen.

Ensimmäisessä sharding-versiossa oletettiin kykyä jakaa jokainen sovelluksemme taulukko eri palvelimille eri suhteissa. Palvelimella A on paljon viestejä - siirretään osa tästä taulukosta palvelimelle B. Tämä päätös yksinkertaisesti huusi ennenaikaisesta optimoinnista, joten päätimme rajoittua usean vuokralaisen lähestymistapaan.

Nettisivuilta voit lukea esimerkiksi monivuokralaisesta Citus Data.

SV:llä on käsitteet sovellus ja tilaaja. Sovellus on yrityssovelluksen, kuten ERP:n tai Accountingin, erityinen asennus käyttäjineen ja yritystietoineen. Tilaaja on organisaatio tai henkilö, jonka puolesta sovellus on rekisteröity SV-palvelimelle. Tilaajalle voi olla rekisteröitynä useita sovelluksia, jotka voivat vaihtaa viestejä keskenään. Tilaajasta tuli vuokralainen järjestelmässämme. Useiden tilaajien viestit voivat sijaita yhdessä fyysisessä tietokannassa; jos näemme, että tilaaja on alkanut tuottaa paljon liikennettä, siirrämme sen erilliseen fyysiseen tietokantaan (tai jopa erilliseen tietokantapalvelimeen).

Meillä on päätietokanta, johon on tallennettu reititystaulukko, joka sisältää tiedot kaikkien tilaajatietokantojen sijainnista.

Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast

Jotta päätietokanta ei muodostu pullonkaulaksi, pidämme reititystaulukon (ja muut usein tarvittavat tiedot) välimuistissa.

Jos tilaajan tietokanta alkaa hidastua, leikkaamme sen sisällä osioihin. Käytämme muissa projekteissa pg_pathman.

Koska käyttäjien viestien menettäminen on huono asia, ylläpidämme tietokantojamme replikoilla. Synkronisten ja asynkronisten replikoiden yhdistelmän avulla voit vakuuttaa itsesi päätietokannan katoamisen varalta. Viesti katoaa vain, jos ensisijainen tietokanta ja sen synkroninen replika epäonnistuvat samanaikaisesti.

Jos synkroninen replika katoaa, asynkroninen replika muuttuu synkroniseksi.
Jos päätietokanta katoaa, synkronisesta replikasta tulee päätietokanta ja asynkronisesta replikästä synkroninen replika.

Elasticsearch hakua varten

Koska muun muassa SV on myös sanansaattaja, vaatii se nopeaa, kätevää ja joustavaa morfologiaa huomioivaa hakua käyttämällä epätarkkoja osumia. Päätimme olla keksimättä pyörää uudelleen ja käyttää ilmaista Elasticsearch-hakukonetta, joka on luotu kirjaston perusteella Lucene. Käytämme myös Elasticsearchia klusterissa (master – data – data) poistaaksemme ongelmat sovellussolmujen epäonnistuessa.

Githubista löysimme Venäjän morfologia-laajennus Elasticsearchille ja käytä sitä. Elasticsearch-hakemistoon tallennamme sanajuuret (jotka laajennus määrittää) ja N-grammia. Kun käyttäjä syöttää tekstiä hakuun, etsimme kirjoitettua tekstiä N-grammien joukosta. Kun sana "tekstit" tallennetaan hakemistoon, se jaetaan seuraaviin N-grammeihin:

[ne, tek, tex, teksti, tekstit, ek, ex, ext, tekstit, ks, kst, ksty, st, sty, sinä],

Ja sanan "teksti" juurijuuri myös säilytetään. Tämän lähestymistavan avulla voit etsiä sanan alusta, keskeltä ja lopusta.

Yleiskuva

Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast
Toista kuva artikkelin alusta, mutta selityksillä:

  • Balancer alttiina Internetissä; meillä on nginx, se voi olla mikä tahansa.
  • Java-sovellusinstanssit kommunikoivat keskenään Hazelcastin kautta.
  • Käytämme verkkopistorasian kanssa työskentelyyn Netty.
  • Java-sovellus on kirjoitettu Java 8:lla ja koostuu paketeista OSGi. Suunnitelmiin kuuluu siirtyminen Java 10:een ja siirtyminen moduuleihin.

Kehitys ja testaus

SV:tä kehitettäessä ja testattaessa törmäsimme käyttämiemme tuotteiden mielenkiintoisiin ominaisuuksiin.

Kuormitustestaus ja muistivuotoja

Jokaisen SV-julkaisun julkaisuun sisältyy kuormitustestaus. Se onnistuu, kun:

  • Testi toimi useita päiviä, eikä palvelussa ollut vikoja
  • Avaintoimintojen vasteaika ei ylittänyt mukavaa kynnystä
  • Suorituskyvyn heikkeneminen edelliseen versioon verrattuna on enintään 10 %

Täytämme testitietokannan tiedoilla - tätä varten saamme tiedot aktiivisimmasta tilaajasta tuotantopalvelimelta, kerromme sen numerot 5:llä (viestien, keskustelujen, käyttäjien määrä) ja testaamme sitä tällä tavalla.

Suoritamme vuorovaikutusjärjestelmän kuormitustestauksen kolmessa kokoonpanossa:

  1. stressitesti
  2. Vain liitännät
  3. Tilaajan rekisteröinti

Stressitestin aikana käynnistämme useita satoja säikeitä, ja ne lataavat järjestelmää pysähtymättä: kirjoitetaan viestejä, luodaan keskusteluja, vastaanotetaan viestilista. Simuloimme tavallisten käyttäjien toimintoja (saat listan lukemattomista viesteistäni, kirjoitamme jollekin) ja ohjelmistoratkaisuja (lähetämme eri kokoonpanon paketin, käsittelemme hälytyksen).

Esimerkiksi stressitestin osa näyttää tältä:

  • Käyttäjä kirjautuu sisään
    • Pyytää lukemattomia keskustelujasi
    • 50 % todennäköisyydellä lukee viestejä
    • 50 % todennäköisyydellä tekstiviestiä
    • Seuraava käyttäjä:
      • Sillä on 20 % mahdollisuus luoda uusi keskustelu
      • Valitsee satunnaisesti minkä tahansa keskusteluistaan
      • Menee sisään
      • Pyytää viestejä, käyttäjäprofiileja
      • Luo viisi satunnaisille käyttäjille osoitettua viestiä tästä keskustelusta
      • Poistuu keskustelusta
      • Toistuu 20 kertaa
      • Kirjautuu ulos, palaa käsikirjoituksen alkuun

    • Chatbot tulee järjestelmään (emuloi viestejä sovelluskoodista)
      • 50 %:lla mahdollisuus luoda uusi kanava tiedonvaihtoon (erityinen keskustelu)
      • 50 % todennäköisyydellä kirjoittaa viestin jollekin olemassa olevista kanavista

Vain yhteydet -skenaario ilmestyi syystä. On tilanne: käyttäjät ovat yhdistäneet järjestelmän, mutta eivät ole vielä osallistuneet. Jokainen käyttäjä käynnistää tietokoneen kello 09:00 aamulla, muodostaa yhteyden palvelimeen ja pysyy hiljaa. Nämä kaverit ovat vaarallisia, heitä on monia - ainoat paketit, joita heillä on, ovat PING/PONG, mutta he säilyttävät yhteyden palvelimeen (he eivät voi pitää sitä yllä - entä jos tulee uusi viesti). Testi toistaa tilanteen, jossa suuri määrä tällaisia ​​käyttäjiä yrittää kirjautua sisään järjestelmään puolessa tunnissa. Se on samanlainen kuin stressitesti, mutta sen painopiste on juuri tässä ensimmäisessä syötössä - jotta ei tule virheitä (henkilö ei käytä järjestelmää, ja se jo putoaa - on vaikea ajatella jotain pahempaa).

Tilaajan rekisteröintiohjelma alkaa ensimmäisestä käynnistämisestä. Teimme stressitestin ja olimme varmoja, että järjestelmä ei hidastunut kirjeenvaihdon aikana. Mutta käyttäjiä tuli ja rekisteröinti alkoi epäonnistua aikakatkaisun vuoksi. Rekisteröityessämme käytimme / Dev / random, joka liittyy järjestelmän entropiaan. Palvelimella ei ollut aikaa kerätä tarpeeksi entropiaa ja kun uusi SecureRandom pyydettiin, se jumiutui kymmeniksi sekunneiksi. Tästä tilanteesta on monia ulospääsyä, esimerkiksi: vaihda vähemmän turvalliseen /dev/urandom-tilaan, asenna erityinen entropiaa luova kortti, luo satunnaislukuja etukäteen ja tallenna ne pooliin. Sulkimme poolin ongelman väliaikaisesti, mutta siitä lähtien olemme tehneet erillistä testiä uusien tilaajien rekisteröimiseksi.

Käytämme kuormitusgeneraattorina JMeter. Se ei osaa työskennellä websocketin kanssa; se tarvitsee laajennuksen. Ensimmäiset hakutuloksissa kyselylle "jmeter websocket" ovat: BlazeMeterin artikkeleita, jotka suosittelevat liitännäinen Maciej Zaleski.

Siitä päätimme aloittaa.

Melkein heti vakavan testauksen aloittamisen jälkeen huomasimme, että JMeter alkoi vuotaa muistia.

Laajennus on erillinen iso tarina; sillä on 176 tähteä ja 132 haarukkaa githubissa. Kirjoittaja itse ei ole sitoutunut siihen vuodesta 2015 lähtien (otimme sen vuonna 2015, silloin se ei herättänyt epäilyksiä), useita muistivuotoja koskevia github-ongelmia, 7 sulkematonta vetopyyntöä.
Jos päätät suorittaa kuormitustestauksen tällä laajennuksella, kiinnitä huomiota seuraaviin keskusteluihin:

  1. Monisäikeisessä ympäristössä käytettiin tavallista LinkedList-listaa, ja tulos oli NPE ajon aikana. Tämä voidaan ratkaista joko vaihtamalla ConcurrentLinkedDequeen tai synkronoiduilla lohkoilla. Valitsimme itsellemme ensimmäisen vaihtoehdon (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Muistivuoto; kun yhteys katkaistaan, yhteystietoja ei poisteta (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Suoratoistotilassa (kun verkkopistorasiaa ei suljeta näytteen lopussa, vaan sitä käytetään myöhemmin suunnitelmassa) vastausmallit eivät toimi (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Tämä on yksi niistä githubissa. Mitä me teimme:

  1. kesti haarukka Elyran Kogan (@elyrank) – se korjaa ongelmat 1 ja 3
  2. Ratkaistu ongelma 2
  3. Päivitetty laituri 9.2.14 - 9.3.12
  4. Wrapped SimpleDateFormat ThreadLocalissa; SimpleDateFormat ei ole säikeen varma, mikä johti NPE:hen suorituksen aikana
  5. Korjattu toinen muistivuoto (yhteys suljettiin väärin, kun se irrotettiin)

Ja silti se virtaa!

Muisti alkoi loppua ei päivässä vaan kahdessa. Aikaa ei ollut enää jäljellä, joten päätimme käynnistää vähemmän säikeitä, mutta neljällä agentilla. Tämän olisi pitänyt riittää ainakin viikoksi.

Kaksi päivää on kulunut...

Nyt Hazelcastin muisti on loppumassa. Lokit osoittivat, että muutaman päivän testauksen jälkeen Hazelcast alkoi valittaa muistin puutteesta, ja jonkin ajan kuluttua klusteri hajosi ja solmut kuolivat edelleen yksitellen. Yhdistimme JVisualVM:n hazelcastiin ja näimme "nousevan sahan" - se kutsui säännöllisesti GC:tä, mutta ei pystynyt tyhjentämään muistia.

Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast

Kävi ilmi, että hazelcast 3.4:ssä, kun karttaa / multikartta poistetaan (map.destroy()), muisti ei vapaudu kokonaan:

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

Vika on nyt korjattu versiossa 3.5, mutta se oli ongelma silloin. Loimme uudet multiMapsit dynaamisilla nimillä ja poistimme ne logiikkamme mukaan. Koodi näytti tältä:

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

Puhelu:

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

MultiMap luotiin jokaiselle tilaukselle ja poistettiin, kun sitä ei tarvittu. Päätimme käynnistää Mapin , avain on tilauksen nimi ja arvot ovat istuntotunnisteita (joista saat tarvittaessa käyttäjätunnukset).

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

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

Kaaviot ovat parantuneet.

Miten ja miksi kirjoitimme suuren kuormituksen skaalautuvan palvelun 1C:lle: Enterprise: Java, PostgreSQL, Hazelcast

Mitä muuta olemme oppineet kuormitustestauksesta?

  1. JSR223 on kirjoitettava groovy-muodossa ja siinä on oltava käännösvälimuisti - se on paljon nopeampi. Linkki.
  2. Jmeter-Plugins-kaavioita on helpompi ymmärtää kuin tavallisia. Linkki.

Tietoja kokemuksestamme Hazelcastista

Hazelcast oli meille uusi tuote, aloitimme työskennellä sen kanssa versiosta 3.4.1, nyt tuotantopalvelimellamme on versio 3.9.2 (kirjoitushetkellä Hazelcastin uusin versio on 3.10).

ID:n luominen

Aloitimme kokonaislukutunnisteilla. Kuvitellaan, että tarvitsemme toisen Longin uudelle kokonaisuudelle. Tietokannan järjestys ei sovi, taulukot ovat osallisena shardingissa - käy ilmi, että DB1:ssä on viesti ID=1 ja DB1:ssa viesti ID=2, et voi laittaa tätä tunnusta Elasticsearchiin eikä Hazelcastiin , mutta pahinta on, jos haluat yhdistää kahden tietokannan tiedot yhdeksi (esimerkiksi päättää, että yksi tietokanta riittää näille tilaajille). Voit lisätä useita AtomicLongeja Hazelcastiin ja pitää laskurin siellä, jolloin uuden tunnuksen saaminen on incrementAndGet plus aikaa Hazelcastin pyyntöön. Mutta Hazelcastilla on jotain optimaalisempaa - FlakeIdGenerator. Kun otetaan yhteyttä jokaiseen asiakkaaseen, hänelle annetaan tunnusalue, esimerkiksi ensimmäinen - 1 - 10 000, toinen - 10 001 - 20 000 ja niin edelleen. Nyt asiakas voi antaa uusia tunnisteita itse, kunnes sille annettu alue päättyy. Se toimii nopeasti, mutta kun käynnistät sovelluksen (ja Hazelcast-asiakkaan) uudelleen, uusi sarja alkaa - tästä syystä ohitukset jne. Lisäksi kehittäjät eivät todellakaan ymmärrä, miksi tunnukset ovat kokonaislukuja, mutta ne ovat niin epäjohdonmukaisia. Punnitsimme kaiken ja vaihdoimme UUID:iin.

Muuten, niille, jotka haluavat olla kuin Twitterissä, on tällainen Snowcast-kirjasto - tämä on Snowflaken toteutus Hazelcastin päällä. Voit katsoa sen täältä:

github.com/noctarius/snowcast
github.com/twitter/lumihiutale

Mutta emme ole päässeet siihen enää.

TransactionMap.replace

Toinen yllätys: TransactionalMap.replace ei toimi. Tässä on testi:

@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

Minun piti kirjoittaa oma korvaus käyttämällä getForUpdatea:

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

Testaa tavallisten tietorakenteiden lisäksi myös niiden tapahtumaversioita. Sattuu niin, että IMap toimii, mutta TransactionalMap ei ole enää olemassa.

Aseta uusi JAR ilman seisokkeja

Ensin päätimme tallentaa luokkamme esineitä Hazelcastiin. Meillä on esimerkiksi Sovellusluokka, haluamme tallentaa ja lukea sen. Tallentaa:

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

Me luimme:

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

Kaikki toimii. Sitten päätimme rakentaa Hazelcastiin indeksin etsiäksemme seuraavilla tavoilla:

map.addIndex("subscriberId", false);

Ja kun kirjoitettiin uutta kokonaisuutta, he alkoivat saada ClassNotFoundExceptionin. Hazelcast yritti lisätä hakemistoon, mutta ei tiennyt mitään luokastamme ja halusi, että sille toimitettaisiin tällä luokalla varustettu JAR. Teimme juuri niin, kaikki toimi, mutta uusi ongelma ilmaantui: kuinka päivittää JAR pysäyttämättä klusteria kokonaan? Hazelcast ei poimi uutta JAR:ia solmukohtaisen päivityksen aikana. Tässä vaiheessa päätimme, että voimme elää ilman hakemistohakua. Loppujen lopuksi, jos käytät Hazelcastia avainarvovarastona, kaikki toimii? Ei oikeastaan. Tässäkin IMap- ja TransactionalMap-sovellukset toimivat eri tavalla. Jos IMap ei välitä, TransactionalMap antaa virheilmoituksen.

IMap. Kirjoitamme 5000 esinettä, luemme niitä. Kaikki on odotettavissa.

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

Mutta se ei toimi tapahtumassa, saamme ClassNotFoundExceptionin:

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

3.8:ssa ilmestyi User Class Deployment -mekanismi. Voit määrittää yhden pääsolmun ja päivittää sen JAR-tiedoston.

Nyt olemme muuttaneet lähestymistapaamme täysin: sarjoimme sen itse JSON-muotoon ja tallennamme sen Hazelcastiin. Hazelcastin ei tarvitse tietää kurssiemme rakennetta, ja voimme päivittää ilman seisokkeja. Sovellus hallitsee verkkoalueen objektien versioimista. Sovelluksen eri versiot voivat olla käynnissä samanaikaisesti, ja on mahdollista tilanne, jossa uusi sovellus kirjoittaa objekteja uusilla kentillä, mutta vanha ei vielä tiedä näistä kentistä. Ja samaan aikaan uusi sovellus lukee vanhan sovelluksen kirjoittamia objekteja, joissa ei ole uusia kenttiä. Käsittelemme tällaiset tilanteet sovelluksen sisällä, mutta yksinkertaisuuden vuoksi emme muuta tai poista kenttiä, vaan laajennamme luokkia lisäämällä uusia kenttiä.

Kuinka varmistamme korkean suorituskyvyn

Neljä matkaa Hazelcastiin - hyvä, kaksi tietokantaan - huono

Tietojen välimuistiin siirtyminen on aina parempi kuin tietokantaan siirtyminen, mutta et myöskään halua tallentaa käyttämättömiä tietueita. Jätämme päätöksen välimuistiin tallentamisesta viimeiseen kehitysvaiheeseen. Kun uusi toiminto on koodattu, otamme käyttöön kaikkien kyselyiden kirjaamisen PostgreSQL:ssä (log_min_duration_statement arvoon 0) ja suoritamme kuormitustestauksen 20 minuutin ajan. Kerättyjen lokien avulla apuohjelmat, kuten pgFouine ja pgBadger, voivat rakentaa analyyttisiä raportteja. Raporteissa etsimme ensisijaisesti hitaita ja toistuvia kyselyitä. Hitaille kyselyille rakennamme suoritussuunnitelman (EXPLAIN) ja arvioimme, voidaanko tällaista kyselyä nopeuttaa. Toistuvat pyynnöt samalle syöttödatalle mahtuvat hyvin välimuistiin. Pyrimme pitämään kyselyt "litteinä", yksi taulukko kyselyä kohden.

hyväksikäyttö

SV verkkopalveluna otettiin käyttöön keväällä 2017 ja erillisenä tuotteena SV julkaistiin marraskuussa 2017 (silloin beta-versiossa).

Yli vuoden aikana CB-verkkopalvelun toiminnassa ei ole ollut vakavia ongelmia. Seuraamme verkkopalvelua kautta Zabbix, kerää ja ota käyttöön Bambu.

SV-palvelinjakelu toimitetaan alkuperäisten pakettien muodossa: RPM, DEB, MSI. Lisäksi Windowsille tarjoamme yhden asennusohjelman yhden EXE:n muodossa, joka asentaa palvelimen, Hazelcastin ja Elasticsearchin yhdelle koneelle. Alun perin kutsuimme tätä asennuksen versiota "demo"-versioksi, mutta nyt on käynyt selväksi, että tämä on suosituin käyttöönottovaihtoehto.

Lähde: will.com

Lisää kommentti