Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast

In questu articulu avemu da parlà di cumu è perchè avemu sviluppatu Sistema di interazzione - un mecanismu chì trasferisce l'infurmazioni trà l'applicazioni di u cliente è 1C: Servitori di l'impresa - da stabilisce un compitu à pensà à traversu l'architettura è i dettagli di implementazione.

U Sistema di Interazione (in seguitu - CB) hè un sistema di messageria distribuitu tolerante à i difetti cù consegna garantita. CB hè cuncepitu cum'è un serviziu d'alta carica cù una scalabilità elevata, dispunibule cum'è un serviziu in linea (furnitu da 1C) è cum'è un pruduttu di produzzione chì pò esse implementatu nantu à e so strutture di u servitore.

SW usa u almacenamentu distribuitu avellana è u mutore di ricerca Elasticsearch. Parleremu ancu di Java è cumu scalamu in orizzontale PostgreSQL.
Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast

Formulazione di u prublema

Per esse chjaru perchè avemu fattu u Sistema di Interazione, vi dicu un pocu di cumu u sviluppu di l'applicazioni cummerciale in 1C funziona.

Prima, un pocu di noi per quelli chì ùn sanu micca ciò chì facemu :) Sviluppemu a piattaforma tecnologica 1C:Enterprise. A piattaforma include un strumentu di sviluppu di l'applicazioni cummerciale, è ancu un runtime chì permette à l'applicazioni cummerciale di travaglià in un ambiente multipiattaforma.

Paradigma di sviluppu client-server

L'applicazioni cummerciale create in 1C: Enterprise operanu in trè livelli client-server architettura "DBMS - servitore d'applicazioni - cliente". Codice di l'applicazione scrittu in lingua integrata 1C, pò eseguisce nantu à u servitore di l'applicazione o nantu à u cliente. Tuttu u travagliu cù l'oggetti di l'applicazione (directori, documenti, etc.), in quantu à leghje è scrive a basa di dati, hè realizatu solu nantu à u servitore. Forme è funziunalità di l'interfaccia di cumanda hè ancu implementata nantu à u servitore. Nantu à u cliente, i formi sò ricevuti, aperti è affissati, "comunicazione" cù l'utilizatore (avvirtimenti, dumande ...), picculi calculi in forme chì necessitanu una risposta rapida (per esempiu, multiplicà u prezzu da a quantità), travagliendu cù schedarii lucali, travagliendu cù l'equipaggiu.

In u codice di l'applicazione, l'intestazione di e prucedure è e funzioni anu da indicà esplicitamente induve u codice serà eseguitu - utilizendu e direttive &AtClient / &AtServer (&AtClient / &AtServer in a versione inglese di a lingua). I sviluppatori 1C mi correggeranu avà dicendu chì e direttive sò in realtà più di più, ma per noi ùn hè micca impurtante avà.

Pudete chjamà u codice di u servitore da u codice di u cliente, ma ùn pudete micca chjamà u codice di u servitore da u codice di u servitore. Questa hè una limitazione fundamentale, fatta da noi per una quantità di ragioni. In particulare, perchè u codice di u servitore deve esse scrittu in tale manera chì eseguisce u listessu modu, ùn importa micca induve hè chjamatu - da u cliente o da u servitore. È in u casu di una chjama à u codice di u servitore da un altru codice di u servitore, ùn ci hè micca cliente cum'è tali. È perchè durante l'esekzione di u codice di u servitore, u cliente chì u chjamava puderia chjude, esce da l'applicazione, è u servitore ùn averebbe nimu per chjamà.

Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast
U codice chì gestisce un clic di buttone: chjamà una prucedura di u servitore da u cliente hà da travaglià, chjamà una prucedura di u cliente da u servitore ùn serà micca

Questu significa chì se vulemu mandà qualchì messagiu da u servitore à l'applicazione di u cliente, per esempiu, chì a furmazione di un rapportu "long-playing" hè finita è u rapportu pò esse vistu, ùn avemu micca cusì. Avete da utilizà i trucchi, per esempiu, sondaghju periodicamente u servitore da u codice di u cliente. Ma questu approcciu carica u sistema cù chjama inutili, è in generale ùn pare micca assai eleganti.

