1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik

Ushbu maqolada biz qanday va nima uchun rivojlanganimiz haqida gapiramiz O'zaro ta'sir tizimi - mijoz ilovalari va 1C: Enterprise serverlari o'rtasida ma'lumotlarni uzatish mexanizmi - vazifani belgilashdan arxitektura va amalga oshirish tafsilotlari orqali fikrlashgacha.

O'zaro aloqa tizimi (keyingi o'rinlarda SV deb yuritiladi) kafolatlangan yetkazib berish bilan taqsimlangan, nosozliklarga chidamli xabar almashish tizimidir. SV yuqori miqyosli yuqori yuklangan xizmat sifatida ishlab chiqilgan boʻlib, u ham onlayn xizmat (1C tomonidan taqdim etilgan) sifatida ham, oʻz server qurilmalarida joylashtirilishi mumkin boʻlgan ommaviy ishlab chiqarilgan mahsulot sifatida ham mavjud.

SV taqsimlangan xotiradan foydalanadi Hazelcast va qidiruv tizimi Elasticsearch. Shuningdek, biz Java haqida va PostgreSQL-ni gorizontal ravishda qanday o'lchashimiz haqida gaplashamiz.
1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik

Muammoni shakllantirish

O'zaro ta'sir tizimini nima uchun yaratganimizni tushunish uchun men sizga 1C-da biznes-ilovalarni ishlab chiqish qanday ishlashi haqida bir oz aytib beraman.

Boshlash uchun nima qilishimizni hali bilmaganlar uchun biz haqimizda bir oz :) Biz 1C: Enterprise texnologiya platformasini yaratmoqdamiz. Platformada biznes ilovalarini ishlab chiqish vositasi, shuningdek, biznes ilovalari platformalararo muhitda ishlash imkonini beruvchi ish vaqti mavjud.

Mijoz-serverni rivojlantirish paradigmasi

1C: Enterprise da yaratilgan biznes ilovalari uch darajada ishlaydi mijoz-server arxitektura “DBMS – amaliy server – mijoz”. Ilova kodi yozilgan o'rnatilgan 1C tili, dastur serverida yoki mijozda bajarilishi mumkin. Ilova ob'ektlari (kataloglar, hujjatlar va boshqalar) bilan barcha ishlar, shuningdek, ma'lumotlar bazasini o'qish va yozish faqat serverda amalga oshiriladi. Formalarning funksionalligi va buyruq interfeysi ham serverda amalga oshiriladi. Mijoz shakllarni qabul qilish, ochish va ko'rsatish, foydalanuvchi bilan "muloqot qilish" (ogohlantirishlar, savollar ...), tezkor javob talab qiladigan shakllarda kichik hisob-kitoblarni (masalan, narxni miqdorga ko'paytirish), mahalliy fayllar bilan ishlashni, uskunalar bilan ishlash.

Ilova kodida protseduralar va funktsiyalar sarlavhalari kodning qayerda bajarilishini aniq ko'rsatishi kerak - &AtClient / &AtServer direktivalari (&AtClient / &AtServer tilning inglizcha versiyasida). 1C ishlab chiquvchilari endi direktivalar haqiqatda ekanligini aytib, meni tuzatadilar ko'proq, lekin biz uchun bu hozir muhim emas.

Siz mijoz kodidan server kodiga qo'ng'iroq qilishingiz mumkin, lekin mijoz kodini server kodidan chaqira olmaysiz. Bu biz bir qancha sabablarga ko'ra qilgan asosiy cheklovdir. Xususan, chunki server kodi shunday yozilishi kerakki, u qayerda chaqirilishidan qat'i nazar, bir xil - mijozdan yoki serverdan ishlaydi. Va boshqa server kodidan server kodini chaqirganda, bunday mijoz yo'q. Va chunki server kodini bajarish paytida uni chaqirgan mijoz yopilishi, dasturdan chiqishi va serverda endi qo'ng'iroq qiladigan hech kim qolmaydi.

1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik
Tugmani bosish bilan ishlaydigan kod: mijozdan server protsedurasini chaqirish ishlaydi, serverdan mijoz protsedurasini chaqirish ishlamaydi.

