Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast

U ovom članku ćemo govoriti o tome kako i zašto smo se razvili Interakcioni sistem - mehanizam koji prenosi informacije između klijentskih aplikacija i 1C: Enterprise servera - od postavljanja zadatka do promišljanja arhitekture i detalja implementacije.

Interakcioni sistem (u daljem tekstu - CB) je distribuirani sistem za razmenu poruka otporan na greške sa garantovanom isporukom. CB je dizajniran kao usluga visokog opterećenja sa visokom skalabilnosti, dostupna i kao online usluga (obezbeđena od 1C) i kao proizvodni proizvod koji se može primeniti na sopstvenim serverskim kapacitetima.

SW koristi distribuiranu pohranu hazelcast i pretraživač Elasticsearch. Takođe ćemo govoriti o Javi i kako horizontalno skaliramo PostgreSQL.
Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Izjava o problemu

Da bi bilo jasno zašto smo napravili Interaction System, reći ću vam malo o tome kako funkcionira razvoj poslovnih aplikacija u 1C.

Prvo, malo o nama za one koji još ne znaju šta radimo :) Razvijamo tehnološku platformu 1C:Enterprise. Platforma uključuje alat za razvoj poslovnih aplikacija, kao i runtime koji omogućava poslovnim aplikacijama da rade u međuplatformskom okruženju.

Paradigma razvoja klijent-server

Poslovne aplikacije kreirane na 1C: Enterprise rade na tri nivoa klijent-server arhitektura "DBMS - aplikacijski server - klijent". Kod aplikacije upisan ugrađeni jezik 1C, može raditi na poslužitelju aplikacija ili na klijentu. Sav rad sa objektima aplikacije (direktoriji, dokumenti itd.), kao i čitanje i pisanje baze podataka, obavlja se samo na serveru. Funkcionalnost formulara i komandnog interfejsa je takođe implementirana na serveru. Na klijentu se primaju, otvaraju i prikazuju obrasci, „komunikacija“ sa korisnikom (upozorenja, pitanja...), mala kalkulacija u obrascima koja zahtevaju brz odgovor (npr. množenje cene sa količinom), rad sa lokalni fajlovi, rad sa opremom.

U kodu aplikacije, zaglavlja procedura i funkcija moraju eksplicitno naznačiti gdje će se kod izvršiti - korištenjem direktiva &AtClient / &AtServer (&AtClient / &AtServer u engleskoj verziji jezika). 1C programeri će me sada ispraviti govoreći da su direktive zapravo više od, ali nama to sada nije bitno.

Možete pozvati serverski kod iz klijentskog koda, ali ne možete pozvati klijentski kod iz serverskog koda. Ovo je osnovno ograničenje koje smo postavili iz više razloga. Konkretno, zato što serverski kod mora biti napisan na način da se izvršava na isti način, bez obzira odakle je pozvan - sa klijenta ili sa servera. A u slučaju poziva serverskog koda sa drugog serverskog koda, ne postoji klijent kao takav. I zato što bi tokom izvršavanja serverskog koda, klijent koji ga je pozvao mogao da zatvori, izađe iz aplikacije, a server ne bi imao koga da pozove.

Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast
Kod koji rukuje klikom na dugme: pozivanje serverske procedure sa klijenta će raditi, pozivanje klijentske procedure sa servera neće

To znači da ako želimo da pošaljemo neku poruku sa servera klijentskoj aplikaciji, na primjer, da je formiranje "dugoigrajućeg" izvještaja završeno i da se izvještaj može pregledati, nemamo takav način. Morate koristiti trikove, na primjer, periodično anketirati server iz klijentskog koda. Ali ovaj pristup opterećuje sistem nepotrebnim pozivima i općenito ne izgleda baš elegantno.

A postoji i potreba, na primjer, kada je telefon SIP-pozovite, o tome obavijestite klijentsku aplikaciju kako bi je pronašla u bazi podataka druge strane po broju pozivaoca i pokazala korisniku informacije o drugoj strani koja poziva. Ili, na primjer, kada narudžba stigne u skladište, obavijestite klijentovu aplikaciju o tome. Općenito, postoji mnogo slučajeva u kojima bi takav mehanizam bio koristan.

Zapravo postavljanje

Kreirajte mehanizam za razmjenu poruka. Brz, pouzdan, sa garantovanom dostavom, sa mogućnošću fleksibilne pretrage poruka. Na osnovu mehanizma implementirajte messenger (poruke, video pozive) koji radi unutar 1C aplikacija.