È ci hè ancu un bisognu, per esempiu, quandu un telefonu A Lettera-call, avvisà l'applicazione di u cliente nantu à questu in modu chì pò truvà in a basa di dati di a contrapartita da u numeru di u chjamante è mostra l'infurmazioni di l'utilizatori nantu à a contraparte chì chjama. O, per esempiu, quandu un ordine ghjunghje à u magazzinu, avvisà l'applicazione di u cliente di u cliente nantu à questu. In generale, ci sò parechji casi induve un tali mecanismu seria utile.

In realtà stallà

Crea un mecanismu di messageria. Veloce, affidabile, cù consegna garantita, cù a pussibilità di ricerca flessibile per i missaghji. Basatu nantu à u mecanismu, implementà un messaggeru (missaghji, videochiamate) chì travaglia in l'applicazioni 1C.

Cuncepisce u sistema scalable horizontalmente. Una carica crescente deve esse coperta da un aumentu di u numeru di nodi.

Реализация

Avemu decisu di ùn incrustà a parte di u servitore di u CB direttamente in a piattaforma 1C: Enterprise, ma di implementà cum'è un pruduttu separatu, l'API di quale pò esse chjamatu da u codice di suluzioni di l'applicazione 1C. Questu hè statu fattu per una quantità di mutivi, u principale di quale era di fà pussibule scambià missaghji trà e diverse applicazioni 1C (per esempiu, trà u Dipartimentu di Cummerciu è Contabilità). Differenti applicazioni 1C ponu eseguisce in diverse versioni di a piattaforma 1C: Enterprise, esse situate in diversi servitori, etc. In tali cundizioni, l'implementazione di CB cum'è un pruduttu separatu, situatu "à u latu" di l'installazione 1C, hè a suluzione ottima.

