Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast

I den här artikeln kommer vi att prata om hur och varför vi utvecklade Interaktionssystem – en mekanism som överför information mellan klientapplikationer och 1C:Enterprise-servrar – från att ställa in en uppgift till att tänka igenom arkitekturen och implementeringsdetaljerna.

Interaktionssystemet (nedan kallat SV) är ett distribuerat, feltolerant meddelandesystem med garanterad leverans. SV är designad som en högbelastningstjänst med hög skalbarhet, tillgänglig både som en onlinetjänst (tillhandahållen av 1C) och som en massproducerad produkt som kan distribueras på dina egna serveranläggningar.

SV använder distribuerad lagring Hasselcast och sökmotor Elasticsearch. Vi kommer också att prata om Java och hur vi horisontellt skalar PostgreSQL.
Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast

Problem uttalande

För att tydliggöra varför vi skapade Interaktionssystemet ska jag berätta lite om hur utvecklingen av affärsapplikationer i 1C fungerar.

Till att börja med, lite om oss för de som ännu inte vet vad vi gör :) Vi gör teknikplattformen 1C:Enterprise. Plattformen inkluderar ett utvecklingsverktyg för affärsapplikationer, samt en runtime som gör att affärsapplikationer kan köras i en plattformsoberoende miljö.

Utvecklingsparadigm för klient-server

Affärsapplikationer skapade på 1C:Enterprise fungerar i tre nivåer klient-server arkitektur "DBMS - applikationsserver - klient". Ansökningskod inskriven inbyggt 1C-språk, kan köras på applikationsservern eller på klienten. Allt arbete med applikationsobjekt (kataloger, dokument, etc.), samt läsning och skrivning av databasen, utförs endast på servern. Funktionaliteten hos formulär och kommandogränssnitt implementeras också på servern. Klienten utför ta emot, öppna och visa formulär, "kommunicera" med användaren (varningar, frågor...), små beräkningar i formulär som kräver ett snabbt svar (exempelvis multiplicera priset med kvantitet), arbeta med lokala filer, arbeta med utrustning.

I applikationskoden måste rubrikerna för procedurer och funktioner uttryckligen ange var koden kommer att exekveras - med hjälp av &AtClient / &AtServer-direktiven (&AtClient / &AtServer i den engelska versionen av språket). 1C-utvecklare kommer nu att rätta mig genom att säga att direktiv faktiskt är det больше, men för oss är detta inte viktigt nu.

Du kan anropa serverkod från klientkod, men du kan inte anropa klientkod från serverkod. Detta är en grundläggande begränsning som vi har gjort av flera anledningar. Speciellt eftersom serverkoden måste skrivas på ett sådant sätt att den körs på samma sätt oavsett var den anropas - från klienten eller från servern. Och i fallet med anrop av serverkod från en annan serverkod finns det ingen klient som sådan. Och eftersom under körningen av serverkoden kunde klienten som anropade den stänga, avsluta applikationen och servern skulle inte längre ha någon att anropa.

Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast
Kod som hanterar ett knappklick: att anropa en serverprocedur från klienten kommer att fungera, att anropa en klientprocedur från servern kommer inte att fungera

Det betyder att om vi vill skicka något meddelande från servern till klientapplikationen, till exempel att genereringen av en "långvarig" rapport har avslutats och rapporten kan ses, så har vi inte en sådan metod. Du måste använda knep, till exempel, regelbundet polla servern från klientkoden. Men detta tillvägagångssätt laddar systemet med onödiga samtal och ser i allmänhet inte särskilt elegant ut.

Och det finns också ett behov till exempel när ett telefonsamtal kommer SIP- när du ringer ett samtal, meddela klientapplikationen om detta så att den kan använda uppringarens nummer för att hitta det i motpartsdatabasen och visa användarinformation om den uppringande motparten. Eller, till exempel, när en order kommer till lagret, meddela kundens kundapplikation om detta. I allmänhet finns det många fall där en sådan mekanism skulle vara användbar.

Själva produktionen

Skapa en meddelandemekanism. Snabb, pålitlig, med garanterad leverans, med möjlighet att flexibelt söka efter meddelanden. Baserat på mekanismen, implementera en budbärare (meddelanden, videosamtal) som körs i 1C-applikationer.