Dizajnirajte sistem horizontalno skalabilan. Povećano opterećenje treba pokriti povećanjem broja čvorova.

Реализация

Odlučili smo da serverski dio CB-a ne ugrađujemo direktno u platformu 1C:Enterprise, već da ga implementiramo kao poseban proizvod, čiji se API može pozvati iz koda 1C aplikativnih rješenja. To je učinjeno iz više razloga, od kojih je glavni bio da se omogući razmjena poruka između različitih 1C aplikacija (na primjer, između Odjela za trgovinu i računovodstvo). Različite 1C aplikacije mogu raditi na različitim verzijama platforme 1C:Enterprise, biti smještene na različitim serverima itd. U takvim uslovima, implementacija CB kao zasebnog proizvoda, koji se nalazi „sa strane“ 1C instalacija, predstavlja optimalno rešenje.

Stoga smo odlučili napraviti CB kao poseban proizvod. Za manje kompanije preporučujemo korištenje CB servera koji smo instalirali u našem oblaku (wss://1cdialog.com) kako bi se izbjegla opterećenja povezana s instalacijom i konfiguracijom lokalnog servera. Veliki korisnici, međutim, mogu smatrati da je svrsishodno instalirati sopstveni CB server u svojim objektima. Koristili smo sličan pristup u našem cloud SaaS proizvodu. 1cFresh – izdaje se kao proizvodni proizvod za instalaciju od strane kupaca, a također je raspoređen u našem oblaku https://1cfresh.com/.

Aplikacija

Za distribuciju opterećenja i toleranciju grešaka, nećemo postaviti jednu Java aplikaciju, već nekoliko, ispred njih ćemo staviti balansator opterećenja. Ako trebate poslati poruku od čvora do čvora, koristite publish/subscribe u Hazelcastu.

Komunikacija između klijenta i servera - preko websocketa. Pogodan je za sisteme u realnom vremenu.

Distribuirana keš memorija

Birajte između Redis, Hazelcast i Ehcache. Napolju 2015. Redis je upravo izdao novi klaster (previše nov, zastrašujući), postoji Sentinel s puno ograničenja. Ehcache ne zna kako da se grupiše (ova funkcionalnost se pojavila kasnije). Odlučili smo da probamo sa Hazelcast 3.4.
Hazelcast je skupljen iz kutije. U režimu sa jednim čvorom, nije baš koristan i može stati samo kao keš memorija - ne zna kako da izbacuje podatke na disk, ako je jedini čvor izgubljen, podaci se gube. Mi implementiramo nekoliko Hazelcasts-a, između kojih pravimo sigurnosnu kopiju kritičnih podataka. Ne pravimo rezervnu kopiju keša - nije nam ga žao.

Za nas je Hazelcast:

  • Pohranjivanje korisničkih sesija. Potrebno je dosta vremena da se ode do baze podataka za sesiju, tako da smo sve sesije stavili u Hazelcast.
  • Skladiste. Tražite korisnički profil - provjerite u kešu. Napisao novu poruku - stavi je u keš memoriju.
  • Teme za komunikaciju instanci aplikacije. Čvor generiše događaj i postavlja ga na Hazelcast temu. Drugi čvorovi aplikacije pretplaćeni na ovu temu primaju i obrađuju događaj.
  • klaster brave. Na primjer, kreiramo raspravu pomoću jedinstvenog ključa (diskusija-singleton u okviru 1C baze):

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

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

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

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

Provjerili smo da nema kanala. Uzeli su bravu, ponovo je proverili, kreirali. Ako ne provjerite nakon preuzimanja zaključavanja, postoji mogućnost da je u tom trenutku provjerila i druga nit i da će sada pokušati kreirati istu diskusiju - a ona već postoji. Nemoguće je zaključati putem sinkroniziranog ili regularnog java Lock-a. Kroz bazu - polako, a baza je šteta, kroz Hazelcast - ono što vam treba.

Odabir DBMS-a

Imamo veliko i uspešno iskustvo sa PostgreSQL-om i saradnju sa programerima ovog DBMS-a.

Sa klasterom, PostgreSQL nije lako - postoji XL, XC, Citus, ali, generalno, nije noSQL ono što se prilagođava izvan okvira. NoSQL se nije smatrao glavnom memorijom, bilo je dovoljno da uzmemo Hazelcast, s kojim ranije nismo radili.

Budući da trebate skalirati relacijsku bazu podataka, to znači shading. Kao što znate, prilikom dijeljenja baze podataka dijelimo na zasebne dijelove tako da se svaki od njih može postaviti na poseban server.

Prva verzija našeg dijeljenja pretpostavljala je mogućnost širenja svake od tablica naše aplikacije na različite servere u različitim omjerima. Mnogo poruka na serveru A - molim vas da premestimo deo ove tabele na server B. Ova odluka je samo vrištala o preuranjenoj optimizaciji, pa smo odlučili da se ograničimo na pristup sa više korisnika.

O multi-stanarima možete pročitati, na primjer, na web stranici Citus Data.

U SV postoje koncepti aplikacije i pretplatnika. Aplikacija je specifična instalacija poslovne aplikacije, kao što je ERP ili Računovodstvo, sa svojim korisnicima i poslovnim podacima. Pretplatnik je organizacija ili pojedinac u čije ime je aplikacija registrovana na CB serveru. Pretplatnik može imati nekoliko registrovanih aplikacija i te aplikacije mogu međusobno razmjenjivati ​​poruke. Pretplatnik je postao zakupac u našem sistemu. Poruke više pretplatnika mogu se nalaziti u jednoj fizičkoj bazi; ako vidimo da je neki pretplatnik počeo generirati mnogo prometa, premještamo ga u zasebnu fizičku bazu podataka (ili čak na poseban server baze podataka).

Imamo glavnu bazu podataka u kojoj se pohranjuje tabela rutiranja sa informacijama o lokaciji svih baza podataka pretplatnika.

Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Kako bismo spriječili da glavna baza podataka bude usko grlo, tabelu usmjeravanja (i druge često tražene podatke) čuvamo u kešu.

Ako baza podataka pretplatnika počne da usporava, izrezaćemo je na particije unutra. Na drugim projektima koristimo se za particioniranje velikih tablica pg_pathman.

Pošto je gubitak korisničkih poruka loš, pravimo rezervne kopije naših baza podataka replikama. Kombinacija sinhronih i asinkronih replika omogućava vam da se osigurate od gubitka glavne baze podataka. Gubitak poruke će se dogoditi samo u slučaju simultanog kvara glavne baze podataka i njene sinhrone replike.

Ako se sinhrona replika izgubi, asinhrona replika postaje sinhrona.
Ako je glavna baza podataka izgubljena, sinhrona replika postaje glavna baza podataka, asinhrona replika postaje sinhrona replika.

Elasticsearch za pretragu

Pošto je, između ostalog, CB i glasnik, ovdje nam je potrebna brza, zgodna i fleksibilna pretraga, uzimajući u obzir morfologiju, po nepreciznim podudaranjima. Odlučili smo da ne izmišljamo točak i koristimo besplatni pretraživač Elasticsearch, kreiran na bazi biblioteke Lucene. Takođe primenjujemo Elasticsearch u klasteru (master - podaci - podaci) da eliminišemo probleme u slučaju kvara aplikacijskih čvorova.

Na githubu smo pronašli Ruski dodatak za morfologiju za Elasticsearch i koristite ga. U Elasticsearch indeksu pohranjujemo korijene riječi (koje dodatak definira) i N-grame. Kako korisnik unese tekst za pretragu, mi tražimo upisani tekst između N-grama. Kada se sačuva u indeksu, riječ "tekstovi" će biti podijeljena na sljedeće N-grame:

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

I također će biti sačuvan korijen riječi "tekst". Ovaj pristup vam omogućava da pretražujete na početku, u sredini i na kraju riječi.

Velika slika

Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast
Ponavljam sliku s početka članka, ali uz objašnjenja:

  • Balanser izložen internetu; imamo nginx, može biti bilo koji.
  • Instance Java aplikacije komuniciraju jedna s drugom putem Hazelcasta.
  • Za rad sa web socketom koristimo Netty.
  • Java aplikacija napisana u Javi 8, sastoji se od paketa OSGi. Planovi su migriranje na Javu 10 i prelazak na module.

Razvoj i ispitivanje

Tokom razvoja i testiranja CB-a naišli smo na niz zanimljivih karakteristika proizvoda koje koristimo.

Testiranje opterećenja i curenje memorije

Otpuštanje svakog CB otpuštanja je test opterećenja. Uspješno je prošao kada:

  • Test je radio nekoliko dana i nije bilo uskraćivanja usluge
  • Vrijeme odgovora za ključne operacije nije premašilo udoban prag
  • Smanjenje performansi u odnosu na prethodnu verziju nije više od 10%

Testnu bazu popunjavamo podacima - za to dobijamo informacije o najaktivnijem pretplatniku sa proizvodnog servera, pomnožimo njegove brojeve sa 5 (broj poruka, diskusija, korisnika) i tako testiramo.

Testiranje opterećenja interakcijskog sistema vršimo u tri konfiguracije:

  1. stres test
  2. Samo veze
  3. Registracija pretplatnika

Tokom stres testa pokrećemo nekoliko stotina niti, a one učitavaju sistem bez zaustavljanja: pišu poruke, kreiraju diskusije, primaju listu poruka. Simuliramo radnje običnih korisnika (dobijem listu mojih nepročitanih poruka, pišem nekome) i programske odluke (prebacimo paket u drugu konfiguraciju, obradimo upozorenje).

Na primjer, ovako izgleda dio stres testa:

  • Korisnik se prijavljuje
    • Zahtijeva vaše nepročitane teme
    • 50% šanse za čitanje poruka
    • 50% šanse za pisanje poruka
    • Sljedeći korisnik:
      • 20% šanse za kreiranje nove teme
      • Nasumično bira bilo koju od svojih rasprava
      • Ulazi unutra
      • Zahtjevi poruke, korisnički profili
      • Kreira pet poruka upućenih nasumičnim korisnicima iz ove teme
      • Van rasprave
      • Ponavlja 20 puta
      • Odjavljuje se, vraća se na početak skripte

    • Chatbot ulazi u sistem (emulira poruke iz koda primijenjenih rješenja)
      • 50% šanse za kreiranje novog kanala podataka (posebna rasprava)
      • 50% šanse da napišete poruku na bilo kom od postojećih kanala

Scenario "Samo veze" pojavio se s razlogom. Postoji situacija: korisnici su povezali sistem, ali još nisu bili uključeni. Svaki korisnik ujutro u 09:00 uključuje računar, uspostavlja vezu sa serverom i ćuti. Ovi momci su opasni, ima ih puno - imaju samo PING/PONG iz paketa, ali drže vezu sa serverom (ne mogu je zadržati - i odjednom nova poruka). Test reproducira situaciju kada se veliki broj takvih korisnika pokuša prijaviti na sistem za pola sata. Izgleda kao stres test, ali je fokus upravo na ovom prvom ulazu - da ne bude kvarova (čovek ne koristi sistem, ali on već pada - teško je smisliti nešto gore).

Scenarij registracije pretplatnika potiče od prvog pokretanja. Napravili smo stres test i bili sigurni da sistem ne usporava u dopisivanju. Ali korisnici su otišli i registracija je počela opadati nakon isteka. Prilikom registracije koristili smo / dev / random, koji je vezan za entropiju sistema. Server nije imao vremena da akumulira dovoljno entropije i, kada je zatražen novi SecureRandom, zamrznuo se na desetine sekundi. Postoji mnogo izlaza iz ove situacije, na primjer: prebacite se na manje siguran /dev/urandom, instalirajte posebnu ploču koja generiše entropiju, generirajte nasumične brojeve unaprijed i pohranite ih u bazen. Privremeno smo zatvorili problem sa pulom, ali od tada vodimo poseban test za registraciju novih pretplatnika.

Kao generator opterećenja koristimo JMeter. Ne zna da radi sa websocketom, potreban je dodatak. Prvi u rezultatima pretrage za upit "jmeter websocket" su članci sa BlazeMeteru kojoj preporučuju dodatak od Macieja Zaleskog.

Odatle smo odlučili da počnemo.

Gotovo odmah nakon početka ozbiljnog testiranja, otkrili smo da je u JMeteru počelo curenje memorije.

Dodatak je posebna velika priča, sa 176 zvjezdica ima 132 viljuške na githubu. Sam autor se nije obavezao na to od 2015. (uzeli smo ga 2015. godine, tada nije izazvalo sumnju), nekoliko github problema o curenju memorije, 7 nezatvorenih pull zahtjeva.
Ako odlučite učitati test s ovim dodatkom, obratite pažnju na sljedeće rasprave:

  1. U okruženju sa više niti, korištena je obična LinkedList, kao rezultat toga, dobili smo NPE u vrijeme izvođenja. Rješava se ili prelaskom na ConcurrentLinkedDeque, ili sinhroniziranim blokovima. Za sebe smo odabrali prvu opcijuhttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Curenje memorije, informacije o vezi se ne brišu prilikom prekida veze (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. U streaming modu (kada websocket nije zatvoren na kraju uzorka, već se koristi dalje u planu), obrasci odgovora ne rade (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Ovo je jedan od onih na githubu. Šta smo uradili:

  1. Uzeli račva Elyrana Kogana (@elyrank) - popravlja probleme 1 i 3
  2. Rešen problem 2
  3. Ažuriran pristanište sa 9.2.14 na 9.3.12
  4. Umotan SimpleDateFormat u ThreadLocal; SimpleDateFormat nije siguran niti koji vodi do NPE u vrijeme izvođenja
  5. Popravljeno još jedno curenje memorije (veza se neispravno zatvorila nakon prekida veze)

A ipak teče!

Sećanje je počelo da se završava ne za dan, već za dva. Nije bilo vremena, odlučili smo da pokrenemo manje niti, ali na četiri agenta. Ovo je trebalo da bude dovoljno za najmanje nedelju dana.

Prošla su dva dana...

Sada Hazelcastu ponestaje memorije. Dnevnici su pokazali da nakon nekoliko dana testiranja, Hazelcast počinje da se žali na nedostatak memorije, a nakon nekog vremena klaster se raspada, a čvorovi nastavljaju da umiru jedan po jedan. Povezali smo JVisualVM sa hazelcast-om i vidjeli „testeru prema gore“ - redovno je zvao GC, ali nije mogao ni na koji način očistiti memoriju.

Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Ispostavilo se da u hazelcast 3.4, prilikom brisanja mape / multiMap (map.destroy()), memorija nije potpuno oslobođena:

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

Greška je sada ispravljena u verziji 3.5, ali je tada bio problem. Napravili smo novu multiMapu sa dinamičkim imenima i izbrisali prema našoj logici. Kod je izgledao otprilike ovako:

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

nazovite:

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

multiMap je kreiran za svaku pretplatu i uklonjen kada nije bio potreban. Odlučili smo da pokrenemo Mapu , ključ će biti naziv pretplate, a vrijednosti će biti identifikatori sesije (po kojima onda možete dobiti identifikatore korisnika, ako je potrebno).

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

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

Grafikoni su se poboljšali.

Kako i zašto smo napisali visoko opterećenu skalabilnu uslugu za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Šta smo još naučili o testiranju opterećenja

  1. JSR223 treba biti napisan u groovy-u i uključivati ​​keš kompilacije - mnogo je brži. link.
  2. Jmeter-Plugins grafikone je lakše razumjeti od standardnih. link.

O našem iskustvu sa Hazelcastom

Hazelcast je bio novi proizvod za nas, počeli smo raditi s njim od verzije 3.4.1, sada imamo verziju 3.9.2 na našem produkcijskom serveru (u vrijeme pisanja ovog teksta, najnovija verzija Hazelcasta je 3.10).

Generisanje ID-a

Počeli smo sa celobrojnim identifikatorima. Zamislimo da nam treba još jedan Long za novi entitet. Slijed u bazi podataka nije prikladan, tabele su uključene u dijeljenje - ispostavilo se da postoji poruka ID=1 u DB1 i poruka ID=1 u DB2, ne možete staviti ovaj ID u Elasticsearch, također u Hazelcast, ali najgore je ako želite svesti podatke sa dvije baze podataka na jednu (na primjer, odlučiti da je jedna baza podataka dovoljna za ove pretplatnike). Možete imati nekoliko AtomicLongova u Hazelcast-u i držati brojač tamo, tada je učinak dobijanja novog ID-a incrementAndGet plus vrijeme za upit u Hazelcastu. Ali Hazelcast ima nešto optimalnije - FlakeIdGenerator. Prilikom kontaktiranja, svakom klijentu se daje niz ID-ova, na primjer, prvi - od 1 do 10, drugi - od 000 do 10 i tako dalje. Sada klijent može sam izdati nove identifikatore sve dok se ne završi raspon koji mu se izdaje. Radi brzo, ali ponovnim pokretanjem aplikacije (i klijenta Hazelcast) pokreće se nova sekvenca - dakle preskakanja itd. Osim toga, programerima nije baš jasno zašto su ID-ovi cijeli brojevi, ali su toliko u suprotnosti. Sve smo izvagali i prešli na UUID.

Usput, za one koji žele biti poput Twittera, postoji takva Snowcast biblioteka - ovo je implementacija Snowflake-a na vrhu Hazelcast-a. Ovdje možete vidjeti:

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

Ali još nismo stigli do toga.

TransactionalMap.replace

Još jedno iznenađenje: TransactionalMap.replace ne radi. Evo testa:

@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

Morao sam napisati svoju zamjenu koristeći 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);
}

Testirajte ne samo regularne strukture podataka, već i njihove transakcione verzije. Dešava se da IMAp radi, ali TransactionalMap više ne postoji.

Pričvrstite novi JAR bez zastoja

Prvo smo odlučili da zapišemo objekte naših klasa u Hazelcast. Na primjer, imamo klasu Application, želimo je pohraniti i pročitati. Sačuvaj:

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

Čitamo:

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

Sve radi. Zatim smo odlučili da napravimo indeks u Hazelcastu da ga pretražimo:

map.addIndex("subscriberId", false);

I kada su pisali novi entitet, počeli su primati ClassNotFoundException. Hazelcast je pokušao dodati u indeks, ali nije znao ništa o našoj klasi i htio je staviti JAR sa ovom klasom u njega. Upravo smo to i uradili, sve je radilo, ali pojavio se novi problem: kako ažurirati JAR bez potpunog zaustavljanja klastera? Hazelcast ne preuzima novi JAR pri ažuriranju po čvoru. U ovom trenutku smo odlučili da možemo živjeti bez pretraživanja indeksa. Uostalom, ako koristite Hazelcast kao ključ/vrijednost skladište, onda će sve raditi? Ne baš. Ovdje opet različito ponašanje IMAp-a i TransactionalMap-a. Gdje je IMAp svejedno, TransactionalMap daje grešku.

IMAp. Zapisujemo 5000 objekata, čitamo. Sve je očekivano.

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

Ali to ne radi u transakciji, dobijamo 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();
        }
    });
}

