Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast

Այս հոդվածում մենք կխոսենք այն մասին, թե ինչպես և ինչու ենք մենք զարգացել Փոխազդեցության համակարգ – մեխանիզմ, որը տեղեկատվություն է փոխանցում հաճախորդի հավելվածների և 1C:Enterprise սերվերների միջև՝ առաջադրանք դնելուց մինչև ճարտարապետության և իրականացման մանրամասների շուրջ մտածելը:

Փոխազդեցության համակարգը (այսուհետ՝ SV) բաշխված, սխալ հանդուրժող հաղորդագրությունների համակարգ է՝ երաշխավորված առաքմամբ: SV-ն նախագծված է որպես բարձր բեռնվածության ծառայություն՝ մեծ մասշտաբայնությամբ, հասանելի և՛ որպես առցանց ծառայություն (տրամադրված 1C-ի կողմից), և՛ որպես զանգվածային արտադրանք, որը կարող է տեղակայվել ձեր սեփական սերվերի օբյեկտներում:

SV-ն օգտագործում է բաշխված պահեստավորում Շնդուկ և որոնման համակարգ Elasticsearch- ը. Մենք նաև կխոսենք Java-ի և այն մասին, թե ինչպես ենք հորիզոնական մասշտաբավորում PostgreSQL-ը:
Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast

Խնդրի ձևակերպում

Հասկանալու համար, թե ինչու ենք մենք ստեղծել Փոխազդեցության համակարգը, ես ձեզ մի փոքր կպատմեմ այն ​​մասին, թե ինչպես է աշխատում 1C-ում բիզնես հավելվածների մշակումը:

Սկզբից մի փոքր մեր մասին նրանց համար, ովքեր դեռ չգիտեն, թե ինչ ենք անում :) Մենք պատրաստում ենք 1C:Enterprise տեխնոլոգիական հարթակը: Հարթակը ներառում է բիզնես հավելվածների մշակման գործիք, ինչպես նաև գործարկման ժամանակ, որը թույլ է տալիս բիզնես հավելվածներին աշխատել միջպլատֆորմային միջավայրում:

Հաճախորդ-սերվերի զարգացման պարադիգմ

1C:Enterprise-ում ստեղծված բիզնես հավելվածները գործում են երեք մակարդակով հաճախորդ-սերվեր ճարտարապետություն «DBMS – հավելվածի սերվեր – հաճախորդ»: Դիմումի կոդը գրված է ներկառուցված 1C լեզու, կարող է իրականացվել հավելվածի սերվերի կամ հաճախորդի վրա: Ծրագրային օբյեկտների հետ (տեղեկատուներ, փաստաթղթեր և այլն) բոլոր աշխատանքները, ինչպես նաև տվյալների բազայի ընթերցումն ու գրառումը կատարվում են միայն սերվերի վրա: Ձևերի և հրամանի ինտերֆեյսի ֆունկցիոնալությունը նույնպես ներդրված է սերվերի վրա: Հաճախորդը կատարում է ձևերի ստացում, բացում և ցուցադրում, «շփվում» օգտատիրոջ հետ (նախազգուշացումներ, հարցեր...), փոքր հաշվարկներ արագ պատասխան պահանջող ձևերով (օրինակ՝ գինը բազմապատկելով քանակով), աշխատում է տեղական ֆայլերի հետ, սարքավորումների հետ աշխատելը.

Հավելվածի կոդում ընթացակարգերի և գործառույթների վերնագրերում պետք է հստակ նշվի, թե որտեղ է կատարվելու կոդը՝ օգտագործելով &AtClient / &AtServer հրահանգները (&AtClient / &AtServer լեզվի անգլերեն տարբերակում): 1C մշակողները հիմա կուղղեն ինձ՝ ասելով, որ հրահանգներն իրականում կան ավելին, քան, բայց մեզ համար դա հիմա կարևոր չէ։

Դուք կարող եք զանգահարել սերվերի կոդը հաճախորդի կոդից, բայց դուք չեք կարող զանգահարել հաճախորդի կոդը սերվերի կոդից: Սա հիմնարար սահմանափակում է, որը մենք դրել ենք մի շարք պատճառներով: Մասնավորապես, քանի որ սերվերի կոդը պետք է գրված լինի այնպես, որ այն կատարվի նույն կերպ, անկախ նրանից, թե որտեղ է այն կոչվում՝ հաճախորդից, թե սերվերից: Իսկ այլ սերվերի կոդից սերվերի կոդը կանչելու դեպքում հաճախորդ որպես այդպիսին չկա։ Եվ քանի որ սերվերի կոդի կատարման ժամանակ այն զանգահարող հաճախորդը կարող էր փակվել, դուրս գալ հավելվածից, և սերվերն այլևս որևէ մեկին զանգահարող չի ունենա:

Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast
Կոդ, որը մշակում է կոճակի սեղմումը. հաճախորդից սերվերի ընթացակարգ կանչելը կաշխատի, սերվերից հաճախորդի պրոցեդուրա կանչելը ոչ

