Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast

V tomto článku budeme hovořit o tom, jak a proč jsme se vyvinuli Interakční systém - mechanismus, který přenáší informace mezi klientskými aplikacemi a servery 1C: Enterprise - od zadání úkolu až po promyšlení architektury a implementačních detailů.

Interakční systém (dále jen CB) je distribuovaný systém zasílání zpráv odolný proti chybám s garantovaným doručením. CB je navržena jako vysoce vytížená služba s vysokou škálovatelností, dostupná jako online služba (poskytovaná společností 1C) i jako produkt hromadné výroby, který lze nasadit na vlastních serverových zařízeních.

SW využívá distribuované úložiště hazelcast a vyhledávač Elastickýsearch. Budeme také mluvit o Javě a o tom, jak horizontálně škálujeme PostgreSQL.
Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast

Formulace problému

Aby bylo jasné, proč jsme vytvořili Interaction System, řeknu vám něco o tom, jak funguje vývoj obchodních aplikací v 1C.

Nejprve něco málo o nás pro ty, kteří ještě nevědí, co děláme :) Vyvíjíme technologickou platformu 1C:Enterprise. Platforma zahrnuje nástroj pro vývoj obchodních aplikací a také běhové prostředí, které umožňuje obchodním aplikacím pracovat v prostředí napříč platformami.

Paradigma vývoje klient-server

Obchodní aplikace vytvořené na 1C:Enterprise fungují na třech úrovních klient-server architektura "DBMS - aplikační server - klient". Kód aplikace napsaný v vestavěný jazyk 1C, může běžet na aplikačním serveru nebo na klientovi. Veškerá práce s aplikačními objekty (adresáře, dokumenty atd.), stejně jako čtení a zápis databáze, probíhá pouze na serveru. Na serveru je implementována také funkce rozhraní formulářů a příkazů. Na klientovi se přijímají, otevírají a zobrazují formuláře, „komunikace“ s uživatelem (upozornění, dotazy...), drobné kalkulace ve formulářích, které vyžadují rychlou reakci (například vynásobení ceny množstvím), práce s lokálními soubory, práce se zařízením.

V kódu aplikace musí hlavičky procedur a funkcí výslovně uvádět, kde bude kód spuštěn – pomocí direktiv &AtClient / &AtServer (&AtClient / &AtServer v anglické verzi jazyka). Vývojáři 1C mě nyní opraví tím, že směrnice skutečně jsou více, ale pro nás to teď není důležité.

Můžete volat kód serveru z kódu klienta, ale nemůžete volat kód klienta z kódu serveru. Toto je zásadní omezení, které jsme učinili z několika důvodů. Zejména proto, že kód serveru musí být napsán tak, aby se spouštěl stejným způsobem, bez ohledu na to, odkud je volán - z klienta nebo ze serveru. A v případě volání kódu serveru z jiného kódu serveru neexistuje žádný klient jako takový. A protože během provádění kódu serveru by se klient, který jej volal, mohl zavřít, ukončit aplikaci a server by neměl nikoho, komu by mohl volat.

Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast
Kód, který zpracovává kliknutí na tlačítko: volání procedury serveru z klienta bude fungovat, volání procedury klienta ze serveru nikoli

To znamená, že pokud chceme ze serveru klientské aplikaci poslat nějakou zprávu, například o tom, že tvorba „dlouhohrajícího“ reportu skončila a report je možné si prohlédnout, takový způsob nemáme. Musíte použít triky, například pravidelně dotazovat server z klientského kódu. Tento přístup ale zatěžuje systém zbytečnými hovory a obecně nevypadá příliš elegantně.

A také je třeba, když například telefon SIP-call, upozornit na to klientskou aplikaci, aby ji mohla najít v databázi protistrany podle čísla volajícího a ukázat uživateli informace o volající protistraně. Nebo například, když objednávka dorazí na sklad, upozorněte na to klientskou aplikaci zákazníka. Obecně existuje mnoho případů, kdy by takový mechanismus byl užitečný.

Vlastně nastavení

Vytvořte mechanismus zasílání zpráv. Rychlé, spolehlivé, s garantovaným doručením, s možností flexibilního vyhledávání zpráv. Na základě mechanismu implementujte messenger (zprávy, videohovory), který funguje uvnitř aplikací 1C.

