Fəsil 09 · 8 dəq oxu

Large-scale sistemlərin building block-ları

Large-scale sistemlərin building block-ları

System design diaqramlarının çoxu eyni görünür: , , , queue, . Bu oxşarlıq təsadüfi deyil, çünki həmin komponentlər böyük sistemlərin təkrarlanan problemlərini həll edir. Amma diaqramı başqasından köçürmək asandır, hər qutunun orada nə üçün olduğunu demək isə çətin.

Bu fəsil hər komponentə ch02-dəki gözlüklə baxır: struktur bir üçün tactic-dir. Hər biri üçün dörd sual verəcəyik: nəyi həll edir, hansı attribute-ə xidmət edir, hansı riski gətirir və nə vaxt lazım deyil.

Request-in yolu

Tipik bir web request-i təxminən belə yol keçir:

  1. Client domain adını DNS vasitəsilə IP ünvanına çevirir.
  2. Statik content və bilən cavablar -in edge server-indən gəlir.
  3. Qalan request-lər -ə çatır və o, request-i sağlam instance-lardan birinə yönləndirir.
  4. , və routing edir.
  5. Service biznes məntiqini icra edir.
  6. Service əvvəlcə -ə, olmayanı isə database-dən oxuyur.
  7. Dərhal cavab tələb etməyən iş (email, hesabat, indexing) -ə yazılır və arxa planda emal olunur.

Bu siyahıdakı hər addım bir hop-dur. Hər hop əlavə edir, ayrıca xarab ola bilir və kimsə onu operate etməlidir. Ona görə də hər addım öz yerini qazanmalıdır.

Load balancer

traffic-i bir neçə instance arasında paylayır. O, iki attribute-ə xidmət edir: , çünki yeni instance əlavə etməklə horizontal böyümək mümkün olur, və , çünki xarab instance traffic almağı dayandırır.

İki səviyyədə işləyə bilər. L4 TCP/UDP səviyyəsində işləyir: yalnız IP və port görür, çox sürətlidir və protokoldan asılı deyil. L7 HTTP-ni anlayır: path-ə, header-ə və ya cookie-yə görə routing edə bilir, amma bunun üçün request-i parse etməli olur və bu da baha başa gəlir.

Paylama alqoritmləri:

  • Round robin request-ləri növbə ilə paylayır. Request-lər təxminən eyni çəkidə olanda yaxşı işləyir.
  • Least connections request-i ən az aktiv connection-u olan instance-a göndərir. Request-lərin müddəti çox fərqlənəndə daha ədalətlidir.
  • eyni key-i həmişə eyni node-a göndərir və node dəyişəndə key-lərin yalnız kiçik hissəsini yerindən tərpədir. və üçün vacibdir, ona data at scale fəslində ətraflı baxırıq.

instance-ın sağlam olub-olmadığını bilməsi -lər vasitəsilə olur. Sadə "port açıqdır" yoxlaması azdır, çünki process işləyə, amma database-ə qoşula bilməyə bilər. Əks istiqamətdə də risk var: bütün dependency-ləri yoxlasa, ortaq database-in qısa müddətli problemi bütün instance-ları eyni anda "xəstə" elan edər və traffic göndərəcək yer tapmaz.

Session affinity (sticky session) eyni istifadəçini həmişə eyni instance-a yönləndirir. Session state yaddaşdadırsa, bu, onu "işlək" saxlamağın yoludur. Amma instance yıxılanda session-lar itir və yük qeyri-bərabər paylanır. Daha yaxşı həll ch07-dəki tactic-dir: service-ləri stateless etmək, session-u isə distributed və ya çıxarmaq. Bundan sonra sticky session-a ehtiyac qalmır.

Və nəhayət: özü də single point of failure ola bilər. Managed cloud bunu adətən özü həll edir. Öz qurursunuzsa, ch07-dəki və qaydaları ona da aiddir.

API gateway

client-lər ilə service-lər arasında tək giriş nöqtəsidir. Tipik vəzifələri: routing, , , termination, request aggregation (bir client request-i üçün bir neçə service-i çağırıb cavabları birləşdirmək). Xidmət etdiyi attribute-lər security və maintainability-dir: cross-cutting məsələlər hər service-də ayrıca deyil, bir yerdə həll olunur.