Bu shuni anglatadiki, agar biz serverdan mijoz ilovasiga ba'zi xabarlarni yubormoqchi bo'lsak, masalan, "uzoq muddatli" hisobotni yaratish tugallangani va hisobotni ko'rish mumkin bo'lsa, bizda bunday usul yo'q. Siz hiyla-nayranglardan foydalanishingiz kerak, masalan, vaqti-vaqti bilan serverni mijoz kodidan so'rang. Ammo bu yondashuv tizimni keraksiz qo'ng'iroqlar bilan yuklaydi va umuman olganda juda oqlangan ko'rinmaydi.

Va, masalan, telefon qo'ng'irog'i kelganda ham ehtiyoj bor SIP- qo'ng'iroq qilayotganda, bu haqda mijoz ilovasini xabardor qiling, shunda u qo'ng'iroq qiluvchining raqamidan uni kontragent ma'lumotlar bazasida topishi va foydalanuvchiga qo'ng'iroq qilayotgan kontragent haqidagi ma'lumotlarni ko'rsatishi mumkin. Yoki, masalan, buyurtma omborga kelganida, bu haqda mijozning mijoz arizasiga xabar bering. Umuman olganda, bunday mexanizm foydali bo'ladigan ko'plab holatlar mavjud.

Ishlab chiqarishning o'zi

Xabar almashish mexanizmini yarating. Tez, ishonchli, kafolatlangan yetkazib berish, xabarlarni moslashuvchan qidirish imkoniyati bilan. Mexanizmga asoslanib, 1C ilovalari ichida ishlaydigan messenjerni (xabarlar, video qo'ng'iroqlar) amalga oshiring.

Tizimni gorizontal ravishda kengaytiriladigan qilib loyihalash. Ortib borayotgan yuk tugunlar sonini ko'paytirish orqali qoplanishi kerak.

Реализация

