Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast

An dësem Artikel wäerte mir schwätzen iwwer wéi a firwat mir entwéckelt hunn Interaktioun System - e Mechanismus deen Informatioun tëscht Client Uwendungen an 1C:Enterprise Server transferéiert - vun enger Aufgab setzen bis duerch d'Architektur an d'Implementatiounsdetailer denken.

D'Interaktioun System (nodréiglech als SV bezeechent) ass e verdeelt, Feeler-tolerant Messagerie System mat garantéiert Liwwerung. SV ass entworf als héich-Laascht Service mat héich scalability, sinn souwuel als online Service (vun 1C geliwwert) an als Mass-produzéiert Produit datt op Är eegen Server Ariichtungen agesat ginn.

SV benotzt verdeelt Stockage hazelcast an Sich-Moteur Elastikerzuch. Mir schwätzen och iwwer Java a wéi mir PostgreSQL horizontal skaléieren.
Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast

Problemerklärung

Fir et kloer ze maachen firwat mir den Interaktiounssystem erstallt hunn, wäert ech Iech e bëssen iwwer soen wéi d'Entwécklung vu Geschäftsapplikatiounen am 1C funktionnéiert.

Fir unzefänken, e bëssen iwwer eis fir déi déi nach net wëssen wat mir maachen :) Mir maachen d'1C:Enterprise Technologie Plattform. D'Plattform enthält e Geschäftsapplikatiounsentwécklungsinstrument, souwéi eng Runtime, déi Geschäftsapplikatiounen erlaabt an engem Cross-Plattform Ëmfeld ze lafen.

Client-Server Entwécklung Paradigma

Business Uwendungen erstallt op 1C:Enterprise funktionnéieren an engem Dräi-Niveau Client-Server Architektur "DBMS - Applikatioun Server - Client". Applikatioun Code geschriwwen an gebaut-an 1C Sprooch, kann um Applikatiounsserver oder um Client ausgefouert ginn. All Aarbecht mat Applikatiounsobjekter (Verzeichnungen, Dokumenter, asw.), souwéi Liesen a Schreiwen vun der Datebank, gëtt nëmmen um Server duerchgefouert. D'Funktionalitéit vu Formen a Kommando-Interface gëtt och um Server implementéiert. De Client mécht Formulairen ophuelen, opmaachen an affichéieren, "Kommunikéieren" mam Benotzer (Warnungen, Froen ...), kleng Berechnungen a Formulairen déi eng séier Äntwert erfuerderen (zum Beispill multiplizéieren de Präis no Quantitéit), schafft mat lokalen Dateien, schaffen mat Ausrüstung.

Am Applikatiounscode mussen d'Header vu Prozeduren a Funktiounen explizit uginn wou de Code ausgefouert gëtt - mat den &AtClient / &AtServer Direktiven (&AtClient / &AtServer an der englescher Versioun vun der Sprooch). 1C Entwéckler wäerte mech elo korrigéieren andeems se soen datt Direktiven tatsächlech sinn méi wéi, mee fir eis ass dat elo net wichteg.

Dir kënnt Server Code aus Client Code Opruff, mee du kanns net Client Code aus Server Code Opruff. Dëst ass eng fundamental Begrenzung déi mir aus enger Rei vu Grënn gemaach hunn. Besonnesch well de Servercode muss sou geschriwwe ginn datt et déiselwecht Manéier ausféiert egal wou et genannt gëtt - vum Client oder vum Server. An am Fall vum Uruff vum Servercode vun engem anere Servercode, gëtt et kee Client als solch. A well während der Ausféierung vum Servercode konnt de Client deen et genannt huet zoumaachen, d'Applikatioun ausgoen, an de Server hätt kee méi ze ruffen.

Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast
Code deen e Knäppche klickt: eng Serverprozedur vum Client uruffen funktionnéiert, eng Clientprozedur vum Server uruffen wäert net