Dunque, avemu decisu di fà CB cum'è un pruduttu separatu. Per e cumpagnie più chjuche, ricumandemu d'utilizà u servitore CB chì avemu stallatu in u nostru nuvulu (wss://1cdialog.com) per evità l'overhead assuciatu cù l'installazione è a cunfigurazione di u servitore locale. I grandi clienti, in ogni modu, ponu cunsiderà conveniente per installà u so servitore CB in e so facilità. Avemu usatu un approcciu simili in u nostru pruduttu SaaS in nuvola. 1c frescu - hè liberatu cum'è un pruduttu di produzzione per a stallazione da i clienti, è hè ancu implementatu in u nostru nuvulu https://1cfresh.com/.

Applicazione

Per a distribuzione di carichi è a tolleranza di difetti, ùn implementeremu micca una sola applicazione Java, ma parechje, metteremu un equilibratore di carica davanti à elli. Sè avete bisognu di mandà un missaghju da node à node, utilizate pubblicà / abbonate in Hazelcast.

A cumunicazione trà u cliente è u servitore - via websocket. Hè bè adattatu per i sistemi in tempu reale.

Cache distribuitu

Sceglite trà Redis, Hazelcast è Ehcache. Fora in 2015. Redis hà appena liberatu un novu cluster (troppu novu, spaventoso), ci hè un Sentinel cù assai restrizioni. Ehcache ùn sà micca cumu cumparisce (sta funziunalità apparsu dopu). Avemu decisu di pruvà cù Hazelcast 3.4.
Hazelcast hè raggruppatu fora di a scatula. In u modu di nodu unicu, ùn hè micca assai utile è pò esse ghjustu cum'è un cache - ùn sapi micca cumu di dump data à u discu, se l'unicu node hè persu, i dati sò persi. Implementemu parechji Hazelcasts, trà quale avemu una copia di salvezza di dati critichi. Ùn avemu micca una copia di salvezza di a cache - ùn avemu micca dispiace per ellu.

Per noi, Hazelcast hè:

  • Archiviazione di sessioni d'utilizatori. Ci vole assai tempu per andà in a basa di dati per una sessione, cusì mettemu tutte e sessioni in Hazelcast.
  • Cache. Cerchendu un prufilu d'utilizatore - verificate in a cache. Scrive un novu missaghju - mette in a cache.
  • Temi per a cumunicazione di l'istanze di l'applicazione. U node genera un avvenimentu è u mette nantu à un tema Hazelcast. L'altri nodi di l'applicazione abbonati à stu tema ricevenu è processanu l'avvenimentu.
  • serrature di cluster. Per esempiu, creamu una discussione per una chjave unica (discussion-singleton in u quadru di a basa 1C):

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

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

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

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

Avemu verificatu chì ùn ci hè micca un canale. Piglionu a serratura, verificatu di novu, creanu. Se ùn avete micca verificatu dopu à piglià a serratura, allora ci hè una chance chì un altru filu hà verificatu ancu in quellu mumentu è avà da pruvà à creà a stessa discussione - è esiste digià. Hè impussibile di fà una serratura attraversu java Lock sincronizatu o regulare. Per mezu di a basa - lentamente, è a basa hè una pietà, attraversu Hazelcast - ciò chì avete bisognu.

Sceglie un DBMS

Avemu una sperienza larga è successu cù PostgreSQL è a cooperazione cù i sviluppatori di stu DBMS.

Cù un cluster, PostgreSQL ùn hè micca faciule - ci hè XL, XC, Citus, ma, in generale, ùn hè micca noSQL chì scala fora di a scatula. NoSQL ùn era micca cunsideratu cum'è l'almacenamiento principale, era abbastanza chì avemu pigliatu Hazelcast, chì ùn avemu micca travagliatu prima.

Siccomu avete bisognu di scala una basa di dati relazionale, significa frammentazione. Comu sapete, quandu u sharding, dividemu a basa di dati in parti separati per chì ognuna di elle pò esse piazzata in un servitore separatu.

A prima versione di u nostru sharding assume a capacità di sparghje ognuna di e tavule di a nostra applicazione à diversi servitori in diverse proporzioni. Un saccu di messagi nantu à u servitore A - per piacè movemu una parte di sta tavula à u servitore B. Sta decisione hà urlatu ghjustu annantu à l'ottimisazione prematura, cusì avemu decisu di limità à un approcciu multi-tenant.

Pudete leghje nantu à multi-tenant, per esempiu, in u situ web Citus Data.

In SV ci sò cuncetti di l'applicazione è l'abbonatu. Una applicazione hè una stallazione specifica di una applicazione cummerciale, cum'è ERP o Accounting, cù i so utilizatori è i dati cummerciale. Un abbonatu hè una urganizazione o un individuu in nome di quale l'applicazione hè registrata in u servitore CB. Un abbonatu pò avè parechje applicazioni registrate, è queste applicazioni ponu scambià missaghji cù l'altri. L'abbonatu hè diventatu un inquilino in u nostru sistema. I missaghji di parechji abbonati ponu esse situatu in una basa fisica; se vedemu chì qualchì abbonatu hà cuminciatu à generà assai trafficu, u movemu in una basa di dati fisica separata (o ancu un servitore di basa di dati separatu).

Avemu una basa di dati principale induve a tabella di routing hè almacenata cù infurmazione nantu à a situazione di tutte e basa di dati di l'abbonati.

Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast

Per impediscenu chì a basa di dati principale ùn sia un collu di buttiglia, mantenemu a tabella di routing (è altre dati frequentemente dumandati) in a cache.

Se a basa di dati di l'abbonatu cumencia à rallentà, a tagliaremu in partizioni in l'internu. Nant'à altri prughjetti, per particionà e grande tavule, usemu pg_pathman.

Siccomu perde i missaghji di l'utilizatori hè male, facemu una copia di salvezza di e nostre basa di dati cù repliche. A cumminazzioni di rèplichi sincroni è asincroni permette di assicurà contra a perdita di a basa di dati principale. A perdita di u messagiu serà solu in casu di fallimentu simultaneo di a basa di dati principale è a so replica sincrona.

Se a replica sincrona hè persa, a replica asincrona diventa sincrona.
Se a basa di dati principale hè persa, a replica sincrona diventa a basa di dati principale, a replica asincrona diventa una replica sincrona.

Elasticsearch per a ricerca

Siccomu, frà altre cose, CB hè ancu un messenger, quì avemu bisognu di una ricerca rapida, còmuda è flexiblee, tenendu in contu a morfologia, da partite inesatti. Avemu decisu di ùn reinventà a rota è aduprà u mutore di ricerca Elasticsearch gratuitu, creatu nantu à a basa di a biblioteca. Lucene. Avemu ancu implementatu Elasticsearch in un cluster (master - data - data) per eliminà i prublemi in casu di fallimentu di i nodi di l'applicazione.

In github avemu trovu Plugin di morfologia russa per Elasticsearch è aduprà. In l'indici Elasticsearch, guardemu e radiche di parolle (chì u plugin definisce) è N-grammi. Quandu l'utilizatore inserisce u testu per circà, cerchemu u testu digitatu trà N-grammi. Quandu hè salvatu à l'indici, a parolla "testi" serà divisa in i seguenti N-grammi:

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

È ancu a radica di a parolla "testu" serà salvatu. Stu approcciu vi permette di circà à u principiu, à mezu, è à a fine di a parolla.

U quadru generale

Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast
Ripitendu a stampa da u principiu di l'articulu, ma cù spiegazioni:

  • Balancer esposti à Internet; avemu nginx, pò esse qualsiasi.
  • L'istanze di l'applicazione Java cumunicanu trà l'altri via Hazelcast.
  • Per travaglià cù un socket web, usemu netta.
  • L'applicazione Java scritta in Java 8, hè custituita da bundle OSGi. I piani sò di migrà à Java 10 è cambià à moduli.

Sviluppu è teste

Durante u sviluppu è a prova di u CB, avemu scontru una quantità di caratteristiche interessanti di i prudutti chì avemu usatu.

Test di carica è perdite di memoria

A liberazione di ogni liberazione CB hè una prova di carica. Hè passatu cù successu quandu:

  • A prova hà travagliatu per parechji ghjorni è ùn ci era micca denegazioni di serviziu
  • U tempu di risposta per l'operazioni chjave ùn superava micca un limitu còmode
  • A degradazione di u rendiment cumparatu cù a versione precedente ùn hè micca più di 10%

Riempite a basa di dati di teste cù dati - per questu avemu infurmazione nantu à l'abbonatu più attivu da u servitore di produzzione, multiplicà i so numeri per 5 (u numeru di missaghji, discussioni, utilizatori) è cusì testemu.

Eseguimu a prova di carica di u sistema di interazzione in trè cunfigurazioni:

  1. prova di stress
  2. Connessioni solu
  3. Registrazione di l'abbonatu

Durante una prova di stress, lanciamu parechji cintunari di fili, è caricanu u sistema senza piantà: scrive messagi, crea discussioni, riceve una lista di missaghji. Simulemu l'azzioni di l'utilizatori ordinali (ottene una lista di i mo messagi micca leghjiti, scrivite à qualchissia) è e decisioni di u prugramma (trasferisce un pacchettu à una altra cunfigurazione, processà una alerta).

Per esempiu, questu hè a parte di a prova di stress:

  • L'utilizatore accede
    • Richiede i vostri fili micca letti
    • 50% chance di leghje i missaghji
    • 50% chance di scrive missaghji
    • U prossimu utilizatore:
      • 20% chance per creà un novu filu
      • Selezziunate casualmente qualsiasi di e so discussioni
      • Veni dentru
      • Richiede missaghji, profili d'utilizatori
      • Crea cinque missaghji indirizzati à l'utilizatori aleatorii da stu thread
      • Fora di discussione
      • Repetite 20 volte
      • Logs out, torna à u principiu di u script

    • Un chatbot entra in u sistema (emula a messageria da u codice di suluzione applicata)
      • 50% chance per creà un novu canale di dati (discussione speciale)
      • 50% chance di scrive un missaghju in qualsiasi di i canali esistenti

U scenariu "Connessioni solu" hè apparsu per una ragione. Ci hè una situazione: l'utilizatori anu cunnessu u sistema, ma ùn anu micca participatu. Ogni utilizatore in a matina à 09:00 accende l'urdinatore, stabilisce una cunnessione cù u servitore è hè silenziu. Questi picciotti sò periculosi, ci sò assai - anu solu PING / PONG fora di i pacchetti, ma mantenenu a cunnessione à u servitore (ùn ponu micca mantene - è di colpu un novu missaghju). A prova riproduce a situazione quandu un gran numaru di tali utilizatori pruvate à accede à u sistema in una meza ora. Sembra una prova di stress, ma u so focusu hè precisamente nantu à questu primu input - perchè ùn ci sò micca fallimenti (una persona ùn usa micca u sistema, ma hè digià cascatu - hè difficiule di vene cun qualcosa peggiu).

U scenariu di registrazione di l'abbonatu hè urigginatu da u primu lanciu. Avemu fattu una prova di stress è eramu sicuri chì u sistema ùn hà micca rallentatu in corrispondenza. Ma l'utilizatori sò andati è a registrazione hà cuminciatu à falà da u timeout. Quandu si registra, avemu usatu / dev / aleatoriu, chì hè ligata à l'entropia di u sistema. U servitore ùn hà micca u tempu di accumulà abbastanza entropia è, quandu un novu SecureRandom hè statu dumandatu, si congelava per decine di seconde. Ci hè parechje manere di fora di sta situazione, per esempiu: cambià à un /dev/urandom menu sicuru, installate un tavulinu speciale chì genera entropia, generà numeri aleatorii in anticipu è guardate in a piscina. Avemu chjusu temporaneamente u prublema cù a piscina, ma da tandu avemu fattu una prova separata per registrà novi abbonati.

Cum'è un generatore di carica usemu jmeter. Ùn sapi micca cumu travaglià cù un websocket, un plugin hè necessariu. I primi in i risultati di ricerca per a query "jmeter websocket" sò articuli cù BlazeMeterin quale ricumandenu plugin di Maciej Zaleski.

Hè quì chì avemu decisu di principià.

Quasi subitu dopu à l'iniziu di teste serii, avemu scupertu chì e fughe di memoria cuminciaru in JMeter.

U plugin hè una grande storia separata, cù 176 stelle hà 132 forks in github. L'autore stessu ùn hà micca impegnatu in questu da u 2015 (l'avemu pigliatu in 2015, allora ùn hà micca suscitatu suspetti), parechji prublemi di github nantu à fughe di memoria, 7 richieste di pull unclosed.
Se sceglite di carica a prova cù stu plugin, per piacè nutate e seguenti discussioni:

  1. In un ambiente multi-threaded, u solitu LinkedList hè stata utilizata, per quessa, avemu avutu NPE in runtime. Hè risolta sia da cambià à ConcurrentLinkedDeque, sia da blocchi sincronizati. Avemu sceltu a prima opzione per noi stessihttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Fuga di memoria, l'infurmazione di cunnessione ùn hè micca sguassata quandu si disconnette (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. In u modu streaming (quandu u websocket ùn hè micca chjusu à a fine di l'esempiu, ma hè utilizatu più in u pianu), i mudelli di risposta ùn funzionanu micca (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Questu hè unu di quelli nantu à github. Ciò chì avemu fattu:

  1. Avete pigliatu forchetta di Elyran Kogan (@elyrank) - risolve i prublemi 1 è 3
  2. Prublemu risoltu 2
  3. Moglie aghjurnata da 9.2.14 à 9.3.12
  4. Impulsatu SimpleDateFormat in ThreadLocal; SimpleDateFormat ùn hè micca sicuru chì porta à NPE in runtime
  5. Riparata una altra perdita di memoria (cunnessione chjusa in modu incorrectu à a disconnessione)

È puru scorri !

A memoria hà cuminciatu à finisce micca in un ghjornu, ma in dui. Ùn ci era micca tempu in tuttu, avemu decisu di curriri menu fili, ma nantu à quattru agenti. Questu duverebbe esse abbastanza per almenu una settimana.

Sò dui ghjorni...

Avà Hazelcast hè scappatu di memoria. I logs hà dimustratu chì dopu à un paru di ghjorni di teste, Hazelcast principia à lagnà per a mancanza di memoria, è dopu à un pocu tempu u cluster fallen, è i nodi cuntinueghjanu à mori unu à unu. Avemu cunnessu JVisualVM à hazelcast è hà vistu a "sega ascendente" - chjamava regularmente u GC, ma ùn pudia micca sguassà a memoria in ogni modu.

Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast

Hè risultatu chì in hazelcast 3.4, quandu si sguassate una mappa / multiMap (map.destroy()), a memoria ùn hè micca liberata cumplettamente:

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

U bug hè avà riparatu in 3.5, ma era un prublema allora. Avemu creatu un novu multiMap cù nomi dinamichi è sguassati secondu a nostra logica. U codice pareva qualcosa cusì:

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

Vogliu:

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

multiMap hè statu creatu per ogni abbonamentu è sguassatu quandu ùn era micca necessariu. Avemu decisu chì avemu da principià una Mappa , a chjave serà u nome di l'abbunamentu, è i valori seranu identificatori di sessione (per quale pudete uttene identificatori d'utilizatori, se ne necessariu).

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

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