Biz SV ning server qismini to'g'ridan-to'g'ri 1C: Enterprise platformasiga integratsiya qilmaslikka qaror qildik, lekin uni alohida mahsulot sifatida amalga oshirishga qaror qildik, uning API-ni 1C dastur yechimlari kodidan chaqirish mumkin. Bu bir qancha sabablarga ko'ra amalga oshirildi, ulardan asosiysi men turli xil 1C ilovalari o'rtasida (masalan, Savdoni boshqarish va Buxgalteriya hisobi o'rtasida) xabar almashish imkonini yaratmoqchi edim. Turli xil 1C ilovalari 1C: Enterprise platformasining turli versiyalarida ishlashi mumkin, turli serverlarda joylashgan bo'lishi mumkin va hokazo. Bunday sharoitda SV ni 1C qurilmalarining "yon tomonida" joylashgan alohida mahsulot sifatida amalga oshirish eng maqbul echimdir.

Shunday qilib, biz SVni alohida mahsulot sifatida ishlab chiqarishga qaror qildik. Mahalliy o'rnatish va serverni sozlash bilan bog'liq qo'shimcha xarajatlarni oldini olish uchun kichik kompaniyalarga bulutimizga (wss://1cdialog.com) o'rnatgan CB serveridan foydalanishni tavsiya qilamiz. Katta mijozlar o'zlarining CB serverlarini o'z muassasalarida o'rnatishni tavsiya etishlari mumkin. Biz bulutli SaaS mahsulotimizda shunga o'xshash yondashuvdan foydalandik 1cFresh - u mijozlar saytlarida o'rnatish uchun ommaviy ishlab chiqarilgan mahsulot sifatida ishlab chiqariladi va bizning bulutimizda ham joylashtiriladi https://1cfresh.com/.

ariza

Yuk va nosozlikka chidamlilikni taqsimlash uchun biz bitta Java ilovasini emas, balki bir nechtasini, ularning oldida yuk balanslagichini joylashtiramiz. Agar siz xabarni tugundan tugunga o'tkazishingiz kerak bo'lsa, Hazelcast-da nashr etish/obuna bo'lish funksiyasidan foydalaning.

Mijoz va server o'rtasidagi aloqa websocket orqali amalga oshiriladi. Bu real vaqt tizimlari uchun juda mos keladi.

Tarqalgan kesh

Biz Redis, Hazelcast va Ehcache o'rtasida tanlov qildik. 2015 yil. Redis yangi klasterni chiqardi (juda yangi, qo'rqinchli), juda ko'p cheklovlarga ega Sentinel mavjud. Ehcache klasterga qanday yig'ishni bilmaydi (bu funksiya keyinroq paydo bo'lgan). Biz buni Hazelcast 3.4 bilan sinab ko'rishga qaror qildik.
Hazelcast qutidan klasterga yig'iladi. Yagona tugun rejimida u juda foydali emas va faqat kesh sifatida ishlatilishi mumkin - u diskka ma'lumotlarni qanday tashlashni bilmaydi, agar siz yagona tugunni yo'qotsangiz, ma'lumotlarni yo'qotasiz. Biz bir nechta Hazelcast-larni joylashtiramiz, ular orasida muhim ma'lumotlarni zaxiralaymiz. Biz keshni zaxira qilmaymiz - bunga qarshi emasmiz.

Biz uchun Hazelcast:

  • Foydalanuvchi seanslarini saqlash. Har safar sessiya uchun ma'lumotlar bazasiga borish uchun ko'p vaqt ketadi, shuning uchun biz barcha seanslarni Hazelcast-ga joylashtirdik.
  • Kesh. Agar siz foydalanuvchi profilini qidirsangiz, keshni tekshiring. Yangi xabar yozdim - uni keshga qo'ying.
  • Ilova misollari o'rtasidagi aloqa uchun mavzular. Tugun hodisa hosil qiladi va uni Hazelcast mavzusiga joylashtiradi. Ushbu mavzuga obuna bo'lgan boshqa dastur tugunlari hodisani qabul qiladi va qayta ishlaydi.
  • Klaster qulflari. Masalan, biz noyob kalit yordamida munozara yaratamiz (1C ma'lumotlar bazasida yagona munozara):

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

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

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

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

Biz kanal yo'qligini tekshirdik. Biz qulfni oldik, uni yana tekshirdik va yaratdik. Agar siz qulfni olganingizdan so'ng qulfni tekshirmasangiz, unda o'sha paytda boshqa mavzu ham tekshirilishi va endi xuddi shu muhokamani yaratishga harakat qilishi mumkin - lekin u allaqachon mavjud. Sinxronlashtirilgan yoki oddiy java Lock yordamida qulflay olmaysiz. Ma'lumotlar bazasi orqali - bu sekin va ma'lumotlar bazasi uchun achinarli; Hazelcast orqali - bu sizga kerak bo'lgan narsa.

DBMSni tanlash

Bizda PostgreSQL bilan ishlash va ushbu DBMS ishlab chiquvchilari bilan hamkorlik qilish bo‘yicha katta va muvaffaqiyatli tajribamiz bor.

PostgreSQL klasteri bilan bu oson emas - bor XL, XC, Situs, lekin umuman olganda, bu qutidan tashqariga chiqadigan NoSQLlar emas. Biz NoSQL-ni asosiy xotira deb hisoblamadik; biz ilgari ishlamagan Hazelcast-ni olishimiz kifoya edi.

Agar siz relyatsion ma'lumotlar bazasini o'lchashingiz kerak bo'lsa, bu degani parchalanish. Ma'lumki, sharding yordamida biz ma'lumotlar bazasini alohida qismlarga ajratamiz, shunda ularning har biri alohida serverga joylashtirilishi mumkin.

Shardingimizning birinchi versiyasi dasturimizning har bir jadvalini turli serverlar bo'ylab turli nisbatlarda tarqatish qobiliyatini o'z ichiga oladi. A serverida juda ko'p xabarlar bor - iltimos, ushbu jadvalning bir qismini B serveriga ko'chiraylik. Bu qaror shunchaki erta optimallashtirish haqida qichqirdi, shuning uchun biz o'zimizni ko'p ijarachilarli yondashuv bilan cheklashga qaror qildik.

Ko'p ijarachi haqida, masalan, veb-saytda o'qishingiz mumkin Citus ma'lumotlari.

SV ilova va abonent tushunchalariga ega. Ilova - bu ERP yoki Buxgalteriya hisobi kabi biznes ilovasining foydalanuvchilari va biznes ma'lumotlari bilan maxsus o'rnatilishi. Abonent - SV serverida uning nomidan ilova ro'yxatdan o'tgan tashkilot yoki jismoniy shaxs. Abonent bir nechta ilovalarni ro'yxatdan o'tkazishi mumkin va bu ilovalar bir-biri bilan xabar almashishi mumkin. Abonent bizning tizimimizda ijarachiga aylandi. Bir nechta abonentlarning xabarlari bitta jismoniy ma'lumotlar bazasida joylashgan bo'lishi mumkin; agar abonent juda ko'p trafik hosil qila boshlaganini ko'rsak, biz uni alohida jismoniy ma'lumotlar bazasiga (yoki hatto alohida ma'lumotlar bazasi serveriga) o'tkazamiz.

Bizda barcha abonent ma'lumotlar bazalarining joylashuvi haqidagi ma'lumotlar bilan marshrutlash jadvali saqlanadigan asosiy ma'lumotlar bazasi mavjud.

1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik

Asosiy ma'lumotlar bazasi to'siq bo'lishiga yo'l qo'ymaslik uchun biz marshrutlash jadvalini (va boshqa tez-tez kerakli ma'lumotlarni) keshda saqlaymiz.