Dëst bedeit datt wa mir e Message vum Server un d'Clientapplikatioun wëllen schécken, zum Beispill datt d'Generatioun vun engem "laang lafende" Bericht fäerdeg ass an de Bericht ka gekuckt ginn, hu mir keng sou eng Method. Dir musst Tricken benotzen, zum Beispill, periodesch de Server aus dem Client Code pollen. Awer dës Approche lued de System mat onnéidege Uriff, a gesäit allgemeng net ganz elegant aus.

An et gëtt och e Besoin, zum Beispill, wann en Telefon ukënnt SIP- wann Dir en Opruff mécht, informéiert de Client Uwendung iwwer dëst sou datt et d'Nummer vum Uruff benotze kann fir se an der Géigepartei Datebank ze fannen an de Benotzerinformatioun iwwer d'Uruff Géigepartei ze weisen. Oder, zum Beispill, wann eng Bestellung an d'Lager ukomm ass, informéiert d'Clientapplikatioun vum Client iwwer dëst. Am Allgemengen ginn et vill Fäll wou esou e Mechanismus nëtzlech wier.

D'Produktioun selwer

Schafen eng Messagerie Mechanismus. Schnell, zouverlässeg, mat garantéierter Liwwerung, mat der Fäegkeet fir flexibel no Messagen ze sichen. Baséiert op de Mechanismus, implementéiert e Messenger (Messagen, Video Appellen) déi bannent 1C Uwendungen leeft.

Designt de System fir horizontal skalierbar ze sinn. D'Erhéijung vun der Belaaschtung muss ofgedeckt ginn andeems d'Zuel vun den Noden eropgeet.

Ëmsetzung

Mir decidéiert net de Server Deel vun SV direkt an der 1C z'integréieren: Enterprise Plattform, mä et als separat Produit ëmsetzen, der API vun deem kann aus dem Code vun 1C Applikatioun Léisungen genannt ginn. Dëst gouf aus enger Rei vu Grënn gemaach, den Haaptgrond vun deenen war datt ech et méiglech maache wollt Messagen tëscht verschiddenen 1C Uwendungen auszetauschen (zum Beispill tëscht Trade Management an Accounting). Verschidde 1C Uwendungen kënnen op verschiddene Versioune vun der 1C:Enterprise Plattform lafen, op verschiddene Serveren, etc. An esou Konditiounen ass d'Ëmsetzung vun SV als separat Produit läit "op der Säit" vun 1C Installatiounen déi optimal Léisung.

