Fəsil 12 · 8 dəq oxu

Distributed data pattern-ləri

Distributed data pattern-ləri

"order yarat, pulu çıx, məhsulu rezerv et" tək database transaction-ıdır: ya hamısı olur, ya heç biri. Sistemi service-lərə böləndə order, payment və inventory ayrı database-lərə düşür və onları bir COMMIT ilə bağlamaq artıq mümkün deyil. ən çətin problem şəbəkə də, da deyil. Ən çətini service boundary-lərini aşan transaction-lardır.

Bu fəsil həmin problemə qarşı pattern-ləri izah edir: , və . Hər biri real problemi həll edir və hər biri ciddi mürəkkəblik gətirir. Məqsəd hansının nə vaxt pulunu çıxardığını görməkdir.

Database-per-service və onun qiyməti

Ch04-dəki qayda burada başlanğıc nöqtəsidir: hər data parçasının tam olaraq bir writer-i var. bunun praktik forması -dir: hər service öz data-sına sahibdir, digərləri ona yalnız API və ya event vasitəsilə çatır. Ayrı server şərt deyil: başqa service-lərin birbaşa toxunmadığı ayrı schema da kifayətdir.

Bunun alternativi olan , yəni bir neçə service-in eyni cədvəllərə yazması, anti-pattern-dir. Join-lər və transaction-lar yerində qalır, amma bir service-in schema dəyişikliyi hamının dəyişikliyinə çevrilir, deploy-lar yenidən birləşir və ch03-dəki alınır.

müstəqil və müstəqil scale verir. Qiyməti isə bu fəslin qalan hissəsidir:

  • Join yoxdur. "Müştərinin adı ilə son order-ləri" artıq iki service-ə çağırış və ya data kopyası tələb edir.
  • Transaction-lar lokaldır. Bir neçə service-i əhatə edən atomik dəyişiklik yoxdur.
  • Reporting çətinləşir. Bütün biznes üzrə hesabat üçün data-nı ayrıca yerə yığmaq lazımdır. Buna data pipeline-lar fəslində qayıdacağıq.

2PC-dən dual write-a: atomiklik problemi

İlk instinkt klassik həllə, əl atmaqdır. Kağız üzərində problem həll olunur, praktikada isə üçün çox vaxt yanlış cavabdır:

  • Blocking. Prepare mərhələsindən sonra iştirakçılar lock-ları saxlayır və coordinator-un qərarını gözləyir. Coordinator bu anda düşsə, onlar ilişib qalır.
  • Coordinator single point of failure-dur. Bütün yazmalar onun sağlamlığından asılıdır.
  • vurulur. Ch07-dəki riyaziyyat burada amansızdır: transaction yalnız bütün iştirakçılar eyni anda sağlam olanda uğurlu olur. Dörd service-in hər biri 99.9%-dirsə, birlikdə təxminən 99.6% alınır.
  • Dəstək yoxdur. Kafka kimi broker-lər və store-ların çoxu XA transaction-larda iştirak etmir.

imtina edəndə daha gizli problem qalır: . Service həm öz database-inə yazmalı, həm də digərlərinə xəbər vermək üçün event publish etməlidir. Bu iki yazmanı ardıcıl edəndə aralarında crash olarsa, ya event itir, ya da heç vaxt commit olunmamış dəyişiklik haqqında event yayılır. Ch05-dəki nümunəsi məhz bu problemi ilə həll edirdi: event biznes dəyişikliyi ilə eyni lokal transaction-da cədvəlinə yazılır, relay və ya isə onu broker-ə çatdırır. Nəticə , ona görə consumer-lər olmalıdır. Etibarlı event publish etmək bu fəsildəki hər şeyin təməlidir: onsuz nə , nə də işləyir.

Saga: lokal transaction-lar zənciri

distributed transaction-ı lokal transaction-lar ardıcıllığına bölür. Hər addım öz service-ində öz database-inə commit edir və növbəti addımı işə salır. Addımlardan biri uğursuz olanda, artıq bitmiş addımlar -larla tarazlanır: hər addımın onu "ləğv edən" cütü olur.

ACID mənasında atomik deyil: sistem bir müddət aralıq vəziyyətdə qalır və sonda ya tam uğurlu, ya da tam kompensasiya olunmuş vəziyyətə çatır. Bu, biznes prosesi səviyyəsində .

Choreography və orchestration

koordinasiya etməyin iki yolu var. -də mərkəz yoxdur: hər service event-ləri dinləyir və öz addımını atıb yeni event publish edir. Orchestration-da isə ayrıca orchestrator hər service-ə nə edəcəyini deyir və cavabları gözləyir. Eyni sifariş prosesi iki üsulla belə görünür:

Addım (event-lər)Orchestration (command-lar)Compensation
1. OrderOrder service order-i PENDING yaradır, OrderCreated publish edirOrchestrator order-i PENDING yaradırOrder CANCELLED olur
2. PaymentPayment service OrderCreated-i dinləyir, pulu bloklayır, PaymentAuthorized publish edirOrchestrator payment-ə AuthorizePayment göndərirBloklanmış məbləğ azad edilir (çıxılıbsa, refund)
3. InventoryInventory PaymentAuthorized-i dinləyir, məhsulu rezerv edir, StockReserved publish edirOrchestrator inventory-yə ReserveStock göndərirRezerv buraxılır
4. ShippingShipping StockReserved-i dinləyir, göndərişi planlaşdırır, ShipmentScheduled publish edirOrchestrator shipping-ə ScheduleShipment göndərirGöndəriş ləğv edilir
Uğursuzluq: anbarda məhsul yoxdurInventory StockReservationFailed publish edir; payment və order onu dinləyib öz compensation-larını edirOrchestrator xətanı alır və compensation-ları tərs sırada çağırır: payment, sonra order—

güclü tərəfi loose : yeni addım əlavə etmək üçün mövcud service-lərə toxunmadan yeni event-i dinləmək kifayətdir. Zəif tərəfi görünürlükdür. Proses heç bir yerdə yazılmayıb, o, beş service-in kodunda səpələnib. "Bu order hazırda harada ilişib?" sualına cavab üçün lazımdır. Addımlar artdıqca service-lər arasında cyclic dependency yaranır: payment inventory-nin event-lərini dinləyir, inventory isə payment-in.

Orchestration prosesi bir yerdə, oxuna bilən state machine kimi saxlayır: uğursuzluq və idarə etmək, order-in vəziyyətini göstərmək asanlaşır. Qiyməti mərkəzləşmədir: orchestrator bütün service-lər haqqında bilir və diqqət edilməsə, biznes məntiqi oraya axır, service-lər isə anemic CRUD-a çevrilir. Orchestrator həm də öz state-i olan critical komponentdir.

Praktik qayda: iki-üç addımlıq sadə axın üçün kifayətdir. Dörd və daha çox addım, budaqlanma və ya ciddi compensation məntiqi varsa, orchestration daha az ağrı verir.

Compensation rollback deyil

Database dəyişikliyin heç vaxt olmadığını iddia edir. isə bunu edə bilməz, çünki dəyişiklik artıq commit olunub və başqaları onu görüb. O, semantic undo-dur: biznes baxımından əks hərəkət.

Bəzi addımları geri qaytarmaq ümumiyyətlə mümkün deyil. Müştəriyə "sifarişiniz təsdiqləndi" e-poçtu gedibsə, onu geri çağırmaq olmaz, yalnız ikinci, üzrxahlıq e-poçtu göndərmək olar. Refund da izsiz deyil: müştəri bank çıxarışında iki əməliyyat görür. Buna görə geri qaytarılmayan addımları sonuna qoymaq lazımdır, uğursuz ola biləcək hər şeydən sonra.

İkinci problem isolation-ın olmamasıdır. gedərkən onun aralıq nəticələri digər transaction-lara görünür. Başqa sorğu bir saniyə sonra buraxılacaq rezervi görə bilər (dirty read). Müdafiə vasitələri:

  • "Pending" state-lər. Order PENDING vəziyyətindədirsə, UI və digər service-lər onu hələ bitmiş kimi qəbul etmir.
  • Semantic lock. Aralıq vəziyyətdəki obyekt digər saga-lar üçün "məşğul" işarələnir və onlar ya gözləyir, ya da imtina edir.

CQRS: oxuma ilə yazmanı ayırmaq

() yazma və oxuma üçün ayrı model saxlamaq deməkdir. Yazma tərəfi biznes qaydalarını yoxlayan normallaşdırılmış modeldir. Oxuma tərəfi bir və ya bir neçə -dir: konkret ekran və ya sorğu üçün hazır formada denormallaşdırılmış data. Yazma tərəfi event-lər publish edir, onları dinləyib yenilənir.

Nə vaxt pulunu çıxarır:

  • Oxuma ilə yazma arasında böyük asimmetriya. Saniyədə 50 yazma və 50 000 oxuma varsa, onları ayrıca scale etmək mənalıdır.
  • Mürəkkəb query-lər. Order, customer və shipping data-sını birləşdirən dashboard join edə bilmir, amma üç service-in event-lərindən qurulan edə bilər.
  • Fərqli storage ehtiyacları. Yazma Postgres-də, axtarış Elasticsearch-də.