Agar abonent ma'lumotlar bazasi sekinlasha boshlasa, biz uni ichki qismlarga ajratamiz. Biz foydalanadigan boshqa loyihalarda pg_pathman.

Foydalanuvchi xabarlarini yo'qotish yomon bo'lgani uchun biz ma'lumotlar bazalarimizni replikalar bilan saqlaymiz. Sinxron va asinxron replikalarning kombinatsiyasi asosiy ma'lumotlar bazasi yo'qolgan taqdirda o'zingizni sug'urta qilish imkonini beradi. Agar asosiy ma'lumotlar bazasi va uning sinxron nusxasi bir vaqtning o'zida ishlamay qolsa, xabar yo'qoladi.

Sinxron replika yo'qolsa, asinxron replika sinxron bo'ladi.
Agar asosiy ma'lumotlar bazasi yo'qolsa, sinxron replika asosiy ma'lumotlar bazasiga, asinxron replika esa sinxron nusxaga aylanadi.

Qidiruv uchun Elasticsearch

Boshqa narsalar qatori, SV ham messenjer bo'lganligi sababli, morfologiyani hisobga olgan holda, aniq bo'lmagan mosliklardan foydalangan holda tez, qulay va moslashuvchan qidiruvni talab qiladi. Biz g'ildirakni qayta ixtiro qilmaslikka va kutubxona asosida yaratilgan bepul Elasticsearch qidiruv tizimidan foydalanishga qaror qildik Lucene. Biz, shuningdek, dastur tugunlari ishlamay qolganda muammolarni bartaraf etish uchun Elasticsearch-ni klasterda (master - ma'lumotlar - ma'lumotlar) joylashtiramiz.

Github-da biz topdik Rus morfologiya plagin Elasticsearch uchun va undan foydalaning. Elasticsearch indeksida biz so'z ildizlarini (plagin belgilaydigan) va N-grammlarni saqlaymiz. Foydalanuvchi matnni qidirish uchun kiritayotganda, biz N-grammlar orasidan terilgan matnni qidiramiz. Indeksga saqlanganida, "matnlar" so'zi quyidagi N-grammlarga bo'linadi:

[bular, tek, tex, matn, matnlar, ek, ex, ext, texts, ks, kst, ksty, st, sty, you],

Va "matn" so'zining ildizi ham saqlanib qoladi. Ushbu yondashuv so'zning boshida, o'rtasida va oxirida qidirish imkonini beradi.

Katta rasm

1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik
Maqolaning boshidan rasmni takrorlang, ammo tushuntirishlar bilan:

  • Internetda ta'sir ko'rsatadigan balanslashtiruvchi; bizda nginx bor, u har qanday bo'lishi mumkin.
  • Java dastur namunalari bir-biri bilan Hazelcast orqali muloqot qiladi.
  • Veb-rozetka bilan ishlash uchun biz foydalanamiz Nett.
  • Java ilovasi Java 8 da yozilgan va paketlardan iborat OSGi. Rejalar Java 10 ga o'tish va modullarga o'tishni o'z ichiga oladi.

Rivojlanish va sinov

SVni ishlab chiqish va sinovdan o'tkazish jarayonida biz foydalanadigan mahsulotlarning bir qator qiziqarli xususiyatlariga duch keldik.

Yuk testi va xotiraning oqishi

Har bir SV versiyasini chiqarish yuk sinovini o'z ichiga oladi. U quyidagi hollarda muvaffaqiyatli bo'ladi:

  • Sinov bir necha kun davomida ishladi va hech qanday xizmatda nosozliklar kuzatilmadi
  • Asosiy operatsiyalar uchun javob vaqti qulay chegaradan oshmadi
  • Oldingi versiyaga nisbatan ishlashning yomonlashishi 10% dan oshmaydi