Also hu mir decidéiert SV als separat Produkt ze maachen. Mir recommandéieren datt kleng Firmen den CB-Server benotzen deen mir an eiser Cloud installéiert hunn (wss://1cdialog.com) fir d'Overheadkäschte mat der lokaler Installatioun an der Konfiguratioun vum Server ze vermeiden. Grouss Clientë kënnen et unzeroden hiren eegene CB Server bei hiren Ariichtungen z'installéieren. Mir hunn eng ähnlech Approche an eisem Cloud SaaS Produkt benotzt 1c frësch - et gëtt als masseproduzéiert Produkt fir Installatioun op Clientsplazen produzéiert an ass och an eiser Cloud ofgesat https://1cfresh.com/.

Applikatioun

Fir d'Laascht an d'Fehlertoleranz ze verdeelen, wäerte mir net eng Java Applikatioun ofsetzen, awer e puer, mat engem Lastbalancer virun hinnen. Wann Dir e Message vun Node zu Node transferéiere musst, benotzt publizéieren / abonnéieren an Hazelcast.

Kommunikatioun tëscht dem Client an dem Server ass iwwer Websocket. Et ass gutt gëeegent fir Echtzäitsystemer.

Verdeelt Cache

Mir hunn tëscht Redis, Hazelcast an Ehcache gewielt. Et ass 2015. Redis huet just en neie Cluster verëffentlecht (ze nei, grujeleg), et gëtt Sentinel mat vill Restriktiounen. Ehcache weess net wéi een an e Cluster zesummesetzt (dës Funktionalitéit erschéngt méi spéit). Mir hu beschloss et mat Hazelcast 3.4 ze probéieren.
Hazelcast ass an e Cluster aus der Këscht zesummegesat. Am Single Node Modus ass et net ganz nëtzlech a kann nëmmen als Cache benotzt ginn - et weess net wéi Dir Daten op Disk dumpt, wann Dir deen eenzegen Node verléiert, verléiert Dir d'Donnéeën. Mir setzen verschidden Hazelcasts aus, tëscht deenen mir kritesch Donnéeën backen. Mir maachen de Cache net zréck - mir sinn et egal.

Fir eis ass Hazelcast:

  • Späichere vu Benotzersessiounen. Et dauert eng laang Zäit fir all Kéier an d'Datebank fir eng Sessioun ze goen, sou datt mir all Sessiounen an Hazelcast setzen.
  • Cache. Wann Dir no engem Benotzerprofil sicht, kontrolléiert de Cache. En neie Message geschriwwen - an de Cache setzen.
  • Themen fir Kommunikatioun tëscht Applikatioun Instanzen. Den Node generéiert en Event a placéiert et am Hazelcast Thema. Aner Applikatiounsknäppchen, déi op dëst Thema abonnéiert sinn, kréien d'Evenement a veraarbecht.
  • Cluster Schleisen. Zum Beispill erstelle mir eng Diskussioun mat engem eenzegaartege Schlëssel (eenzel Diskussioun an der 1C Datebank):

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

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

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

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

Mir hu gepréift datt et kee Kanal ass. Mir hunn d'Schloss geholl, nach eng Kéier iwwerpréift an erstallt. Wann Dir d'Schloss net iwwerpréift nodeems Dir d'Schloss geholl hutt, da besteet d'Chance datt en anere Fuedem och dee Moment gepréift huet an elo probéiert déiselwecht Diskussioun ze kreéieren - awer et gëtt et schonn. Dir kënnt net mat synchroniséierten oder reguläre Java Lock spären. Duerch d'Datebank - et ass lues, an et ass schued fir d'Datebank; duerch Hazelcast - dat ass wat Dir braucht.

Wiel vun engem DBMS

Mir hunn extensiv an erfollegräich Erfahrung mat PostgreSQL a kollaboréieren mat den Entwéckler vun dësem DBMS.

Et ass net einfach mat engem PostgreSQL Cluster - et gëtt XL, XC, Citus, awer am Allgemengen sinn dës keng NoSQLs déi aus der Këscht scaléieren. Mir hunn NoSQL net als Haaptspeicher ugesinn; et war genuch datt mir Hazelcast geholl hunn, mat deem mir net virdru geschafft hunn.

Wann Dir eng relational Datebank muss skaléieren, heescht dat schaarf. Wéi Dir wësst, mat Sharding trennen mir d'Datebank an getrennten Deeler, sou datt jidderee vun hinnen op engem separaten Server plazéiert ka ginn.

Déi éischt Versioun vun eisem Sharding huet d'Fäegkeet ugeholl fir jiddereng vun den Dëscher vun eiser Applikatioun iwwer verschidde Serveren a verschiddene Proportiounen ze verdeelen. Et gi vill Messagen um Server A - wann ech glift, loosst eis en Deel vun dëser Tabell op de Server B réckelen. Dës Decisioun huet einfach iwwer virzäiteg Optimisatioun gejaut, also hu mir decidéiert eis op eng Multi-Tenant Approche ze limitéieren.

Dir kënnt iwwer Multi-Locataire liesen, zum Beispill, op der Websäit Citus Daten.

SV huet d'Konzepter vun Applikatioun an Abonnent. Eng Applikatioun ass eng spezifesch Installatioun vun enger Geschäftsapplikatioun, sou wéi ERP oder Accounting, mat senge Benotzer a Geschäftsdaten. En Abonnent ass eng Organisatioun oder Eenzelpersoun, op deem am Numm d'Applikatioun um SV Server registréiert ass. En Abonnent kann e puer Uwendungen registréiert hunn, an dës Uwendungen kënnen Messagen mateneen austauschen. Den Abonnent gouf e Locataire an eisem System. Messagen vu verschiddene Abonnente kënnen an enger kierperlecher Datebank lokaliséiert ginn; wa mir gesinn datt en Abonnent ugefaang huet vill Traffic ze generéieren, réckelen mir et op eng separat kierperlech Datebank (oder souguer eng separat Datebankserver).

Mir hunn eng Haaptdatebank wou e Routing-Table mat Informatioun iwwer d'Location vun all Abonnentdatenbanken gespäichert ass.

Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast

Fir ze verhënneren datt d'Haaptdatenbank e Flaschenhals ass, halen mir d'Routing-Tabelle (an aner dacks gebraucht Donnéeën) an engem Cache.

Wann d'Datebank vum Abonnent ufänkt ze luesen, wäerte mir et an Partitionen bannen schneiden. Op anere Projete benotze mir pg_pathman.

Zënter datt Benotzer Messagen verléieren schlecht ass, behalen mir eis Datenbanken mat Repliken. D'Kombinatioun vu synchronen an asynchronen Repliken erlaabt Iech Iech selwer am Fall vum Verloscht vun der Haaptdatenbank ze versécheren. Message Verloscht wäert nëmme geschéien wann déi primär Datebank a seng synchron Replika gläichzäiteg feelen.

Wann eng synchron Replika verluer ass, gëtt déi asynchroner Replik synchron.
Wann d'Haaptdatenbank verluer ass, gëtt d'synchron Replika d'Haaptdatenbank, an d'asynchrone Replik gëtt eng synchron Replik.

Elasticsearch fir Sich

Well ënner anerem SV och e Messenger ass, erfuerdert eng séier, praktesch a flexibel Sich, mat der Morphologie berücksichtegt, mat onpräzise Mätscher. Mir hunn decidéiert d'Rad net nei z'erfannen an de gratis Sichmotor Elasticsearch ze benotzen, erstallt op Basis vun der Bibliothéik Lucene. Mir installéieren och Elasticsearch an engem Cluster (Master - Data - Data) fir Probleemer am Fall vun Ausfall vun Applikatiounsknäppchen ze eliminéieren.

Op github hu mir fonnt Russesch Morphologie Plugin fir Elasticsearch a benotzt se. Am Elasticsearch Index späichere mir Wuertwurzelen (déi de Plugin bestëmmt) an N-Gram. Wéi de Benotzer Text aginn fir ze sichen, sichen mir no den getippten Text ënner N-Gram. Wann Dir an den Index gespäichert gëtt, gëtt d'Wuert "Texter" an déi folgend N-Gram opgedeelt:

[déi, tek, tex, text, texts, ek, ex, ext, texts, ks, kst, ksty, st, sty, du],

An d'Wuerzel vum Wuert "Text" wäert och erhale bleiwen. Dës Approche erlaabt Iech am Ufank, an der Mëtt an um Enn vum Wuert ze sichen.

Gesamtbild

Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast
Widderhuelen vum Bild vum Ufank vum Artikel, awer mat Erklärungen:

  • Balancer um Internet ausgesat; mir hunn nginx, et kann all sinn.
  • Java Applikatioun Instanzen kommunizéieren mateneen iwwer Hazelcast.
  • Ze schaffen mat engem Web Socket mir benotzen Netty.
  • D'Java Applikatioun ass am Java 8 geschriwwen a besteet aus Bündel OSGi. D'Pläng enthalen Migratioun op Java 10 an Iwwergank op Moduler.

Entwécklung an Testen

Am Prozess vun Entwécklungslänner an Testen der SV, hu mir eng Rei vun interessant Fonctiounen vun de Produiten begéint mir benotzen.

Luede Testen an Erënnerung Fuite

D'Verëffentlechung vun all SV Verëffentlechung ëmfaasst Laaschttest. Et ass erfollegräich wann:

  • Den Test huet e puer Deeg geschafft an et goufe keng Servicefehler
  • D'Äntwertzäit fir d'Schlësseloperatioune huet net eng komfortabel Schwell iwwerschratt
  • Leeschtung Verschlechterung am Verglach zu der viregter Versioun ass net méi wéi 10%

Mir fëllen d'Testdatenbank mat Daten - fir dëst ze maachen, kréie mir Informatioun iwwer den aktivsten Abonnent vum Produktiounsserver, multiplizéieren seng Zuelen mat 5 (d'Zuel vun de Messagen, Diskussiounen, Benotzer) an testen et esou.