Adi CRUD ekranlarında, oxuma və yazma modeli eyni görünəndə isə iki model, sinxronizasiya kodu və yeni failure rejimləri gətirir, əvəzində heç nə vermir.

Ən çox unudulan qiymət eventual consistency-sidir: istifadəçi order yaradır, siyahıya qayıdır və order orada yoxdur. Bu, ch10-dakı ilə eyni problemdir və həlləri də oxşardır: UI-da yeni order-i optimistic göstərmək, command cavabında versiya qaytarıb həmin versiyaya çatana qədər gözləmək, və ya "emal olunur" vəziyyətini açıq göstərmək.

Event sourcing: state əvəzinə tarixçə

Adi sistem cari state-i saxlayır: hesabın balansı 120 manatdır. isə state-ə gətirən event-ləri saxlayır: hesab açıldı, 200 manat mədaxil, 80 manat məxaric. Cari state bu event-lərin ardıcıl tətbiqi (replay) ilə alınır. Event-lər yalnız əlavə olunan, dəyişdirilməyən -da saxlanır.

Nə verir:

  • Tam audit. Nəyin, nə vaxt və niyə dəyişdiyi data modelinin özündədir, sonradan əlavə edilmiş log deyil.
  • Temporal query-lər. "Bu hesab 3 mart tarixində necə görünürdü?" sualı replay ilə cavablanır.
  • Yeni . Sabahın hesabatını bütün tarixçə üzərində qurmaq olar.

Uzun tarixçəni hər dəfə replay etmək baha olduğu üçün snapshot-lar saxlanır və replay sonuncusundan davam edir.

Risklər isə ciddidir. Birincisi, event schema təkamülü: event-lər əbədidir, ona görə beş il əvvəlki OrderPlaced formatını kod hələ də oxumalıdır. Versioning və upcasting daimi işə çevrilir. İkincisi, öyrənmə əyrisi: səhv data "sətri düzəltməklə" yox, düzəldici yeni event ilə düzəlir. Üçüncüsü, ən geniş yayılmış səhv: hər yerə tətbiq etmək. O, mühasibat, ödəniş və hüquqi audit tələb olunan domain-lər üçündür, istifadəçi profilləri və parametrlər üçün yox.

və tez-tez birlikdə gəlir, çünki sorğu üçün əlverişsizdir və ehtiyac yaranır. Amma onlar bir-birindən asılı deyil: adi database ilə, isə ayrıca olmadan, birbaşa replay ilə də işləyə bilər.

Mürəkkəblik büdcəsi

Hər pattern mürəkkəblik büdcəsindən xərcləyir. Onları dəb üçün yox, konkret problemə görə seçin:

Problemİlk baxılacaq həllNə vaxt lazım deyil
Dəyişiklik və event birlikdə yazılmalıdır və ya Event publish etmirsinizsə
Biznes prosesi bir neçə service-i əhatə edir; addım çoxdursa orchestrationAddımlar bir service-ə sığırsa: boundary-ləri yenidən düşünün
Service-lər üzrə sorğu və ya oxuma yükü Bir database və bir neçə index kifayət edirsə
Audit, tarixçə, "o vaxt necə idi?" cədvəli kifayət edirsə

İkinci sətirdəki qeyd təsadüfi deyil: ehtiyac çox vaxt boundary-nin yanlış yerdə çəkildiyinin, ch04-dəki "chatty interface"-in əlamətidir. Bəzən ən yaxşı distributed transaction iki service-i birləşdirib onu lazımsız etməkdir. Hansı pattern-in hansı problem üçün seçildiyi isə ch05-dəki mənada layiq qərardır.

Hazırkı sisteminiz haqqında suallar

  1. Hansı cədvəllərinizə birdən çox service yazır və ya birbaşa oxuyur? Onların schema-sını dəyişmək üçün neçə komanda ilə danışmalısınız?
  2. Database-ə yazıb sonra event və ya mesaj göndərən kod yolunu tapın. Aralarında process crash olarsa, nə itir?
  3. Bir neçə service-dən keçən ən uzun biznes prosesiniz üçün hər addımın compensation-ını yazın. Hansı addım geri qaytarıla bilmir və o, prosesin sonundadırmı?
  4. və ya geri qaldıqda istifadəçi nə görür? Bunu UI-da kimsə qəsdən dizayn edibmi?