Designa systemet så att det är horisontellt skalbart. Den ökande belastningen måste täckas genom att öka antalet noder.

genomförande

Vi beslutade att inte integrera serverdelen av SV direkt i 1C:Enterprise-plattformen, utan att implementera den som en separat produkt, vars API kan anropas från koden för 1C-applikationslösningar. Detta gjordes av ett antal anledningar, varav den främsta var att jag ville göra det möjligt att utbyta meddelanden mellan olika 1C-applikationer (till exempel mellan Trade Management och Accounting). Olika 1C-applikationer kan köras på olika versioner av 1C:Enterprise-plattformen, finnas på olika servrar osv. Under sådana förhållanden är implementeringen av SV som en separat produkt placerad "på sidan" av 1C-installationer den optimala lösningen.

Så vi bestämde oss för att göra SV som en separat produkt. Vi rekommenderar att små företag använder CB-servern som vi installerade i vårt moln (wss://1cdialog.com) för att undvika de overheadkostnader som är förknippade med lokal installation och konfiguration av servern. Stora kunder kan tycka att det är tillrådligt att installera sin egen CB-server på sina anläggningar. Vi använde ett liknande tillvägagångssätt i vår moln SaaS-produkt 1cFärskt – den produceras som en serietillverkad produkt för installation på kunders ställen och är även utplacerad i vårt moln https://1cfresh.com/.

ansökan

För att fördela belastningen och feltoleransen kommer vi att distribuera inte en Java-applikation, utan flera, med en lastbalanserare framför sig. Om du behöver överföra ett meddelande från nod till nod, använd publicera/prenumerera i Hazelcast.

Kommunikationen mellan klienten och servern sker via websocket. Den är väl lämpad för realtidssystem.

Distribuerad cache

Vi valde mellan Redis, Hazelcast och Ehcache. Det är 2015. Redis har precis släppt ett nytt kluster (för nytt, skrämmande), det finns Sentinel med många restriktioner. Ehcache vet inte hur man sätter ihop till ett kluster (denna funktion dök upp senare). Vi bestämde oss för att prova det med Hazelcast 3.4.
Hazelcast sätts ihop till ett kluster ur kartongen. I singelnodsläge är det inte särskilt användbart och kan bara användas som en cache - det vet inte hur man dumpar data till disk, om du tappar den enda noden förlorar du data. Vi distribuerar flera Hazelcasts, mellan vilka vi säkerhetskopierar kritisk data. Vi säkerhetskopierar inte cachen – vi har inget emot det.

För oss är Hazelcast:

  • Lagring av användarsessioner. Det tar lång tid att gå till databasen för en session varje gång, så vi lägger alla sessioner i Hazelcast.
  • Cache. Om du letar efter en användarprofil, kolla cachen. Skrev ett nytt meddelande - lägg det i cachen.
  • Ämnen för kommunikation mellan applikationsinstanser. Noden genererar en händelse och placerar den i Hazelcast-ämnet. Andra applikationsnoder som prenumererar på detta ämne tar emot och bearbetar händelsen.
  • Kluster lås. Till exempel skapar vi en diskussion med en unik nyckel (singleton diskussion inom 1C-databasen):

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

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

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

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

Vi kollade att det inte finns någon kanal. Vi tog låset, kontrollerade det igen och skapade det. Om du inte kontrollerar låset efter att ha tagit låset, så finns det en chans att en annan tråd också kontrollerade vid det tillfället och nu kommer att försöka skapa samma diskussion - men den finns redan. Du kan inte låsa med synkroniserat eller vanligt java-lås. Genom databasen - det är långsamt, och det är synd för databasen; genom Hazelcast - det är vad du behöver.

Att välja en DBMS

Vi har lång och framgångsrik erfarenhet av att arbeta med PostgreSQL och samarbeta med utvecklarna av detta DBMS.

Det är inte lätt med ett PostgreSQL-kluster - det finns XL, XC, Citus, men i allmänhet är dessa inte NoSQLs som skalas ur lådan. Vi betraktade inte NoSQL som huvudlagring; det räckte med att vi tog Hazelcast, som vi inte hade arbetat med tidigare.

Om du behöver skala en relationsdatabas betyder det skärning. Som ni vet delar vi med sharding databasen i separata delar så att var och en av dem kan placeras på en separat server.

Den första versionen av vår sharding antog möjligheten att distribuera var och en av tabellerna i vår applikation över olika servrar i olika proportioner. Det finns många meddelanden på server A - snälla, låt oss flytta en del av den här tabellen till server B. Det här beslutet skrek helt enkelt om för tidig optimering, så vi bestämde oss för att begränsa oss till ett tillvägagångssätt med flera hyresgäster.

Du kan läsa om flerhyresgäster till exempel på hemsidan Citus Data.

SV har begreppen applikation och abonnent. En applikation är en specifik installation av en affärsapplikation, såsom ERP eller Accounting, med dess användare och affärsdata. En prenumerant är en organisation eller individ för vars räkning applikationen är registrerad i SV-servern. En abonnent kan ha flera applikationer registrerade, och dessa applikationer kan utbyta meddelanden med varandra. Prenumeranten blev hyresgäst i vårt system. Meddelanden från flera abonnenter kan finnas i en fysisk databas; om vi ser att en abonnent har börjat generera mycket trafik flyttar vi den till en separat fysisk databas (eller till och med en separat databasserver).

Vi har en huvuddatabas där en routingtabell lagras med information om var alla abonnentdatabaser finns.

Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast

För att förhindra att huvuddatabasen blir en flaskhals håller vi routingtabellen (och annan data som ofta behövs) i en cache.

Om abonnentens databas börjar sakta ner kommer vi att klippa den i partitioner inuti. På andra projekt använder vi pg_pathman.

Eftersom det är dåligt att förlora användarmeddelanden underhåller vi våra databaser med repliker. Kombinationen av synkrona och asynkrona repliker gör att du kan försäkra dig i händelse av förlust av huvuddatabasen. Meddelandeförlust kommer bara att inträffa om den primära databasen och dess synkrona replik misslyckas samtidigt.

Om en synkron replik går förlorad, blir den asynkrona repliken synkron.
Om huvuddatabasen går förlorad blir den synkrona repliken huvuddatabasen och den asynkrona repliken blir en synkron replik.

Elasticsearch för sökning

Eftersom bland annat SV också är en budbärare kräver det en snabb, bekväm och flexibel sökning, med hänsyn till morfologi, med oprecisa matchningar. Vi bestämde oss för att inte uppfinna hjulet på nytt och använda den kostnadsfria sökmotorn Elasticsearch, skapad utifrån biblioteket Lucene. Vi distribuerar även Elasticsearch i ett kluster (master – data – data) för att eliminera problem i händelse av fel på applikationsnoder.

På github hittade vi Rysk morfologi plugin för Elasticsearch och använd den. I Elasticsearch-indexet lagrar vi ordrötter (som pluginen bestämmer) och N-gram. När användaren skriver in text för att söka letar vi efter den inskrivna texten bland N-gram. När det sparas i indexet kommer ordet "texter" att delas upp i följande N-gram:

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

Och roten till ordet "text" kommer också att bevaras. Detta tillvägagångssätt låter dig söka i början, i mitten och i slutet av ordet.

Hela bilden

Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast
Repetition av bilden från början av artikeln, men med förklaringar:

  • Balanser exponerad på Internet; vi har nginx, det kan vara vilket som helst.
  • Java-applikationsinstanser kommunicerar med varandra via Hazelcast.
  • För att arbeta med ett webbuttag använder vi Netty.
  • Java-applikationen är skriven i Java 8 och består av paket OSGi. Planerna inkluderar migrering till Java 10 och övergång till moduler.

Utveckling och testning

I processen med att utveckla och testa SV:n stötte vi på ett antal intressanta egenskaper hos de produkter vi använder.

Belastningstestning och minnesläckor

Utgivningen av varje SV-release involverar lasttestning. Det är framgångsrikt när:

  • Testet fungerade i flera dagar och det förekom inga servicefel
  • Svarstiden för nyckeloperationer översteg inte en bekväm tröskel
  • Prestandaförsämringen jämfört med den tidigare versionen är inte mer än 10 %

Vi fyller testdatabasen med data - för att göra detta får vi information om den mest aktiva abonnenten från produktionsservern, multiplicerar dess nummer med 5 (antalet meddelanden, diskussioner, användare) och testar det på det sättet.

Vi utför belastningstester av interaktionssystemet i tre konfigurationer:

  1. stresstest
  2. Endast anslutningar
  3. Registrering av prenumeranter

Under stresstestet lanserar vi flera hundra trådar, och de laddar systemet utan uppehåll: att skriva meddelanden, skapa diskussioner, ta emot en lista med meddelanden. Vi simulerar vanliga användares handlingar (få en lista över mina olästa meddelanden, skriv till någon) och mjukvarulösningar (sänder ett paket med en annan konfiguration, bearbetar en varning).

Så här ser till exempel en del av stresstestet ut:

  • Användaren loggar in
    • Begär dina olästa diskussioner
    • 50 % sannolikt att läsa meddelanden
    • 50 % sannolikt att sms:a
    • Nästa användare:
      • Har 20 % chans att skapa en ny diskussion
      • Väljer slumpmässigt någon av sina diskussioner
      • Går in
      • Begär meddelanden, användarprofiler
      • Skapar fem meddelanden riktade till slumpmässiga användare från denna diskussion
      • Lämnar diskussion
      • Upprepas 20 gånger
      • Loggar ut, går tillbaka till början av skriptet

    • En chatbot kommer in i systemet (emulerar meddelanden från applikationskod)
      • Har 50 % chans att skapa en ny kanal för datautbyte (särskild diskussion)
      • 50 % sannolikt att skriva ett meddelande till någon av de befintliga kanalerna

Scenariot "Endast anslutningar" dök upp av en anledning. Det finns en situation: användare har anslutit systemet, men har ännu inte engagerat sig. Varje användare slår på datorn klockan 09:00 på morgonen, upprättar en anslutning till servern och förblir tyst. Dessa killar är farliga, det finns många av dem - de enda paketen de har är PING/PONG, men de behåller anslutningen till servern (de kan inte hålla det kvar - tänk om det finns ett nytt meddelande). Testet återger en situation där ett stort antal sådana användare försöker logga in på systemet på en halvtimme. Det liknar ett stresstest, men dess fokus ligger just på denna första ingång - så att det inte finns några misslyckanden (en person använder inte systemet, och det faller redan av - det är svårt att tänka på något värre).

Prenumerantregistreringsskriptet startar från den första lanseringen. Vi gjorde ett stresstest och var säkra på att systemet inte saktade ner under korrespondensen. Men användare kom och registreringen började misslyckas på grund av en timeout. Vid registrering använde vi / dev / random, som är relaterad till systemets entropi. Servern hann inte samla tillräckligt med entropi och när en ny SecureRandom begärdes frös den i tiotals sekunder. Det finns många vägar ur den här situationen, till exempel: byta till det mindre säkra /dev/urandom, installera ett speciellt kort som genererar entropi, generera slumptal i förväg och lagra dem i en pool. Vi stängde tillfälligt problemet med poolen, men sedan dess har vi kört ett separat test för att registrera nya prenumeranter.

Vi använder som lastgenerator JMeter. Den vet inte hur den fungerar med websocket, den behöver en plugin. De första i sökresultaten för frågan "jmeter websocket" är: artiklar från BlazeMeter, som rekommenderar plugin av Maciej Zaleski.

Det var där vi bestämde oss för att börja.

Nästan omedelbart efter att vi påbörjat seriösa tester upptäckte vi att JMeter började läcka minne.

Insticksprogrammet är en separat stor historia; med 176 stjärnor har den 132 gafflar på github. Författaren själv har inte förbundit sig till det sedan 2015 (vi tog det 2015, då väckte det inga misstankar), flera github-problem angående minnesläckor, 7 oslutna pull-förfrågningar.
Om du bestämmer dig för att utföra belastningstestning med detta plugin, var uppmärksam på följande diskussioner:

  1. I en flertrådig miljö användes en vanlig LinkedList, och resultatet blev det NPE i körtid. Detta kan lösas antingen genom att byta till ConcurrentLinkedDeque eller genom synkroniserade block. Vi valde det första alternativet för oss själva (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Minnesläcka; vid frånkoppling raderas inte anslutningsinformation (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. I streamingläge (när websocket inte är stängt i slutet av provet, utan används senare i planen), fungerar inte svarsmönster (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Det här är en av dem på github. Vad vi gjorde:

  1. Har tagit gaffel Elyran Kogan (@elyrank) – det fixar problem 1 och 3
  2. Löste problem 2
  3. Uppdaterad brygga från 9.2.14 till 9.3.12
  4. Wrapped SimpleDateFormat i ThreadLocal; SimpleDateFormat är inte trådsäkert, vilket ledde till NPE vid körning
  5. Fixat ytterligare en minnesläcka (anslutningen stängdes felaktigt när den kopplades bort)

Och ändå flyter det!

Minnet började ta slut inte på en dag, utan på två. Det fanns absolut ingen tid kvar, så vi bestämde oss för att lansera färre trådar, men på fyra agenter. Detta borde ha räckt i minst en vecka.

Två dagar har gått...

Nu har Hazelcast slut på minne. Loggarna visade att efter ett par dagars testning började Hazelcast klaga på bristande minne, och efter en tid föll klustret isär och noderna fortsatte att dö en efter en. Vi kopplade JVisualVM till hasselgjutning och såg en "stigande såg" - den kallade regelbundet GC, men kunde inte rensa minnet.

Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast

Det visade sig att i hazelcast 3.4, när man raderar en karta / multiMap (map.destroy()), är minnet inte helt frigjort:

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

Felet är nu åtgärdat i 3.5, men det var ett problem då. Vi skapade nya multikartor med dynamiska namn och raderade dem enligt vår logik. Koden såg ut ungefär så här:

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

Ring upp:

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

multiMap skapades för varje prenumeration och raderades när det inte behövdes. Vi bestämde att vi skulle starta Map , nyckeln kommer att vara namnet på prenumerationen, och värdena kommer att vara sessionsidentifierare (från vilken du sedan kan få användaridentifierare, om det behövs).

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

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

Diagrammen har förbättrats.

Hur och varför skrev vi en skalbar tjänst med hög belastning för 1C: Enterprise: Java, PostgreSQL, Hazelcast

Vad mer har vi lärt oss om lasttestning?

  1. JSR223 måste skrivas i groovy och inkludera kompileringscache - det är mycket snabbare. Länk.
  2. Jmeter-Plugins-grafer är lättare att förstå än vanliga. Länk.

Om vår erfarenhet av Hazelcast

Hazelcast var en ny produkt för oss, vi började arbeta med den från version 3.4.1, nu kör vår produktionsserver version 3.9.2 (i skrivande stund är den senaste versionen av Hazelcast 3.10).

ID generering

Vi började med heltalsidentifierare. Låt oss föreställa oss att vi behöver ytterligare en Långa för en ny varelse. Sekvens i databasen är inte lämplig, tabellerna är inblandade i sharding - det visar sig att det finns ett meddelande ID=1 i DB1 och ett meddelande ID=1 i DB2, du kan inte lägga detta ID i Elasticsearch, inte heller i Hazelcast , men det värsta är om du vill kombinera data från två databaser till en (till exempel bestämma att en databas räcker för dessa prenumeranter). Du kan lägga till flera AtomicLongs till Hazelcast och hålla en räknare där, då blir prestandan för att få ett nytt ID incrementAndGet plus tiden för en begäran till Hazelcast. Men Hazelcast har något mer optimalt - FlakeIdGenerator. När de kontaktar varje klient får de ett ID-intervall, till exempel den första – från 1 till 10 000, den andra – från 10 001 till 20 000, och så vidare. Nu kan klienten utfärda nya identifierare på egen hand tills intervallet som utfärdats till den tar slut. Det fungerar snabbt, men när du startar om applikationen (och Hazelcast-klienten) börjar en ny sekvens - därav överhoppningarna osv. Dessutom förstår utvecklarna inte riktigt varför ID:n är heltal, men är så inkonsekventa. Vi vägde allt och bytte till UUID.

Förresten, för de som vill vara som Twitter, det finns ett sådant Snowcast-bibliotek - det här är en implementering av Snowflake ovanpå Hazelcast. Du kan se den här:

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

Men vi har inte hunnit med det längre.

TransactionalMap.replace

En annan överraskning: TransactionalMap.replace fungerar inte. Här är ett 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

Jag var tvungen att skriva min egen ersättare med 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);
}

Testa inte bara vanliga datastrukturer utan även deras transaktionsversioner. Det händer att IMap fungerar, men TransactionalMap finns inte längre.

Sätt i en ny JAR utan stillestånd

Först bestämde vi oss för att spela in föremål från våra klasser i Hazelcast. Vi har till exempel en Application-klass, vi vill spara och läsa den. Spara:

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

Vi läser:

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

Allt fungerar. Sedan bestämde vi oss för att bygga ett index i Hazelcast för att söka efter:

map.addIndex("subscriberId", false);

Och när de skrev en ny enhet började de ta emot ClassNotFoundException. Hazelcast försökte lägga till indexet, men visste ingenting om vår klass och ville att en JAR med denna klass skulle levereras till den. Vi gjorde just det, allt fungerade, men ett nytt problem dök upp: hur uppdaterar man JAR utan att helt stoppa klustret? Hazelcast hämtar inte den nya JAR under en nod-för-nod-uppdatering. Vid det här laget bestämde vi oss för att vi kunde leva utan indexsökning. När allt kommer omkring, om du använder Hazelcast som en nyckel-värde butik, kommer allt att fungera? Inte riktigt. Även här är beteendet hos IMap och TransactionalMap annorlunda. Där IMap inte bryr sig, skickar TransactionalMap ett fel.

IMap. Vi skriver 5000 föremål, läser dem. Allt förväntas.

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

Men det fungerar inte i en transaktion, vi får en 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();
        }
    });
}

