1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq

Bu yazıda necə və niyə inkişaf etdiyimizdən danışacağıq Qarşılıqlı əlaqə sistemi - müştəri proqramları və 1C: Müəssisə serverləri arasında məlumat ötürən mexanizm - tapşırıq təyin etməkdən arxitektura və icra detalları vasitəsilə düşünməyə qədər.

Qarşılıqlı Fəaliyyət Sistemi (bundan sonra - CB) zəmanətli çatdırılma ilə paylanmış nasazlığa davamlı mesajlaşma sistemidir. CB həm onlayn xidmət (1C tərəfindən təmin edilir) kimi, həm də öz server obyektlərində yerləşdirilə bilən istehsal məhsulu kimi mövcud olan yüksək miqyaslılığa malik yüksək yüklü xidmət kimi nəzərdə tutulmuşdur.

SW paylanmış yaddaşdan istifadə edir fındıq və axtarış motoru Elasticsearch. Java və PostgreSQL-i üfüqi olaraq necə miqyaslandırdığımızdan da danışacağıq.
1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq

Problem problemi

Qarşılıqlı Əlaqə Sistemini niyə yaratdığımızı aydınlaşdırmaq üçün sizə 1C-də biznes proqramlarının inkişafının necə işlədiyi barədə bir az məlumat verəcəyəm.

Birincisi, hələ nə etdiyimizi bilməyənlər üçün bizim haqqımızda bir az :) Biz 1C:Enterprise texnologiya platformasını inkişaf etdiririk. Platforma biznes proqramlarının inkişaf etdirilməsi alətini, eləcə də biznes proqramlarının platformalararası mühitdə işləməsinə imkan verən icra müddətini ehtiva edir.

Müştəri-server inkişaf paradiqması

1C:Enterprise-də yaradılmış biznes proqramları üç səviyyədə fəaliyyət göstərir müştəri-server arxitektura "DBMS - proqram serveri - müştəri". Tətbiq kodu yazılmışdır daxili dil 1C, proqram serverində və ya müştəridə işləyə bilər. Tətbiq obyektləri ilə (kataloqlar, sənədlər və s.), həmçinin verilənlər bazasının oxunması və yazılması ilə bütün işlər yalnız serverdə yerinə yetirilir. Formalar və komanda interfeysi funksionallığı da serverdə həyata keçirilir. Müştəridə formalar qəbul edilir, açılır və nümayiş etdirilir, istifadəçi ilə "ünsiyyət" (xəbərdarlıqlar, suallar ...), sürətli cavab tələb edən formalarda kiçik hesablamalar (məsələn, qiyməti kəmiyyətə vurmaq), yerli fayllar, avadanlıqla işləmək.

Tətbiq kodunda prosedurların və funksiyaların başlıqları kodun harada icra ediləcəyini açıq şəkildə göstərməlidir - &AtClient / &AtServer (&AtClient / &AtServer dilin ingilis versiyasında) direktivlərindən istifadə etməklə. 1C tərtibatçıları indi direktivlərin əslində olduğunu söyləyərək məni düzəldəcəklər daha çox, amma bizim üçün indi vacib deyil.

Siz müştəri kodundan server koduna zəng edə bilərsiniz, lakin server kodundan müştəri koduna zəng edə bilməzsiniz. Bu, bir sıra səbəblərə görə bizim tərəfimizdən qoyulmuş əsas məhdudiyyətdir. Xüsusilə, ona görə ki, server kodu elə yazılmalıdır ki, haradan çağırılmasından asılı olmayaraq, eyni şəkildə yerinə yetirilsin - müştəridən və ya serverdən. Başqa bir server kodundan server koduna zəng edildiyi təqdirdə, belə bir müştəri yoxdur. Və ona görə ki, server kodunun icrası zamanı onu çağıran müştəri bağlana, proqramdan çıxa bilər və serverdə zəng edəcək heç kim olmayacaq.

1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq
Düyməni klikləməyi idarə edən kod: müştəridən server prosedurunu çağırmaq işləyəcək, serverdən müştəri prosedurunu çağırmaq işləməyəcək.

