Հինգ ուսանող և երեքը բաշխեցին առանցքային արժեքի խանութներ

Կամ ինչպես ենք մենք գրել հաճախորդի C++ գրադարան ZooKeeper-ի, etcd-ի և Consul KV-ի համար

Բաշխված համակարգերի աշխարհում կան մի շարք բնորոշ առաջադրանքներ՝ կլաստերի կազմի մասին տեղեկատվության պահպանում, հանգույցների կոնֆիգուրացիայի կառավարում, անսարք հանգույցների հայտնաբերում, առաջնորդի ընտրություն։ այլ. Այս խնդիրները լուծելու համար ստեղծվել են հատուկ բաշխված համակարգեր՝ համակարգման ծառայություններ։ Այժմ մեզ կհետաքրքրի դրանցից երեքը՝ ZooKeeper, etcd և Consul: Հյուպատոսի բոլոր հարուստ գործառույթներից մենք կկենտրոնանանք Consul KV-ի վրա:

Հինգ ուսանող և երեքը բաշխեցին առանցքային արժեքի խանութներ

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

Այս խնդիրը լուծելու գաղափարը ծագել է ավստրալական խորհրդատվական գործակալությունում, և այն իրականացնելու համար մեզ՝ ուսանողների փոքր թիմին բաժին ընկավ, ինչի մասին ես խոսելու եմ:

Մեզ հաջողվեց ստեղծել գրադարան, որն ապահովում է ընդհանուր ինտերֆեյս ZooKeeper-ի, etcd-ի և Consul KV-ի հետ աշխատելու համար: Գրադարանը գրված է C++-ով, սակայն նախատեսվում է այն տեղափոխել այլ լեզուներով:

Տվյալների մոդելներ

Երեք տարբեր համակարգերի համար ընդհանուր ինտերֆեյս մշակելու համար դուք պետք է հասկանաք, թե դրանք ինչ ընդհանուր են և ինչպես են դրանք տարբերվում: Եկեք պարզենք այն:

ZooKeeper

Հինգ ուսանող և երեքը բաշխեցին առանցքային արժեքի խանութներ

Բանալիները կազմակերպվում են ծառի մեջ և կոչվում են հանգույցներ: Համապատասխանաբար, հանգույցի համար կարող եք ստանալ նրա երեխաների ցուցակը: Znode ստեղծելու (ստեղծել) և արժեքի փոփոխման (setData) գործողությունները տարանջատված են. կարելի է կարդալ և փոխել միայն առկա ստեղները: Ժամացույցները կարող են կցվել հանգույցի առկայությունը ստուգելու, արժեք կարդալու և երեխաներ ստանալու գործողություններին: Watch-ը միանգամյա գործարկիչ է, որն աշխատում է, երբ սերվերի վրա համապատասխան տվյալների տարբերակը փոխվում է: Վատ հանգույցները օգտագործվում են ձախողումները հայտնաբերելու համար: Նրանք կապված են հաճախորդի նիստին, որը ստեղծել է դրանք: Երբ հաճախորդը փակում է նիստը կամ դադարում է ZooKeeper-ին ծանուցել դրա գոյության մասին, այդ հանգույցներն ավտոմատ կերպով ջնջվում են: Աջակցվում են պարզ գործարքներ. գործողությունների մի շարք, որոնք կամ բոլորը հաջողվում են, կամ ձախողվում, եթե դա հնարավոր չէ դրանցից գոնե մեկի համար:

և այլն

Հինգ ուսանող և երեքը բաշխեցին առանցքային արժեքի խանութներ

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

etcd-ը չունի ստանդարտ համեմատել և սահմանել գործողություն, բայց ունի ավելի լավ բան՝ գործարքներ: Իհարկե, դրանք կան բոլոր երեք համակարգերում, բայց etcd գործարքները հատկապես լավ են: Դրանք բաղկացած են երեք բլոկներից՝ ստուգում, հաջողություն, ձախողում: Առաջին բլոկը պարունակում է մի շարք պայմաններ, երկրորդը և երրորդը՝ գործողություններ: Գործարքը կատարվում է ատոմային: Եթե ​​բոլոր պայմանները ճշմարիտ են, ապա հաջողության բլոկը կատարվում է, հակառակ դեպքում՝ ձախողման բլոկը: API 3.3-ում հաջողության և ձախողման բլոկները կարող են ներառված գործարքներ պարունակել: Այսինքն՝ հնարավոր է ատոմային կերպով կատարել գրեթե կամայական բնադրման մակարդակի պայմանական կոնստրուկցիաներ։ Դուք կարող եք ավելին իմանալ այն մասին, թե ինչ ստուգումներից և գործառնություններից են գոյություն ունենում փաստաթղթավորում.