U verziji 3.8 pojavio se mehanizam za postavljanje klase korisnika. Možete odrediti jedan glavni čvor i ažurirati JAR datoteku na njemu.

Sada smo potpuno promijenili pristup: sami se serijalizuje u JSON i spremamo u Hazelcast. Hazelcast ne mora znati strukturu naših klasa i možemo ažurirati bez zastoja. Aplikacija kontrolira verzijama objekata domene. Istovremeno se mogu pokretati različite verzije aplikacije, a moguće je da nova aplikacija upisuje objekte sa novim poljima, dok stara još ne zna za ta polja. I u isto vrijeme, nova aplikacija čita objekte koje je napisala stara aplikacija koji nemaju nova polja. Takve situacije rješavamo unutar aplikacije, ali radi jednostavnosti ne mijenjamo ili uklanjamo polja, samo proširujemo klase dodavanjem novih polja.

Kako pružamo visoke performanse

Četiri putovanja u Hazelcast su dobra, dva putovanja u bazu podataka su loša

Traženje podataka u keš memoriji je uvijek bolje nego u bazi podataka, ali ne želite pohranjivati ​​ni zapise bez zahtjeva. Odlučivanje o tome šta keširati prepušteno je posljednjoj fazi razvoja. Kada je nova funkcionalnost kodirana, omogućavamo evidentiranje svih upita u PostgreSQL-u (log_min_duration_statement na 0) i pokrećemo testiranje opterećenja u trajanju od 20 minuta.Uslužni programi kao što su pgFouine i pgBadger mogu graditi analitičke izvještaje na osnovu prikupljenih dnevnika. U izvještajima prvenstveno tražimo spore i česte upite. Za spore upite gradimo plan izvršenja (EXPLAIN) i procjenjujemo da li se takav upit može ubrzati. Česti zahtjevi za istim ulazom dobro se uklapaju u keš memoriju. Trudimo se da upiti budu „ravni“, jedna tabela po upitu.

Eksploatacija

CB kao online servis pokrenut je u proleće 2017. godine, kao poseban CB proizvod je objavljen u novembru 2017. (u to vreme u beta statusu).

Za više od godinu dana rada nije bilo ozbiljnijih problema u radu online servisa CB. Pratimo online uslugu putem Zabbix, prikupiti i rasporediti iz bambus.

Distribucija CB servera dolazi u obliku nativnih paketa: RPM, DEB, MSI. Plus, za Windows, nudimo jedan instalater u obliku jednog EXE-a koji instalira server, Hazelcast i Elasticsearch na jednoj mašini. U početku smo ovu verziju instalacije nazvali "demo", ali sada je postalo jasno da je ovo najpopularnija opcija implementacije.

izvor: www.habr.com

Dodajte komentar