Bu o deməkdir ki, əgər biz serverdən müştəri proqramına bir mesaj göndərmək istəsək, məsələn, “uzunmüddətli” hesabatın formalaşması başa çatdı və hesabata baxıla bilərsə, bizim belə bir yolumuz yoxdur. Siz fəndlərdən istifadə etməlisiniz, məsələn, vaxtaşırı serveri müştəri kodundan sorğulayın. Ancaq bu yanaşma sistemi lazımsız zənglərlə yükləyir və ümumiyyətlə, çox zərif görünmür.

Həm də ehtiyac var, məsələn, telefon olduqda SIP-zəng edin, bu barədə müştəri tətbiqini xəbərdar edin ki, onu zəng edənin nömrəsi ilə qarşı tərəfin məlumat bazasında tapa bilsin və zəng edən qarşı tərəf haqqında istifadəçi məlumatını göstərsin. Və ya, məsələn, anbara sifariş gəldikdə, bu barədə müştərinin müştəri ərizəsinə məlumat verin. Ümumiyyətlə, belə bir mexanizmin faydalı olacağı bir çox hallar var.

Əslində təyinat

Mesajlaşma mexanizmi yaradın. Sürətli, etibarlı, zəmanətli çatdırılma ilə, mesajların çevik axtarışı imkanı ilə. Mexanizmə əsaslanaraq, 1C proqramları daxilində işləyən messencer (mesajlar, video zənglər) həyata keçirin.

Sistemi üfüqi olaraq genişlənə bilən dizayn edin. Artan yük qovşaqların sayının artması ilə örtülməlidir.

Tətbiq

Biz CB-nin server hissəsini birbaşa 1C: Enterprise platformasına yerləşdirməyə deyil, onu ayrıca bir məhsul kimi həyata keçirməyə qərar verdik, onun API-si 1C tətbiqi həllərinin kodundan çağırıla bilər. Bu, bir sıra səbəblərə görə edildi, bunlardan başlıcası müxtəlif 1C tətbiqləri arasında (məsələn, Ticarət və Mühasibat Departamenti arasında) mesaj mübadiləsini mümkün etmək idi. Müxtəlif 1C proqramları 1C:Enterprise platformasının müxtəlif versiyalarında işləyə bilər, müxtəlif serverlərdə yerləşə bilər və s. Belə şəraitdə CB-nin 1C qurğularının "yan tərəfində" yerləşən ayrıca məhsul kimi həyata keçirilməsi optimal həlldir.