Ժամացույցներ կան նաև այստեղ, թեև դրանք մի փոքր ավելի բարդ են և կարող են կրկնակի օգտագործել: Այսինքն՝ առանցքային տիրույթում ժամացույցը տեղադրելուց հետո դուք կստանաք այս տիրույթի բոլոր թարմացումները, մինչև չեղարկեք ժամացույցը, և ոչ միայն առաջինը։ Եվ այլն, ZooKeeper հաճախորդների նիստերի անալոգը վարձակալությունն է:

Հյուպատոս Կ.Վ.

Այստեղ նույնպես չկա խիստ հիերարխիկ կառուցվածք, բայց հյուպատոսը կարող է ստեղծել այն տեսքը, որ այն կա. դուք կարող եք ստանալ և ջնջել բոլոր ստեղները նշված նախածանցով, այսինքն՝ աշխատել բանալու «ենթածառի» հետ: Նման հարցումները կոչվում են ռեկուրսիվ: Բացի այդ, հյուպատոսը կարող է ընտրել միայն այն ստեղները, որոնք չեն պարունակում նախածանցից հետո նշված նիշը, որը համապատասխանում է անմիջական «երեխաներ» ստանալուն: Բայց հարկ է հիշել, որ սա հենց հիերարխիկ կառուցվածքի տեսքն է. միանգամայն հնարավոր է ստեղծել բանալի, եթե նրա ծնողը գոյություն չունի կամ ջնջել բանալին, որն ունի երեխաներ, մինչդեռ երեխաները կշարունակեն պահպանվել համակարգում:

Հինգ ուսանող և երեքը բաշխեցին առանցքային արժեքի խանութներ
Ժամացույցների փոխարեն հյուպատոսն արգելափակում է HTTP հարցումները: Ըստ էության, դրանք սովորական զանգեր են տվյալների ընթերցման մեթոդին, որոնց համար, այլ պարամետրերի հետ մեկտեղ, նշվում է տվյալների վերջին հայտնի տարբերակը։ Եթե ​​սերվերի վրա համապատասխան տվյալների ընթացիկ տարբերակը ավելի մեծ է, քան նշվածը, պատասխանը վերադարձվում է անմիջապես, հակառակ դեպքում՝ երբ արժեքը փոխվում է: Կան նաև նիստեր, որոնք ցանկացած պահի կարող են կցվել ստեղներին: Հարկ է նշել, որ ի տարբերություն etcd-ի և ZooKeeper-ի, որտեղ նիստերի ջնջումը հանգեցնում է հարակից ստեղների ջնջմանը, կա մի ռեժիմ, որտեղ նիստը պարզապես անջատված է դրանցից: Հասանելի է գործարքներ, առանց մասնաճյուղերի, բայց բոլոր տեսակի չեկերով։

Այս ամենը միասին դնելով

ZooKeeper-ն ունի տվյալների ամենախիստ մոդելը: Էքսպրեսիվ տիրույթի հարցումները, որոնք հասանելի են etcd-ում, չեն կարող արդյունավետ կերպով ընդօրինակվել ոչ ZooKeeper-ում, ոչ էլ Consul-ում: Փորձելով ներառել լավագույնը բոլոր ծառայություններից, մենք ստացանք ZooKeeper ինտերֆեյսի գրեթե համարժեք ինտերֆեյս՝ հետևյալ նշանակալի բացառություններով.

  • հաջորդականություն, կոնտեյներ և TTL հանգույցներ չի ապահովվում
  • ACL-ները չեն աջակցվում
  • set մեթոդը ստեղծում է բանալի, եթե այն գոյություն չունի (ZK-ում setData-ն այս դեպքում վերադարձնում է սխալ)
  • set և cas մեթոդները առանձնացված են (ZK-ում դրանք ըստ էության նույն բանն են)
  • Ջնջման մեթոդը ջնջում է հանգույցն իր ենթածառի հետ միասին (ZK delete-ում սխալ է վերադարձվում, եթե հանգույցը երեխաներ ունի)
  • Յուրաքանչյուր բանալիի համար կա միայն մեկ տարբերակ՝ արժեքային տարբերակը (ZK-ում դրանք երեքն են)

