Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast

V tem članku bomo govorili o tem, kako in zakaj smo se razvili Interakcijski sistem – mehanizem za prenos informacij med odjemalskimi aplikacijami in strežniki 1C:Enterprise – od postavitve naloge do razmišljanja o arhitekturi in podrobnostih izvedbe.

Interakcijski sistem (v nadaljevanju SV) je porazdeljen sistem za sporočanje, odporen na napake, z zajamčeno dostavo. SV je zasnovan kot visoko obremenjena storitev z visoko razširljivostjo, ki je na voljo kot spletna storitev (zagotavlja 1C) in kot množično izdelan izdelek, ki ga je mogoče namestiti na lastnih strežniških objektih.

SV uporablja porazdeljeno shranjevanje lešnik in iskalnik Elastično iskanje. Govorili bomo tudi o Javi in ​​o tem, kako horizontalno prilagajamo PostgreSQL.
Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Izjava o težavah

Da bo jasno, zakaj smo ustvarili sistem interakcije, vam bom povedal nekaj o tem, kako poteka razvoj poslovnih aplikacij v 1C.

Za začetek nekaj o nas za tiste, ki še ne veste kaj počnemo :) Izdelujemo tehnološko platformo 1C:Enterprise. Platforma vključuje orodje za razvoj poslovnih aplikacij in izvajalno okolje, ki omogoča izvajanje poslovnih aplikacij v okolju na več platformah.

Razvojna paradigma odjemalec-strežnik

Poslovne aplikacije, ustvarjene na 1C:Enterprise, delujejo v treh ravneh odjemalec-strežnik arhitektura “DBMS – aplikacijski strežnik – odjemalec”. Koda aplikacije, zapisana v vgrajen jezik 1C, se lahko izvede na aplikacijskem strežniku ali na odjemalcu. Vse delo z objekti aplikacije (imeniki, dokumenti itd.), kot tudi branje in pisanje podatkovne baze, se izvaja samo na strežniku. Na strežniku je implementirana tudi funkcionalnost obrazcev in ukaznega vmesnika. Naročnik izvaja sprejem, odpiranje in prikazovanje obrazcev, »komunikacijo« z uporabnikom (opozorila, vprašanja ...), male izračune v obrazcih, ki zahtevajo hiter odziv (npr. množenje cene s količino), delo z lokalnimi datotekami, delo z opremo.

V kodi aplikacije morajo glave procedur in funkcij izrecno navesti, kje se bo koda izvajala – z uporabo direktiv &AtClient / &AtServer (&AtClient / &AtServer v angleški različici jezika). Razvijalci 1C me bodo zdaj popravili, češ da so direktive dejansko več kot, vendar za nas to zdaj ni pomembno.

Kodo strežnika lahko pokličete iz kode odjemalca, ne morete pa kode odjemalca iz kode strežnika. To je temeljna omejitev, ki smo jo naredili iz več razlogov. Predvsem zato, ker mora biti strežniška koda napisana tako, da se izvaja na enak način ne glede na to, kje je klicana - od odjemalca ali iz strežnika. In v primeru klica strežniške kode iz druge strežniške kode odjemalca kot takega ni. In ker bi se lahko med izvajanjem kode strežnika odjemalec, ki ga je poklical, zaprl, zapustil aplikacijo in strežnik ne bi imel več koga klicati.

Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast
Koda, ki obravnava klik gumba: klicanje strežniške procedure iz odjemalca bo delovalo, klicanje odjemalske procedure s strežnika ne

To pomeni, da če želimo s strežnika odjemalski aplikaciji poslati neko sporočilo, na primer, da je generiranje »dolgotrajnega« poročila končano in si poročilo lahko ogledamo, take metode nimamo. Uporabiti morate trike, na primer občasno anketirati strežnik iz kode odjemalca. Toda ta pristop obremeni sistem z nepotrebnimi klici in na splošno ni videti zelo eleganten.