Beləliklə, biz CB-ni ayrıca məhsul kimi etmək qərarına gəldik. Kiçik şirkətlər üçün yerli serverin quraşdırılması və konfiqurasiyası ilə bağlı əlavə xərclərin qarşısını almaq üçün buludumuzda (wss://1cdialog.com) quraşdırdığımız CB serverindən istifadə etməyi tövsiyə edirik. Böyük müştərilər isə öz obyektlərində öz CB serverlərini quraşdırmağı məqsədəuyğun hesab edə bilərlər. Bulud SaaS məhsulumuzda oxşar yanaşmadan istifadə etdik. 1cTəzə – o, müştərilər tərəfindən quraşdırmaq üçün istehsal məhsulu kimi buraxılır və həmçinin buludumuzda yerləşdirilir https://1cfresh.com/.

App

Yük bölgüsü və nasazlığa dözümlülük üçün biz bir Java tətbiqini deyil, bir neçəsini yerləşdirəcəyik, onların qarşısına yük balanslaşdırıcısı qoyacağıq. Əgər qovşaqdan node-a mesaj göndərmək lazımdırsa, Hazelcast-da dərc et/abunə et.

Müştəri və server arasında əlaqə - websocket vasitəsilə. O, real vaxt sistemləri üçün çox uyğundur.

Paylanmış keş

Redis, Hazelcast və Ehcache arasında seçim edin. 2015-ci ildə xaricdə. Redis yeni bir çoxluq buraxdı (çox yeni, qorxulu), bir çox məhdudiyyəti olan bir Sentinel var. Ehcache necə klaster etməyi bilmir (bu funksionallıq sonradan ortaya çıxdı). Hazelcast 3.4 ilə sınamağa qərar verdik.
Hazelcast qutudan çıxarılıb. Tək node rejimində o, çox faydalı deyil və yalnız bir önbellek kimi uyğunlaşa bilər - o, diskə məlumatların necə atılacağını bilmir, əgər yeganə node itərsə, məlumatlar itirilir. Biz kritik məlumatların ehtiyat nüsxəsini çıxardığımız bir neçə Hazelcast tətbiq edirik. Biz önbelleğin ehtiyat nüsxəsini çıxarmırıq - ona təəssüflənmirik.

Bizim üçün Hazelcast:

  • İstifadəçi sessiyalarının saxlanması. Seans üçün verilənlər bazasına getmək çox vaxt tələb edir, ona görə də bütün seansları Hazelcast-a qoyuruq.
  • Gizli yer. İstifadəçi profili axtarırsınız - önbelleği yoxlayın. Yeni bir mesaj yazdı - onu önbelleğe qoyun.
  • Tətbiq nümunələrinin əlaqəsi üçün mövzular. Düyün bir hadisə yaradır və onu Hazelcast mövzusuna yerləşdirir. Bu mövzuya abunə olan digər proqram qovşaqları hadisəni qəbul edir və emal edir.
  • klaster kilidləri. Məsələn, biz unikal açarla müzakirə yaradırıq (1C bazası çərçivəsində müzakirə-singleton):

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

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

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

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

Yoxladıq ki, kanal yoxdur. Kilidi götürdülər, yenidən yoxladılar, yaratdılar. Kilidi götürdükdən sonra yoxlamasanız, o zaman başqa bir mövzunun da yoxlanılması şansı var və indi eyni müzakirə yaratmağa çalışacaq - və o, artıq mövcuddur. Sinxronlaşdırılmış və ya adi java Lock vasitəsilə kilid etmək mümkün deyil. Baza vasitəsilə - yavaş-yavaş və baza təəssüf ki, Hazelcast vasitəsilə - sizə lazım olan şey.

DBMS-nin seçilməsi

PostgreSQL ilə geniş və uğurlu təcrübəmiz və bu DBMS-nin tərtibatçıları ilə əməkdaşlığa malikik.

Klaster ilə PostgreSQL asan deyil - var XL, XC, Situs, lakin, ümumiyyətlə, qutudan kənara çıxan noSQL deyil. NoSQL əsas yaddaş hesab edilmirdi, əvvəllər işləmədiyimiz Hazelcast-ı götürməyimiz kifayət idi.

Relational verilənlər bazası miqyasına ehtiyacınız olduğundan, bu deməkdir parçalanma. Bildiyiniz kimi, sharding zamanı verilənlər bazasını ayrı-ayrı hissələrə bölürük ki, onların hər biri ayrıca serverə yerləşdirilsin.

Shardingimizin ilk versiyası tətbiqimizin hər bir cədvəlini müxtəlif nisbətlərdə fərqli serverlərə yaymaq qabiliyyətini qəbul etdi. A serverində çoxlu mesajlar - lütfən, bu cədvəlin bir hissəsini server B-yə köçürək. Bu qərar sadəcə vaxtından əvvəl optimallaşdırma ilə bağlı qışqırdı, ona görə də biz özümüzü çoxlu kirayəçi yanaşması ilə məhdudlaşdırmaq qərarına gəldik.

Çox kirayəçi haqqında, məsələn, veb saytında oxuya bilərsiniz Citus Data.

SV-də proqram və abunəçi anlayışları var. Tətbiq istifadəçiləri və biznes məlumatları ilə ERP və ya Mühasibat kimi bir iş tətbiqinin xüsusi quraşdırılmasıdır. Abunəçi proqramın CB serverində qeydiyyatdan keçdiyi təşkilat və ya fiziki şəxsdir. Abunəçinin qeydiyyatdan keçmiş bir neçə proqramı ola bilər və bu proqramlar bir-biri ilə mesaj mübadiləsi edə bilər. Abunəçi sistemimizdə kirayəçi oldu. Bir fiziki bazada bir neçə abunəçinin mesajları yerləşdirilə bilər; əgər hansısa abunəçinin çoxlu trafik yaratmağa başladığını görsək, onu ayrıca fiziki verilənlər bazasına (və ya hətta ayrıca verilənlər bazası serverinə) köçürürük.

Bizim əsas verilənlər bazamız var ki, burada marşrutlaşdırma cədvəli bütün abunəçi verilənlər bazalarının yeri haqqında məlumatla saxlanılır.

1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq

Əsas verilənlər bazasının darboğaz olmasının qarşısını almaq üçün biz marşrutlaşdırma cədvəlini (və tez-tez tələb olunan digər məlumatları) keş yaddaşında saxlayırıq.

Abunəçinin məlumat bazası yavaşlamağa başlasa, biz onu içəridə bölmələrə kəsəcəyik. Digər layihələrdə böyük cədvəlləri bölmək üçün istifadə edirik pg_pathman.

İstifadəçi mesajlarını itirmək pis olduğundan, biz verilənlər bazalarımızı replikalarla ehtiyat nüsxəsini çıxarırıq. Sinxron və asinxron replikaların birləşməsi əsas verilənlər bazasının itirilməsindən sığortalanmağa imkan verir. Mesaj itkisi yalnız əsas verilənlər bazası və onun sinxron replikasının eyni vaxtda sıradan çıxması halında baş verəcək.

Sinxron replika itirilərsə, asinxron replika sinxron olur.
Əsas verilənlər bazası itirilərsə, sinxron replika əsas verilənlər bazasına, asinxron replika sinxron replikaya çevrilir.

Axtarış üçün Elasticsearch

Digər şeylərlə yanaşı, CB də bir messencer olduğundan, burada morfologiyanı nəzərə alaraq, qeyri-dəqiq uyğunluqlarla sürətli, rahat və çevik axtarış lazımdır. Biz təkəri yenidən kəşf etməmək və kitabxana əsasında yaradılmış pulsuz Elasticsearch axtarış sistemindən istifadə etmək qərarına gəldik. Lucene. Tətbiq qovşaqlarının nasazlığı halında problemləri aradan qaldırmaq üçün biz həmçinin Elasticsearch-i klasterdə (master - verilənlər - verilənlər) yerləşdiririk.

Github-da tapdıq Rus morfologiya plugin Elasticsearch üçün və ondan istifadə edin. Elasticsearch indeksində biz söz köklərini (pluginin müəyyən etdiyi) və N-qramları saxlayırıq. İstifadəçi axtarış etmək üçün mətn daxil etdikcə biz N-qramlar arasında yığılmış mətni axtarırıq. İndeksdə saxlandıqda "mətnlər" sözü aşağıdakı N-qramlara bölünəcək:

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

Həm də "mətn" sözünün kökü saxlanacaq. Bu yanaşma sözün əvvəlində, ortasında və sonunda axtarış aparmağa imkan verir.

Böyük mənzərə

1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq
Məqalənin əvvəlindən şəkli təkrarlamaq, lakin izahatlarla:

  • İnternetə məruz qalan balanslaşdırıcı; bizdə nginx var, istənilən ola bilər.
  • Java proqram nümunələri Hazelcast vasitəsilə bir-biri ilə əlaqə qurur.
  • Veb yuvası ilə işləmək üçün istifadə edirik Netty.
  • Java 8-də yazılmış Java proqramı paketlərdən ibarətdir OSGi. Planlar Java 10-a keçmək və modullara keçməkdir.

İnkişaf və sınaq

CB-nin hazırlanması və sınaqdan keçirilməsi zamanı istifadə etdiyimiz məhsulların bir sıra maraqlı xüsusiyyətləri ilə qarşılaşdıq.

Yük testi və yaddaş sızması

Hər bir CB buraxılışının buraxılması bir yük testidir. O zaman uğurla keçdi:

  • Test bir neçə gün işlədi və xidmətdən imtina edilmədi
  • Əsas əməliyyatlar üçün cavab müddəti rahat həddi keçmədi
  • Əvvəlki versiya ilə müqayisədə performansın azalması 10% -dən çox deyil

Test bazasını məlumatlarla doldururuq - bunun üçün istehsal serverindən ən aktiv abunəçi haqqında məlumat alırıq, onun nömrələrini 5-ə (mesajların, müzakirələrin, istifadəçilərin sayı) vururuq və buna görə də test edirik.

Qarşılıqlı təsir sisteminin yük testini üç konfiqurasiyada həyata keçiririk:

  1. stress testi
  2. Yalnız bağlantılar
  3. Abunəçi qeydiyyatı

Stress testi zamanı biz bir neçə yüz mövzu işə salırıq və onlar sistemi dayanmadan yükləyir: mesajlar yazın, müzakirələr yaradın, mesajların siyahısını qəbul edin. Biz adi istifadəçilərin hərəkətlərini (oxunmamış mesajlarımın siyahısını əldə edin, kiməsə yazın) və proqram qərarlarını (paketi başqa konfiqurasiyaya köçürmək, xəbərdarlıq emal etmək) təqlid edirik.

Məsələn, stress testinin bir hissəsi belə görünür:

  • İstifadəçi daxil olur
    • Oxunmamış mövzularınızı tələb edir
    • Mesajları oxumaq şansı 50%
    • Mesaj yazma şansı 50%
    • Növbəti istifadəçi:
      • Yeni mövzu yaratmaq üçün 20% şans
      • Müzakirələrindən hər hansı birini təsadüfi seçir
      • İçəri girir
      • Mesajları, istifadəçi profillərini tələb edir
      • Bu mövzudan təsadüfi istifadəçilərə ünvanlanmış beş mesaj yaradır
      • Müzakirədən kənar
      • 20 dəfə təkrarlanır
      • Çıxır, skriptin əvvəlinə qayıdır

    • Çatbot sistemə daxil olur (tətbiq olunan həllərin kodundan mesajlaşmanı təqlid edir)
      • Yeni məlumat kanalı yaratmaq üçün 50% şans (xüsusi müzakirə)
      • Mövcud kanallardan hər hansı birində mesaj yazmaq üçün 50% şans

“Yalnız Bağlantılar” ssenarisi bir səbəbdən ortaya çıxdı. Bir vəziyyət var: istifadəçilər sistemi birləşdirdilər, lakin hələ də qoşulmayıblar. Hər bir istifadəçi səhər saat 09:00-da kompüteri açır, serverlə əlaqə qurur və susur. Bu uşaqlar təhlükəlidir, onların çoxu var - paketlərdən yalnız PING / PONG var, lakin serverlə əlaqəni saxlayırlar (onu saxlaya bilmirlər - və birdən yeni mesaj). Test, çox sayda belə istifadəçinin yarım saat ərzində sistemə daxil olmağa çalışdığı vəziyyəti təkrarlayır. Bu, stress testinə bənzəyir, lakin onun diqqəti məhz bu ilk girişə yönəlib - belə ki, heç bir uğursuzluq olmasın (insan sistemdən istifadə etmir, lakin artıq yıxılır - daha pis bir şey tapmaq çətindir).

Abunəçinin qeydiyyatı ssenarisi ilk buraxılışdan başlayır. Stress testi keçirdik və sistemin yazışmalarda yavaşlamadığına əmin olduq. Lakin istifadəçilər getdi və qeydiyyat taymout ilə düşməyə başladı. Qeydiyyatdan keçəndə istifadə etdik / dev / təsadüfi, sistemin entropiyasına bağlıdır. Serverin kifayət qədər entropiya toplamaq üçün vaxtı yox idi və yeni SecureRandom tələb olunduqda o, onlarla saniyə ərzində dondu. Bu vəziyyətdən bir çox çıxış yolu var, məsələn: daha az təhlükəsiz /dev/urandom-a keçin, entropiya yaradan xüsusi lövhə quraşdırın, əvvəlcədən təsadüfi ədədlər yaradın və onları hovuzda saxlayın. Hovuzla bağlı problemi müvəqqəti bağladıq, lakin o vaxtdan bəri yeni abunəçilərin qeydiyyatı üçün ayrıca sınaq keçirdik.

Yük generatoru olaraq istifadə edirik JMeter. Websocket ilə necə işləməyi bilmir, plagin lazımdır. "jmeter websocket" sorğusu üçün axtarış nəticələrində birincidir BlazeMeter ilə məqalələrhansında tövsiyə edirlər Maciej Zaleski tərəfindən plagin.

Elə buradan başlamağa qərar verdik.

Demək olar ki, ciddi sınaqların başlamasından dərhal sonra biz JMeter-də yaddaş sızmasının başladığını aşkar etdik.

Plugin ayrıca böyük bir hekayədir, 176 ulduzla github-da 132 çəngəl var. Müəllifin özü 2015-ci ildən bəri buna əməl etməyib (biz bunu 2015-ci ildə almışıq, sonra şübhə doğurmadı), yaddaş sızması ilə bağlı bir neçə github problemi, 7 açıqlanmayan çəkmə sorğusu.
Testi bu plaginlə yükləməyi seçsəniz, aşağıdakı müzakirələrə diqqət yetirin:

  1. Çox yivli mühitdə adi LinkedList istifadə edildi, nəticədə əldə etdik NPE icra zamanı. Bu ya ConcurrentLinkedDeque-ə keçidlə, ya da sinxronlaşdırılmış bloklarla həll olunur. Biz özümüz üçün birinci variantı seçdikhttps://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Yaddaş sızması, əlaqəni kəsərkən əlaqə məlumatı silinmir (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Axın rejimində (veb-rozet nümunənin sonunda bağlanmadıqda, lakin planda daha çox istifadə edildikdə), Cavab nümunələri işləmir (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Bu github-da olanlardan biridir. Nə etdik:

  1. Aldılar Elyran Kogan çəngəl (@elyrank) - 1 və 3-cü məsələləri həll edir
  2. Həll olunmuş problem 2
  3. 9.2.14-dən 9.3.12-ə qədər iskele yeniləndi
  4. ThreadLocal-a bükülmüş SimpleDateFormat; SimpleDateFormat iş vaxtı NPE-yə aparan təhlükəsiz deyil
  5. Başqa bir yaddaş sızması düzəldildi (bağlantı kəsildikdə səhv bağlandı)

Və yenə də axır!

Yaddaş bir gündə yox, iki gündə bitməyə başladı. Heç vaxt yox idi, biz daha az mövzu işlətməyə qərar verdik, ancaq dörd agent üzərində. Bu, ən azı bir həftə kifayət etməli idi.

İki gün keçdi...

İndi Hazelcast yaddaşı tükənir. Qeydlər göstərdi ki, bir neçə günlük sınaqdan sonra Hazelcast yaddaş çatışmazlığından şikayətlənməyə başlayır və bir müddət sonra klaster dağılır və qovşaqlar bir-bir ölməyə davam edir. Biz JVisualVM-ni hazelcast-a bağladıq və “yuxarıya doğru mişarı” gördük - o, müntəzəm olaraq GC çağırırdı, lakin yaddaşı heç bir şəkildə təmizləyə bilmədi.

1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq

Məlum oldu ki, hazelcast 3.4-də xəritə/multiMap (map.destroy()) silinərkən yaddaş tam boşalmır:

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

Səhv indi 3.5-də düzəldildi, lakin o vaxt problem idi. Biz dinamik adlarla yeni multiMap yaratdıq və məntiqimizə uyğun olaraq sildik. Kod belə bir şeyə baxdı:

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

Zəng edin:

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

multiMap hər bir abunə üçün yaradılmış və lazım olmadıqda silinmişdir. Qərara gəldik ki, Xəritəyə başlayaq , açar abunəliyin adı olacaq və dəyərlər sessiya identifikatorları olacaq (bununla lazım olduqda istifadəçi identifikatorlarını əldə edə bilərsiniz).

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

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

Qrafiklər yaxşılaşıb.

1C: Müəssisə: Java, PostgreSQL, Hazelcast üçün yüksək yüklənmiş genişlənə bilən xidməti necə və niyə yazdıq

Yük testi haqqında başqa nə öyrəndik

  1. JSR223 groovy yazılmalı və kompilyasiya keşini daxil etməlidir - bu, daha sürətlidir. Əlaqə.
  2. Jmeter-Plugins diaqramlarını başa düşmək standartlardan daha asandır. Əlaqə.

Hazelcast ilə təcrübəmiz haqqında

Hazelcast bizim üçün yeni məhsul idi, biz onunla 3.4.1 versiyasından işləməyə başladıq, indi istehsal serverimizdə 3.9.2 versiyası var (bu yazı hazırlanarkən Hazelcast-ın ən son versiyası 3.10-dur).

ID nəsli

Tam ədəd identifikatorları ilə başladıq. Təsəvvür edək ki, yeni bir varlıq üçün bizə başqa bir Long lazımdır. Verilənlər bazasında ardıcıllıq uyğun deyil, cədvəllər parçalanmada iştirak edir - belə çıxır ki, DB1-də ID=1 mesajı və DB1-də ID=2 mesajı var, siz bu ID-ni Elasticsearch-ə, Hazelcast-a da qoya bilməzsiniz, lakin ən pisi, məlumatları iki verilənlər bazasından birinə azaltmaq istəyirsinizsə (məsələn, bu abunəçilər üçün bir verilənlər bazası üçün kifayət olduğuna qərar vermək). Siz Hazelcast-da bir neçə AtomicLong-a sahib ola bilərsiniz və sayğacı orada saxlaya bilərsiniz, onda yeni ID əldə etməyin performansı incrementAndGet və Hazelcast-da sorğu üçün vaxtdır. Ancaq Hazelcast-ın daha optimal bir şeyi var - FlakeIdGenerator. Əlaqə qurarkən hər bir müştəriyə bir sıra şəxsiyyət vəsiqələri verilir, məsələn, birincisi - 1-dən 10-dək, ikincisi - 000-dən 10-dək və s. İndi müştəri ona verilən diapazon bitənə qədər özbaşına yeni identifikatorlar verə bilər. Sürətlə işləyir, lakin proqramın (və Hazelcast müştərisinin) yenidən işə salınması yeni ardıcıllığa başlayır - buna görə də atlamalar və s. Bundan əlavə, tərtibatçılar üçün identifikatorların niyə tam ədədlər olduğu çox aydın deyil, lakin onlar çox ziddiyyət təşkil edirlər. Hər şeyi ölçdük və UUID-lərə keçdik.

Yeri gəlmişkən, Twitter kimi olmaq istəyənlər üçün belə bir Snowcast kitabxanası var - bu, Hazelcast-ın üstündəki Snowflake-in tətbiqidir. Burada görə bilərsiniz:

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

Amma hələ ki, buna nail olmamışıq.

TransactionalMap.replace

Başqa bir sürpriz: TransactionalMap.replace işləmir. Budur bir 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

GetForUpdate istifadə edərək öz əvəzimi yazmalı oldum:

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

Yalnız adi məlumat strukturlarını yox, həm də onların əməliyyat versiyalarını sınaqdan keçirin. Belə olur ki, IMap işləyir, lakin TransactionalMap artıq mövcud deyil.

Yeni JAR-ı fasiləsiz əlavə edin

Əvvəlcə siniflərimizin obyektlərini Hazelcast-a yazmağa qərar verdik. Məsələn, bizim Tətbiq sinifimiz var, onu saxlamaq və oxumaq istəyirik. Yadda saxla:

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

Oxuyuruq:

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

Hər şey işləyir. Sonra onu axtarmaq üçün Hazelcast-da indeks yaratmağa qərar verdik:

map.addIndex("subscriberId", false);

Və yeni bir varlıq yazarkən, onlar ClassNotFoundException almağa başladılar. Hazelcast indeksə əlavə etməyə çalışdı, lakin bizim sinif haqqında heç nə bilmirdi və bu siniflə JAR qoymaq istəyirdi. Biz bunu etdik, hər şey işlədi, amma yeni bir problem ortaya çıxdı: klasteri tamamilə dayandırmadan JAR-ı necə yeniləmək olar? Hazelcast hər node yeniləməsində yeni JAR seçmir. Bu nöqtədə, indeks axtarışları olmadan yaşaya biləcəyimizə qərar verdik. Axı, Hazelcast-ı əsas dəyər mağazası kimi istifadə etsəniz, hər şey işləyəcək? Həqiqətən yox. Burada yenə IMap və TransactionalMap-ın fərqli davranışı. IMap-ın əhəmiyyət vermədiyi yerdə TransactionalMap xəta verir.

IMap. 5000 obyekt yazırıq, oxuyuruq. Hər şey gözlənilir.

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

Lakin bu, əməliyyatda işləmir, biz ClassNotFoundException alırıq:

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

3.8-də User Class Deployment mexanizmi ortaya çıxdı. Siz bir master node təyin edə və onun üzərindəki JAR faylını yeniləyə bilərsiniz.

İndi biz yanaşmamızı tamamilə dəyişmişik: biz özümüz JSON-da seriallaşdırırıq və Hazelcast-da saxlayırıq. Hazelcast-ın dərslərimizin strukturunu bilməsi lazım deyil və biz fasiləsiz yeniləyə bilərik. Domen obyektlərinin versiyaları proqram tərəfindən idarə olunur. Tətbiqin müxtəlif versiyaları eyni vaxtda işə salına bilər və ola bilsin ki, yeni proqram obyektləri yeni sahələrlə yazır, köhnəsi isə hələ bu sahələr haqqında bilmir. Və eyni zamanda, yeni proqram köhnə tətbiqin yazdığı, yeni sahələri olmayan obyektləri oxuyur. Biz proqram daxilində belə hallarla məşğul oluruq, lakin sadəlik üçün biz sahələri dəyişdirmirik və ya silmirik, yalnız yeni sahələr əlavə etməklə sinifləri genişləndiririk.

Yüksək performansı necə təqdim edirik

Hazelcast-a dörd səfər yaxşıdır, verilənlər bazasına iki səfər pisdir

Keşdə məlumat axtarmaq həmişə verilənlər bazasından daha yaxşıdır, lakin siz tələb olunmamış qeydləri də saxlamaq istəmirsiniz. Nəyin önbelleğe alınmasına qərar vermək inkişafın son mərhələsinə qalır. Yeni funksionallıq kodlaşdırıldıqda, biz PostgreSQL-də bütün sorğuların qeydinə icazə veririk (log_min_duration_statement - 0) və 20 dəqiqə ərzində yükləmə testini həyata keçiririk. pgFouine və pgBadger kimi utilitlər toplanmış qeydlər əsasında analitik hesabatlar yarada bilər. Hesabatlarda biz ilk növbədə yavaş və tez-tez sorğular axtarırıq. Yavaş sorğular üçün biz icra planı qururuq (İZAHI) və belə bir sorğunun sürətləndirilə biləcəyini qiymətləndiririk. Eyni daxiletmə üçün tez-tez edilən sorğular önbelleğe yaxşı uyğun gəlir. Biz sorğuları "düz" saxlamağa çalışırıq, hər sorğu üçün bir cədvəl.

Istismar

Onlayn xidmət kimi CB 2017-ci ilin yazında istifadəyə verildi, çünki ayrıca CB məhsulu 2017-ci ilin noyabrında buraxıldı (o vaxt beta statusunda idi).

Bir ildən artıq fəaliyyət göstərdiyi müddətdə CB onlayn xidmətinin fəaliyyətində heç bir ciddi problem yaranmayıb. vasitəsilə onlayn xidmətə nəzarət edirik Zabbix, toplamaq və yerləşdirmək Bambuq.

CB server paylanması yerli paketlər şəklində gəlir: RPM, DEB, MSI. Üstəlik, Windows üçün biz bir maşında server, Hazelcast və Elasticsearch quraşdıran tək EXE şəklində tək quraşdırıcı təqdim edirik. Əvvəlcə quraşdırmanın bu versiyasını "demo" adlandırdıq, lakin indi bunun ən populyar yerləşdirmə variantı olduğu aydın oldu.

Mənbə: www.habr.com

Добавить комментарий