Biz test ma'lumotlar bazasini ma'lumotlar bilan to'ldiramiz - buning uchun ishlab chiqarish serveridan eng faol abonent haqida ma'lumot olamiz, uning raqamlarini 5 ga (xabarlar, muhokamalar, foydalanuvchilar soni) ko'paytiramiz va shu tarzda sinab ko'ramiz.

Biz o'zaro ta'sir tizimining yuk sinovini uchta konfiguratsiyada o'tkazamiz:

  1. stress testi
  2. Faqat ulanishlar
  3. Abonentni ro'yxatdan o'tkazish

Stress-test paytida biz bir necha yuzlab mavzularni ishga tushiramiz va ular tizimni to'xtovsiz yuklaydi: xabarlar yozish, muhokamalar yaratish, xabarlar ro'yxatini olish. Biz oddiy foydalanuvchilarning harakatlarini simulyatsiya qilamiz (mening o'qilmagan xabarlarim ro'yxatini oling, kimgadir yozing) va dasturiy echimlarni (boshqa konfiguratsiya paketini uzatish, ogohlantirishni qayta ishlash).

Masalan, stress testining bir qismi quyidagicha ko'rinadi:

  • Foydalanuvchi tizimga kiradi
    • O'qilmagan muhokamalaringizni so'raydi
    • Xabarlarni o'qish ehtimoli 50%
    • 50% matn yozish ehtimoli
    • Keyingi foydalanuvchi:
      • Yangi munozara yaratish uchun 20% imkoniyat bor
      • Har qanday muhokamani tasodifiy tanlaydi
      • Ichkariga kiradi
      • Xabarlarni, foydalanuvchi profillarini so'raydi
      • Ushbu muhokamadan tasodifiy foydalanuvchilarga yuborilgan beshta xabarni yaratadi
      • Munozarani tark etadi
      • 20 marta takrorlanadi
      • Tizimdan chiqadi, skriptning boshiga qaytadi

    • Chatbot tizimga kiradi (ilova kodidan xabar almashishni taqlid qiladi)
      • Ma'lumotlar almashinuvi uchun yangi kanal yaratish imkoniyati 50% (maxsus muhokama)
      • 50% mavjud kanallardan biriga xabar yozish ehtimoli bor