I charts anu migliuratu.

Cumu è perchè avemu scrittu un serviziu scalabile altamente caricatu per 1C: Enterprise: Java, PostgreSQL, Hazelcast

Chì altru avemu amparatu nantu à a prova di carica

  1. JSR223 deve esse scrittu in groovy è include cache di compilazione - hè assai più veloce. a lea.
  2. I charts Jmeter-Plugins sò più faciuli di capiscenu cà quelli standard. a lea.

Circa a nostra sperienza cù Hazelcast

Hazelcast era un novu pruduttu per noi, avemu cuminciatu à travaglià cun ellu da a versione 3.4.1, avà avemu a versione 3.9.2 in u nostru servitore di pruduzzione (à u mumentu di a scrittura, l'ultima versione di Hazelcast hè 3.10).

generazione di ID

Avemu principiatu cù identificatori interi. Imaginemu chì avemu bisognu di un altru Long per una nova entità. A sequenza in a basa di dati ùn hè micca adattatu, i tavulini sò implicati in sharding - risulta chì ci hè un missaghju ID = 1 in DB1 è un missaghju ID = 1 in DB2, ùn pudete micca mette stu ID in Elasticsearch, in Hazelcast troppu, ma u peghju hè s'è vo vulete riduce e dati da duie basa di dati à una (per esempiu, decide chì una basa di dati hè abbastanza per questi abbonati). Pudete avè parechji AtomicLongs in Hazelcast è mantene u cuntatore quì, allora u rendiment di ottene un novu ID hè incrementAndGet plus u tempu di dumandà in Hazelcast. Ma Hazelcast hà qualcosa di più ottimali - FlakeIdGenerator. Quandu cuntattate, ogni cliente hè datu una varietà di ID, per esempiu, u primu - da 1 à 10, u sicondu - da 000 à 10, etc. Avà u cliente pò emette novi identificatori per sè stessu finu à a fine di a gamma emessa. Funziona rapidamente, ma riavvia l'app (è u cliente Hazelcast) principia una nova sequenza - da quì i salti, etc. Inoltre, ùn hè micca assai chjaru per i sviluppatori perchè l'ID sò interi, ma vanu tantu in odds. Avemu pisatu tuttu è cambiatu à UUIDs.

In modu, per quelli chì volenu esse cum'è Twitter, ci hè una tale biblioteca di Snowcast - questu hè una implementazione di Snowflake sopra Hazelcast. Pudete vede quì:

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

Ma ùn l'avemu ancu ghjuntu.

TransactionalMap.replace

Un'altra sorpresa: TransactionalMap.replace ùn funziona micca. Eccu una prova:

@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

Aghju avutu à scrive u mo propiu rimpiazzà cù 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);
}

