Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast

Ebben a cikkben arról fogunk beszélni, hogyan és miért fejlesztettük Interakciós rendszer – egy olyan mechanizmus, amely információkat továbbít az ügyfélalkalmazások és az 1C:Enterprise szerverek között – a feladat beállításától az architektúra és a megvalósítás részleteinek átgondolásáig.

Az Interaction System (a továbbiakban: SV) egy elosztott, hibatűrő üzenetküldő rendszer, garantált kézbesítéssel. Az SV-t nagy terhelésű, nagy skálázhatóságú szolgáltatásnak tervezték, amely online szolgáltatásként (az 1C által biztosított) és sorozatgyártású termékként is elérhető, amelyet saját szerverlétesítményein is telepíthet.

Az SV elosztott tárolást használ Hazelcast és keresőmotor Elasticsearch. Szó lesz a Java-ról és arról is, hogy hogyan méretezzük vízszintesen a PostgreSQL-t.
Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast

Probléma nyilatkozat

Hogy világos legyen, miért hoztuk létre az interakciós rendszert, elmondok egy kicsit arról, hogyan működik az üzleti alkalmazások fejlesztése az 1C-ben.

Kezdésként egy kicsit rólunk azoknak, akik még nem tudják, mit csinálunk :) Készítjük az 1C:Enterprise technológiai platformot. A platform tartalmaz egy üzleti alkalmazásfejlesztő eszközt, valamint egy futtatókörnyezetet, amely lehetővé teszi az üzleti alkalmazások többplatformos környezetben történő futtatását.

Kliens-szerver fejlesztési paradigma

Az 1C:Enterprise-en létrehozott üzleti alkalmazások három szinten működnek kliens-szerver architektúra „DBMS – alkalmazásszerver – kliens”. Az alkalmazás kódja be van írva beépített 1C nyelv, végrehajtható az alkalmazásszerveren vagy a kliensen. Az alkalmazásobjektumokkal (könyvtárak, dokumentumok stb.) végzett minden munka, valamint az adatbázis olvasása és írása csak a szerveren történik. Az űrlapok és a parancsfelület funkcionalitása is megvalósul a szerveren. A kliens nyomtatványok fogadását, megnyitását, megjelenítését, a felhasználóval való „kommunikációt” (figyelmeztetések, kérdések...), gyors választ igénylő űrlapokon apró számításokat (pl. ár mennyiségi szorzása), helyi fájlokkal való munkát, berendezéssel dolgozni.

Az alkalmazáskódban az eljárások és függvények fejléceinek kifejezetten jelezniük kell, hogy a kód hol kerül végrehajtásra – az &AtClient / &AtServer direktívák segítségével (a nyelv angol változatában az &AtClient / &AtServer). Az 1C fejlesztői most kijavítanak azzal, hogy az irányelvek valójában azok több mint, de számunkra ez most nem fontos.

A klienskódból hívhatja a szerverkódot, de a szerverkódból nem. Ez egy alapvető korlátozás, amelyet több okból is bevezettünk. Különösen azért, mert a szerver kódját úgy kell megírni, hogy az ugyanúgy fusson, függetlenül attól, hogy hol hívják - a kliensről vagy a szerverről. Abban az esetben pedig, ha egy másik szerverkódból hívjuk meg a szerverkódot, akkor nincs kliens, mint olyan. És mert a szerverkód végrehajtása közben az azt hívó kliens bezárhat, kiléphet az alkalmazásból, és a szervernek már nem lesz kit hívnia.

Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast
Kód, amely kezeli a gombkattintást: a szerver eljárás meghívása a kliensről működik, a kliens eljárás meghívása a szerverről nem

Ez azt jelenti, hogy ha valamilyen üzenetet szeretnénk küldeni a szerverről a kliens alkalmazásnak, például azt, hogy egy „hosszú ideje futó” jelentés generálása befejeződött és a jelentés megtekinthető, akkor nincs ilyen módszerünk. Muszáj bevetni a trükköket, például rendszeresen le kell kérni a szervert a klienskódból. Ez a megközelítés azonban felesleges hívásokkal terheli meg a rendszert, és általában nem tűnik túl elegánsnak.