In obstaja tudi potreba, na primer, ko pride telefonski klic SIP- ob klicu o tem obvesti odjemalsko aplikacijo, da jo lahko po številki kličočega poišče v bazi nasprotne stranke in uporabniku prikaže podatke o kličoči nasprotni stranki. Ali na primer, ko naročilo prispe v skladišče, o tem obvestite strankino odjemalsko aplikacijo. Na splošno obstaja veliko primerov, ko bi bil tak mehanizem uporaben.

Sama proizvodnja

Ustvarite mehanizem za sporočanje. Hitro, zanesljivo, z zagotovljeno dostavo, z možnostjo fleksibilnega iskanja sporočil. Na podlagi mehanizma implementirajte messenger (sporočila, video klici), ki deluje znotraj aplikacij 1C.

Načrtujte sistem tako, da bo vodoravno razširljiv. Naraščajoča obremenitev mora biti pokrita s povečanjem števila vozlišč.

Реализация

Odločili smo se, da strežniškega dela SV ne bomo integrirali neposredno v platformo 1C:Enterprise, ampak ga bomo implementirali kot ločen izdelek, katerega API je mogoče priklicati iz kode aplikacijskih rešitev 1C. To je bilo storjeno iz več razlogov, od katerih je bil glavni ta, da sem želel omogočiti izmenjavo sporočil med različnimi aplikacijami 1C (na primer med vodenjem trgovine in računovodstvom). Različne aplikacije 1C se lahko izvajajo na različnih različicah platforme 1C:Enterprise, se nahajajo na različnih strežnikih itd. V takšnih razmerah je implementacija SV kot ločenega izdelka, ki se nahaja "ob strani" instalacij 1C, optimalna rešitev.