Pruvate micca solu strutture di dati regularmente, ma ancu e so versioni transazzione. Succede chì IMap funziona, ma TransactionalMap ùn esiste più.

Attaccà un novu JAR senza tempi di inattività

Prima, avemu decisu di scrive oggetti di e nostre classi à Hazelcast. Per esempiu, avemu una classa Applicazioni, vulemu almacenà è leghje. Salvà:

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

Leghjemu:

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

Tuttu travaglia. Allora avemu decisu di custruisce un indice in Hazelcast per circà lu:

map.addIndex("subscriberId", false);

È quandu scrive una nova entità, cuminciaru à riceve una ClassNotFoundException. Hazelcast hà pruvatu à aghjunghje à l'indici, ma ùn sapia nunda di a nostra classe è vulia mette un JAR cù questa classa in questu. Avemu fattu cusì, tuttu hà travagliatu, ma un novu prublema hè apparsu: cumu aghjurnà u JAR senza fermà completamente u cluster? Hazelcast ùn piglia micca un novu JAR nantu à una aghjurnazione per node. À questu puntu, avemu decisu chì pudemu campà senza ricerca d'indici. Dopu tuttu, se aduprate Hazelcast cum'è una tenda di valore chjave, allora tuttu funziona? Micca essatamente. Quì dinò un cumpurtamentu diversu di IMap è TransactionalMap. Induve IMap ùn importa micca, TransactionalMap tira un errore.