Mir maachen Laaschttesten vum Interaktiounssystem an dräi Konfiguratiounen:

  1. Stress Test
  2. Nëmme Verbindungen
  3. Abonnent Aschreiwung

Wärend dem Stresstest lancéiere mir e puer honnert Threads, a si lueden de System ouni ze stoppen: Messagen schreiwen, Diskussiounen erstellen, eng Lëscht vu Messagen kréien. Mir simuléieren d'Aktiounen vun gewéinleche Benotzer (kréien eng Lëscht vu menge ongelies Messagen, schreiwen un een) a Softwareléisungen (iwwerdroen e Package vun enger anerer Konfiguratioun, veraarbecht eng Alarm).

Zum Beispill, dëst ass wéi en Deel vum Stresstest ausgesäit:

  • Benotzer aloggen an
    • Ufroen Är ongelies Diskussiounen
    • 50% wahrscheinlech Messagen ze liesen
    • 50% wahrscheinlech Text
    • Nächste Benotzer:
      • Huet eng 20% ​​Chance eng nei Diskussioun ze schafen
      • Wielt zoufälleg eng vu senge Diskussiounen
      • Gitt bannen
      • Ufro Messagen, Benotzer Profiler
      • Erstellt fënnef Messagen un zoufälleg Benotzer aus dëser Diskussioun adresséiert
      • Blieder Diskussioun
      • Widderhuelt 20 Mol
      • Logt aus, geet zréck op den Ufank vum Skript

    • E Chatbot kënnt an de System (emuléiert Messagerie vum Applikatiounscode)
      • Huet eng 50% Chance fir en neie Kanal fir Datenaustausch ze kreéieren (speziell Diskussioun)
      • 50% Wahrscheinlechkeet e Message un ee vun de bestehend Channels ze schreiwen

