Архитектура на отпорни API-ја: Планирање за неочекуваното
Повеќето неуспеси на API не се драматични. Тие се појавуваат како бавно барање кон базата на податоци, клиент што премногу агресивно се обидува повторно, надворешна услуга што враќа погрешно форматирани податоци или контејнер што се рестартира на половина од распоредувањето. Успешната патека може да е точна, но системот сепак станува ненадежен кога реалноста врши притисок.
Дизајнот на отпорни API е дисциплина на третирање на тие услови како нормални оперативни случаи. Не станува збор за ветување нула прекини или обвиткување на секоја операција во генерички try/catch. Станува збор за тоа неуспесите да бидат ограничени, разбирливи, поправливи и безбедни и за клиентите и за операторите.
Почнете со дефинирање на границите на неуспех
Крајна точка ретко е единствено дејство. Барањето може да помине низ балансер на оптоварување, PHP извршувачка околина, апликациски код, кеш, база на податоци, редица и една или повеќе надворешни услуги. Секоја граница има различни начини на неуспех и различни опции за опоравување.
Пред да ја оптимизирате, мапирајте ги зависностите на важна крајна точка. Поставете практични прашања: Што се случува ако базата на податоци е бавна? Што ако давателот на плаќања истече по прифаќањето на барањето? Што ако Redis не е достапен? Која работа може да се повтори и која работа мора да се изврши точно еднаш?
Оваа вежба често открива дека „достапност“ не е едно барање. Каталог на производи може разумно да послужува малку застарени кеширани податоци. Паричен трансфер не треба тивко да продолжи со неизвесна состојба. Добрата архитектура прави разлика меѓу овие случаи наместо да применува една стратегија за достапност насекаде.
Направете ги истекувањата на време и повторните обиди намерни
Без истекување на време, недостапна зависност може да троши PHP работници сè додека целиот API не престане да одговара. Секој излезен мрежен повик треба да има ограничено истекување на време за поврзување и ограничено истекување на време за целото барање. Точните вредности зависат од буџетот за доцнење видлив за корисникот и очекуваното однесување на зависноста, но „чекај неограничено“ речиси никогаш не е корисна политика.
Повторните обиди може да ја подобрат отпорноста, но може и да го засилат прекинот. Ако многу барања истечат и секој повикувач веднаш се обиде повторно, услугата што откажува прима повеќе сообраќај токму кога е најмалку способна да се справи со него. Повторувајте само минливи неуспеси, ограничете го бројот на обиди и додајте постепено зголемување на паузата со случајна варијација.
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;
}
Овој образец е безбеден само кога повторувањето на операцијата е безбедно. Барање GET обично може да се повтори. Барање што создава нарачка можеби не може, освен ако API поддржува идемпотентност.
Користете идемпотентност за заштита на важните записи
Клиентите губат одговори. Мобилните мрежи прекинуваат врски. Обратните проксија може да истечат дури и откако апликацијата ја завршила својата работа. Ако клиент повтори барање за запис, серверот мора да може да утврди дали станува збор за ново барање или дупликат.
За операции со значајни споредни ефекти, прифатете клуч за идемпотентност и зачувајте го со записот за резултирачката операција. Клучот треба да биде ограничен на автентицираниот клиент и крајната точка. Повторено барање со истиот клуч треба да го врати првичниот резултат, додека клуч повторно употребен со различна содржина на барањето треба јасно да биде одбиен.
Ограничувањето во базата на податоци е важно исто колку и апликациската логика. Единствен индекс на соодветниот идентитет и клучот за идемпотентност спречува два истовремени PHP работници и двајцата да одлучат дека треба да го создадат истиот запис.
CREATE UNIQUE INDEX orders_client_idempotency_key_unique
ON orders (client_id, idempotency_key);
Не држете трансакција на база на податоци отворена додека чекате на надворешно HTTP барање. Одржувајте ги трансакциите кратки: валидирајте ги податоците, направете ја промената на локалната состојба, потврдете ја и потоа координирајте ја последователната работа преку траен механизам, како редица или outbox табела.
Дизајнирајте одговори за неуспех исто толку внимателно како и за успех
Отпорен API им дава на клиентите доволно информации за правилно да одговорат без да изложува внатрешни детали. Користете доследни статусни кодови и стабилна структура на грешки. Неуспесите при валидација треба да ги посочат невалидните полиња. Неуспесите при автентикација и авторизација треба да бидат различни. Привремените проблеми со зависностите треба да им кажат на клиентите дека можеби е соодветно повторување подоцна.
{
"error": {
"code": "DEPENDENCY_UNAVAILABLE",
"message": "The request could not be completed at this time.",
"request_id": "req_8f2c"
}
}
Идентификаторот на барањето го поврзува неуспехот видлив за клиентот со апликациските дневници и траги. Тој е многу покорисен од враќање сурова порака за исклучок, која може да открие детали за имплементацијата и ретко му кажува на клиентот што да направи понатаму.
Ограничете ги каскадните неуспеси
Кога зависноста е во нездрава состојба, продолжувањето со нејзино повикување при секое барање троши ресурси. Прекинувач на коло може привремено да ги запре повиците по повторени неуспеси, а потоа да дозволи ограничени пробни барања по период на ладење. Дури и без наменска библиотека, принципот е вреден: откажете брзо кога е познато дека зависноста откажува.
Преградите обезбедуваат уште еден слој заштита. Одделете базени на работници, потрошувачи на редици, ограничувања на врски или буџети за ресурси за бавна интеграција за известување да не може да го исцрпи капацитетот потребен за главниот API сообраќај. Docker не ја создава автоматски оваа изолација; ограничувањата на контејнерите и поставките за распоредување мора намерно да се изберат.
Корисни оперативни заштити вклучуваат:
- проверки на здравјето што одразуваат дали инстанца може безбедно да прима сообраќај;
- ограничувања на врските со базата на податоци што оставаат простор за административен пристап и други услуги;
- потрошувачи на редици што можат да поднесат дупли пораки и безбедно да продолжат по рестартирање;
- структурирани дневници што содржат идентификатори на барања, идентификатори на операции, имиња на зависности и категории на грешки;
- аларми засновани на трајни симптоми, како покачени стапки на грешки или исцрпен капацитет, наместо на секој изолиран исклучок.
Распоредувајте за враќање назад, не за оптимизам
Распоредувањето е сценарио за неуспех со распоред. Направете ги промените во шемата компатибилни и со старите и со новите верзии на апликацијата секогаш кога е можно. Додајте nullable колона пред да ја направите задолжителна. Распоредете код што може да ги чита двете претставувања пред да го отстраните старото. Избегнувајте миграции што бараат секоја инстанца на апликацијата да се префрли во точно истиот момент.
Контејнерите треба да бидат заменливи, а не да се третираат како трајни сервери. Чувајте ја состојбата во соодветни надворешни системи, обработувајте сигнали за прекинување за работниците да престанат да прифаќаат нова работа и осигурете проверките за подготвеност да спречат сообраќајот да стигне до инстанца пред таа да биде подготвена.
Отпорноста е производ на јасни компромиси
Најсилните API не се оние со најмногу инфраструктурни обрасци. Туку оние чии тимови знаат кои операции може да бидат одложени, повторени, одбиени или послужени од застарени податоци. Тие поставуваат ограничувања пред преоптоварувањето да им наметне ограничувања.
Планирајте ги неповолните патеки додека системот е мирен. Додајте истекувања на време, направете ги записите идемпотентни, заштитете ги зависностите и направете ги неуспесите набљудливи. Кога неочекуваното на крајот ќе пристигне, API нема да треба да биде совршен. Ќе треба да откажува на начин што им овозможува на корисниците, клиентите и инженерите да се опорават.