Navrhněte systém horizontálně škálovatelný. Zvyšující se zátěž by měla být pokryta zvýšením počtu uzlů.

uskutečnění

Serverovou část CB jsme se rozhodli nevkládat přímo do platformy 1C:Enterprise, ale implementovat ji jako samostatný produkt, jehož API lze volat z kódu aplikačních řešení 1C. Bylo to provedeno z několika důvodů, z nichž hlavním bylo umožnit výměnu zpráv mezi různými aplikacemi 1C (například mezi ministerstvem obchodu a účetnictví). Různé aplikace 1C mohou běžet na různých verzích platformy 1C:Enterprise, být umístěny na různých serverech atd. Za takových podmínek je optimálním řešením implementace CB jako samostatného produktu, umístěného „na straně“ instalací 1C.

Rozhodli jsme se tedy vyrobit CB jako samostatný produkt. Pro menší společnosti doporučujeme použít CB server, který jsme nainstalovali do našeho cloudu (wss://1cdialog.com), aby se předešlo režii spojené s instalací a konfigurací místního serveru. Velcí zákazníci však mohou považovat za účelné instalovat vlastní CB server ve svých zařízeních. Podobný přístup jsme použili v našem cloudovém produktu SaaS. 1cČerstvé – je vydáván jako produkční produkt pro instalaci zákazníky a je také nasazen v našem cloudu https://1cfresh.com/.

Aplikace

Pro rozložení zátěže a odolnost proti chybám nasadíme ne jednu Java aplikaci, ale několik, postavíme před ně load balancer. Pokud potřebujete poslat zprávu z uzlu na uzel, použijte publikovat/přihlásit se k odběru v Hazelcast.

Komunikace mezi klientem a serverem - přes websocket. Dobře se hodí pro systémy pracující v reálném čase.

Distribuovaná mezipaměť

Vyberte si mezi Redis, Hazelcast a Ehcache. Venku v roce 2015. Redis právě vydal nový cluster (příliš nový, děsivý), je tam Sentinel se spoustou omezení. Ehcache neumí clusterovat (tato funkce se objevila později). Rozhodli jsme se vyzkoušet s Hazelcast 3.4.
Hazelcast je seskupený po vybalení z krabice. V režimu jednoho uzlu není příliš užitečný a vejde se pouze jako mezipaměť - neumí vypsat data na disk, pokud dojde ke ztrátě jediného uzlu, data se ztratí. Nasazujeme několik Hazelcastů, mezi kterými zálohujeme kritická data. Mezipaměť nezálohujeme – není nám ho líto.

Pro nás je Hazelcast:

  • Ukládání uživatelských relací. Cesta do databáze na relaci trvá dlouho, takže všechny relace vkládáme do Hazelcastu.
  • Mezipaměti. Hledáte uživatelský profil - zkontrolujte mezipaměť. Napsal novou zprávu - vložte ji do mezipaměti.
  • Témata pro komunikaci instancí aplikací. Uzel vygeneruje událost a umístí ji na téma Hazelcast. Ostatní uzly aplikace přihlášené k odběru tohoto tématu událost přijímají a zpracovávají.
  • clusterové zámky. Například vytvoříme diskuzi pomocí jedinečného klíče (diskuse-singleton v rámci základny 1C):

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

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

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

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

Zkontrolovali jsme, že neexistuje žádný kanál. Vzali zámek, znovu ho zkontrolovali, vytvořili. Pokud po převzetí zámku nezkontrolujete, pak je šance, že v tu chvíli kontrolovalo i jiné vlákno a nyní se pokusí založit stejnou diskuzi - a ta již existuje. Není možné provést zámek prostřednictvím synchronizovaného nebo běžného java zámku. Přes základnu - pomalu a základna je škoda, přes Hazelcast - to, co potřebujete.

Výběr DBMS

Máme bohaté a úspěšné zkušenosti s PostgreSQL a spoluprací s vývojáři tohoto DBMS.

S clusterem to PostgreSQL není snadné – ano XL, XC, Citus, ale obecně to není noSQL, co se škáluje hned po vybalení. NoSQL nebylo považováno za hlavní úložiště, stačilo vzít Hazelcast, se kterým jsme předtím nepracovali.

Protože potřebujete škálovat relační databázi, znamená to stříhání. Jak víte, při shardování rozdělujeme databázi na samostatné části tak, aby každou z nich bylo možné umístit na samostatný server.

První verze našeho shardingu předpokládala schopnost rozšířit každou z tabulek naší aplikace na různé servery v různých poměrech. Spousta zpráv na serveru A – přesuňme prosím část této tabulky na server B. Toto rozhodnutí jen křičelo o předčasné optimalizaci, takže jsme se rozhodli omezit na přístup s více nájemci.

O multi-tenantu se dočtete například na webu Citus Data.

Ve SV jsou koncepty aplikace a předplatitele. Aplikace je konkrétní instalace podnikové aplikace, jako je ERP nebo Účetnictví, s jejími uživateli a obchodními daty. Předplatitel je organizace nebo fyzická osoba, jejímž jménem je aplikace registrována na serveru CB. Předplatitel může mít zaregistrovaných několik aplikací a tyto aplikace si mohou vyměňovat zprávy. Předplatitel se stal nájemcem v našem systému. Zprávy několika předplatitelů mohou být umístěny v jedné fyzické základně; pokud vidíme, že některý předplatitel začal generovat velký provoz, přesuneme jej do samostatné fyzické databáze (nebo dokonce na samostatný databázový server).

Máme hlavní databázi, kde je uložena směrovací tabulka s informacemi o umístění všech účastnických databází.

Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast

Aby hlavní databáze nebyla úzkým hrdlem, uchováváme směrovací tabulku (a další často požadovaná data) v mezipaměti.

Pokud se databáze předplatitele začne zpomalovat, rozřežeme ji uvnitř na oddíly. Na jiných projektech, k rozdělení velkých tabulek, používáme pg_pathman.

Protože ztráta uživatelských zpráv je špatná, zálohujeme naše databáze pomocí replik. Kombinace synchronních a asynchronních replik umožňuje pojistit se proti ztrátě hlavní databáze. Ke ztrátě zprávy dojde pouze v případě současného selhání hlavní databáze a její synchronní repliky.

Pokud dojde ke ztrátě synchronní repliky, stane se asynchronní replika synchronní.
Pokud dojde ke ztrátě hlavní databáze, synchronní replika se stane hlavní databází, asynchronní replika se stane synchronní replikou.

Elasticsearch pro vyhledávání

Protože je CB mimo jiné také messenger, potřebujeme zde rychlé, pohodlné a flexibilní vyhledávání s ohledem na morfologii podle nepřesných shod. Rozhodli jsme se znovu nevynalézat kolo a použít bezplatný vyhledávač Elasticsearch, vytvořený na základě knihovny Lucene. Elasticsearch také nasazujeme v clusteru (master - data - data) pro eliminaci problémů v případě výpadku aplikačních uzlů.

Na githubu jsme našli Plugin ruské morfologie pro Elasticsearch a použijte jej. V indexu Elasticsearch ukládáme kořeny slov (které plugin definuje) a N-gramy. Když uživatel zadává text k vyhledávání, hledáme zadaný text mezi N-gramy. Po uložení do indexu bude slovo „texty“ rozděleno do následujících N-gramů:

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

A také se uloží kořen slova "text". Tento přístup umožňuje vyhledávat na začátku, uprostřed a na konci slova.

Celkový obraz

Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast
Opakuji obrázek ze začátku článku, ale s vysvětlením:

  • Balancer vystavený internetu; máme nginx, může to být jakýkoli.
  • Instance aplikací Java spolu komunikují přes Hazelcast.
  • Pro práci s webovou zásuvkou používáme Netty.
  • Java aplikace napsaná v Javě 8 se skládá z balíčků OSGi. V plánu je migrace na Java 10 a přechod na moduly.

Vývoj a testování

Při vývoji a testování CB jsme se setkali s řadou zajímavých vlastností námi používaných produktů.

Testování zátěže a úniky paměti

Uvolnění každého uvolnění CB je zátěžovým testem. Úspěšně prošel, když:

  • Test fungoval několik dní a nedošlo k žádnému odmítnutí služby
  • Doba odezvy pro klíčové operace nepřesáhla pohodlný práh
  • Snížení výkonu ve srovnání s předchozí verzí není větší než 10 %

Testovací databázi naplníme daty - k tomu získáme informace o nejaktivnějším účastníkovi z produkčního serveru, vynásobíme jeho čísla 5 (počet zpráv, diskuzí, uživatelů) a tak testujeme.

Zátěžové testování interakčního systému provádíme ve třech konfiguracích:

  1. stresový test
  2. Pouze připojení
  3. Registrace předplatitele

Během zátěžového testu spustíme několik stovek vláken, která bez zastavení načítají systém: pište zprávy, zakládejte diskuse, přijímejte seznam zpráv. Simulujeme akce běžných uživatelů (získat seznam mých nepřečtených zpráv, napsat někomu) a programová rozhodnutí (přenést balíček do jiné konfigurace, zpracovat upozornění).

Například takto vypadá část zátěžového testu:

  • Uživatel se přihlásí
    • Požaduje vaše nepřečtená vlákna
    • 50% šance na čtení zpráv
    • 50% šance na psaní zpráv
    • Další uživatel:
      • 20% šance na vytvoření nového vlákna
      • Náhodně vybere kteroukoli ze svých diskuzí
      • Vchází dovnitř
      • Žádosti o zprávy, uživatelské profily
      • Vytvoří pět zpráv adresovaných náhodným uživatelům z tohoto vlákna
      • Mimo diskusi
      • Opakuje se 20krát
      • Odhlásí se, vrátí se zpět na začátek skriptu

    • Chatbot vstoupí do systému (emuluje zprávy z kódu aplikovaných řešení)
      • 50% šance na vytvoření nového datového kanálu (zvláštní diskuze)
      • 50% šance napsat zprávu v některém ze stávajících kanálů

Scénář „Pouze připojení“ se objevil z nějakého důvodu. Existuje situace: uživatelé připojili systém, ale ještě nebyli zapojeni. Každý uživatel ráno v 09:00 zapne počítač, naváže spojení se serverem a mlčí. Tihle kluci jsou nebezpeční, je jich hodně - mají jen PING / PONG z paketů, ale udržují připojení k serveru (nemohou ho udržet - a najednou nová zpráva). Test reprodukuje situaci, kdy se velké množství takových uživatelů pokouší přihlásit do systému za půl hodiny. Vypadá to jako zátěžový test, ale jeho zaměření je právě na tento první vstup - aby nedocházelo k výpadkům (člověk ten systém nepoužívá, ale už padá - těžko přijde na něco horšího).

Scénář registrace předplatitele vychází z prvního spuštění. Provedli jsme zátěžový test a byli jsme si jisti, že systém nezpomaluje korespondenci. Uživatelé ale odešli a registrace začala klesat časovým limitem. Při registraci jsme použili / dev / náhodný, která je vázána na entropii systému. Server nestihl nashromáždit dostatek entropie a při požadavku na nový SecureRandom na desítky sekund zamrzl. Existuje mnoho způsobů, jak z této situace ven, například: přejít na méně bezpečný /dev/urandom, nainstalovat speciální desku, která generuje entropii, předem vygenerovat náhodná čísla a uložit je do fondu. Problém s poolem jsme dočasně uzavřeli, ale od té doby spouštíme samostatný test pro registraci nových předplatitelů.

Jako generátor zátěže používáme JMeter. Neumí pracovat s websocketem, je potřeba plugin. První ve výsledcích vyhledávání na dotaz "jmeter websocket" jsou články s BlazeMeterve kterém doporučují plugin od Macieje Zaleskiho.

Tam jsme se rozhodli začít.

Téměř okamžitě po zahájení seriózního testování jsme zjistili, že v JMeteru začaly úniky paměti.

Plugin je samostatný velký příběh, se 176 hvězdičkami má 132 forků na githubu. Sám autor se k tomu od roku 2015 nezavázal (vzali jsme to v roce 2015, tehdy to nevzbuzovalo podezření), několik problémů s githubem o úniku paměti, 7 neuzavřených požadavků na stažení.
Pokud se rozhodnete načíst test pomocí tohoto pluginu, věnujte prosím pozornost následujícím diskusím:

  1. Ve vícevláknovém prostředí byl použit běžný LinkedList, v důsledku čehož jsme dostali NPE za běhu. Řeší se to buď přepnutím na ConcurrentLinkedDeque, nebo synchronizovanými bloky. Pro sebe jsme zvolili první možnosthttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Únik paměti, informace o připojení se při odpojování neodstraní (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. V režimu streamování (když webová zásuvka není uzavřena na konci vzorku, ale používá se dále v plánu), vzory odezvy nefungují (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Toto je jeden z těch na githubu. Co jsme udělali:

  1. Vzali vidlice Elyrana Kogana (@elyrank) - opravuje problémy 1 a 3
  2. Vyřešený problém 2
  3. Aktualizováno molo z 9.2.14 na 9.3.12
  4. Zabalený SimpleDateFormat v ThreadLocal; SimpleDateFormat není bezpečný pro vlákna vedoucí k NPE za běhu
  5. Opraven další únik paměti (při odpojení se nesprávně uzavřelo připojení)

A přesto teče!

Paměť začala končit ne za den, ale za dva. Nebyl vůbec čas, rozhodli jsme se spustit méně vláken, ale na čtyřech agentech. To by mělo stačit alespoň na týden.

Jsou to dva dny...

Nyní Hazelcastu dochází paměť. Protokoly ukázaly, že po několika dnech testování si Hazelcast začne stěžovat na nedostatek paměti a po chvíli se cluster rozpadne a uzly pokračují v umírání jeden po druhém. Připojili jsme JVisualVM k hazelcast a viděli jsme „pilu vzhůru“ – pravidelně volala GC, ale nedokázala žádným způsobem vymazat paměť.

Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast

Ukázalo se, že v hazelcast 3.4 se při mazání mapy / multiMap (map.destroy()) paměť zcela neuvolní:

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

Chyba je nyní opravena ve verzi 3.5, ale tehdy to byl problém. Vytvořili jsme novou multiMap s dynamickými názvy a smazali jsme ji podle naší logiky. Kód vypadal asi takto:

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

Ano:

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

multiMap byl vytvořen pro každé předplatné a odstraněn, když nebyl potřeba. Rozhodli jsme se, že založíme mapu , klíč bude název předplatného a hodnoty budou identifikátory relace (pomocí kterých pak můžete v případě potřeby získat identifikátory uživatele).

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

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

Grafy se zlepšily.

Jak a proč jsme napsali vysoce nabitou škálovatelnou službu pro 1C: Enterprise: Java, PostgreSQL, Hazelcast

Co dalšího jsme se dozvěděli o zátěžovém testování

  1. JSR223 musí být napsán v groovy a zahrnout kompilační mezipaměť - je to mnohem rychlejší. Odkaz.
  2. Grafy Jmeter-Plugins jsou srozumitelnější než ty standardní. Odkaz.

O našich zkušenostech s Hazelcast

Hazelcast byl pro nás nový produkt, začali jsme s ním pracovat od verze 3.4.1, nyní máme na našem produkčním serveru verzi 3.9.2 (v době psaní tohoto článku je nejnovější verze Hazelcast 3.10).

Generování ID

Začali jsme s celočíselnými identifikátory. Představme si, že potřebujeme další Long pro novou entitu. Sekvence v databázi není vhodná, tabulky jsou zapojeny do shardingu - ukázalo se, že v DB1 je ID zprávy = 1 a v DB1 ID = 2, toto ID nemůžete vložit do Elasticsearch, ani do Hazelcastu, ale nejhorší je, když chcete zredukovat data ze dvou databází do jedné (například se rozhodnout, že těmto odběratelům stačí jedna databáze). Můžete mít několik AtomicLongs v Hazelcast a ponechat tam počítadlo, pak se výkon získávání nového ID zvýší AndGet plus čas na dotaz v Hazelcast. Hazelcast má ale něco optimálnějšího – FlakeIdGenerator. Při kontaktování je každému klientovi přidělena řada ID, například první - od 1 do 10 000, druhé - od 10 001 do 20 000 atd. Nyní může klient vydávat nové identifikátory sám, dokud neskončí rozsah, který mu byl vydán. Funguje rychle, ale restartování aplikace (a klienta Hazelcast) spustí novou sekvenci – tedy přeskakování atd. Kromě toho není vývojářům příliš jasné, proč jsou ID celá čísla, ale jsou velmi v rozporu. Vše jsme zvážili a přešli na UUID.

Mimochodem, pro ty, kteří chtějí být jako Twitter, existuje taková knihovna Snowcast - to je implementace Snowflake nad Hazelcast. Můžete vidět zde:

github.com/noctarius/snowcast
github.com/twitter/sněhová vločka

Ale ještě jsme se k tomu nedostali.

TransactionalMap.replace

Další překvapení: TransactionalMap.replace nefunguje. Zde 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

Musel jsem napsat vlastní náhradu pomocí 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);
}

Testujte nejen běžné datové struktury, ale i jejich transakční verze. Stává se, že IMap funguje, ale TransactionalMap již neexistuje.

Připojte nový JAR bez prostojů

Nejprve jsme se rozhodli zapsat objekty našich tříd do Hazelcastu. Máme například třídu Application, kterou chceme ukládat a číst. Uložit:

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

Přečtěte si:

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

Všechno funguje. Pak jsme se rozhodli vytvořit index v Hazelcast, abychom jej prohledali:

map.addIndex("subscriberId", false);

A při psaní nové entity začali dostávat ClassNotFoundException. Hazelcast se pokusil přidat do indexu, ale nevěděl nic o naší třídě a chtěl do ní dát JAR s touto třídou. Udělali jsme to, vše fungovalo, ale objevil se nový problém: jak aktualizovat JAR bez úplného zastavení clusteru? Hazelcast nezvedne nový JAR při aktualizaci na uzel. V tuto chvíli jsme se rozhodli, že bychom mohli žít bez vyhledávání indexů. Koneckonců, pokud používáte Hazelcast jako úložiště klíč-hodnota, bude vše fungovat? Spíš ne. Zde je opět odlišné chování IMap a TransactionalMap. Tam, kde je to IMap jedno, TransactionalMap vyvolá chybu.

IMap. Zapisujeme si 5000 objektů, čteme. Všechno se očekává.

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

Ale v transakci to nefunguje, dostaneme výjimku 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();
        }
    });
}