És szükség van arra is, ha például telefonhívás érkezik SIP- hívás kezdeményezésekor értesítse erről az ügyfélalkalmazást, hogy az a hívó telefonszámát felhasználva megtalálja azt a partner adatbázisában, és megjelenítse a hívó félre vonatkozó felhasználói információkat. Vagy például ha megrendelés érkezik a raktárba, értesítse erről a vevő ügyfélalkalmazását. Általában sok olyan eset van, amikor egy ilyen mechanizmus hasznos lenne.

Maga a produkció

Hozzon létre egy üzenetküldési mechanizmust. Gyors, megbízható, garantált kézbesítéssel, az üzenetek rugalmas kereshetőségével. A mechanizmus alapján valósítson meg egy üzenetküldőt (üzenetek, videohívások), amely az 1C alkalmazásokon belül fut.

Tervezze meg a rendszert vízszintesen méretezhetővé. A növekvő terhelést a csomópontok számának növelésével kell fedezni.

Реализация

Úgy döntöttünk, hogy az SV szerver részét nem közvetlenül az 1C:Enterprise platformba integráljuk, hanem külön termékként implementáljuk, melynek API-ja az 1C alkalmazásmegoldások kódjából hívható meg. Ez több okból is megtörtént, amelyek közül a legfontosabb az volt, hogy lehetővé akartam tenni az üzenetváltást a különböző 1C alkalmazások között (például a Trade Management és a Accounting között). A különböző 1C alkalmazások futhatnak az 1C:Enterprise platform különböző verzióin, különböző szervereken stb. Ilyen körülmények között az SV mint különálló termék megvalósítása az 1C telepítések „oldalán” az optimális megoldás.