Den "Connections Only" Szenario ass aus engem Grond opgetaucht. Et gëtt eng Situatioun: d'Benotzer hunn de System ugeschloss, awer sinn nach net bedeelegt. All Benotzer schalt de Computer um 09:00 moies un, stellt eng Verbindung mam Server a bleift roueg. Dës Kärelen si geféierlech, et gi vill vun hinnen - déi eenzeg Packagen déi se hunn PING / PONG, awer si halen d'Verbindung mam Server (si kënnen et net halen - wat wann et en neie Message gëtt). Den Test reproduzéiert eng Situatioun wou eng grouss Zuel vun esou Benotzer probéiert an eng hallef Stonn an de System aloggen. Et ass ähnlech wéi e Stresstest, awer säi Fokus ass genau op dësen éischten Input - sou datt et keng Feeler gëtt (eng Persoun benotzt de System net, an et fällt scho - et ass schwéier un eppes méi schlëmmer ze denken).

Den Abonnentregistrierungsskript fänkt vum éischte Start un. Mir hunn e Stresstest gemaach a ware sécher datt de System net während der Korrespondenz verlangsamt huet. Awer d'Benotzer koumen an d'Aschreiwung huet ugefaang wéinst engem Timeout ze versoen. Beim Aschreiwung hu mir benotzt / Dev / zoufälleg, déi mat der Entropie vum System verbonnen ass. De Server huet keng Zäit genuch Entropie ze sammelen a wann en neie SecureRandom gefrot gouf, ass et fir zéng Sekonnen gefruer. Et gi vill Méiglechkeeten aus dëser Situatioun, Zum Beispill: schalt op déi manner sécher / Dev / Urandom, installéiert eng speziell Verwaltungsrot datt Entropie generéiert, Generéiere zoufälleg Zuelen am Viraus an Buttek hinnen an engem Pool. Mir hunn de Problem mam Pool temporär zougemaach, awer zënterhier hu mir e separaten Test gemaach fir nei Abonnenten ze registréieren.