ilə fərqi nədədir? (məsələn, nginx) request-i backend-ə ötürür, bitirir, bəzən edir. bunun üzərində API səviyyəsində anlayış əlavə edir: , quota-lar, versiyalar, consumer-lər. Hər , amma hər deyil.

Əsas risk gateway-in tədricən yeni monolitə çevrilməsidir. Əvvəlcə bir az transformation əlavə olunur, sonra "sadəcə bu if-i", sonra da bütöv biznes qaydası. Nəticədə hər dəyişiklik gateway komandasından keçir və ch04-dəki boundary-lər pozulur. Qayda sadədir: gateway request-in necə gəldiyini bilir, nə demək olduğunu yox.

Fərqli client-lərin (mobile, web) ehtiyacları çox fərqlənəndə BFF (backend for frontend) istifadə olunur: hər client növü üçün ayrıca, nazik gateway, ona sahib olan da həmin frontend komandasıdır. Bu, ümumi gateway-in hamı üçün kompromisə çevrilməsinin qarşısını alır.

Bir monolit və bir client-iniz varsa, çox güman ki, lazım deyil. və framework middleware-i kifayətdir.

Caching

bahalı nəticəni ucuz yerdə saxlamaqdır. O, performance () və üçün tactic-dir: hər hit backend-dən götürülmüş bir yükdür.

bir neçə səviyyədə yaşaya bilər: client-də (browser, mobile app), , service-in öz yaddaşında (in-memory) və ayrıca distributed -də (Redis, Memcached). In-memory ən sürətlisidir, amma hər instance-ın öz nüsxəsi olur və onlar bir-birindən fərqlənə bilər.

Əsas strategiyalar:

  • : application əvvəlcə baxır, miss olanda database-dən oxuyub yazır. Ən geniş yayılmış və ən sadə strategiyadır.
  • : eyni məntiq, amma miss-i application deyil, layer-in özü idarə edir.
  • : yazı eyni anda və database-ə gedir. təzə qalır, amma hər yazı yavaşlayır.
  • : yazı əvvəlcə gedir, database-ə isə sonra asynchronous yazılır. Çox sürətlidir, amma node-u yıxılarsa, data itə bilər.

Phil Karlton-a aid edilən məşhur zarafata görə, computer science-də iki çətin şey var: və adlandırma. Ən sadə yol TTL-dir: data müəyyən müddətdən sonra köhnəlmiş sayılır. TTL qısa olanda hit ratio düşür, uzun olanda istifadəçi köhnə data görür. Event-lə invalidation daha dəqiqdir, amma ch05-dəki problemi burada da yaranır: database yeniləndi, invalidation mesajı isə itdi.

Ən təhlükəli problem stampede-dir (thundering herd). Populyar key-in TTL-i bitir və min request eyni anda miss alıb database-ə hücum edir. Bunun qarşısını almağın yolları: key başına lock (yalnız bir request yenidən hesablayır), TTL-lərə təsadüfi əlavə etmək və ya dəyəri vaxtı bitməmişdən əvvəl arxa planda yeniləmək.

qiyməti consistency-dir. əlavə etdiyiniz andan sisteminiz ilə yaşayır. Bunu bilərəkdən qəbul etmək lazımdır. Hit ratio-nu da mütləq ölçün: 30% hit ratio olan tez-tez yalnız mürəkkəblik və əlavə hop gətirir. Data hər dəfə fərqlidirsə və ya database onsuz da yükü rahat daşıyırsa, lazım deyil.

CDN

content-i istifadəçiyə coğrafi cəhətdən yaxın edge server-lərdə saxlayan paylanmış şəbəkəsidir. O, -yə (Bakıdakı istifadəçi Frankfurt-dakı origin-ə deyil, yaxın edge-ə gedir) və origin-in -sinə xidmət edir. Yaxşı konfiqurasiya olunmuş statik traffic-in 90%-dən çoxunu origin-ə çatmadan cavablandırır.

Statik content (şəkil, JS, CSS, video) üçün ideal namizəddir. Dinamik content də mümkündür: qısa TTL ilə API cavabları, edge-də termination və hətta edge-də işləyən kod. Amma istifadəçiyə xas cavabları təhlükəlidir. Səhv konfiqurasiya olunmuş key bir istifadəçinin səhifəsini başqasına göstərə bilər.