Ezért úgy döntöttünk, hogy az SV-t külön termékként készítjük el. Azt javasoljuk, hogy a kisvállalatok az általunk a felhőnkbe telepített CB-kiszolgálót használják (wss://1cdialog.com), hogy elkerüljék a szerver helyi telepítésével és konfigurálásával kapcsolatos általános költségeket. A nagy ügyfeleknek tanácsos lehet saját CB-szervert telepíteni a létesítményeikre. Hasonló megközelítést alkalmaztunk a felhőalapú SaaS termékünkben is 1cFresh - tömegtermékként állítják elő az ügyfelek telephelyére történő telepítéshez, és a felhőnkben is telepítve van https://1cfresh.com/.

App

A terhelés és a hibatűrés elosztása érdekében nem egy Java alkalmazást telepítünk, hanem több terheléselosztóval is. Ha át kell vinnie egy üzenetet csomópontról csomópontra, használja a közzététel/feliratkozás lehetőséget a Hazelcastban.

A kliens és a szerver közötti kommunikáció websocketen keresztül történik. Kiválóan alkalmas valós idejű rendszerekhez.

Elosztott gyorsítótár

Redis, Hazelcast és Ehcache között választottunk. 2015 van. A Redis most kiadott egy új klasztert (túl új, ijesztő), ott van a Sentinel, sok korlátozással. Az Ehcache nem tudja, hogyan kell fürtté összeállítani (ez a funkció később jelent meg). Úgy döntöttünk, hogy kipróbáljuk a Hazelcast 3.4-el.
A Hazelcast a dobozból egy fürtté van összeállítva. Egycsomópontos módban nem túl hasznos, és csak gyorsítótárként használható - nem tudja, hogyan kell adatokat kiíratni a lemezre, ha elveszíti az egyetlen csomópontot, elveszíti az adatokat. Számos Hazelcastot telepítünk, amelyek között biztonsági mentést készítünk a kritikus adatokról. Nem készítünk biztonsági másolatot a gyorsítótárról – nem bánjuk.

Számunkra a Hazelcast:

  • Felhasználói munkamenetek tárolása. Sok időbe telik, amíg minden alkalommal az adatbázisba megyünk egy munkamenethez, ezért az összes munkamenetet a Hazelcastba helyezzük.
  • Gyorsítótár. Ha felhasználói profilt keres, ellenőrizze a gyorsítótárat. Új üzenetet írt - tegye a gyorsítótárba.
  • Az alkalmazáspéldányok közötti kommunikáció témakörei. A csomópont létrehoz egy eseményt, és elhelyezi a Hazelcast témában. A témakörre feliratkozott többi alkalmazáscsomópont fogadja és feldolgozza az eseményt.
  • Klaszter zárak. Például létrehozunk egy beszélgetést egy egyedi kulcs használatával (egyetlen beszélgetés az 1C adatbázison belül):

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

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

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

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

Ellenőriztük, hogy nincs-e csatorna. Elvettük a zárat, újra ellenőriztük, és létrehoztuk. Ha nem ellenőrzi a zárolást a zár feloldása után, akkor előfordulhat, hogy egy másik szál is ellenőrizte ebben a pillanatban, és most megpróbálja létrehozni ugyanazt a vitát – de az már létezik. Nem zárolható szinkronizált vagy normál Java Lock használatával. Az adatbázison keresztül - ez lassú, és kár az adatbázisért; a Hazelcaston keresztül - erre van szüksége.

DBMS kiválasztása

Kiterjedt és sikeres tapasztalattal rendelkezünk a PostgreSQL-lel való munkavégzés és a DBMS fejlesztőivel való együttműködés terén.

Ez nem könnyű egy PostgreSQL-fürttel – van XL, XC, Citus, de általában ezek nem a dobozból méretezhető NoSQL-ek. Nem a NoSQL-t tekintettük fő tárhelynek, elég volt, hogy vettük a Hazelcastot, amivel korábban nem dolgoztunk.

Ha relációs adatbázist kell méretezned, az azt jelenti szilánkos. Tudniillik a shardingnál külön részekre osztjuk az adatbázist, így mindegyik külön szerveren helyezhető el.

Felosztásunk első verziója azt feltételezte, hogy az alkalmazásunk egyes tábláit különböző arányban oszthatjuk szét a különböző szervereken. Nagyon sok üzenet van az A szerveren – kérem, helyezzük át ennek a táblázatnak egy részét a B szerverre. Ez a döntés egyszerűen az idő előtti optimalizálásról sikított, ezért úgy döntöttünk, hogy a több bérlős megközelítésre korlátozzuk magunkat.

Több bérlőről például olvashat a weboldalon Citus Data.

Az SV rendelkezik az alkalmazás és az előfizető fogalmával. Az alkalmazás egy üzleti alkalmazás, például az ERP vagy a Számvitel speciális telepítése a felhasználókkal és az üzleti adatokkal. Az előfizető olyan szervezet vagy magánszemély, akinek nevében az alkalmazás regisztrálva van az SV-kiszolgálón. Egy előfizető több alkalmazást is regisztrálhat, és ezek az alkalmazások üzenetet válthatnak egymással. Az előfizető bérlő lett a rendszerünkben. Egy fizikai adatbázisban több előfizetőtől érkező üzenetek is elhelyezhetők; ha azt látjuk, hogy egy előfizető elkezdett nagy forgalmat generálni, akkor áthelyezzük egy külön fizikai adatbázisba (vagy akár egy külön adatbázis-kiszolgálóra).

Van egy fő adatbázisunk, ahol egy útválasztási tábla tárolódik az összes előfizetői adatbázis helyére vonatkozó információkkal.

Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast

Annak elkerülése érdekében, hogy a fő adatbázis szűk keresztmetszetet képezzen, az útválasztási táblát (és más gyakran szükséges adatokat) a gyorsítótárban tároljuk.

Ha az előfizető adatbázisa lassulni kezd, akkor belül partíciókra vágjuk. Más projekteknél, amelyeket használunk pg_pathman.

Mivel a felhasználói üzenetek elvesztése rossz, adatbázisainkat replikákkal karbantartjuk. A szinkron és aszinkron replikák kombinációja lehetővé teszi, hogy biztosítsa magát a fő adatbázis elvesztése esetén. Üzenetvesztés csak akkor következik be, ha az elsődleges adatbázis és annak szinkron replikája egyidejűleg meghibásodik.

Ha egy szinkron replika elveszik, az aszinkron replika szinkron lesz.
Ha a fő adatbázis elveszik, a szinkron replika lesz a fő adatbázis, az aszinkron replika pedig szinkron replikává.

Elasticsearch kereséshez

Mivel többek között az SV is hírvivő, gyors, kényelmes és rugalmas keresést igényel, a morfológiát figyelembe véve, pontatlan egyezéseket használva. Úgy döntöttünk, hogy nem találjuk fel újra a kereket, és használjuk a könyvtár alapján létrehozott ingyenes Elasticsearch keresőt Lucene. Az Elasticsearch-et egy fürtben (fő – adatok – adatok) is telepítjük, hogy kiküszöböljük a problémákat az alkalmazás csomópontjainak meghibásodása esetén.

Githubon találtuk Orosz morfológiai bővítmény az Elasticsearch számára, és használja azt. Az Elasticsearch indexben szógyököket (amelyeket a plugin határoz meg) és N-grammokat tárolunk. Miközben a felhasználó szöveget ír be a kereséshez, a begépelt szöveget az N-grammok között keressük. Ha az indexbe menti, a „texts” szó a következő N-grammokra lesz felosztva:

[azok, tek, tex, szöveg, szövegek, ek, ex, ext, szövegek, ks, kst, ksty, st, sty, te],

És a „szöveg” szó gyökere is megmarad. Ez a megközelítés lehetővé teszi a keresést a szó elején, közepén és végén.

A nagy kép

Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast
Ismételje meg a cikk elején látható képet, de magyarázatokkal:

  • Kiegyensúlyozó az interneten; nekünk nginxünk van, bármilyen lehet.
  • A Java alkalmazáspéldányok a Hazelcaston keresztül kommunikálnak egymással.
  • Az általunk használt web sockettel való munkához Netty.
  • A Java alkalmazás Java 8 nyelven íródott, és csomagokból áll OSGi. A tervek között szerepel a Java 10-re való migráció és a modulokra való átállás.

Fejlesztés és tesztelés

Az SV fejlesztése és tesztelése során számos érdekes tulajdonsággal találkoztunk az általunk használt termékekben.

Terhelési tesztelés és memóriaszivárgás

Az egyes SV-kiadások kiadása terhelési teszteléssel jár. Akkor sikeres, ha:

  • A teszt több napig működött, és nem volt szolgáltatási hiba
  • A kulcsműveletek válaszideje nem haladta meg a kényelmes küszöböt
  • A teljesítményromlás az előző verzióhoz képest legfeljebb 10%

A tesztadatbázist feltöltjük adatokkal - ehhez a legaktívabb előfizetőről kapunk információkat az éles szerverről, annak számát megszorozzuk 5-tel (üzenetek, beszélgetések, felhasználók száma), és így teszteljük.

Az interakciós rendszer terhelési tesztelését három konfigurációban végezzük:

  1. stressz teszt
  2. Csak kapcsolatok
  3. Előfizető regisztráció

A stresszteszt során több száz szálat indítunk el, amelyek megállás nélkül töltik a rendszert: üzeneteket írnak, megbeszéléseket készítenek, üzenetlistát kapnak. Simuláljuk a hétköznapi felhasználók műveleteit (olvasatlan üzeneteim listáját kapom, írok valakinek) és szoftveres megoldásokat (más konfigurációjú csomag továbbítása, riasztás feldolgozása).

Például így néz ki a stresszteszt része:

  • Felhasználó bejelentkezik
    • Az Ön olvasatlan beszélgetéseit kéri
    • 50%-os valószínűséggel elolvassa az üzeneteket
    • 50% a valószínűsége, hogy SMS-t küld
    • Következő felhasználó:
      • 20% esélye van új vita létrehozására
      • Véletlenszerűen kiválasztja a megbeszéléseket
      • Bemegy
      • Üzeneteket, felhasználói profilokat kér
      • Öt, véletlenszerű felhasználóknak címzett üzenetet hoz létre ebből a beszélgetésből
      • Kihagyja a vitát
      • 20-szor ismétlődik
      • Kijelentkezik, visszalép a szkript elejére

    • Egy chatbot lép be a rendszerbe (az alkalmazáskódból emulálja az üzeneteket)
      • 50% esélye van új adatcsere-csatorna létrehozására (különleges megbeszélés)
      • 50%-a valószínű, hogy üzenetet ír bármelyik meglévő csatornára

A „Csak kapcsolatok” forgatókönyv okkal jelent meg. Van egy helyzet: a felhasználók csatlakoztatták a rendszert, de még nem kapcsolódtak be. Minden felhasználó reggel 09:00-kor bekapcsolja a számítógépet, kapcsolatot létesít a szerverrel és csendben marad. Ezek a srácok veszélyesek, sok van belőlük – az egyetlen csomagjuk a PING/PONG, de fenntartják a kapcsolatot a szerverrel (nem tudják fenntartani – mi van, ha új üzenet érkezik). A teszt azt a helyzetet reprodukálja, amikor nagyszámú ilyen felhasználó próbál meg fél óra alatt bejelentkezni a rendszerbe. Hasonlít egy stresszteszthez, de a hangsúly pontosan erre az első bemenetre irányul - hogy ne legyenek meghibásodások (az ember nem használja a rendszert, és máris leesik - ennél rosszabbat nehéz elképzelni).

Az előfizető regisztrációs szkript az első indításkor indul. Stressz tesztet végeztünk, és biztosak voltunk abban, hogy a rendszer nem lassult le levelezés közben. De jöttek a felhasználók, és a regisztráció kezdett meghiúsulni egy időkorlát miatt. Regisztrációkor használtuk / dev / random, ami a rendszer entrópiájához kapcsolódik. A szervernek nem volt ideje elegendő entrópiát felhalmozni, és amikor új SecureRandomot kértek, több tíz másodpercre lefagyott. Sok kiút van ebből a helyzetből, például: váltson át a kevésbé biztonságos /dev/urandom-ra, telepítsen egy speciális entrópiát generáló táblát, előzetesen generáljon véletlenszámokat, és tárolja azokat egy készletben. Átmenetileg lezártuk a pool problémáját, de azóta külön tesztet futtatunk az új előfizetők regisztrálására.

Terhelésgenerátorként használjuk JMeter. Nem tudja, hogyan kell dolgozni a websocket-tel; szüksége van egy bővítményre. A „jmeter websocket” lekérdezés első találatai a következők: cikkei a BlazeMetertől, amelyek azt javasolják Maciej Zaleski bővítmény.

Itt döntöttünk úgy, hogy elkezdjük.

A komoly tesztelés megkezdése után szinte azonnal felfedeztük, hogy a JMeter memóriaszivárgásba kezdett.

A plugin egy külön nagy történet; 176 csillagával 132 fork van a githubon. A szerző maga 2015 óta nem kötelezte el magát (2015-ben vettük, akkor nem keltett gyanút), több githubos probléma a memóriaszivárgással kapcsolatban, 7 lezáratlan pull kérés.
Ha úgy dönt, hogy terhelési tesztet végez ezzel a bővítménnyel, kérjük, vegye figyelembe a következő megbeszéléseket:

  1. Többszálú környezetben normál LinkedList-et használtak, és az eredmény az lett NPE futásidőben. Ez megoldható a ConcurrentLinkedDeque-re váltással vagy szinkronizált blokkokkal. Az első lehetőséget választottuk magunknak (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Memóriaszivárgás; leválasztáskor a csatlakozási információk nem törlődnek (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Streaming módban (amikor a websocket nincs bezárva a minta végén, hanem később a tervben használatos), a válaszminták nem működnek (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Ez az egyik a githubon. Mit tettünk:

  1. Elvitte villát Elyran Kogan (@elyrank) – az 1. és 3. problémát javítja
  2. 2. probléma megoldva
  3. Móló frissítve 9.2.14-ről 9.3.12-re
  4. Wrapped SimpleDateFormat a ThreadLocal-ban; A SimpleDateFormat nem szálbiztos, ami futás közben NPE-hez vezetett
  5. Egy másik memóriaszivárgás javítása (a kapcsolat hibásan záródott leválasztáskor)

És mégis folyik!

A memória nem egy nap, hanem két nap alatt kezdett kifogyni. Abszolút nem maradt idő, ezért úgy döntöttünk, hogy kevesebb szálat indítunk, de négy ügynökön. Ennek legalább egy hétre elégnek kellett volna lennie.

Eltelt két nap...

Most a Hazelcast memóriája fogy. A naplók azt mutatták, hogy néhány napos tesztelés után a Hazelcast memóriahiányra kezdett panaszkodni, majd egy idő után a klaszter szétesett, és a csomópontok egyenként haltak meg. Csatlakoztattuk a JVisualVM-et a hazelcasthoz, és láttunk egy „felszálló fűrészt” – rendszeresen hívta a GC-t, de nem tudta törölni a memóriát.

Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast

Kiderült, hogy a hazelcast 3.4-ben egy térkép / multiMap törlésekor (map.destroy()) a memória nem szabadul fel teljesen:

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

A hibát most javították a 3.5-ben, de akkoriban probléma volt. Új, dinamikus nevekkel rendelkező multiMap-eket hoztunk létre, és a mi logikánk szerint töröltük őket. A kód valahogy így nézett ki:

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

Hívás:

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

A multiMap minden egyes előfizetéshez létrejött, és törölve lett, amikor nem volt rá szükség. Úgy döntöttünk, hogy elindítjuk a Térképet , a kulcs az előfizetés neve lesz, az értékek pedig munkamenet-azonosítók (amelyekből szükség esetén felhasználói azonosítókat szerezhet be).

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

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

A grafikonok javultak.

Hogyan és miért írtunk nagy terhelésű, méretezhető szolgáltatást 1C-hez: Enterprise: Java, PostgreSQL, Hazelcast

Mit tanultunk még a terheléses tesztelésről?

  1. A JSR223-at groovy-ban kell írni, és tartalmaznia kell a fordítási gyorsítótárat – ez sokkal gyorsabb. Link.
  2. A Jmeter-Plugins grafikonok könnyebben érthetők, mint a szabványosak. Link.

A Hazelcasttal kapcsolatos tapasztalatainkról

A Hazelcast egy új termék volt számunkra, a 3.4.1-es verziótól kezdtünk el vele dolgozni, jelenleg éles szerverünkön a 3.9.2-es verzió fut (a cikk írásakor a Hazelcast legújabb verziója 3.10).

ID generálás

Integer azonosítókkal kezdtük. Képzeljük el, hogy szükségünk van egy másik Longra egy új entitáshoz. Az adatbázisban nem megfelelő a szekvencia, a táblák felosztásban vesznek részt - kiderül, hogy a DB1-ben ID=1, a DB1-ben pedig ID=2 üzenet van, ezt az azonosítót nem tudod berakni az Elasticsearch-be, sem a Hazelcastba , de a legrosszabb az, ha két adatbázis adatait szeretnéd egybe vonni (például úgy dönteni, hogy ezeknek az előfizetőknek elég egy adatbázis). Hozzáadhat több AtomicLong-ot a Hazelcasthoz, és ott tarthatja a számlálót, ekkor az új azonosító megszerzésének teljesítménye incrementAndGet plusz a Hazelcast kérésének ideje. De a Hazelcastnak van valami optimálisabb is - a FlakeIdGenerator. Az egyes ügyfelekkel való kapcsolatfelvételkor egy azonosító tartományt kapnak, például az első – 1-től 10 000-ig, a második – 10 001-től 20 000-ig stb. Mostantól a kliens önállóan adhat ki új azonosítókat mindaddig, amíg a számára kiadott tartomány véget nem ér. Gyorsan működik, de amikor újraindítja az alkalmazást (és a Hazelcast klienst), egy új sorozat kezdődik - ezért a kihagyások stb. Ráadásul a fejlesztők nem igazán értik, hogy az azonosítók miért egészek, de annyira ellentmondásosak. Mindent lemértünk és áttértünk UUID-re.

Egyébként azok számára, akik olyanok akarnak lenni, mint a Twitter, van egy ilyen Snowcast könyvtár - ez a Snowflake megvalósítása a Hazelcast tetején. Itt tudod megnézni:

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

De már nem jutottunk hozzá.

TransactionMap.replace

Egy másik meglepetés: a TransactionMap.replace nem működik. Íme egy teszt:

@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

Saját cserét kellett írnom a getForUpdate segítségével:

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

Tesztelje nemcsak a szokásos adatstruktúrákat, hanem azok tranzakciós verzióit is. Előfordul, hogy az IMap működik, de a TransactionMap már nem létezik.

Helyezzen be egy új JAR-t állásidő nélkül

Először is úgy döntöttünk, hogy osztályaink tárgyait rögzítjük a Hazelcastban. Például van egy Application osztályunk, azt szeretnénk elmenteni és elolvasni. Megment:

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

Olvasás:

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

Minden működik. Aztán úgy döntöttünk, hogy létrehozunk egy indexet a Hazelcastban, hogy a következők szerint kereshessünk:

map.addIndex("subscriberId", false);

És amikor új entitást írtak, elkezdték megkapni a ClassNotFoundException kivételt. A Hazelcast megpróbált hozzáadni az indexhez, de nem tudott semmit az osztályunkról, és azt akarta, hogy egy ilyen osztályú JAR-t adjanak hozzá. Mi is ezt tettük, minden működött, de egy új probléma jelent meg: hogyan lehet frissíteni a JAR-t a fürt teljes leállítása nélkül? A Hazelcast nem veszi fel az új JAR-t a csomópontonkénti frissítés során. Ezen a ponton úgy döntöttünk, hogy élhetünk indexkeresés nélkül is. Végül is, ha kulcsérték tárolóként használja a Hazelcastot, akkor minden működni fog? Nem igazán. Itt is más az IMap és a Tranzakciós térkép viselkedése. Ahol az IMap nem törődik, a TransactionalMap hibát jelez.

IMap. 5000 tárgyat írunk, olvassunk. Minden várható.

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

De ez nem működik egy tranzakcióban, kapunk egy ClassNotFoundException kivételt:

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

A 3.8-ban megjelent a User Class Deployment mechanizmus. Kijelölhet egy főcsomópontot, és frissítheti rajta a JAR-fájlt.

Most teljesen megváltoztattuk a megközelítésünket: magunk szerializáljuk JSON-ba, és elmentjük a Hazelcastba. A Hazelcastnak nem kell ismernie az osztályaink szerkezetét, és leállás nélkül tudunk frissíteni. A tartományobjektumok verziószámát az alkalmazás szabályozza. Az alkalmazás különböző verziói futhatnak egyszerre, és előfordulhat olyan helyzet, amikor az új alkalmazás új mezőkkel ír objektumokat, de a régi még nem tud ezekről a mezőkről. Ugyanakkor az új alkalmazás beolvassa a régi alkalmazás által írt objektumokat, amelyek nem tartalmaznak új mezőket. Az ilyen helyzeteket az alkalmazáson belül kezeljük, de az egyszerűség kedvéért nem módosítunk, nem törölünk mezőket, csak új mezők hozzáadásával bővítjük az osztályokat.

Hogyan biztosítjuk a magas teljesítményt

Négy út a Hazelcasthoz - jó, kettő az adatbázishoz - rossz

A gyorsítótárba menni adatokért mindig jobb, mint az adatbázisba menni, de a fel nem használt rekordokat sem szeretné tárolni. A gyorsítótárazással kapcsolatos döntést a fejlesztés utolsó szakaszára hagyjuk. Az új funkció kódolásakor bekapcsoljuk az összes lekérdezés naplózását a PostgreSQL-ben (log_min_duration_statement 0-ra), és 20 percig terhelési tesztet futtatunk.Az összegyűjtött naplók segítségével olyan segédprogramok, mint a pgFouine és a pgBadger, analitikai jelentéseket készíthetnek. A jelentésekben elsősorban a lassú és gyakori lekérdezéseket keressük. A lassú lekérdezésekhez végrehajtási tervet készítünk (EXPLAIN), és kiértékeljük, hogy egy ilyen lekérdezés felgyorsítható-e. Az azonos bemeneti adatokra vonatkozó gyakori kérések jól illeszkednek a gyorsítótárba. Igyekszünk a lekérdezéseket „laposan” tartani, lekérdezésenként egy táblázatot.

kizsákmányolás

Az SV mint online szolgáltatás 2017 tavaszán került üzembe, külön termékként pedig 2017 novemberében jelent meg (akkor még béta verzióban).

A több mint egy éves működés során komolyabb problémák nem merültek fel a CB online szolgáltatás működésében. Az online szolgáltatást ezen keresztül figyeljük Zabbix, gyűjtsd össze és telepítsd innen Bambusz.

Az SV-kiszolgáló disztribúciója natív csomagok formájában érhető el: RPM, DEB, MSI. Plusz a Windows számára egyetlen telepítőt biztosítunk egyetlen EXE formájában, amely egy gépre telepíti a szervert, a Hazelcastot és az Elasticsearch-et. Kezdetben a telepítés ezen verzióját „demó” verziónak neveztük, de mára világossá vált, hogy ez a legnépszerűbb telepítési lehetőség.

Forrás: will.com

Hozzászólás