Razvoj

Architecting Resilient APIs: Planning for the Unexpected

Projektiranje otpornih API-ja: Planiranje za nepredviđeno

Većina neuspjeha API-ja nije dramatična. Dolaze u obliku sporog upita prema bazi podataka, klijenta koji preagresivno ponavlja zahtjeve, uzlazne usluge koja vraća neispravne podatke ili spremnika koji se ponovno pokrene usred implementacije. Sretni put može biti ispravan, no sustav i dalje postaje nepouzdan kada ga stvarnost stavi pod pritisak.

Dizajn otpornih API-ja disciplina je u kojoj se takvi uvjeti tretiraju kao normalni radni slučajevi. Ne radi se o obećavanju nula prekida rada ni o omotavanju svake operacije generičkim try/catch. Radi se o tome da neuspjesi budu ograničeni, razumljivi, popravljivi i sigurni i za klijente i za operatere.

Započnite definiranjem granica neuspjeha

Krajnja točka rijetko je jedna radnja. Zahtjev može proći kroz raspodjelnik opterećenja, PHP okruženje za izvršavanje, aplikacijski kôd, predmemoriju, bazu podataka, red i jednu ili više vanjskih usluga. Svaka granica ima različite načine neuspjeha i različite mogućnosti oporavka.

Mapirajte ovisnosti važne krajnje točke prije njezine optimizacije. Postavite praktična pitanja: Što se događa ako je baza podataka spora? Što ako pružatelj usluga plaćanja istekne nakon prihvaćanja zahtjeva? Što ako Redis nije dostupan? Koji se posao može ponoviti, a koji se mora dogoditi točno jednom?

Ova vježba često otkriva da „dostupnost” nije jedan zahtjev. Katalog proizvoda može opravdano poslužiti malo zastarjele podatke iz predmemorije. Prijenos novca ne bi trebao tiho nastaviti s nesigurnim stanjem. Dobra arhitektura razlikuje te slučajeve umjesto da svugdje primjenjuje jednu strategiju dostupnosti.

Namjerno postavite vremenska ograničenja i ponavljanja

Bez vremenskog ograničenja nedostupna ovisnost može zauzeti PHP radnike sve dok cijeli API ne prestane odgovarati. Svaki odlazni mrežni poziv trebao bi imati ograničeno vremensko ograničenje povezivanja i ograničeno ukupno vremensko ograničenje zahtjeva. Točne vrijednosti ovise o proračunu latencije vidljivom korisniku i očekivanom ponašanju ovisnosti, no pravilo „čekaj neograničeno” gotovo nikada nije korisno.

Ponavljanja mogu poboljšati otpornost, ali mogu i pojačati prekid rada. Ako mnogo zahtjeva istekne i svaki pozivatelj odmah ponovi zahtjev, usluga u kvaru prima više prometa upravo kada ga najmanje može obraditi. Ponovite samo prolazne neuspjehe, ograničite broj pokušaja i dodajte postupno povećavanje čekanja s nasumičnim odstupanjem.

function retry(callable $operation, int $attempts = 3): mixed
{
    $lastException = null;

    for ($attempt = 1; $attempt <= $attempts; $attempt++) {
        try {
            return $operation();
        } catch (TransientDependencyException $exception) {
            $lastException = $exception;

            if ($attempt === $attempts) {
                break;
            }

            $baseDelayMilliseconds = 100 * (2 ** ($attempt - 1));
            $jitterMilliseconds = random_int(0, 100);
            usleep(($baseDelayMilliseconds + $jitterMilliseconds) * 1000);
        }
    }

    throw $lastException;
}

Ovaj je obrazac siguran samo kada je sigurno ponoviti operaciju. Zahtjev GET obično se može ponoviti. Zahtjev koji stvara narudžbu možda se ne može ponoviti, osim ako API podržava idempotentnost.

Koristite idempotentnost za zaštitu važnih upisa

Klijenti gube odgovore. Mobilne mreže prekidaju veze. Obrnuti proxyji mogu isteći čak i nakon što aplikacija dovrši svoj posao. Ako klijent ponovi zahtjev za upis, poslužitelj mora moći utvrditi je li riječ o novom zahtjevu ili duplikatu.

Za operacije sa značajnim nuspojavama prihvatite ključ idempotentnosti i pohranite ga uz zapis rezultirajuće operacije. Ključ bi trebao biti ograničen na autentificiranog klijenta i krajnju točku. Ponovljeni zahtjev s istim ključem trebao bi vratiti izvorni rezultat, dok ključ ponovno upotrijebljen s drukčijim sadržajem zahtjeva treba jasno odbiti.

Ograničenje baze podataka jednako je važno kao i aplikacijska logika. Jedinstveni indeks nad odgovarajućim identitetom i ključem idempotentnosti sprječava dva istodobna PHP radnika da oba zaključe kako trebaju stvoriti isti zapis.