Zato smo se odločili narediti SV kot ločen izdelek. Majhnim podjetjem priporočamo, da uporabljajo strežnik CB, ki smo ga namestili v naš oblak (wss://1cdialog.com), da se izognemo režijskim stroškom, povezanim z lokalno namestitvijo in konfiguracijo strežnika. Velikim strankam bo morda priporočljivo namestiti lasten strežnik CB v svojih objektih. Podoben pristop smo uporabili v našem izdelku SaaS v oblaku 1cSveže – izdelan je kot serijsko izdelan izdelek za namestitev na lokacijah strank in je nameščen tudi v našem oblaku https://1cfresh.com/.

Vloga

Za porazdelitev obremenitve in odpornosti na napake ne bomo uvedli ene aplikacije Java, ampak več, z izravnalnikom obremenitve pred njimi. Če morate prenesti sporočilo iz vozlišča v vozlišče, uporabite objavo/naročitev v Hazelcastu.

Komunikacija med odjemalcem in strežnikom poteka preko spletne vtičnice. Zelo je primeren za sisteme v realnem času.

Porazdeljeni predpomnilnik

Izbirali smo med Redis, Hazelcast in Ehcache. Piše se leto 2015. Redis je pravkar izdal novo gručo (preveč novo, strašljivo), tu je Sentinel z veliko omejitvami. Ehcache ne zna sestaviti v gručo (ta funkcionalnost se je pojavila kasneje). Odločili smo se, da poskusimo s Hazelcastom 3.4.
Hazelcast je takoj sestavljen v grozd. V načinu z enim vozliščem ni zelo uporaben in se lahko uporablja samo kot predpomnilnik - ne ve, kako izpisati podatke na disk, če izgubite edino vozlišče, izgubite podatke. Razmestimo več Hazelcastov, med katerimi varnostno kopiramo kritične podatke. Ne varnostno kopiramo predpomnilnika – ne moti nas.

Za nas je Hazelcast:

  • Shranjevanje uporabniških sej. Vsakič, ko gremo v bazo podatkov za sejo, traja veliko časa, zato smo vse seje dali v Hazelcast.
  • Predpomnilnik. Če iščete uporabniški profil, preverite predpomnilnik. Napisal novo sporočilo - shrani ga v predpomnilnik.
  • Teme za komunikacijo med primerki aplikacije. Vozlišče ustvari dogodek in ga postavi v temo Hazelcast. Druga vozlišča aplikacij, naročena na to temo, prejmejo in obdelajo dogodek.
  • Grozdne ključavnice. Na primer, ustvarimo razpravo z edinstvenim ključem (singleton razprava v bazi podatkov 1C):

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

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

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

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

Preverili smo, da ni kanala. Vzeli smo ključavnico, jo ponovno preverili in ustvarili. Če po zaklepanju ne preverite zaklepanja, potem obstaja možnost, da je v tistem trenutku preverila tudi druga nit in bo zdaj poskušala ustvariti isto razpravo - vendar že obstaja. Ne morete zakleniti s sinhroniziranim ali običajnim zaklepanjem java. Prek baze podatkov - počasno je in škoda je za bazo podatkov; prek Hazelcasta - to je tisto, kar potrebujete.

Izbira DBMS

Imamo bogate in uspešne izkušnje pri delu s PostgreSQL in sodelovanju z razvijalci tega DBMS.

Z gručo PostgreSQL ni enostavno - obstaja XL, XC, Citus, vendar na splošno to niso NoSQL-ji, ki bi se spreminjali takoj. NoSQL nismo upoštevali kot glavnega pomnilnika, dovolj je bilo, da smo vzeli Hazelcast, s katerim prej nismo delali.

Če morate povečati relacijsko bazo podatkov, to pomeni drobljenje. Kot veste, s shardingom bazo podatkov razdelimo na ločene dele, tako da lahko vsakega od njih postavimo na ločen strežnik.

Prva različica našega razdeljevanja je predvidevala možnost porazdelitve vsake tabele naše aplikacije po različnih strežnikih v različnih razmerjih. Na strežniku A je veliko sporočil – prosim, prestavimo del te tabele na strežnik B. Ta odločitev je preprosto kričala o prezgodnji optimizaciji, zato smo se odločili, da se omejimo na multi-tenant pristop.

O večnajemniku si lahko preberete na primer na spletni strani Podatki Citus.

SV ima pojma aplikacija in naročnik. Aplikacija je posebna namestitev poslovne aplikacije, kot je ERP ali računovodstvo, s svojimi uporabniki in poslovnimi podatki. Naročnik je organizacija ali posameznik, v imenu katerega je aplikacija prijavljena v strežnik SV. Naročnik ima lahko prijavljenih več aplikacij, ki si lahko med seboj izmenjujejo sporočila. Naročnik je postal najemnik v našem sistemu. Sporočila več naročnikov se lahko nahajajo v eni fizični bazi podatkov; če vidimo, da je naročnik začel ustvarjati veliko prometa, ga premaknemo v ločeno fizično bazo podatkov (ali celo ločen strežnik baz podatkov).

Imamo glavno bazo podatkov, kjer je shranjena usmerjevalna tabela s podatki o lokaciji vseh baz podatkov naročnikov.

Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Da glavna baza podatkov ne bi bila ozko grlo, hranimo usmerjevalno tabelo (in druge pogosto potrebne podatke) v predpomnilniku.

Če se baza podatkov naročnika začne upočasnjevati, jo bomo znotraj razdelili na particije. Pri drugih projektih, ki jih uporabljamo pg_pathman.

Ker je izguba uporabniških sporočil slaba, naše zbirke podatkov vzdržujemo s kopijami. Kombinacija sinhronih in asinhronih replik vam omogoča, da se zavarujete v primeru izgube glavne baze podatkov. Do izgube sporočila pride le, če sta primarna baza podatkov in njena sinhrona replika odpovedani hkrati.

Če se sinhrona replika izgubi, postane asinhrona replika sinhrona.
Če se glavna baza podatkov izgubi, sinhrona replika postane glavna baza podatkov, asinhrona replika pa sinhrona replika.

Elasticsearch za iskanje

Ker je SV med drugim tudi messenger, zahteva hitro, priročno in prilagodljivo iskanje ob upoštevanju morfologije z uporabo nenatančnih ujemanj. Odločili smo se, da ne bomo znova izumljali kolesa in uporabili brezplačen iskalnik Elasticsearch, ustvarjen na podlagi knjižnice Lucen. Elasticsearch umestimo tudi v gručo (master – data – data) za odpravo težav v primeru izpada aplikacijskih vozlišč.

Na githubu smo našli Ruski morfološki vtičnik za Elasticsearch in ga uporabite. V indeksu Elasticsearch hranimo korene besed (ki jih določi vtičnik) in N-grame. Ko uporabnik vnese besedilo za iskanje, iščemo vtipkano besedilo med N-grami. Ko bo beseda »texts« shranjena v indeks, bo razdeljena na naslednje N-grame:

[those, tek, tex, text, texts, ek, ex, ext, texts, ks, kst, ksty, st, sty, you],

Prav tako bo ohranjen koren besede "besedilo". Ta pristop vam omogoča iskanje na začetku, v sredini in na koncu besede.

Velika slika

Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast
Ponovite sliko z začetka članka, vendar s pojasnili:

  • Balancer izpostavljen na internetu; imamo nginx, lahko je katerikoli.
  • Primerki aplikacij Java med seboj komunicirajo prek Hazelcasta.
  • Za delo s spletno vtičnico uporabljamo Netty.
  • Aplikacija Java je napisana v Javi 8 in je sestavljena iz svežnjev OSGi. Načrti vključujejo prehod na Javo 10 in prehod na module.

Razvoj in testiranje

V procesu razvoja in testiranja SV smo naleteli na številne zanimive lastnosti izdelkov, ki jih uporabljamo.

Testiranje obremenitve in puščanje pomnilnika

Izdaja vsake izdaje SV vključuje testiranje obremenitve. Uspešno je, ko:

  • Test je deloval več dni in ni bilo nobenih napak pri servisiranju
  • Odzivni čas za ključne operacije ni presegel udobnega praga
  • Poslabšanje zmogljivosti v primerjavi s prejšnjo različico ni več kot 10%

Testno bazo napolnimo s podatki - za to dobimo informacijo o najbolj aktivnem naročniku iz produkcijskega strežnika, njegovo število pomnožimo s 5 (število sporočil, razprav, uporabnikov) in tako testiramo.

Izvajamo obremenitveno testiranje interakcijskega sistema v treh konfiguracijah:

  1. stresni test
  2. Samo povezave
  3. Registracija naročnika

Med stresnim testom zaženemo več sto niti, ki naložijo sistem brez ustavljanja: pisanje sporočil, ustvarjanje razprav, prejemanje seznama sporočil. Simuliramo dejanja običajnih uporabnikov (pridobite seznam mojih neprebranih sporočil, pišite nekomu) in programske rešitve (prenesite paket drugačne konfiguracije, obdelajte opozorilo).

Takole je na primer videti del stresnega testa:

  • Uporabnik se prijavi
    • Zahteva vaše neprebrane razprave
    • 50 % verjetnosti, da bodo prebrali sporočila
    • 50 % verjetnost pošiljanja sporočila
    • Naslednji uporabnik:
      • Ima 20 % možnosti za ustvarjanje nove razprave
      • Naključno izbere katero koli od svojih razprav
      • Gre noter
      • Zahteve za sporočila, uporabniške profile
      • Iz te razprave ustvari pet sporočil, naslovljenih na naključne uporabnike
      • Zapusti razpravo
      • Ponovi 20-krat
      • Odjava, vrnitev na začetek skripta

    • Klepetalni robot vstopi v sistem (posnema sporočanje iz kode aplikacije)
      • Ima 50% možnost, da ustvari nov kanal za izmenjavo podatkov (posebna razprava)
      • 50 % verjetnosti, da bo napisal sporočilo na katerega od obstoječih kanalov

Scenarij »Samo povezave« se je pojavil z razlogom. Obstaja situacija: uporabniki so povezali sistem, vendar se še niso vključili. Vsak uporabnik prižge računalnik ob 09:00 zjutraj, vzpostavi povezavo s strežnikom in molči. Ti tipi so nevarni, veliko jih je - edini paketi, ki jih imajo, so PING/PONG, vendar ohranjajo povezavo s strežnikom (ne morejo je vzdrževati - kaj če pride novo sporočilo). Test reproducira situacijo, ko se v pol ure poskuša v sistem prijaviti večje število takih uporabnikov. Je podoben stresnemu testu, vendar je njegov fokus ravno na tem prvem vložku - da ne pride do okvar (človek ne uporablja sistema, pa že odpade - hujšega si težko izmisliš).

Skript za registracijo naročnika se začne ob prvem zagonu. Izvedli smo stresni test in bili prepričani, da se sistem med dopisovanjem ne upočasni. Toda uporabniki so prišli in registracija je začela odpovedovati zaradi časovne omejitve. Pri registraciji smo uporabili / dev / naključno, kar je povezano z entropijo sistema. Strežnik ni imel časa, da bi zbral dovolj entropije, in ko je bil zahtevan nov SecureRandom, je zamrznil za več deset sekund. Obstaja veliko izhodov iz te situacije, na primer: preklopite na manj varen /dev/urandom, namestite posebno ploščo, ki generira entropijo, vnaprej ustvarite naključna števila in jih shranite v bazen. Težavo s bazenom smo začasno odpravili, vendar od takrat izvajamo ločen test za registracijo novih naročnikov.

Uporabljamo kot generator obremenitve JMeter. Ne zna delati z websocket; potrebuje vtičnik. Prvi v rezultatih iskanja za poizvedbo “jmeter websocket” so: članki iz BlazeMeter, ki priporočajo vtičnik Maciej Zaleski.

Tam smo se odločili začeti.

Skoraj takoj po začetku resnega testiranja smo odkrili, da je JMeter začel uhajati iz pomnilnika.

Vtičnik je ločena velika zgodba; s 176 zvezdicami ima 132 forkov na githubu. Avtor sam se temu ni zavezal od leta 2015 (vzeli smo ga leta 2015, takrat ni vzbudil suma), več težav z githubom glede uhajanja pomnilnika, 7 nezaprtih zahtev za vleko.
Če se odločite za izvedbo obremenitvenega testiranja s tem vtičnikom, bodite pozorni na naslednje razprave:

  1. V večnitnem okolju je bil uporabljen običajni LinkedList in rezultat je bil NPE med izvajanjem. To je mogoče rešiti s preklopom na ConcurrentLinkedDeque ali s sinhroniziranimi bloki. Zase smo izbrali prvo možnost (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Puščanje pomnilnika; ob prekinitvi povezave se informacije o povezavi ne izbrišejo (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. V pretočnem načinu (ko spletna vtičnica ni zaprta na koncu vzorca, ampak je uporabljena pozneje v načrtu), vzorci odziva ne delujejo (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

To je eden od tistih na githubu. Kaj smo storili:

  1. Sem vzel vilice Elyran Kogan (@elyrank) – odpravlja težave 1 in 3
  2. Rešen problem 2
  3. Posodobljen pomol od 9.2.14 do 9.3.12
  4. Zavit SimpleDateFormat v ThreadLocal; SimpleDateFormat ni varen za niti, kar je povzročilo NPE med izvajanjem
  5. Odpravljeno drugo uhajanje pomnilnika (povezava je bila ob prekinitvi nepravilno zaprta)

In vendar teče!

Spomin je začel zmanjkovati v enem dnevu, ampak v dveh. Ni bilo več časa, zato smo se odločili zagnati manj niti, vendar na štirih agentih. To bi moralo zadostovati vsaj za en teden.

Dva dni sta minila...

Zdaj Hazelcastu zmanjkuje pomnilnika. Dnevniki so pokazali, da se je Hazelcast po nekaj dneh testiranja začel pritoževati zaradi pomanjkanja pomnilnika in čez nekaj časa je grozd razpadel, vozlišča pa so še naprej umirala eno za drugim. JVisualVM smo povezali s hazelcastom in videli "dvigajočo žago" - redno je klical GC, vendar ni mogel počistiti pomnilnika.

Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Izkazalo se je, da v hazelcast 3.4 pri brisanju zemljevida / multiMap (map.destroy()) pomnilnik ni popolnoma osvobojen:

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

Napaka je zdaj odpravljena v 3.5, vendar je bila takrat težava. Ustvarili smo nove multiMape z dinamičnimi imeni in jih izbrisali v skladu z našo logiko. Koda je bila videti nekako takole:

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

Vizov:

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

multiMap je bil ustvarjen za vsako naročnino in izbrisan, ko ni bil potreben. Odločili smo se, da bomo začeli Map , ključ bo ime naročnine, vrednosti pa bodo identifikatorji seje (iz katerih lahko nato po potrebi pridobite identifikatorje uporabnikov).

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

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

Grafi so se izboljšali.

Kako in zakaj smo napisali visoko obremenjeno razširljivo storitev za 1C: Enterprise: Java, PostgreSQL, Hazelcast

Kaj smo se še naučili o obremenitvenem testiranju?

  1. JSR223 mora biti napisan v groovyju in mora vključevati predpomnilnik prevajanja - je veliko hitrejši. Povezava.
  2. Grafe Jmeter-Plugins je lažje razumeti kot standardne. Povezava.

O naših izkušnjah s Hazelcastom

Hazelcast je bil za nas nov izdelek, z njim smo začeli delati od različice 3.4.1, zdaj naš produkcijski strežnik uporablja različico 3.9.2 (v času pisanja je zadnja različica Hazelcasta 3.10).

generiranje ID-ja

Začeli smo s celoštevilskimi identifikatorji. Predstavljajmo si, da potrebujemo še en Long za novo entiteto. Zaporedje v zbirki podatkov ni primerno, tabele so vključene v razrezovanje - izkaže se, da obstaja sporočilo ID=1 v DB1 in sporočilo ID=1 v DB2, tega ID-ja ne morete dati v Elasticsearch, niti v Hazelcast , najslabše pa je, če želite podatke iz dveh baz združiti v eno (npr. odločiti se, da je za te naročnike ena baza dovolj). V Hazelcast lahko dodate več AtomicLongov in tam obdržite števec, nato pa je zmogljivost pridobivanja novega ID-ja incrementAndGet plus čas za zahtevo v Hazelcast. Toda Hazelcast ima nekaj bolj optimalnega - FlakeIdGenerator. Pri stiku z vsako stranko dobi razpon ID-ja, na primer prvi - od 1 do 10, drugi - od 000 do 10 itd. Zdaj lahko odjemalec sam izda nove identifikatorje, dokler se obseg, ki mu je bil izdan, ne konča. Deluje hitro, ko pa ponovno zaženeš aplikacijo (in odjemalca Hazelcast) se začne novo zaporedje - zato preskoki ipd. Poleg tega razvijalci v resnici ne razumejo, zakaj so ID-ji celi, ampak so tako nedosledni. Vse smo stehtali in prešli na UUID.

Mimogrede, za tiste, ki želijo biti kot Twitter, obstaja taka knjižnica Snowcast - to je implementacija Snowflake na vrhu Hazelcasta. Ogledate si ga lahko tukaj:

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

Ampak nismo več prišli do tega.

TransactionalMap.replace

Še eno presenečenje: TransactionalMap.replace ne deluje. Tukaj je 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

Moral sem napisati svojo zamenjavo z uporabo 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);
}

Preizkusite ne samo običajne podatkovne strukture, ampak tudi njihove transakcijske različice. Zgodi se, da IMap deluje, TransactionalMap pa ne obstaja več.

Vstavite nov JAR brez izpadov

Najprej smo se odločili posneti predmete naših razredov v Hazelcast. Na primer, imamo razred Application, želimo ga shraniti in prebrati. Shrani:

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

Beremo:

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

Vse dela. Nato smo se odločili zgraditi indeks v Hazelcastu za iskanje po:

map.addIndex("subscriberId", false);

In ko so pisali novo entiteto, so začeli prejemati ClassNotFoundException. Hazelcast je poskušal dodati v indeks, vendar ni vedel ničesar o našem razredu in je želel, da se mu zagotovi JAR s tem razredom. Naredili smo prav to, vse je delovalo, a pojavila se je nova težava: kako posodobiti JAR, ne da bi popolnoma zaustavili gručo? Hazelcast ne prevzame novega JAR med posodabljanjem vozlišča za vozlišči. Na tej točki smo se odločili, da lahko živimo brez iskanja po indeksih. Konec koncev, če uporabljate Hazelcast kot shrambo ključev in vrednosti, potem bo vse delovalo? res ne. Tudi tukaj je obnašanje IMap in TransactionalMap drugačno. Če IMap ne skrbi, TransactionalMap vrže napako.

IMap. Napišemo 5000 predmetov, jih preberemo. Vse je pričakovano.

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

Toda v transakciji ne deluje, dobimo izjemo 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();
        }
    });
}