Mir benotzen als Laaschtgenerator JMeter. Et weess net wéi mat Websocket ze schaffen; et brauch e Plugin. Déi éischt an de Sichresultater fir d'Ufro "jmeter websocket" sinn: Artikelen aus BlazeMeter, déi recommandéieren Plugin vum Maciej Zaleski.

Do hu mer décidéiert fir unzefänken.

Bal direkt nodeems se seriösen Tester ugefaang hunn, hu mir entdeckt datt JMeter ugefaang Erënnerung ze lecken.

De Plugin ass eng separat grouss Geschicht; mat 176 Stären huet et 132 Gabel op Github. Den Auteur selwer huet sech zënter 2015 net dofir engagéiert (mir hunn et 2015 geholl, dann huet et keng Verdacht opgeworf), verschidde Github Themen betreffend Memory Leaks, 7 unclosed pull requests.
Wann Dir décidéiert Last Tester mat dësem Plugin auszeféieren, gitt w.e.g. op déi folgend Diskussiounen opmierksam:

  1. An engem Multi-threaded Ëmfeld gouf eng regulär LinkedList benotzt, an d'Resultat war NPE an der Runtime. Dëst kann entweder geléist ginn andeems Dir op ConcurrentLinkedDeque wiesselt oder duerch synchroniséiert Blocks. Mir hunn déi éischt Optioun fir eis selwer gewielt (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Gedächtnisleck; beim Trennen gëtt d'Verbindungsinformatioun net geläscht (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Am Streaming Modus (wann de Websocket net um Enn vun der Probe zougemaach ass, awer méi spéit am Plang benotzt gëtt), funktionnéieren d'Äntwertmuster net (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Dëst ass ee vun deenen op github. Wat mir gemaach hunn:

  1. Huelt Gabel Elyran Kogan (@elyrank) - et fixéiert Probleemer 1 an 3
  2. Problem geléist 2
  3. Aktualiséiert Jetty vum 9.2.14 bis den 9.3.12
  4. Gewéckelt SimpleDateFormat an ThreadLocal; SimpleDateFormat ass net thread-sécher, wat zu NPE bei der Runtime gefouert huet
  5. Eng aner Erënnerungsleck fixéiert (d'Verbindung gouf falsch zougemaach wann se ofgeschalt gouf)

An awer fléisst et!

D'Erënnerung huet ugefaang net an engem Dag ze lafen, mee an zwee. Et war absolut keng Zäit méi, also hu mir beschloss manner Threads ze lancéieren, awer op véier Agenten. Dëst sollt op d'mannst eng Woch duergoen.

Zwee Deeg sinn vergaangen...

Elo leeft Hazelcast aus Erënnerung. D'Logbicher weisen datt no e puer Deeg Tester den Hazelcast ugefaang huet iwwer e Manktem u Gedächtnis ze beschwéieren, an no enger Zäit ass de Cluster ausernee gefall, an d'Knueten stierwen weider een nom aneren. Mir hunn JVisualVM mat Hazelcast verbonnen an hunn eng "Rising See" gesinn - et huet regelméisseg de GC genannt, awer konnt d'Erënnerung net läschen.

Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast

Et huet sech erausgestallt datt am Hazelcast 3.4, wann Dir eng Kaart / multiMap (map.destroy() läschen), d'Erënnerung net komplett befreit ass:

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

De Feeler ass elo am 3.5 fixéiert, awer et war deemools e Problem. Mir hunn nei MultiMaps mat dynamesche Nimm erstallt an se no eiser Logik geläscht. De Code huet sou ausgesinn:

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

Virdrun:

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

multiMap gouf fir all Abonnement erstallt a geläscht wann et net gebraucht gouf. Mir hunn decidéiert datt mir Map ufänken , de Schlëssel wäert den Numm vum Abonnement sinn, an d'Wäerter wäerten Sessiounsidentifizéierer sinn (aus deenen Dir dann Benotzeridentifizéierer kritt, wann néideg).

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

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

D'Charts hu verbessert.

Wéi a firwat mir e High-load skalierbare Service fir 1C geschriwwen hunn: Enterprise: Java, PostgreSQL, Hazelcast

Wat soss hu mir iwwer Lasttest geléiert?

  1. JSR223 muss a groovy geschriwwe ginn an Compilatiounscache enthalen - et ass vill méi séier. Link.
  2. Jmeter-Plugins Grafike si méi einfach ze verstoen wéi Standard. Link.

Iwwer eis Erfahrung mat Hazelcast

Hazelcast war en neit Produkt fir eis, mir hunn ugefaang mat der Versioun 3.4.1 ze schaffen, elo leeft eise Produktiounsserver Versioun 3.9.2 (zu der Zäit vum Schreiwen ass déi lescht Versioun vum Hazelcast 3.10).

ID Generatioun

Mir hunn ugefaang mat ganzer Identifizéierer. Loosst eis virstellen datt mir eng aner Long fir eng nei Entitéit brauchen. D'Sequenz an der Datebank ass net gëeegent, d'Dëscher si mat Sharding involvéiert - et stellt sech eraus datt et e Message ID=1 an DB1 an e Message ID=1 an DB2 ass, Dir kënnt dës ID net an Elasticsearch setzen, nach an Hazelcast , awer dat Schlëmmst ass wann Dir d'Donnéeën vun zwou Datenbanken an eng kombinéiere wëllt (zum Beispill entscheeden datt eng Datebank fir dës Abonnenten genuch ass). Dir kënnt e puer AtomicLongs op Hazelcast addéieren an de Konter do halen, da gëtt d'Leeschtung fir eng nei ID ze kréien incrementAndGet plus d'Zäit fir eng Ufro un Hazelcast. Awer Hazelcast huet eppes méi optimal - FlakeIdGenerator. Wann Dir all Client kontaktéiert, kréie se eng ID-Gamme, zum Beispill, déi éischt - vun 1 bis 10, déi zweet - vun 000 bis 10, a sou weider. Elo kann de Client nei Identifizéierer eleng erausginn bis d'Gamme, déi him erausginn ass, eriwwer ass. Et funktionnéiert séier, awer wann Dir d'Applikatioun (an den Hazelcast Client) nei start, fänkt eng nei Sequenz un - also d'Sprangen, etc. Zousätzlech verstinn d'Entwéckler net wierklech firwat d'IDen ganz Zuelen sinn, awer sou inkonsistent. Mir hunn alles gewien an op UUIDen ëmgewiesselt.

Iwwregens, fir déi, déi wëlle wéi Twitter sinn, gëtt et sou eng Snowcast Bibliothéik - dëst ass eng Implementatioun vu Snowflake uewen op Hazelcast. Dir kënnt et hei kucken:

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

Mee mir sinn net méi dorop komm.

TransactionalMap.replace

Eng aner Iwwerraschung: TransactionalMap.replace funktionnéiert net. Hei ass en 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

Ech hu misse meng eegen Ersatz schreiwen mat 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);
}