IMap. Scrivemu 5000 ogetti, leghjemu. Tuttu hè aspittatu.

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

Ma ùn funziona micca in una transazzione, avemu una 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();
        }
    });
}

In 3.8, apparsu u mecanismu di implementazione di classi d'utilizatori. Pudete designà un nodu maestru è aghjurnà u schedariu JAR nantu à questu.

Avà avemu cambiatu cumplettamente u nostru approcciu: noi stessi serializemu in JSON è salvemu in Hazelcast. Hazelcast ùn hà micca bisognu di cunnosce a struttura di e nostre classi, è pudemu aghjurnà senza downtime. A versione di l'uggetti di u duminiu hè cuntrullata da l'applicazione. Diversi versioni di l'applicazione ponu esse lanciate à u stessu tempu, è hè pussibule chì una nova applicazione scrive oggetti cù novi campi, mentre chì u vechju ùn cunnosci micca di sti campi. È à u stessu tempu, a nova applicazione leghje l'uggetti scritti da a vechja applicazione chì ùn anu micca novi campi. Trattemu tali situazioni in l'applicazione, ma per simplicità ùn cambiamu micca o sguassate i campi, estendemu solu e classi aghjunghjendu novi campi.

Cumu furnisce un altu rendiment

Quattru viaghji à Hazelcast hè bonu, dui viaghji à a basa di dati hè male