CREATE UNIQUE INDEX orders_client_idempotency_key_unique
ON orders (client_id, idempotency_key);

Nemojte držati transakciju baze podataka otvorenom dok čekate vanjski HTTP zahtjev. Transakcije neka budu kratke: provjerite podatke, napravite lokalnu promjenu stanja, potvrdite je, a zatim koordinirajte naknadni rad putem trajnog mehanizma kao što su red ili tablica izlaznih poruka.

Dizajnirajte odgovore za neuspjeh jednako pažljivo kao i za uspjeh

Otporan API klijentima daje dovoljno informacija da ispravno reagiraju, bez izlaganja internih pojedinosti. Koristite dosljedne statusne kodove i stabilnu strukturu pogrešaka. Neuspjesi validacije trebali bi navesti neispravna polja. Neuspjesi autentifikacije i autorizacije trebali bi biti različiti. Privremeni problemi s ovisnostima trebali bi klijentima reći da bi ponavljanje kasnije moglo biti prikladno.

{
  "error": {
    "code": "DEPENDENCY_UNAVAILABLE",
    "message": "The request could not be completed at this time.",
    "request_id": "req_8f2c"
  }
}

Identifikator zahtjeva povezuje neuspjeh vidljiv klijentu s aplikacijskim zapisnicima i tragovima. Daleko je korisniji od vraćanja neobrađene poruke iznimke, koja može otkriti pojedinosti implementacije i rijetko klijentu govori što učiniti sljedeće.

Obuzdajte kaskadne neuspjehe

Kada ovisnost nije zdrava, nastavak njezina pozivanja pri svakom zahtjevu troši resurse. Prekidač strujnog kruga može privremeno zaustaviti pozive nakon ponovljenih neuspjeha, a zatim dopustiti ograničene probne zahtjeve nakon razdoblja hlađenja. Čak i bez namjenske biblioteke, načelo je vrijedno: neuspjeh treba nastupiti brzo kada je poznato da ovisnost ne uspijeva.

Pregrade pružaju dodatni sloj zaštite. Odvojite skupove radnika, potrošače reda, ograničenja veza ili proračune resursa kako spora integracija za izvještavanje ne bi iscrpila kapacitet potreban za ključni API promet. Docker ne stvara automatski tu izolaciju; ograničenja spremnika i postavke implementacije moraju se odabrati namjerno.

Korisne operativne zaštitne mjere uključuju:

  • provjere zdravlja koje odražavaju može li instanca sigurno primati promet;
  • ograničenja veza prema bazi podataka koja ostavljaju prostor za administrativni pristup i druge usluge;
  • potrošače reda koji mogu tolerirati duplicirane poruke i sigurno nastaviti nakon ponovnog pokretanja;
  • strukturirane zapisnike koji sadrže identifikatore zahtjeva, identifikatore operacija, nazive ovisnosti i kategorije pogrešaka;
  • upozorenja temeljena na trajnim simptomima, kao što su povišene stope pogrešaka ili iscrpljen kapacitet, a ne na svakoj izoliranoj iznimci.

Implementirajte za vraćanje prethodne verzije, a ne za optimizam

Implementacija je scenarij neuspjeha s rasporedom. Kad god je moguće, promjene sheme učinite kompatibilnima i sa starim i s novim verzijama aplikacije. Dodajte stupac koji dopušta null prije nego što ga učinite obaveznim. Implementirajte kôd koji može čitati oba prikaza prije uklanjanja staroga. Izbjegavajte migracije koje zahtijevaju da se svaka instanca aplikacije prebaci u potpuno istom trenutku.

Spremnici bi trebali biti zamjenjivi, a ne tretirani kao trajni poslužitelji. Pohranjujte stanje u odgovarajuće vanjske sustave, obradite signale prekida kako bi radnici prestali prihvaćati novi posao i osigurajte da provjere spremnosti spriječe promet da dosegne instancu prije nego što bude pripremljena.

Otpornost je rezultat jasnih kompromisa

Najjači API-ji nisu oni s najviše infrastrukturnih obrazaca. To su oni čiji timovi znaju koje se operacije mogu odgoditi, ponoviti, odbiti ili poslužiti iz zastarjelih podataka. Postavljaju ograničenja prije nego što im preopterećenje nametne ograničenja.

Planirajte nepovoljne putove dok je sustav miran. Dodajte vremenska ograničenja, učinite upise idempotentnima, zaštitite ovisnosti i učinite neuspjehe vidljivima. Kada neočekivano naposljetku stigne, API neće morati biti savršen. Morat će zakazati na način koji korisnicima, klijentima i inženjerima omogućuje oporavak.

Portret autora bloga

Mihajlo

Ja sam Mihajlo — programer vođen znatiželjom, disciplinom i stalnom željom da stvorim nešto smisleno. Dijelim uvide, tutorijale i besplatne usluge kako bih pomogao drugima da pojednostave svoj rad i rastu u svijetu softvera i umjetne inteligencije koji se neprestano razvija.