Test net nëmme regelméisseg Datestrukturen, awer och hir Transaktiounsversioune. Et geschitt datt IMap funktionnéiert, awer TransactionalMap existéiert net méi.

Füügt en neie JAR ouni Ausdauer

Als éischt hu mir beschloss Objekter vun eise Klassen an Hazelcast opzehuelen. Zum Beispill hu mir eng Applikatiounsklass, mir wëllen se späicheren a liesen. Späicheren:

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

Mir liesen:

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

Alles funktionnéiert. Dunn hu mir beschloss en Index an Hazelcast ze bauen fir ze sichen no:

map.addIndex("subscriberId", false);

A wann se eng nei Entitéit schreiwen, hunn se ugefaang ClassNotFoundException ze kréien. Den Hazelcast huet probéiert an den Index ze addéieren, wousst awer näischt iwwer eis Klass a wollt e JAR mat dëser Klass dozou geliwwert ginn. Mir hunn dat just gemaach, alles huet geschafft, awer en neie Problem erschéngt: wéi aktualiséieren ech de JAR ouni de Cluster komplett ze stoppen? Hazelcast hëlt den neie JAR net wärend engem Node-by-Node Update op. Zu dësem Zäitpunkt hu mir décidéiert datt mir ouni Index Sich kënne liewen. No allem, wann Dir Hazelcast als Schlësselwäertgeschäft benotzt, da funktionnéiert alles? Net wierklech. Hei ass erëm d'Behuele vun IMap an TransactionalMap anescht. Wou IMap egal ass, werft TransactionalMap e Feeler.