V 3.8 se je pojavil mehanizem za razmestitev uporabniškega razreda. Določite lahko eno glavno vozlišče in na njem posodobite datoteko JAR.

Zdaj smo popolnoma spremenili pristop: sami ga serializiramo v JSON in shranimo v Hazelcast. Hazelcastu ni treba poznati strukture naših razredov in posodabljamo ga lahko brez izpadov. Različice predmetov domene nadzira aplikacija. Hkrati se lahko izvajajo različne različice aplikacije, možna pa je tudi situacija, ko nova aplikacija piše objekte z novimi polji, stara pa teh polj še ne pozna. In hkrati nova aplikacija bere objekte, ki jih je napisala stara aplikacija in nimajo novih polj. Takšne situacije obravnavamo znotraj aplikacije, vendar zaradi enostavnosti ne spreminjamo ali brišemo polj, le razširimo razrede z dodajanjem novih polj.

Kako zagotavljamo visoko učinkovitost

Štiri poti v Hazelcast - dobro, dve v bazo podatkov - slabo

Iti v predpomnilnik za podatke je vedno bolje kot iti v bazo podatkov, vendar tudi ne želite shranjevati neuporabljenih zapisov. Odločitev o tem, kaj predpomniti, pustimo do zadnje stopnje razvoja. Ko je nova funkcionalnost kodirana, vklopimo beleženje vseh poizvedb v PostgreSQL (log_min_duration_statement na 0) in zaženemo obremenitveno testiranje 20 minut. Z uporabo zbranih dnevnikov lahko pripomočki, kot sta pgFouine in pgBadger, sestavijo analitična poročila. V poročilih iščemo predvsem počasne in pogoste poizvedbe. Za počasne poizvedbe izdelamo izvedbeni načrt (EXPLAIN) in ocenimo, ali je tako poizvedbo mogoče pospešiti. Pogoste zahteve za iste vhodne podatke se dobro prilegajo predpomnilniku. Trudimo se, da so poizvedbe "ravne", ena tabela na poizvedbo.

Izkoriščanje

SV kot spletna storitev je začela delovati spomladi 2017, kot ločen produkt pa je SV izšel novembra 2017 (takrat v statusu beta različice).

V več kot letu dni delovanja ni bilo večjih težav pri delovanju spletne storitve CB. Spletno storitev spremljamo preko Zabbix, zberite in razporedite iz Bambus.

Distribucija strežnika SV je na voljo v obliki izvornih paketov: RPM, DEB, MSI. Poleg tega za Windows nudimo en sam namestitveni program v obliki enega EXE, ki namesti strežnik, Hazelcast in Elasticsearch na en računalnik. To različico namestitve smo sprva imenovali »demo« različica, zdaj pa je postalo jasno, da je to najbolj priljubljena možnost uvajanja.

Vir: www.habr.com

Dodaj komentar