I 3.8 dök User Class Deployment-mekanismen upp. Du kan ange en huvudnod och uppdatera JAR-filen på den.

Nu har vi helt ändrat vårt tillvägagångssätt: vi serialiserar det själva till JSON och sparar det i Hazelcast. Hazelcast behöver inte känna till strukturen för våra klasser, och vi kan uppdatera utan stillestånd. Versionering av domänobjekt styrs av applikationen. Olika versioner av applikationen kan köras samtidigt, och en situation är möjlig när den nya applikationen skriver objekt med nya fält, men den gamla känner ännu inte till dessa fält. Och samtidigt läser den nya applikationen objekt skrivna av den gamla applikationen som inte har nya fält. Vi hanterar sådana situationer inom applikationen, men för enkelhetens skull ändrar eller tar vi inte bort fält, vi utökar bara klasserna genom att lägga till nya fält.

Hur vi säkerställer hög prestanda

Fyra resor till Hazelcast - bra, två till databasen - dåliga

Att gå till cachen för data är alltid bättre än att gå till databasen, men du vill inte heller lagra oanvända poster. Vi lämnar beslutet om vad som ska cache till det sista utvecklingsstadiet. När den nya funktionen är kodad aktiverar vi loggning av alla frågor i PostgreSQL (log_min_duration_statement till 0) och kör belastningstestning i 20 minuter. Med hjälp av de insamlade loggarna kan verktyg som pgFouine och pgBadger bygga analytiska rapporter. I rapporter letar vi främst efter långsamma och frekventa frågor. För långsamma frågor bygger vi en exekveringsplan (EXPLAIN) och utvärderar om en sådan fråga kan påskyndas. Frekventa förfrågningar om samma indata passar väl in i cachen. Vi försöker hålla frågor "platta", en tabell per fråga.

Utnyttjande

SV som onlinetjänst togs i drift under våren 2017 och som en separat produkt släpptes SV i november 2017 (då i betaversionsstatus).

Under mer än ett års drift har det inte förekommit några allvarliga problem i driften av CB:s onlinetjänst. Vi övervakar onlinetjänsten via Zabbix, samla in och distribuera från Bambu.

SV-serverdistributionen levereras i form av inbyggda paket: RPM, DEB, MSI. Plus för Windows tillhandahåller vi ett enda installationsprogram i form av en enda EXE som installerar servern, Hazelcast och Elasticsearch på en maskin. Vi kallade först den här versionen av installationen som "demo"-versionen, men det har nu blivit klart att detta är det mest populära installationsalternativet.

Källa: will.com

Lägg en kommentar