IMap. Mir schreiwen 5000 Objeten, liesen se. Alles gëtt erwaart.

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

Awer et funktionnéiert net an enger Transaktioun, mir kréien eng 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();
        }
    });
}

Am 3.8 ass de User Class Deployment Mechanismus erschéngt. Dir kënnt ee Master Node designéieren an d'JAR Datei dorop aktualiséieren.

Elo hu mir eis Approche komplett geännert: mir serialiséieren et selwer an JSON a späicheren et an Hazelcast. Hazelcast brauch d'Struktur vun eise Klassen net ze kennen, a mir kënnen ouni Ënnerbriechung aktualiséieren. Versionéierung vun Domainobjekte gëtt vun der Applikatioun kontrolléiert. Verschidde Versioune vun der Applikatioun kënne gläichzäiteg lafen, an eng Situatioun ass méiglech wann déi nei Applikatioun Objete mat neie Felder schreift, awer déi al weess nach net iwwer dës Felder. A gläichzäiteg liest déi nei Applikatioun Objete geschriwwe vun der aler Applikatioun déi keng nei Felder hunn. Mir handhaben esou Situatiounen an der Applikatioun, awer fir Einfachheet änneren oder läschen mir keng Felder, mir erweideren d'Klassen nëmmen andeems nei Felder derbäigesat ginn.

Wéi mir eng héich Leeschtung garantéieren

Véier Reesen op Hazelcast - gutt, zwee an d'Datebank - schlecht

Gitt an de Cache fir Daten ass ëmmer besser wéi an d'Datebank ze goen, awer Dir wëllt och net onbenotzt records späicheren. Mir verloossen d'Entscheedung iwwer wat fir ze cache bis déi lescht Etapp vun der Entwécklung. Wann déi nei Funktionalitéit kodéiert ass, schalten mir d'Protokolléiere vun all Ufroen am PostgreSQL (log_min_duration_statement op 0) un a lafen Laaschtestung fir 20 Minutten. Mat de gesammelten Logbicher kënnen Utilitys wéi pgFouine a pgBadger analytesch Berichter bauen. A Berichter sichen mir haaptsächlech no luesen a reegelméissegen Ufroen. Fir lues Ufroen bauen mir en Ausféierungsplang (EXPLAIN) an evaluéieren ob sou eng Ufro ka beschleunegt ginn. Heefeg Ufroe fir déiselwecht Inputdaten passen gutt an de Cache. Mir probéieren Ufroen "flaach" ze halen, eng Tabell pro Ufro.

Operatioun

SV als online Service war am Fréijoer a Betrib geholl 2017, an als separat Produit war SV Verëffentlechung am November 2017 (deemools an Beta Versioun Status).

A méi wéi engem Joer Operatioun gouf et keng sérieux Problemer an der Operatioun vum CB Online Service. Mir iwwerwaachen den Online Service iwwer Zabbix, sammelen an ofsetzen vun Bamboo.

D'SV Server Verdeelung gëtt a Form vun nativen Packagen geliwwert: RPM, DEB, MSI. Plus fir Windows bidde mir en eenzegen Installateur a Form vun engem eenzegen EXE deen de Server, Hazelcast an Elasticsearch op enger Maschinn installéiert. Mir hunn ufanks dës Versioun vun der Installatioun als "Demo" Versioun bezeechent, awer et ass elo kloer ginn datt dëst déi populärste Deploymentoptioun ass.

Source: will.com

Setzt e Commentaire