Andà per i dati in u cache hè sempre megliu cà in a basa di dati, ma ùn vulete micca almacenà i registri micca reclamati. A decisione di ciò chì cache hè lasciatu à l'ultima tappa di sviluppu. Quandu a nova funziunalità hè codificata, permettemu u logu di tutte e dumande in PostgreSQL (log_min_duration_statement à 0) è eseguite a prova di carica per i minuti 20. Utilità cum'è pgFouine è pgBadger ponu custruisce rapporti analitici basati nantu à i logs cullati. In i rapporti, circhemu principalmente richieste lenti è frequenti. Per e dumande lente, custruemu un pianu di esecuzione (SPPLAIN) è valutemu se una tale dumanda pò esse accelerata. E dumande frequenti per a listessa input entranu bè in a cache. Pruvemu di mantene e dumande "piatte", una tavola per dumanda.

Operazione

CB cum'è serviziu in linea hè stata lanciata in a primavera di 2017, cum'è un pruduttu CB separatu hè statu liberatu in nuvembre 2017 (à quellu tempu in versione beta).

Per più di un annu di funziunamentu, ùn ci sò micca prublemi serii in u funziunamentu di u serviziu in linea CB. Monitoremu u serviziu in linea attraversu Zabbix, raccoglie è implementà da Bambù.

A distribuzione di u servitore CB vene in forma di pacchetti nativi: RPM, DEB, MSI. In più, per Windows, furnisce un installatore unicu in a forma di un unicu EXE chì stalla u servitore, Hazelcast è Elasticsearch in una macchina. Prima avemu chjamatu sta versione di a stallazione "demo", ma avà hè diventatu chjaru chì questu hè l'opzione di implementazione più populari.

Source: www.habr.com

Add a comment