Սա նշանակում է, որ եթե մենք ցանկանում ենք սերվերից ինչ-որ հաղորդագրություն ուղարկել հաճախորդի հավելվածին, օրինակ, որ «երկարատև» զեկույցի ստեղծումն ավարտվել է, և հաշվետվությունը կարելի է դիտել, մենք նման մեթոդ չունենք: Դուք պետք է օգտագործեք հնարքներ, օրինակ, պարբերաբար հարցումներ կատարեք սերվերի վրա հաճախորդի կոդից: Բայց այս մոտեցումը համակարգը բեռնում է անհարկի զանգերով և, ընդհանուր առմամբ, այնքան էլ էլեգանտ տեսք չունի:

Եվ կա նաև անհրաժեշտություն, օրինակ, երբ հեռախոսազանգ է գալիս SIP- զանգ կատարելիս տեղեկացրեք հաճախորդի հավելվածին այդ մասին, որպեսզի նա կարողանա օգտագործել զանգահարողի համարը՝ այն գտնելու կոնտրագենտների տվյալների բազայում և օգտատիրոջը ցույց տալ զանգող գործընկերոջ մասին տեղեկությունները: Կամ, օրինակ, երբ պատվերը հասնում է պահեստ, այդ մասին տեղեկացրեք հաճախորդի հաճախորդի դիմումին: Ընդհանրապես, շատ են դեպքերը, երբ նման մեխանիզմը օգտակար կլիներ։

Արտադրությունն ինքնին

Ստեղծեք հաղորդագրությունների մեխանիզմ: Արագ, հուսալի, երաշխավորված առաքմամբ, հաղորդագրությունների ճկուն որոնման ունակությամբ: Մեխանիզմի հիման վրա ներդրեք մեսենջեր (հաղորդագրություններ, տեսազանգեր), որոնք աշխատում են 1C հավելվածների ներսում:

Նախագծեք համակարգը, որպեսզի լինի հորիզոնական մասշտաբային: Աճող բեռը պետք է ծածկվի հանգույցների քանակի ավելացմամբ:

Իրականացման

Մենք որոշեցինք ոչ թե ինտեգրել SV-ի սերվերային մասը ուղղակիորեն 1C:Enterprise հարթակում, այլ այն իրականացնել որպես առանձին արտադրանք, որի API-ն կարելի է կանչել 1C հավելվածի լուծումների կոդից։ Դա արվեց մի շարք պատճառներով, որոնցից հիմնականն այն էր, որ ես ուզում էի հնարավորություն տալ հաղորդագրությունների փոխանակում տարբեր 1C հավելվածների միջև (օրինակ՝ Առևտրի կառավարման և հաշվապահության միջև): Տարբեր 1C հավելվածներ կարող են աշխատել 1C:Enterprise հարթակի տարբեր տարբերակների վրա, տեղակայվել տարբեր սերվերների վրա և այլն: Նման պայմաններում SV-ի ներդրումը որպես առանձին արտադրանք, որը գտնվում է 1C կայանքների «կողքին» օպտիմալ լուծումն է:

Այսպիսով, մենք որոշեցինք SV-ն պատրաստել որպես առանձին ապրանք։ Մենք խորհուրդ ենք տալիս փոքր ընկերություններին օգտագործել CB սերվերը, որը մենք տեղադրել ենք մեր ամպում (wss://1cdialog.com), որպեսզի խուսափեն սերվերի տեղական տեղադրման և կազմաձևման հետ կապված գերավճարներից: Խոշոր հաճախորդները կարող են նպատակահարմար համարել տեղադրել իրենց սեփական ԿԲ սերվերը իրենց օբյեկտներում: Մենք նմանատիպ մոտեցում օգտագործեցինք մեր ամպային SaaS արտադրանքում 1cFresh – այն արտադրվում է որպես զանգվածային արտադրանք՝ հաճախորդների կայքերում տեղադրելու համար, ինչպես նաև տեղակայվում է մեր ամպում https://1cfresh.com/.

App

Բեռնվածության և սխալների հանդուրժողականությունը բաշխելու համար մենք կտեղակայենք ոչ թե մեկ Java հավելված, այլ մի քանիսը, որոնց առջև կտեղակայվի բեռի հավասարակշռող սարք: Եթե ​​Ձեզ անհրաժեշտ է հաղորդագրություն փոխանցել հանգույցից հանգույց, օգտագործեք հրապարակել/բաժանորդագրվել Hazelcast-ում:

Հաճախորդի և սերվերի միջև հաղորդակցությունը իրականացվում է վեբսոկետի միջոցով: Այն լավ հարմարեցված է իրական ժամանակի համակարգերի համար:

Բաշխված քեշ

Մենք ընտրեցինք Redis-ի, Hazelcast-ի և Ehcache-ի միջև: 2015 թվականն է։ Redis-ը նոր թողարկեց նոր կլաստեր (չափազանց նոր, սարսափելի), կա Sentinel-ը շատ սահմանափակումներով: Ehcache-ը չգիտի, թե ինչպես հավաքվել կլաստերի մեջ (այս ֆունկցիոնալությունը հայտնվեց ավելի ուշ): Մենք որոշեցինք փորձել Hazelcast 3.4-ով:
Hazelcast-ը հավաքվում է տուփից դուրս կլաստերի մեջ: Մեկ հանգույցի ռեժիմում այն ​​այնքան էլ օգտակար չէ և կարող է օգտագործվել միայն որպես քեշ. այն չգիտի, թե ինչպես պետք է տվյալները թափել սկավառակի վրա, եթե կորցնում եք միակ հանգույցը, ապա կորցնում եք տվյալները: Մենք տեղադրում ենք մի քանի Hazelcasts, որոնց միջև մենք կրկնօրինակում ենք կարևոր տվյալները: Մենք չենք կրկնօրինակում քեշը, մենք դեմ չենք դրան:

Մեզ համար Hazelcast-ը հետևյալն է.

  • Օգտագործողի նիստերի պահպանում: Ամեն անգամ սեսիայի համար տվյալների բազա գնալը երկար ժամանակ է պահանջում, ուստի մենք բոլոր նիստերը դնում ենք Hazelcast-ում:
  • Քեշ. Եթե ​​դուք փնտրում եք օգտվողի պրոֆիլ, ստուգեք քեշը: Գրել է նոր հաղորդագրություն՝ տեղադրել այն քեշում:
  • Հավելվածների օրինակների միջև հաղորդակցության թեմաներ: Հանգույցը ստեղծում է իրադարձություն և տեղադրում այն ​​Hazelcast թեմայում։ Այս թեմային բաժանորդագրված այլ հավելվածների հանգույցները ստանում և մշակում են իրադարձությունը:
  • Կլաստերային կողպեքներ. Օրինակ՝ մենք քննարկում ենք ստեղծում՝ օգտագործելով եզակի բանալի (մեկ քննարկում 1C տվյալների բազայում).

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

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

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

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

Մենք ստուգեցինք, որ ալիք չկա։ Մենք վերցրեցինք կողպեքը, նորից ստուգեցինք և ստեղծեցինք: Եթե ​​կողպեքը վերցնելուց հետո չստուգեք կողպեքը, ապա հնարավորություն կա, որ մեկ այլ թեմա նույնպես ստուգվել է այդ պահին և այժմ կփորձի ստեղծել նույն քննարկումը, բայց այն արդեն կա: Դուք չեք կարող կողպել՝ օգտագործելով համաժամացված կամ սովորական java Lock-ը: Տվյալների բազայի միջոցով՝ դա դանդաղ է, և ափսոս է տվյալների բազայի համար, Hazelcast-ի միջոցով՝ դա այն է, ինչ ձեզ հարկավոր է:

DBMS-ի ընտրություն

Մենք PostgreSQL-ի հետ աշխատելու և այս DBMS մշակողների հետ համագործակցելու մեծ և հաջողակ փորձ ունենք:

PostgreSQL կլաստերի հետ հեշտ չէ. կա XL, XC, Ցիտուս, բայց ընդհանուր առմամբ սրանք NoSQL-ներ չեն, որոնք դուրս են գալիս արկղից: Մենք NoSQL-ն չէինք համարում որպես հիմնական պահեստ, բավական էր, որ վերցրեցինք Hazelcast-ը, որի հետ նախկինում չէինք աշխատել։

Եթե ​​Ձեզ անհրաժեշտ է չափել հարաբերական տվյալների բազան, դա նշանակում է sharding. Ինչպես գիտեք, sharding-ով մենք տվյալների բազան բաժանում ենք առանձին մասերի, որպեսզի դրանցից յուրաքանչյուրը տեղադրվի առանձին սերվերի վրա։

Մեր շարդինգի առաջին տարբերակը ենթադրում էր մեր հավելվածի աղյուսակներից յուրաքանչյուրը տարբեր սերվերների վրա տարբեր համամասնություններով բաշխելու հնարավորություն: A սերվերի վրա շատ հաղորդագրություններ կան. խնդրում եմ, եկեք այս աղյուսակի մի մասը տեղափոխենք սերվեր B: Այս որոշումը պարզապես բղավում էր վաղաժամ օպտիմալացման մասին, ուստի մենք որոշեցինք սահմանափակվել բազմաբնակարան վարձակալական մոտեցմամբ:

Դուք կարող եք կարդալ բազմաբնակարան վարձակալի մասին, օրինակ, կայքում Citus Data.

SV-ն ունի հավելված և բաժանորդ հասկացությունները։ Հավելվածը բիզնես հավելվածի հատուկ տեղադրումն է, ինչպիսին է ERP-ն կամ Հաշվապահությունը, իր օգտագործողների և բիզնեսի տվյալների հետ: Բաժանորդը կազմակերպություն կամ անհատ է, որի անունից դիմումը գրանցված է SV սերվերում: Բաժանորդը կարող է գրանցել մի քանի հավելված, և այդ հավելվածները կարող են հաղորդագրություններ փոխանակել միմյանց հետ։ Բաժանորդը դարձել է վարձակալ մեր համակարգում: Մի քանի բաժանորդների հաղորդագրությունները կարող են տեղակայվել մեկ ֆիզիկական տվյալների բազայում. եթե տեսնում ենք, որ բաժանորդը սկսել է մեծ տրաֆիկ ստեղծել, այն տեղափոխում ենք առանձին ֆիզիկական տվյալների բազա (կամ նույնիսկ տվյալների բազայի առանձին սերվեր):

Մենք ունենք հիմնական տվյալների բազա, որտեղ երթուղային աղյուսակը պահվում է բոլոր բաժանորդների տվյալների բազաների գտնվելու վայրի մասին տեղեկություններով:

Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast

Որպեսզի հիմնական տվյալների բազան խցանման մեջ չլինի, մենք պահում ենք երթուղային աղյուսակը (և հաճախ անհրաժեշտ այլ տվյալներ) քեշում:

Եթե ​​բաժանորդի տվյալների բազան սկսի դանդաղել, մենք այն կկտրենք միջնորմների ներսում: Այլ նախագծերում մենք օգտագործում ենք pg_pathman.

Քանի որ օգտվողների հաղորդագրությունները կորցնելը վատ է, մենք պահպանում ենք մեր տվյալների բազաները կրկնօրինակներով: Սինքրոն և ասինխրոն կրկնօրինակների համադրությունը թույլ է տալիս ապահովագրել ձեզ հիմնական տվյալների բազայի կորստի դեպքում։ Հաղորդագրության կորուստը տեղի կունենա միայն այն դեպքում, եթե հիմնական տվյալների բազան և դրա համաժամանակյա կրկնօրինակը միաժամանակ ձախողվեն:

Եթե ​​համաժամանակյա կրկնօրինակը կորչում է, ապա ասինխրոն կրկնօրինակը դառնում է համաժամանակյա:
Եթե ​​հիմնական տվյալների բազան կորչում է, համաժամանակյա կրկնօրինակը դառնում է հիմնական տվյալների բազա, իսկ ասինխրոն կրկնօրինակը՝ համաժամանակյա կրկնօրինակ:

Elastics որոնել որոնման համար

Քանի որ, ի թիվս այլ բաների, SV-ն նաև մեսենջեր է, այն պահանջում է արագ, հարմար և ճկուն որոնում՝ հաշվի առնելով մորֆոլոգիան՝ օգտագործելով ոչ ճշգրիտ համընկնումներ։ Մենք որոշեցինք չհորինել անիվը և օգտվել գրադարանի հիման վրա ստեղծված անվճար որոնողական համակարգից՝ Elasticsearch Լուցեն. Մենք նաև տեղակայում ենք Elasticsearch-ը կլաստերի մեջ (հիմնական – տվյալներ – տվյալներ)` հավելվածի հանգույցների ձախողման դեպքում խնդիրները վերացնելու համար:

Github-ում մենք գտանք Ռուսական մորֆոլոգիայի հավելված Elasticsearch-ի համար և օգտագործեք այն: Elasticsearch ինդեքսում մենք պահում ենք բառերի արմատները (որոնք որոշում է plugin-ը) և N-գրամները: Երբ օգտվողը մուտքագրում է տեքստ որոնման համար, մենք փնտրում ենք մուտքագրված տեքստը N-գրամների մեջ: Երբ պահվում է ինդեքսում, «տեքստեր» բառը կբաժանվի հետևյալ N-գրամների.

[նրանք, tek, tex, text, texts, ek, ex, ext, texts, ks, kst, ksty, st, sty, you],

Եվ կպահպանվի նաև «տեքստ» բառի արմատը։ Այս մոտեցումը թույլ է տալիս որոնել բառի սկզբում, մեջտեղում և վերջում:

Ընդհանուր պատկերը

Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast
Կրկնել նկարը հոդվածի սկզբից, բայց բացատրություններով.

  • Ինտերնետում բացահայտված հավասարակշռող; մենք ունենք nginx, այն կարող է լինել ցանկացած:
  • Java հավելվածների օրինակները միմյանց հետ շփվում են Hazelcast-ի միջոցով:
  • Վեբ վարդակից աշխատելու համար մենք օգտագործում ենք Netty.
  • Java հավելվածը գրված է Java 8-ով և բաղկացած է փաթեթներից OSGi. Ծրագրերը ներառում են միգրացիա Java 10 և անցում դեպի մոդուլներ:

Մշակում և փորձարկում

SV-ի մշակման և փորձարկման ընթացքում մենք հանդիպեցինք մեր օգտագործած արտադրանքի մի շարք հետաքրքիր առանձնահատկությունների:

Բեռնվածության փորձարկում և հիշողության արտահոսք

Յուրաքանչյուր SV թողարկման թողարկումը ներառում է բեռի փորձարկում: Հաջող է, երբ.

  • Թեստն աշխատել է մի քանի օր և սպասարկման խափանումներ չեն եղել
  • Հիմնական գործողությունների համար արձագանքման ժամանակը չի գերազանցել հարմարավետ շեմը
  • Կատարողականի վատթարացումը նախորդ տարբերակի համեմատ կազմում է ոչ ավելի, քան 10%

Մենք լցնում ենք թեստային տվյալների բազան տվյալների հետ. դա անելու համար մենք արտադրական սերվերից ստանում ենք տեղեկատվություն ամենաակտիվ բաժանորդի մասին, նրա համարները բազմապատկում ենք 5-ով (հաղորդագրությունների, քննարկումների, օգտվողների քանակը) և այդպես փորձարկում:

Մենք իրականացնում ենք փոխազդեցության համակարգի բեռնվածության փորձարկում երեք կոնֆիգուրացիաներով.

  1. սթրես թեստ
  2. Միայն միացումներ
  3. Բաժանորդի գրանցում

Սթրես թեստի ժամանակ մենք գործարկում ենք մի քանի հարյուր թեմա, և դրանք առանց կանգ առնելու բեռնում են համակարգը՝ գրել հաղորդագրություններ, ստեղծել քննարկումներ, ստանալ հաղորդագրությունների ցուցակ։ Մենք մոդելավորում ենք սովորական օգտատերերի գործողությունները (ստացեք իմ չընթերցված հաղորդագրությունների ցուցակը, գրեք որևէ մեկին) և ծրագրային լուծումները (փոխանցեք այլ կոնֆիգուրացիայի փաթեթ, մշակեք ահազանգ):

Օրինակ, սթրես-թեստի մի մասն այսպիսի տեսք ունի.

  • Օգտագործողը մուտք է գործում
    • Խնդրում է ձեր չկարդացված քննարկումները
    • Հաղորդագրությունները կարդալու հավանականությունը 50% է
    • 50% հավանական է, որ տեքստը գրվի
    • Հաջորդ օգտվողը.
      • Ունի նոր քննարկում ստեղծելու 20% հնարավորություն
      • Պատահականորեն ընտրում է իր ցանկացած քննարկում
      • Ներս է մտնում
      • Հայցում է հաղորդագրություններ, օգտվողի պրոֆիլներ
      • Ստեղծում է հինգ հաղորդագրություն՝ ուղղված պատահական օգտատերերին այս քննարկումից
      • Հեռանում է քննարկումից
      • Կրկնում է 20 անգամ
      • Դուրս է գալիս, վերադառնում է սցենարի սկիզբ

    • Չաթ-բոտը մտնում է համակարգ (ընդօրինակում է հաղորդագրությունները հավելվածի կոդից)
      • Ունի տվյալների փոխանակման նոր ալիք ստեղծելու 50% հնարավորություն (հատուկ քննարկում)
      • 50% հավանական է, որ հաղորդագրություն գրի գոյություն ունեցող ալիքներից որևէ մեկին

«Միայն միացումներ» սցենարը հայտնվեց մի պատճառով. Կա մի իրավիճակ՝ օգտատերերը միացրել են համակարգը, բայց դեռ չեն ներգրավվել։ Յուրաքանչյուր օգտատեր առավոտյան ժամը 09:00-ին միացնում է համակարգիչը, կապ է հաստատում սերվերի հետ և լռում։ Այս տղաները վտանգավոր են, նրանցից շատերը կան. նրանց միակ փաթեթը PING/PONG-ն է, բայց նրանք պահպանում են կապը սերվերի հետ (նրանք չեն կարող պահպանել այն, իսկ եթե նոր հաղորդագրություն լինի): Թեստը վերարտադրում է մի իրավիճակ, երբ մեծ թվով նման օգտատերեր կես ժամում փորձում են մուտք գործել համակարգ։ Այն նման է սթրես-թեստին, բայց դրա կենտրոնացումը հենց այս առաջին մուտքի վրա է, որպեսզի ձախողումներ չլինեն (մարդը չի օգտագործում համակարգը, և այն արդեն ընկնում է. դժվար է ավելի վատ բան մտածել):

Բաժանորդների գրանցման սցենարը սկսվում է առաջին գործարկումից: Մենք սթրես-թեստ անցկացրինք և վստահ էինք, որ նամակագրության ընթացքում համակարգը չի դանդաղում։ Բայց օգտատերերը եկան, և գրանցումը սկսեց ձախողվել ժամանակի ավարտի պատճառով։ Գրանցվելիս մենք օգտագործել ենք / dev / պատահական, որը կապված է համակարգի էնտրոպիայի հետ։ Սերվերը ժամանակ չուներ բավականաչափ էնտրոպիա կուտակելու համար, և երբ նոր SecureRandom պահանջվեց, այն սառեցվեց տասնյակ վայրկյաններով: Այս իրավիճակից դուրս գալու բազմաթիվ ուղիներ կան, օրինակ՝ անցում դեպի պակաս ապահով /dev/urandom, տեղադրեք հատուկ տախտակ, որը առաջացնում է էնտրոպիա, նախօրոք ստեղծեք պատահական թվեր և պահեք դրանք լողավազանում: Մենք ժամանակավորապես փակեցինք լողավազանի հետ կապված խնդիրը, սակայն այդ ժամանակվանից մենք առանձին թեստ ենք իրականացնում նոր բաժանորդների գրանցման համար։

Մենք օգտագործում ենք որպես բեռի գեներատոր MՄետր. Այն չգիտի, թե ինչպես աշխատել websocket-ի հետ, դրա համար անհրաժեշտ է plugin: «jmeter websocket» հարցման որոնման արդյունքներում առաջիններն են. հոդվածներ BlazeMeter-ից, որոնք խորհուրդ են տալիս plugin կողմից Maciej Zaleski.

Ահա թե որտեղից մենք որոշեցինք սկսել:

Լուրջ թեստավորում սկսելուց գրեթե անմիջապես հետո մենք հայտնաբերեցինք, որ JMeter-ը սկսեց արտահոսել հիշողությունից:

Պլագինը առանձին մեծ պատմություն է, 176 աստղերով, այն ունի 132 պատառաքաղ github-ում: Ինքը՝ հեղինակը, դրան չի պարտավորվել 2015 թվականից ի վեր (մենք վերցրել ենք 2015-ին, այնուհետև այն կասկածներ չի առաջացրել), մի քանի github խնդիրներ՝ կապված հիշողության արտահոսքի, 7 չփակված ձգման հարցումների հետ։
Եթե ​​որոշել եք բեռնվածության փորձարկում կատարել այս հավելվածի միջոցով, խնդրում ենք ուշադրություն դարձնել հետևյալ քննարկումներին.

  1. Բազմաթելային միջավայրում օգտագործվել է սովորական LinkedList, և արդյունքը եղել է Նոթ գործարկման ժամանակ: Սա կարող է լուծվել կամ անցնելով ConcurrentLinkedDeque-ին կամ համաժամացված բլոկների միջոցով: Մենք մեզ համար ընտրեցինք առաջին տարբերակը (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/43).
  2. Հիշողության արտահոսք; անջատելիս կապի տեղեկատվությունը չի ջնջվում (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/44).
  3. Հոսքային ռեժիմում (երբ վեբ-սոկետը փակված չէ նմուշի վերջում, բայց օգտագործվում է ավելի ուշ պլանում), արձագանքման օրինաչափությունները չեն աշխատում (https://github.com/maciejzaleski/JMeter-WebSocketSampler/issues/19).

Սա github-ում գտնվողներից մեկն է: Ինչ արեցինք.

  1. Վերցրել են պատառաքաղ Elyran Kogan (@elyrank) – այն ուղղում է 1 և 3 խնդիրները
  2. Լուծված խնդիր 2
  3. Թարմացված նավամատույցը 9.2.14-ից մինչև 9.3.12
  4. Փաթաթված SimpleDateFormat ThreadLocal-ում; SimpleDateFormat-ը անվտանգ չէ թելի համար, ինչը գործարկման ժամանակ հանգեցրեց NPE-ի
  5. Հիշողության հերթական արտահոսքը շտկվել է (միացումն անջատվելիս սխալ է փակվել)

Եվ այնուամենայնիվ այն հոսում է:

Հիշողությունը սկսեց սպառվել ոչ թե մեկ օրում, այլ երկուսում։ Բացարձակապես ժամանակ չէր մնացել, ուստի մենք որոշեցինք ավելի քիչ թեմաներ բացել, բայց չորս գործակալների վրա: Սա պետք է բավարար լիներ առնվազն մեկ շաբաթվա համար։

Երկու օր է անցել...

Այժմ Hazelcast-ի հիշողությունը սպառվում է: Տեղեկամատյանները ցույց տվեցին, որ մի քանի օր փորձարկումից հետո Hazelcast-ը սկսեց բողոքել հիշողության պակասից, և որոշ ժամանակ անց կլաստերը քանդվեց, և հանգույցները շարունակեցին հերթով մեռնել: Մենք JVisualVM-ը միացրինք hazelcast-ին և տեսանք «բարձրացող սղոց». այն պարբերաբար կանչում էր GC, բայց չէր կարողանում մաքրել հիշողությունը:

Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast

Պարզվեց, որ hazelcast 3.4-ում քարտեզ / multiMap (map.destroy()) ջնջելիս հիշողությունն ամբողջությամբ չի ազատվում.

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

Սխալն այժմ շտկված է 3.5-ում, բայց այն ժամանակ դա խնդիր էր: Ստեղծեցինք նոր մուլտիՔարտեզներ դինամիկ անուններով և ջնջեցինք դրանք մեր տրամաբանությամբ։ Կոդն այսպիսի տեսք ուներ.

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

Զանգել՝

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

multiMap-ը ստեղծվել է յուրաքանչյուր բաժանորդագրության համար և ջնջվել, երբ դրա կարիքը չկար: Որոշեցինք, որ սկսելու ենք Քարտեզը , բանալին կլինի բաժանորդագրության անվանումը, իսկ արժեքները կլինեն նստաշրջանի նույնացուցիչները (որից հետո անհրաժեշտության դեպքում կարող եք ձեռք բերել օգտվողի նույնացուցիչներ):

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

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

Գծապատկերները բարելավվել են։

Ինչպե՞ս և ինչու ենք մենք գրել 1C-ի համար բարձր բեռնված մասշտաբային ծառայություն. Ձեռնարկություն՝ Java, PostgreSQL, Hazelcast

Էլ ի՞նչ ենք սովորել բեռնվածության փորձարկման մասին:

  1. JSR223-ը պետք է գրվի ակոսով և ներառի կոմպիլյացիոն քեշը. դա շատ ավելի արագ է: ՈՒղեցույց.
  2. Jmeter-Plugins գրաֆիկներն ավելի հեշտ են հասկանալի, քան ստանդարտները: ՈՒղեցույց.

Hazelcast-ի հետ մեր փորձի մասին

Hazelcast-ը մեզ համար նոր արտադրանք էր, մենք դրա հետ սկսել ենք աշխատել 3.4.1 տարբերակից, այժմ մեր արտադրական սերվերն աշխատում է 3.9.2 տարբերակով (գրելու պահին Hazelcast-ի վերջին տարբերակը 3.10-ն է):

ID-ի ստեղծում

Մենք սկսեցինք ամբողջ թվերի նույնացուցիչներից: Եկեք պատկերացնենք, որ նոր էության համար մեզ պետք է ևս մեկ Երկար: Տվյալների բազայում հաջորդականությունը հարմար չէ, աղյուսակները ներգրավված են sharding-ում. պարզվում է, որ DB1-ում կա ID=1 հաղորդագրություն և DB1-ում ID=2, դուք չեք կարող այս ID-ն տեղադրել Elasticsearch-ում, ոչ էլ Hazelcast-ում: , բայց ամենավատն այն է, եթե ցանկանում եք միավորել երկու տվյալների բազայի տվյալները մեկի մեջ (օրինակ՝ որոշել, որ մեկ տվյալների բազան բավարար է այս բաժանորդների համար)։ Դուք կարող եք ավելացնել մի քանի AtomicLongs Hazelcast-ին և պահել հաշվիչը այնտեղ, այնուհետև նոր ID-ի ստացման արդյունավետությունը կլինի incrementAndGet գումարած Hazelcast-ին հարցում ստանալու ժամանակը: Բայց Hazelcast-ն ավելի օպտիմալ բան ունի՝ FlakeIdGenerator: Յուրաքանչյուր հաճախորդի հետ կապվելիս նրանց տրվում է ID միջակայք, օրինակ՝ առաջինը՝ 1-ից մինչև 10, երկրորդը՝ 000-ից մինչև 10 և այլն: Այժմ հաճախորդը կարող է ինքնուրույն թողարկել նոր նույնացուցիչներ, քանի դեռ իրեն տրված տիրույթը չի ավարտվել: Այն արագ է աշխատում, բայց երբ դուք վերագործարկում եք հավելվածը (և Hazelcast հաճախորդը), սկսվում է նոր հաջորդականություն, հետևաբար բացթողումներ և այլն: Բացի այդ, մշակողները իրականում չեն հասկանում, թե ինչու են ID-ները ամբողջ թվով, բայց այդքան անհամապատասխան: Մենք կշռեցինք ամեն ինչ և անցանք UUID-ներին:

Ի դեպ, նրանց համար, ովքեր ցանկանում են նմանվել Twitter-ին, կա այսպիսի Snowcast գրադարան՝ սա Snowflake-ի իրականացումն է Hazelcast-ի վերևում։ Այն կարող եք դիտել այստեղ՝

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

Բայց մենք այլևս չենք հասել դրան:

TransactionMap.replace

Մեկ այլ անակնկալ. TransactionalMap.replace-ը չի աշխատում: Ահա թեստ.

@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.

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

Փորձարկեք ոչ միայն սովորական տվյալների կառուցվածքները, այլև դրանց գործարքային տարբերակները: Պատահում է, որ IMap-ն աշխատում է, բայց TransactionalMap-ն այլևս գոյություն չունի։

Տեղադրեք նոր JAR առանց խափանումների

Նախ, մենք որոշեցինք գրանցել մեր դասերի առարկաները Hazelcast-ում: Օրինակ, մենք ունենք Application դաս, մենք ցանկանում ենք պահպանել և կարդալ այն: Պահպանել:

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

Մենք կարդում ենք:

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

Ամեն ինչ աշխատում է։ Այնուհետև մենք որոշեցինք ստեղծել ինդեքս Hazelcast-ում՝ որոնելու համար՝

map.addIndex("subscriberId", false);

Իսկ նոր էություն գրելիս նրանք սկսեցին ստանալ ClassNotFoundException։ Hazelcast-ը փորձեց ավելացնել ինդեքսը, բայց ոչինչ չգիտեր մեր դասի մասին և ցանկանում էր, որ այս դասի հետ JAR մատակարարվի դրան: Մենք հենց այդպես էլ արեցինք, ամեն ինչ աշխատեց, բայց հայտնվեց նոր խնդիր՝ ինչպե՞ս թարմացնել JAR-ը առանց կլաստերի ամբողջական դադարեցման: Hazelcast-ը չի վերցնում նոր JAR-ը հանգույց առ հանգույց թարմացման ժամանակ: Այս պահին մենք որոշեցինք, որ կարող ենք ապրել առանց ինդեքսների որոնման: Ի վերջո, եթե դուք օգտագործում եք Hazelcast-ը որպես առանցքային արժեքի խանութ, ապա ամեն ինչ կաշխատի: Իրականում ոչ: Այստեղ կրկին տարբերվում է IMap-ի և TransactionalMap-ի վարքագիծը։ Այնտեղ, որտեղ IMap-ին չի հետաքրքրում, TransactionalMap-ը սխալ է թույլ տալիս:

IM քարտեզ. Գրում ենք 5000 առարկա, կարդում։ Ամեն ինչ սպասելի է.

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

Բայց դա չի աշխատում գործարքի մեջ, մենք ստանում ենք 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();
        }
    });
}

3.8-ում հայտնվեց User Class Deployment մեխանիզմը: Դուք կարող եք նշանակել մեկ հիմնական հանգույց և թարմացնել JAR ֆայլը դրա վրա:

Այժմ մենք ամբողջովին փոխել ենք մեր մոտեցումը. մենք ինքներս այն սերիականացնում ենք JSON-ում և պահում այն ​​Hazelcast-ում: Hazelcast-ը կարիք չունի իմանալու մեր դասերի կառուցվածքը, և մենք կարող ենք թարմացնել առանց պարապուրդի: Դոմենի օբյեկտների տարբերակումը վերահսկվում է հավելվածի կողմից: Հավելվածի տարբեր տարբերակները կարող են միաժամանակ գործարկվել, և հնարավոր է իրավիճակ, երբ նոր հավելվածը նոր դաշտերով օբյեկտներ է գրում, իսկ հինը դեռ չգիտի այդ դաշտերի մասին։ Եվ միևնույն ժամանակ, նոր հավելվածը կարդում է հին հավելվածի կողմից գրված օբյեկտները, որոնք չունեն նոր դաշտեր։ Մենք դիմում ենք նման իրավիճակներին, բայց պարզության համար մենք դաշտերը չենք փոխում կամ ջնջում, մենք միայն ընդլայնում ենք դասերը՝ ավելացնելով նոր դաշտեր։

Ինչպես ենք մենք ապահովում բարձր կատարողականություն

Չորս ուղևորություն դեպի Hazelcast՝ լավ, երկու՝ դեպի տվյալների բազա՝ վատ

Տվյալների համար քեշ գնալը միշտ ավելի լավ է, քան տվյալների բազա գնալը, բայց դուք նույնպես չեք ցանկանում պահել չօգտագործված գրառումները: Մենք թողնում ենք որոշումը այն մասին, թե ինչ քեշել մինչև զարգացման վերջին փուլը։ Երբ նոր ֆունկցիոնալությունը կոդավորված է, մենք միացնում ենք բոլոր հարցումների գրանցումը PostgreSQL-ում (log_min_duration_statement մինչև 0) և 20 րոպե անցկացնում բեռնվածության փորձարկումը: Օգտագործելով հավաքագրված տեղեկամատյանները՝ pgFouine-ը և pgBadger-ը կարող են ստեղծել վերլուծական հաշվետվություններ: Հաշվետվություններում մենք հիմնականում փնտրում ենք դանդաղ և հաճախակի հարցումներ: Դանդաղ հարցումների համար մենք կառուցում ենք կատարման պլան (EXPLAIN) և գնահատում, թե արդյոք նման հարցումը կարող է արագացվել: Նույն մուտքային տվյալների հաճախակի հարցումները լավ տեղավորվում են քեշի մեջ: Մենք փորձում ենք հարցումները պահել «հարթ»՝ մեկ հարցման համար մեկ սեղան:

Շահագործում

SV-ն որպես առցանց ծառայություն շահագործման է հանձնվել 2017 թվականի գարնանը, իսկ որպես առանձին ապրանք SV-ն թողարկվել է 2017 թվականի նոյեմբերին (այն ժամանակ բետա տարբերակի կարգավիճակում)։

Ավելի քան մեկ տարվա գործունեության ընթացքում ԿԲ առցանց ծառայության գործունեության մեջ լուրջ խնդիրներ չեն եղել։ Մենք վերահսկում ենք առցանց ծառայությունը միջոցով Zabbix- ը, հավաքել և տեղակայել Բամբուկ.

SV սերվերի բաշխումը մատակարարվում է բնիկ փաթեթների տեսքով՝ RPM, DEB, MSI: Plus Windows-ի համար մենք տրամադրում ենք մեկ տեղադրող՝ մեկ EXE-ի տեսքով, որը տեղադրում է սերվերը, Hazelcast-ը և Elasticsearch-ը մեկ մեքենայի վրա: Մենք ի սկզբանե վերաբերում էինք տեղադրման այս տարբերակին որպես «դեմո» տարբերակ, սակայն այժմ պարզ է դարձել, որ սա ամենահայտնի տեղակայման տարբերակն է:

Source: www.habr.com

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