Հերթական հանգույցների մերժումը պայմանավորված է նրանով, որ etcd-ը և Consul-ը ներկառուցված աջակցություն չունեն դրանց համար, և դրանք հեշտությամբ կարող են իրականացվել օգտագործողի կողմից ստացված գրադարանի միջերեսի վերևում:

Գագաթը ջնջելիս ZooKeeper-ի նման վարքագիծը կպահանջի պահպանել առանձին երեխա հաշվիչ յուրաքանչյուր բանալի համար etcd-ում և Consul-ում: Քանի որ մենք փորձեցինք խուսափել մետա տեղեկատվության պահպանումից, որոշվեց ջնջել ամբողջ ենթածառը:

Իրականացման նրբությունները

Եկեք ավելի սերտ նայենք տարբեր համակարգերում գրադարանային ինտերֆեյսի ներդրման որոշ ասպեկտներին:

Հիերարխիա և այլն

etcd-ում հիերարխիկ տեսակետի պահպանումն ամենահետաքրքիր խնդիրներից մեկն էր: Շրջանակի հարցումները հեշտացնում են ստեղների ցանկը նշված նախածանցով առբերելը: Օրինակ, եթե ձեզ անհրաժեշտ է այն ամենը, ինչից սկսվում է "/foo", դուք միջակայք եք խնդրում ["/foo", "/fop"). Բայց սա կվերադարձնի բանալու ամբողջ ենթածառը, որը կարող է անընդունելի լինել, եթե ենթածառը մեծ է: Սկզբում մենք նախատեսում էինք օգտագործել թարգմանության հիմնական մեխանիզմը, իրականացվում է zetcd-ում. Այն ներառում է մեկ բայթ ավելացնելով բանալու սկզբում, որը հավասար է ծառի հանգույցի խորությանը: Մի օրինակ բերեմ.

"/foo" -> "u01/foo"
"/foo/bar" -> "u02/foo/bar"

Ապա ստացեք բանալին բոլոր անմիջական երեխաներին "/foo" հնարավոր է՝ տիրույթ խնդրելով ["u02/foo/", "u02/foo0"). Այո, ASCII-ում "0" կանգնած է անմիջապես հետո "/".

Բայց ինչպե՞ս իրականացնել այս դեպքում գագաթի հեռացումը: Ստացվում է, որ դուք պետք է ջնջեք տիպի բոլոր միջակայքերը ["uXX/foo/", "uXX/foo0") XX-ի համար 01-ից մինչև FF: Եվ հետո մենք բախվեցինք գործառնությունների քանակի սահմանափակում մեկ գործարքի ընթացքում:

Արդյունքում հայտնագործվեց բանալիների փոխակերպման պարզ համակարգ, որը հնարավորություն տվեց արդյունավետ կերպով իրականացնել ինչպես բանալին ջնջելը, այնպես էլ երեխաների ցուցակը ստանալը։ Բավական է վերջին նշանից առաջ հատուկ նիշ ավելացնել։ Օրինակ:

"/very" -> "/u00very"
"/very/long" -> "/very/u00long"
"/very/long/path" -> "/very/long/u00path"

Այնուհետև ջնջելով բանալին "/very" վերածվում է ջնջման "/u00very" և միջակայք ["/very/", "/very0"), և ստանալ բոլոր երեխաներին՝ տիրույթից ստեղների խնդրանքով ["/very/u00", "/very/u01").

Բանալին հեռացնելով ZooKeeper-ում