Ve 3.8 se objevil mechanismus User Class Deployment. Můžete určit jeden hlavní uzel a aktualizovat na něm soubor JAR.

Nyní jsme zcela změnili náš přístup: sami serializujeme do JSON a ukládáme do Hazelcast. Hazelcast nepotřebuje znát strukturu našich tříd a můžeme aktualizovat bez prostojů. Verze objektů domény je řízena aplikací. Různé verze aplikace mohou být spuštěny současně a je možné, že nová aplikace zapisuje objekty s novými poli, zatímco stará o těchto polích ještě neví. A zároveň nová aplikace čte objekty zapsané starou aplikací, které nemají nová pole. Takové situace řešíme uvnitř aplikace, ale pro jednoduchost pole neměníme ani neodstraňujeme, pouze rozšiřujeme třídy přidáním nových polí.

Jak podáváme vysoký výkon

Čtyři cesty do Hazelcast jsou dobré, dvě cesty do databáze jsou špatné

Hledání dat v mezipaměti je vždy lepší než v databázi, ale také nechcete ukládat nevyžádané záznamy. Rozhodnutí, co uložit do mezipaměti, je ponecháno na poslední fázi vývoje. Když je nová funkcionalita nakódována, povolíme protokolování všech dotazů v PostgreSQL (log_min_duration_statement na 0) a spustíme zátěžové testování po dobu 20 minut.Utilities jako pgFouine a pgBadger mohou vytvářet analytické sestavy založené na shromážděných protokolech. V přehledech hledáme především pomalé a časté požadavky. Pro pomalé dotazy sestavíme plán provádění (EXPLAIN) a vyhodnotíme, zda lze takový dotaz urychlit. Časté požadavky na stejný vstup dobře zapadají do mezipaměti. Snažíme se, aby dotazy byly „ploché“, jedna tabulka na dotaz.

Vykořisťování

CB jako online služba byla spuštěna na jaře 2017, jako samostatný produkt CB byl vydán v listopadu 2017 (tehdy ve stavu beta).

Za více než rok provozu se v provozu služby CB online nevyskytly žádné vážnější problémy. Službu online monitorujeme prostřednictvím Zabbix, sbírat a nasazovat z Bambus.

Distribuce CB serverů přichází ve formě nativních balíčků: RPM, DEB, MSI. Navíc pro Windows poskytujeme jeden instalační program ve formě jednoho EXE, který nainstaluje server, Hazelcast a Elasticsearch na jeden počítač. Nejprve jsme tuto verzi instalace nazývali „demo“, ale nyní se ukázalo, že se jedná o nejoblíbenější možnost nasazení.

Zdroj: www.habr.com

Přidat komentář