Bir checkout, üç fərqli risk
Müştəri onlayn mağazada sifarişini təsdiqləyir və checkout service payment provider-ə sorğu göndərir. Ödəniş uğurla tamamlanır, amma cavab checkout service-ə çatmır. Bir neçə saniyə gözlədikdən sonra tətbiq xəta göstərir, müştəri isə “Yenidən cəhd et” düyməsini sıxır.
Checkout service ödənişin nəticəsini bilmədiyi üçün təkrar sorğu eyni əməliyyatın yenidən icrasına səbəb ola bilər. Bu müddətdə gecikən sorğular resursları məşğul saxlaya, avtomatik retry-lar isə sistemin yükünü artıra bilər. Beləliklə, itən bir cavab həm ödənişin vəziyyəti, həm də sistemin işləkliyi ilə bağlı problem yaradır.
Bu ssenarini anlamaq üçün paylanmış sistemlərin üç məhdudiyyətini birlikdə nəzərə almaq lazımdır:
- Məlumatın ötürülməsi vaxt tələb edir.
- Asılı olduğumuz sistemlər bəzən əlçatmaz olur.
- Resurslar məhduddur.
Məqalədə həmin checkout nümunəsi üzərindən bu məhdudiyyətlərin timeout, retry, məlumatın aktuallığı və yükün idarə edilməsi ilə bağlı qərarlara necə təsir etdiyinə baxacağıq.
Məlumatın ötürülməsi vaxt tələb edir
Kodda remote call adi metod çağırışı kimi görünə bilər:
inventoryClient.reserve(productId, quantity);
Bu çağırışın yerinə yetirilməsi üçün məlumat serialize edilməli, şəbəkə ilə ötürülməli və qarşı tərəfdə emal olunmalıdır. Remote service işi tamamladıqdan sonra cavab da geri göndərilir. Connection qurulması, şəbəkədə sıxlıq və queue-da gözləmə bu müddəti uzada bilər. HTTP client həmin addımların çoxunu koddan gizlətsə də, onların tələb etdiyi vaxt qalır.
Gecikmə məlumatın digər komponentlərə nə vaxt çatmasına da təsir edir. Məsələn, müştəri anbarda qalan son məhsulu aldıqda inventory service öz database-ini yeniləyir, search index və product cache isə bu dəyişikliyi asinxron qəbul edir. Yenilənmə onlara çatana qədər eyni məhsul haqqında fərqli məlumat görünə bilər:
- Inventory service məhsulun mövcud olmadığını bildirir.
- Search nəticələri məhsulu hələ də mövcud göstərir.
- Cache-dəki məhsul səhifəsində hələ də “Stokda var” yazılır.
Komponentlər həmin ana qədər aldıqları məlumatla işləyirlər. Buna görə dizaynda köhnəlmiş məlumatın, yəni stale data-nın hansı əməliyyatlarda qəbul edilə biləcəyi aydın olmalıdır. Axtarış nəticəsinin qısa müddət köhnə qalması məqbul sayıla bilər; ödənişə keçərkən isə məhsulun həqiqətən mövcud olduğuna əmin olmaq lazımdır.
Bu iki axının tələbləri fərqlidir. Məhsula baxış səhifəsi sürətli cavab üçün cache-dən oxuya bilər. Checkout isə inventory service-in əsas məlumatına söykənməli və son məhsula gələn paralel sifarişləri düzgün idarə edən rezerv əməliyyatından istifadə etməlidir.
Eyni məhsulun, fərqli anda görünən vəziyyəti
Alışdan əvvəl bütün komponentlər eyni vəziyyəti göstərir.
Checkout qərarı cache-ə deyil, paralel rezervasiyaları düzgün idarə edən inventory əməliyyatına əsaslanmalıdır.Hər çağırışın vaxt büdcəsində yeri var
Hər remote call sorğunun latency budget-indən, yəni cavab üçün ayrılmış ümumi vaxtdan pay götürür. Ardıcıl çağırışların gecikmələri toplanır; bir dependency-nin yavaşlaması digər addımlara ayrılan vaxtı da azaldır. Buna görə yeni service call əlavə edərkən onun cavabı üçün nə qədər gözləyə biləcəyimizi və timeout baş verdikdə nə edəcəyimizi müəyyənləşdirməliyik.
Bu qərarı vermək üçün məlumatın aktuallığına olan tələbi dəqiqləşdirmək faydalıdır:
Bu əməliyyata məhz indi remote service-dəki ən son məlumat lazımdır, yoxsa lokal olaraq mövcud məlumatdan istifadə etmək olar?
Lokal məlumat kifayət edirsə, əlavə şəbəkə çağırışına ehtiyac qalmaya bilər. Ən son vəziyyət vacibdirsə, həmin dependency-yə müraciət üçün vaxt ümumi deadline daxilində ayrılmalıdır.
Dependency əlçatmaz, əməliyyatın nəticəsi isə naməlum ola bilər
Server nasazlığı, şəbəkə bağlantısının kəsilməsi və ya konfiqurasiya dəyişikliyi asılı olduğumuz service-i əlçatmaz edə bilər. Sorğu göndərən tətbiq üçün çətinlik təkcə cavab ala bilməmək deyil: bəzən əməliyyatın qarşı tərəfdə icra olunub-olunmadığı da bilinmir.
Məsələn, ödəniş sorğusu timeout ilə bitdikdə aşağıdakı hallardan hər hansı biri baş vermiş ola bilər:
- Sorğu payment provider-ə ümumiyyətlə çatmayıb.
- Provider sorğunu qəbul edib, amma emalı hələ tamamlamayıb.
- Ödəniş tamamlanıb, amma cavab itib.
Caller yalnız gözlədiyi cavabın gəlmədiyini görür və bu halları bir-birindən ayıra bilməyə bilər. Timeout caller-in gözləməyi dayandırdığını bildirir. Əməliyyatın uğursuz olduğunu sübut etmir. Bu fərq retry qərarında vacibdir, çünki təkrar sorğu artıq tamamlanmış ödənişi yenidən icra edə bilər.
Bunun qarşısını almaq üçün eyni ödənişin retry-larında əməliyyat identifikatoru dəyişməməlidir. Idempotency key bu funksiyanı dəstəkləyən provider-ə təkrar sorğunu tanımağa və müştəridən ikinci dəfə ödəniş tutulmasının qarşısını almağa imkan verir.
Nəticə hələ məlum deyilsə, tətbiq ödənişi pending statusunda saxlayıb provider-dəki vəziyyəti yoxlaya bilər. Reconciliation prosesi lokal qeydlə provider-in nəticəsini tutuşdurmağa kömək edir. Belə halda istifadəçiyə ödənişin yoxlanıldığını göstərmək, onu dərhal uğursuz elan edib yeni ödənişə yönləndirməkdən daha uyğundur.
Timeout oldu. Pul tutulubmu?
payment-order-1042Bütün cəhdlərdə eynidirCheckout service eyni biznes əməliyyatı üçün sabit idempotency key göndərir.
Hansı mexanizm hansı problemi həll edir?
Timeout, retry və digər resilience mexanizmlərinin vəzifələri fərqlidir. Onları seçərkən hansı nasazlığı idarə etdiklərini ayırmaq lazımdır:
| Mexanizm | Məqsəd |
|---|---|
| Timeout | Caller-in nə qədər gözləyəcəyini məhdudlaşdırır |
| Retry with backoff and jitter | Cəhdlər arasında artan və təsadüfi dəyişən fasilələr yaradaraq müvəqqəti nasazlıqdan sonra yenidən yoxlamağa imkan verir |
| Circuit breaker | Ardıcıl xətalar verən dependency-yə çağırışları müvəqqəti azaldır |
| Bulkhead | Bir dependency-nin və ya workload-un ortaq resurslardan nə qədər istifadə edə biləcəyini məhdudlaşdırır |
| Idempotency | Dəstəklənən əməliyyatlarda təkrar cəhdləri təhlükəsiz edir |
Bu mexanizmlərin limitləri bir-birinə uyğun seçilməlidir: hər retry üçün ayrılan gözləmə müddəti və cəhdlər arasındakı fasilələr sorğunun ümumi deadline-ını aşmamalıdır. Fallback də əməliyyatın mənasına uyğun olmalıdır. Məsələn, məhsul haqqında cache-dəki məlumatı göstərmək qəbul edilə bilər, amma provider-dən təsdiq almadan ödənişi uğurlu göstərmək olmaz.
Resurslar məhduddur
Sistemin qəbul edə biləcəyi yük CPU, memory, network bandwidth, database connection-ları, worker thread-lər və queue tutumu ilə məhdudlaşır. Cloud infrastrukturu bu imkanların bir hissəsini genişləndirə bilər, amma scaling vaxt tələb edir; xərc, kvota və downstream sistemlərin limitləri də nəzərə alınmalıdır.
Sorğular yavaş tamamlandıqda eyni anda aktiv qalan işlərin sayı artır. Sabit axına yaxın şəraitdə bu əlaqəni təxminən belə ifadə etmək olar:
Orta in-flight sorğu sayı ≈ sorğuların daxilolma sürəti × sistemdə keçirilən orta vaxt.
Service saniyədə 200 sorğu qəbul edir və hər sorğu orta hesabla 100 millisaniyəyə tamamlanırsa, eyni anda təxminən 20 sorğu aktiv olur. Daxilolma sürəti dəyişmədən orta müddət iki saniyəyə qalxdıqda bu say təxminən 400-ə çatır. Yəni daha çox trafik gəlməsə belə, təkcə gecikmənin artması sistemdə saxlanılan işi xeyli çoxalda bilər.
Bu hesab 400 thread və ya database connection tələb olunduğu demək deyil; konkret resurs istifadəsi implementation-dan asılıdır. Bununla belə, gözləyən sorğular connection, memory və digər məhdud resursları tutursa, yeni işi qəbul etmək üçün yer azalır.
Retry-lar da eyni resurslardan istifadə edir. Yavaşlayan dependency-yə əlavə cəhdlər göndərildikcə yük arta, queue isə getdikcə böyüyə bilər. Limitsiz queue problemi sonraya ötürür: memory sərfi artır və bəzi sorğular emala çatana qədər aktuallığını itirir.
Trafik eynidir. Bəs yük?
200 sorğu/s × 0,1 s ≈ 20 aktiv sorğu
Bu hesabı necə oxumalı?
Little’s Law əsasında sabit orta axın üçün sadələşdirilmiş hesab. Zolaq 400 sorğuya nisbəti göstərir, real capacity limitini deyil. Bu rəqəm thread və ya connection sayı demək deyil; limitsiz böyüyən queue üçün sabit vəziyyət fərziyyəsi ödənmir.Capacity bitəndə nə etməli?
Sistem tutumuna yaxınlaşdıqda məqsəd qəbul edilmiş işi tamamlamaq üçün kifayət qədər resurs saxlamaqdır. Bunun üçün limitlər və limitə çatdıqda tətbiq ediləcək davranış əvvəlcədən müəyyənləşdirilməlidir:
- Queue ölçüsünü və eyni anda işləyən əməliyyatların sayını məhdudlaşdırın.
- Retry sayına və ümumi retry müddətinə limit qoyun.
- Uyğun hallarda rate limiting və backpressure tətbiq edin.
- Əlavə işi qəbul etmək sistemin ümumi availability-sini təhlükəyə atacaqsa, onu erkən rədd edin.
- CPU istifadəsi ilə yanaşı gözləmə müddətini və resource saturation səviyyəsini də izləyin.
Yeni sorğunu erkən rədd etmək istifadəçi üçün xoş nəticə olmasa da, bütün sorğuların uzun müddət gözləyib timeout almasının qarşısını ala bilər. Hansı işin qəbul ediləcəyi və hansının təxirə salınacağı xidmətin tələblərinə uyğun seçilməlidir.
Bu üç qayda bir-birinin təsirini necə gücləndirir?
Checkout ssenarisinə qayıdaq: payment provider yavaşladıqda sorğular daha uzun müddət aktiv qalır və ortaq resurslar dolmağa başlayır. Timeout alan caller-lər retry etdikcə əlavə yük yaranır. Beləcə, bir dependency-dəki gecikmə tətbiqin normal işləyən digər hissələrinə də yayıla bilər.
Bu axında hər mexanizmin ayrıca işi var. Timeout caller-in gözləməsini məhdudlaşdırır, concurrency limiti isə aktiv işlərin resursları tam tükətməsinə mane olur. Retry budget təkrar cəhdlərin yükünü nəzarətdə saxlayır; idempotency eyni biznes əməliyyatının təkrar icrasını idarə edir. Bu mexanizmləri birlikdə seçmək lazımdır, çünki yalnız timeout qoymaq əlavə retry yükünü və ya təkrar ödəniş riskini həll etmir.
Dizaynı yoxlamaq üçün üç sual
Service-lər arasındakı hər mühüm əməliyyat üçün bu üç sualın konkret cavabı olmalıdır:
- Məlumat gecikərkən nə baş verir?
- Dependency əlçatmaz olduqda və ya əməliyyatın nəticəsi məlum olmadıqda nə baş verir?
- Mövcud capacity tükəndikdə nə baş verir?
Checkout üçün bu cavablar praktik davranışa çevrilir: nəticəsi məlum olmayan ödəniş yoxlanılır, retry eyni idempotency key ilə göndərilir və sistem tutumunu aşan sorğular üçün aydın cavab qaytarılır. İstifadəçi də ödənişinin uğursuz olduğunu deyil, hələ yoxlanıldığını görür.
Arxitektura qərarlarını nəzərdən keçirərkən normal axınla yanaşı bu halları da izləmək faydalıdır. Hansı məlumatın köhnə qala biləcəyi, hansı əməliyyatın təkrarlana biləcəyi və yük artanda hansı işin dayandırılacağı aydın olduqda həm implementasiya, həm də nasazlıq zamanı qərar vermək asanlaşır.