Ինչպես արդեն նշեցի, ZooKeeper-ում դուք չեք կարող ջնջել հանգույցը, եթե այն երեխաներ ունի: Մենք ցանկանում ենք ջնջել բանալին ենթածառի հետ միասին: Ինչ պետք է անեմ? Մենք դա անում ենք լավատեսությամբ։ Նախ, մենք ռեկուրսիվորեն անցնում ենք ենթածառի վրա՝ ստանալով յուրաքանչյուր գագաթի երեխաները առանձին հարցումով: Այնուհետև մենք կառուցում ենք գործարք, որը փորձում է ճիշտ հերթականությամբ ջնջել ենթածառի բոլոր հանգույցները: Իհարկե, փոփոխություններ կարող են տեղի ունենալ ենթածառը կարդալու և այն ջնջելու միջև: Այս դեպքում գործարքը կտապալվի: Ավելին, ենթածառը կարող է փոխվել ընթերցման գործընթացում։ Հաջորդ հանգույցի երեխաների հարցումը կարող է սխալ վերադարձնել, եթե, օրինակ, այս հանգույցն արդեն ջնջված է: Երկու դեպքում էլ մենք նորից կրկնում ենք ամբողջ գործընթացը:

Այս մոտեցումը շատ անարդյունավետ է դարձնում բանալին ջնջելը, եթե այն երեխաներ ունի, և նույնիսկ ավելին, եթե հավելվածը շարունակում է աշխատել ենթածառի հետ՝ ջնջելով և ստեղծելով ստեղներ: Այնուամենայնիվ, դա թույլ տվեց մեզ խուսափել այլ մեթոդների ներդրման բարդացումից և այլն և հյուպատոսում:

դրված է ZooKeeper-ում

ZooKeeper-ում կան առանձին մեթոդներ, որոնք աշխատում են ծառի կառուցվածքի հետ (ստեղծել, ջնջել, ստանալChildren) և որոնք աշխատում են տվյալների հետ հանգույցներում (setData, getData): Ավելին, բոլոր մեթոդներն ունեն խիստ նախապայմաններ. create-ը կվերադարձնի սխալ, եթե հանգույցն արդեն ունի: ստեղծվել, ջնջել կամ setData – եթե այն արդեն գոյություն չունի: Մեզ անհրաժեշտ էր մի շարք մեթոդ, որը կարելի է կանչել առանց բանալիի առկայության մասին մտածելու:

Տարբերակներից մեկը լավատեսական մոտեցում ցուցաբերելն է, ինչպես ջնջման դեպքում: Ստուգեք, արդյոք գոյություն ունի հանգույց: Եթե ​​կա, զանգահարեք setData, այլապես ստեղծեք: Եթե ​​վերջին մեթոդը վերադարձրեց սխալ, կրկնեք այն նորից: Առաջինը, որ պետք է նշել, այն է, որ գոյության թեստն անիմաստ է: Դուք կարող եք անմիջապես զանգահարել ստեղծել: Հաջողությամբ ավարտը կնշանակի, որ հանգույցը գոյություն չի ունեցել, և այն ստեղծվել է: Հակառակ դեպքում, create-ը կվերադարձնի համապատասխան սխալը, որից հետո պետք է զանգահարել setData: Իհարկե, զանգերի միջև գագաթը կարող է ջնջվել մրցակցային զանգի միջոցով, և setData-ն նույնպես սխալ կվերադարձնի: Այս դեպքում դուք կարող եք ամեն ինչ նորից անել, բայց արժե՞ արդյոք:

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

Լրացուցիչ տեխնիկական մանրամասներ

Այս բաժնում մենք կդադարենք բաշխված համակարգերից և կխոսենք կոդավորման մասին:
Հաճախորդի հիմնական պահանջներից մեկը քրոսպլատֆորմն էր. ծառայություններից առնվազն մեկը պետք է ապահովված լինի Linux-ի, MacOS-ի և Windows-ի վրա: Սկզբում մենք մշակեցինք միայն Linux-ի համար, իսկ ավելի ուշ սկսեցինք փորձարկել այլ համակարգերում: Սա բազմաթիվ խնդիրներ առաջացրեց, որոնց որոշ ժամանակ ամբողջովին անհասկանալի էր, թե ինչպես մոտենալ։ Արդյունքում, բոլոր երեք համակարգման ծառայություններն այժմ աջակցվում են Linux-ում և MacOS-ում, մինչդեռ միայն Consul KV-ն աջակցվում է Windows-ում:

Մենք ի սկզբանե փորձել ենք օգտվել պատրաստի գրադարաններից՝ ծառայություններից օգտվելու համար։ ZooKeeper-ի դեպքում ընտրությունն ընկավ ZooKeeper C++, որն ի վերջո չհաջողվեց հավաքել Windows-ում: Սա, սակայն, զարմանալի չէ. գրադարանը տեղադրված է միայն Linux-ով: Հյուպատոսի համար միակ տարբերակն էր ppconsul. Պետք էր դրան ավելացնել աջակցություն նիստեր и գործարքներ. etcd-ի համար արձանագրության վերջին տարբերակը աջակցող լիարժեք գրադարան չի գտնվել, ուստի մենք պարզապես ստեղծվել է grpc հաճախորդ.

Ոգեշնչված ZooKeeper C++ գրադարանի ասինխրոն ինտերֆեյսից՝ մենք որոշեցինք կիրառել նաև ասինխրոն ինտերֆեյս: ZooKeeper C++-ը դրա համար օգտագործում է ապագա/խոստումնալից պրիմիտիվներ: STL-ում, ցավոք, դրանք շատ համեստ են իրականացվում։ Օրինակ՝ ոչ ապա մեթոդ, որը կիրառում է անցած ֆունկցիան ապագայի արդյունքի վրա, երբ այն հասանելի է դառնում։ Մեր դեպքում նման մեթոդ է անհրաժեշտ արդյունքը մեր գրադարանի ձևաչափի վերածելու համար։ Այս խնդիրը շրջանցելու համար մենք պետք է գործադրեինք մեր սեփական պարզ thread pool-ը, քանի որ հաճախորդի խնդրանքով մենք չէինք կարող օգտագործել ծանր երրորդ կողմի գրադարանները, ինչպիսին է Boost-ը:

Մեր այն ժամանակվա իրականացումն աշխատում է այսպես. Երբ կանչվում է, լրացուցիչ խոստում/ապագա զույգ է ստեղծվում: Նոր ապագան վերադարձվում է, իսկ անցածը տեղադրվում է համապատասխան ֆունկցիայի և լրացուցիչ խոստման հետ միասին հերթում։ Լողավազանից շարանը ընտրում է մի քանի ֆյուչերսներ հերթից և հարցումներ անում՝ օգտագործելով wait_for: Երբ արդյունքը հասանելի է դառնում, համապատասխան ֆունկցիան կանչվում է և դրա վերադարձի արժեքը փոխանցվում է խոստմանը:

Մենք օգտագործեցինք նույն thread pool-ը՝ etcd-ին և Consul-ին հարցումներ կատարելու համար: Սա նշանակում է, որ հիմքում ընկած գրադարանները կարող են մուտք գործել մի քանի տարբեր թելերով: ppconsul-ը անվտանգ չէ թելի համար, ուստի զանգերը դրան պաշտպանված են կողպեքներով:
Դուք կարող եք աշխատել grpc-ի հետ մի քանի թելերից, բայց կան նրբություններ: etcd-ում ժամացույցներն իրականացվում են grpc հոսքերի միջոցով: Սրանք երկկողմանի ալիքներ են որոշակի տեսակի հաղորդագրությունների համար: Գրադարանը ստեղծում է մեկ թեմա բոլոր ժամացույցների համար և մեկ թեմա, որը մշակում է մուտքային հաղորդագրությունները: Այսպիսով, grpc-ն արգելում է զուգահեռ գրել հոսքին: Սա նշանակում է, որ ժամացույցը սկզբնավորելիս կամ ջնջելիս դուք պետք է սպասեք, մինչև նախորդ հարցումն ավարտի ուղարկումը, նախքան հաջորդը ուղարկելը: Մենք օգտագործում ենք համաժամացման համար պայմանական փոփոխականներ.

Լրիվ

Տես համար Ձեզ: լիբոֆկվ.

Մեր թիմը. Ռաեդ Ռոմանով, Իվան Գլուշենկով, Դմիտրի Քամալդինով, Վիկտոր Կրապիվենսկի, Վիտալի Իվանին.

Source: www.habr.com

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