Burada da invalidation əsas problemdir. Purge API var, amma bütün edge-lərə yayılması vaxt aparır. Daha etibarlı yol versioned URL-lərdir: app.3f9a2c.js kimi fayl adları dəyişmir, yeni versiya yeni URL deməkdir. Belə faylları çox uzun TTL ilə olar və purge-a ehtiyac qalmır.

İstifadəçiləriniz bir regiondadırsa və statik content azdırsa, faydası kiçikdir. Amma qiyməti də aşağıdır, ona görə statik asset-lər üçün nadir hallarda pis qərar olur.

Message broker

producer ilə consumer arasında asynchronous vasitəçidir. Ch02-dəki ifadə ilə: queue producer-ləri consumer failure-dan ayırmaq üçün tactic-dir. Xidmət etdiyi attribute-lər (consumer yıxılanda producer işləməyə davam edir) və -dir (load leveling: qəfil traffic artımı queue-da yığılır və consumer-lər onu öz tempində emal edir).

İki əsas model var:

  • Queue: hər mesajı bir consumer emal edir. İş paylamaq üçündür, məsələn email göndərmək və ya şəkil emal etmək.
  • : hər mesaj bütün subscriber-lərə çatır. Bir event bir neçə müstəqil reaksiya doğuranda istifadə olunur: "sifariş yaradıldı" event-i həm anbara, həm analitikaya, həm də bildiriş service-inə lazımdır.

Ch03-dəki bu building block üzərində qurulur. ən böyük faydası : yeni subscriber əlavə etmək üçün producer-ə toxunmaq lazım deyil.

Risk tərəfi az deyil. Broker-lər adətən verir, yəni eyni mesaj iki dəfə gələ bilər, və consumer olmalıdır. Request-in yolu asynchronous addımlara bölünür və debug çətinləşir. Queue-nun dərinliyi yeni bir metric-ə çevrilir: consumer-lər geri qalanda problem dərhal görünmür, saatlar sonra üzə çıxır. Uğursuz mesajlarla nə etməli olduğunuza reliability pattern-ləri fəslində qayıdacağıq.

İstifadəçi nəticəni dərhal görməlidirsə (məsələn, ödəniş təsdiqi), asynchronous emal UX-i pisləşdirir. Yük də azdırsa və hər iki tərəf eyni vahidindədirsə, sadə funksiya çağırışı daha yaxşıdır.

Komponenti nə vaxt əlavə etməməli

Hər komponentin öz qiyməti var. Ch07-dəki riyaziyyatı xatırlayın: critical path-dəki komponentlərin bir-birinə vurulur. 99.95%-lik beş ardıcıl hop artıq 99.75% deməkdir. Hər hop həm də network round-trip, əlavə config, əlavə dashboard və gecə saat üçdə oyanmaq üçün əlavə səbəbdir.

KomponentXidmət etdiyi attributeGətirdiyi riskNə vaxt lazım deyil
, Özü SPOF ola bilər; səhv bütün instance-ları çıxarırTək instance və downtime-a dözümlü daxili alət
Security, maintainabilityYeni monolit, biznes məntiqinin toplandığı yer, əlavə hopBir client və bir backend
Performance, Köhnə data, stampede, invalidation bug-larıAşağı hit ratio; database yükü rahat daşıyır
, origin Səhv key ilə data sızması; purge gecikməsiBir region, az statik content
, ()Duplicate-lər, gizli lag, çətin debugİstifadəçi nəticəni dərhal gözləyir; yük azdır

Cədvəlin sağ sütunu ən çox unudulandır. Komponenti problem ortaya çıxanda əlavə edin, "gələcəkdə lazım olacaq" deyə yox. sonradan əlavə etmək bir günlük işdir. Lazımsız çıxarmaq isə aylar çəkə bilər.

Hazırkı sisteminiz haqqında suallar

  1. Ən vacib request-inizin yolunu client-dən database-ə qədər yazın. Hər hop hansı xidmət edir? Cavabı olmayan hop varmı?
  2. hit ratio-sunu bilirsinizmi? Populyar key-in TTL-i bitəndə database-ə nə olur?
  3. və ya proxy config-inizdə biznes qaydası varmı? Onun sahibi kimdir?
  4. Load balancer-inizin nəyi yoxlayır? Ortaq dependency-nin qısa problemi bütün instance-ları eyni anda traffic-dən çıxara bilərmi?