"Faqat ulanishlar" stsenariysi bir sababga ko'ra paydo bo'ldi. Vaziyat bor: foydalanuvchilar tizimga ulangan, ammo hali aralashmagan. Har bir foydalanuvchi ertalab soat 09:00 da kompyuterni yoqadi, server bilan aloqa o'rnatadi va jim turadi. Bu bolalar xavfli, ularning ko'plari bor - ularda mavjud bo'lgan yagona paketlar PING/PONG, lekin ular serverga ulanishni saqlab turishadi (ular buni davom ettira olmaydilar - agar yangi xabar bo'lsa nima bo'ladi). Sinov bunday foydalanuvchilar ko'p sonli yarim soat ichida tizimga kirishga harakat qiladigan vaziyatni takrorlaydi. Bu stress testiga o'xshaydi, lekin uning diqqat markazida aynan shu birinchi kirishda - hech qanday nosozliklar bo'lmasligi uchun (odam tizimdan foydalanmaydi va u allaqachon tushib ketadi - bundan ham yomoni haqida o'ylash qiyin).

Abonentni ro'yxatga olish skripti birinchi ishga tushirishdan boshlanadi. Biz stress testini o'tkazdik va yozishmalar paytida tizim sekinlashmaganiga amin bo'ldik. Lekin foydalanuvchilar keldi va ro'yxatdan o'tish vaqt tugashi sababli muvaffaqiyatsiz bo'ldi. Ro'yxatdan o'tishda biz foydalandik / dev / random, bu tizimning entropiyasi bilan bog'liq. Server yetarlicha entropiya to‘plashga ulgurmadi va yangi SecureRandom so‘ralganda, u o‘nlab soniya davomida qotib qoldi. Ushbu vaziyatdan chiqishning ko'plab usullari mavjud, masalan: kamroq xavfsiz /dev/urandom ga o'tish, entropiyani yaratadigan maxsus platani o'rnatish, oldindan tasodifiy raqamlarni yaratish va ularni hovuzda saqlash. Biz hovuz bilan bog'liq muammoni vaqtincha yopdik, ammo o'shandan beri biz yangi obunachilarni ro'yxatdan o'tkazish uchun alohida test o'tkazmoqdamiz.

Biz yuk generatori sifatida foydalanamiz JMeter. U websocket bilan qanday ishlashni bilmaydi; unga plagin kerak. “Jmeter websocket” soʻrovi boʻyicha qidiruv natijalarida birinchi boʻlib: BlazeMeter-dan maqolalar, bu tavsiya qiladi Maciej Zaleski tomonidan plagin.

Bu erda biz boshlashga qaror qildik.

Jiddiy sinovdan deyarli darhol biz JMeter xotiradan oqib chiqa boshlaganini aniqladik.

Plagin alohida katta hikoya; 176 yulduzli, github-da 132 ta vilkalar mavjud. Muallifning o'zi 2015 yildan beri bunga rozi bo'lmagan (biz uni 2015 yilda olganmiz, keyin u shubha tug'dirmadi), xotiraning oqishi bilan bog'liq bir nechta github muammolari, 7 ta yopilmagan tortishish so'rovlari.
Agar siz ushbu plagin yordamida yuk testini o'tkazishga qaror qilsangiz, iltimos, quyidagi muhokamalarga e'tibor bering:

  1. Ko'p tarmoqli muhitda oddiy LinkedList ishlatildi va natija shunday bo'ldi NPE ish vaqtida. Buni ConcurrentLinkedDeque-ga o'tish yoki sinxronlashtirilgan bloklar orqali hal qilish mumkin. Biz o'zimiz uchun birinchi variantni tanladik (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Xotiraning oqishi; o'chirilganda ulanish ma'lumotlari o'chirilmaydi (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Oqim rejimida (namuna oxirida veb-rozetka yopilmaganda, lekin rejada keyinroq foydalanilganda), javob naqshlari ishlamaydi (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Bu github-dagilardan biri. Biz nima qildik:

  1. Olingan vilkalar Elyran Kogan (@elyrank) - 1 va 3 muammolarni hal qiladi
  2. Yechilgan muammo 2
  3. Jety 9.2.14 dan 9.3.12 gacha yangilandi
  4. SimpleDateFormat ThreadLocal-ga o'ralgan; SimpleDateFormat ish zarralari uchun xavfsiz emas, bu ish vaqtida NPEga olib keldi
  5. Yana bir xotira oqishini tuzatdi (ulanish uzilganda noto‘g‘ri yopilgan)

Va shunga qaramay u oqadi!

Xotira bir kunda emas, ikki kunda tuga boshladi. Mutlaqo vaqt qolmadi, shuning uchun biz kamroq mavzularni ochishga qaror qildik, lekin to'rtta agentda. Bu kamida bir hafta etarli bo'lishi kerak edi.

Ikki kun o'tdi...

Endi Hazelcast xotirasi tugaydi. Jurnallar shuni ko'rsatdiki, bir necha kunlik sinovdan so'ng Hazelcast xotira etishmasligidan shikoyat qila boshladi va bir muncha vaqt o'tgach, klaster parchalanib ketdi va tugunlar birin-ketin o'lishni davom ettirdi. Biz JVisualVM-ni hazelcast-ga uladik va "ko'tarilgan arra" ni ko'rdik - u muntazam ravishda GC deb ataladi, lekin xotirani tozalay olmadi.

1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik

Ma'lum bo'lishicha, hazelcast 3.4-da xarita / multiMap (map.destroy()) o'chirilganda xotira to'liq bo'shatilmagan:

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

Xato endi 3.5 da tuzatildi, lekin o'sha paytda bu muammo edi. Biz dinamik nomlar bilan yangi multiMaps yaratdik va ularni mantiqimizga ko'ra o'chirib tashladik. Kod shunday ko'rinardi:

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

Vizov:

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

multiMap har bir obuna uchun yaratilgan va kerak bo'lmaganda o'chirib tashlangan. Biz xaritani ishga tushirishga qaror qildik , kalit obuna nomi bo'ladi va qiymatlar sessiya identifikatorlari bo'ladi (agar kerak bo'lsa, undan keyin foydalanuvchi identifikatorlarini olishingiz mumkin).

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

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

Grafiklar yaxshilandi.

1C: Enterprise: Java, PostgreSQL, Hazelcast uchun yuqori yuklangan kengaytiriladigan xizmatni qanday va nima uchun yozdik

Yuk sinovlari haqida yana nimani bilib oldik?

  1. JSR223 ajoyib tarzda yozilishi va kompilyatsiya keshini o'z ichiga olishi kerak - bu juda tezroq. aloqa.
  2. Jmeter-Plugins grafiklarini tushunish standartga qaraganda osonroq. aloqa.

Hazelcast bilan tajribamiz haqida

Hazelcast biz uchun yangi mahsulot edi, biz u bilan 3.4.1 versiyasidan ishlay boshladik, hozir ishlab chiqarish serverimiz 3.9.2 versiyasida ishlamoqda (yozish vaqtida Hazelcastning so'nggi versiyasi 3.10).

ID yaratish

Biz butun son identifikatorlaridan boshladik. Tasavvur qilaylik, yangi ob'ekt uchun bizga yana bir Long kerak. Ma'lumotlar bazasidagi ketma-ketlik mos emas, jadvallar shardingda ishtirok etadi - ma'lum bo'lishicha, DB1da ID=1 xabar va DB1da ID=2 xabar bor, siz bu identifikatorni Elasticsearch-ga ham, Hazelcast-ga ham qo'ya olmaysiz. , lekin eng yomoni, agar siz ikkita ma'lumotlar bazasidagi ma'lumotlarni bittaga birlashtirmoqchi bo'lsangiz (masalan, bitta ma'lumotlar bazasi ushbu obunachilar uchun etarli deb qaror qilish). Siz Hazelcast-ga bir nechta AtomicLong-larni qo'shishingiz va hisoblagichni o'sha erda saqlashingiz mumkin, keyin yangi identifikatorni olish unumdorligi incrementAndGet va Hazelcast-ga so'rov uchun vaqt. Ammo Hazelcastda yanada maqbulroq narsa bor - FlakeIdGenerator. Har bir mijoz bilan bog'lanishda ularga ID diapazoni beriladi, masalan, birinchisi - 1 dan 10 000 gacha, ikkinchisi - 10 001 dan 20 000 gacha va hokazo. Endi mijoz yangi identifikatorlarni unga berilgan diapazon tugaguncha mustaqil ravishda chiqarishi mumkin. U tez ishlaydi, lekin dasturni (va Hazelcast mijozini) qayta ishga tushirganingizda, yangi ketma-ketlik boshlanadi - shuning uchun o'tkazib yuboriladi va hokazo. Bundan tashqari, ishlab chiquvchilar identifikatorlar nima uchun butun son ekanligini tushunishmaydi, lekin ular juda mos kelmaydi. Biz hamma narsani tortdik va UUIDlarga o'tdik.

Aytgancha, Twitter kabi bo'lishni xohlaydiganlar uchun Snowcast kutubxonasi mavjud - bu Hazelcast tepasida Snowflake-ning amalga oshirilishi. Bu yerda koʻrishingiz mumkin:

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

Ammo biz endi bunga erishmadik.

TransactionalMap.replace

Yana bir ajablanib: TransactionalMap.replace ishlamaydi. Mana sinov:

@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 yordamida o'z o'rnimni yozishim kerak edi:

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

Nafaqat oddiy ma'lumotlar tuzilmalarini, balki ularning tranzaksiya versiyalarini ham sinab ko'ring. IMap ishlaydi, lekin TransactionalMap endi mavjud emas.

To'xtab qolmasdan yangi JARni joylashtiring

Birinchidan, biz Hazelcastda sinflarimiz ob'ektlarini yozib olishga qaror qildik. Misol uchun, bizda Application klassi bor, biz uni saqlashni va o'qishni xohlaymiz. Saqlash:

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

O'qing:

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

Hammasi ishlayapti. Keyin biz Hazelcastda indeks yaratishga qaror qildik:

map.addIndex("subscriberId", false);

Va yangi ob'ektni yozishda ular ClassNotFoundException ni olishni boshladilar. Hazelcast indeksga qo'shishga harakat qildi, lekin bizning sinfimiz haqida hech narsa bilmas edi va unga ushbu sinfga ega JAR taqdim etilishini xohladi. Biz shunday qildik, hamma narsa ishladi, lekin yangi muammo paydo bo'ldi: klasterni to'liq to'xtatmasdan JARni qanday yangilash kerak? Hazelcast tugunma-tugun yangilash paytida yangi JAR-ni tanlamaydi. Shu nuqtada biz indeks qidiruvisiz yashashga qaror qildik. Axir, agar siz Hazelcast-dan asosiy qiymat do'koni sifatida foydalansangiz, unda hamma narsa ishlaydi? Unchalik emas. Bu erda yana IMap va TransactionalMap xatti-harakatlari boshqacha. IMap ahamiyat bermasa, TransactionalMap xatoga yo'l qo'yadi.

IMap. Biz 5000 ta ob'ektni yozamiz, ularni o'qiymiz. Hamma narsa kutilmoqda.

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

Ammo bu tranzaksiyada ishlamaydi, biz ClassNotFoundException ni olamiz:

@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 da User Class Deployment mexanizmi paydo bo'ldi. Siz bitta asosiy tugunni belgilashingiz va undagi JAR faylini yangilashingiz mumkin.

Endi biz yondashuvimizni butunlay o'zgartirdik: biz uni o'zimiz JSON-ga seriyalashtiramiz va uni Hazelcast-da saqlaymiz. Hazelcast bizning sinflarimiz tuzilishini bilishi shart emas va biz to'xtab qolmasdan yangilashimiz mumkin. Domen ob'ektlarining versiyalari ilova tomonidan boshqariladi. Ilovaning turli versiyalari bir vaqtning o'zida ishlayotgan bo'lishi mumkin va yangi dastur ob'ektlarni yangi maydonlar bilan yozganda, lekin eskisi bu maydonlar haqida hali bilmaydi. Va shu bilan birga, yangi dastur eski dastur tomonidan yozilgan yangi maydonlarga ega bo'lmagan ob'ektlarni o'qiydi. Biz bunday vaziyatlarni ilova ichida hal qilamiz, lekin soddaligi uchun biz maydonlarni o'zgartirmaymiz yoki o'chirmaymiz, faqat yangi maydonlarni qo'shish orqali sinflarni kengaytiramiz.

Qanday qilib biz yuqori ishlashni ta'minlaymiz

Hazelcastga to'rtta sayohat - yaxshi, ikkitasi ma'lumotlar bazasiga - yomon

Ma'lumotlar uchun keshga o'tish har doim ma'lumotlar bazasiga kirishdan ko'ra yaxshiroqdir, lekin siz foydalanilmagan yozuvlarni ham saqlashni xohlamaysiz. Nimani keshlash haqida qarorni rivojlanishning oxirgi bosqichiga qoldiramiz. Yangi funksiya kodlanganda, biz PostgreSQL-dagi barcha so‘rovlarni jurnalga yozishni yoqamiz (log_min_duration_statement 0 ga) va 20 daqiqa davomida yuk testini o‘tkazamiz.Yig‘ilgan jurnallardan foydalanib, pgFouine va pgBadger kabi yordamchi dasturlar analitik hisobotlarni yaratishi mumkin. Hisobotlarda biz birinchi navbatda sekin va tez-tez so'rovlarni qidiramiz. Sekin so'rovlar uchun biz bajarish rejasini tuzamiz (IZOH) va bunday so'rovni tezlashtirish mumkinligini baholaymiz. Bir xil kirish ma'lumotlari uchun tez-tez so'rovlar keshga yaxshi joylashadi. Biz so'rovlarni "tekis" saqlashga harakat qilamiz, har bir so'rov uchun bitta jadval.

Operatsiya

SV onlayn xizmat sifatida 2017 yil bahorida foydalanishga topshirilgan va alohida mahsulot sifatida SV 2017 yil noyabr oyida chiqarilgan (o'sha paytda beta versiya holatida).

Bir yildan ortiq faoliyat davomida CB onlayn xizmatining ishlashida jiddiy muammolar yuzaga kelmadi. orqali onlayn xizmatni kuzatib boramiz Zabbixdan to'plash va joylashtirish Bambukdan.

SV server taqsimoti mahalliy paketlar ko'rinishida taqdim etiladi: RPM, DEB, MSI. Bundan tashqari, Windows uchun biz bitta EXE ko'rinishidagi bitta o'rnatuvchini taqdim etamiz, u server, Hazelcast va Elasticsearchni bitta mashinaga o'rnatadi. Biz dastlab o'rnatishning ushbu versiyasini "demo" versiyasi deb atagan edik, ammo endi bu eng mashhur joylashtirish varianti ekanligi ayon bo'ldi.

Manba: www.habr.com

a Izoh qo'shish