Model asynchronního Request-Reply
Oddělte back-endové zpracování od front-endového hostitele – back-endové zpracování back-endu musí být asynchronní, ale front-end pořád potřebuje jasnou odpověď.
Kontext a problém
V moderních vývojech aplikací je běžné, že klientské aplikace — často používají kód spuštěný v klientském počítači (v prohlížeči) na — vzdálených rozhraních API, aby poskytovaly obchodní logiku a funkce psaní. Tato rozhraní API můžou být přímo v souvislosti s aplikací nebo může být sdílená služba poskytovaná třetí stranou. Obvykle tato volání rozhraní API probíhají přes protokol HTTP (S) a sledují sémantiku REST.
Ve většině případů jsou rozhraní API pro klientskou aplikaci navržená tak, aby reagovala rychle, a to v pořadí 100 ms nebo méně. Pro latenci odpovědí může být ovlivněno mnoho faktorů, včetně:
- Balík hostování aplikace.
- Součásti zabezpečení.
- Relativní geografické umístění volajícího a back-endu.
- Síťová infrastruktura.
- Aktuální zatížení.
- Velikost datové části požadavku.
- Zpracovává se délka fronty.
- Čas, po který back-end požadavek zpracuje.
Každý z těchto faktorů může do odpovědi přidat latenci. U některých z nich se dá zmírnit škálováním na back-end. Jiní uživatelé, jako je síťová infrastruktura, jsou z řízení vývojářů aplikací z velké části. Většina rozhraní API může rychle reagovat na odpovědi, aby se mohly vracet zpátky přes stejné připojení. Kód aplikace může provést synchronní volání rozhraní API neblokujícím způsobem, který poskytuje vzhled asynchronního zpracování, které se doporučuje pro operace vázané na vstupně-výstupní operace.
V některých scénářích ale práce prováděná v back-endu může být dlouhodobě spuštěná, v řádu sekund nebo může být proces na pozadí, který se spouští během několika minut nebo dokonce hodin. V takovém případě není vhodné čekat na dokončení práce před tím, než odpoví na požadavek. Tato situace je potenciální problém pro jakýkoli vzor synchronních požadavků a odpovědí.
Některé architektury vyřeší tento problém pomocí zprostředkovatele zpráv, který odděluje fáze žádosti a odpovědi. Toto oddělení se často dosahuje použitím modelu vyrovnávání zatížení založeného na frontě. Toto oddělení může umožňovat nezávislou škálování procesů klienta a back-endu rozhraní API. Ale toto oddělení také přináší další složitost, pokud klient vyžaduje oznámení o úspěšnosti, protože tento krok je potřeba provést jako asynchronní.
Mnohé z těchto důležitých informací, které jsou popsány pro klientské aplikace, platí také pro volání REST API mezi servery v distribuovaných systémech — , například v architektuře mikroslužeb.
Řešení
Jedním z řešení tohoto problému je použití cyklického dotazování HTTP. Cyklické dotazování je užitečné pro kód na straně klienta, protože může být obtížné poskytnout koncové body zpětného volání nebo používat dlouho běžící připojení. I v případě, že je možné zpětná volání, další knihovny a služby, které jsou požadovány, mohou někdy přidat příliš velkou složitost navíc.
Klientská aplikace provede synchronní volání rozhraní API, které aktivuje dlouhotrvající operaci na back-endu.
Rozhraní API se asynchronně odpoví co nejrychleji. Vrátí stavový kód HTTP 202 (přijatý) a potvrdí, že žádost byla přijata ke zpracování.
Poznámka
Rozhraní API by mělo ověřit požadavek i akci, která má být provedena před spuštěním dlouhotrvajícího procesu. Pokud je požadavek neplatný, odpovězte ihned s kódem chyby, jako je HTTP 400 (chybný požadavek).
Odpověď obsahuje odkaz na umístění, který odkazuje na koncový bod, ke kterému se může klient dotázat, aby zkontroloval výsledek dlouhotrvající operace.
Rozhraní API přesměruje zpracování do jiné komponenty, jako je například fronta zpráv.
I když stále probíhá práce, vrátí koncový bod stavu HTTP 202. Po dokončení práce koncový bod stavu může vrátit prostředek, který indikuje dokončení, nebo přesměrovat na jinou adresu URL prostředku. Například pokud asynchronní operace vytvoří nový prostředek, koncový bod stavu by se přesměruje na adresu URL tohoto prostředku.
Následující diagram znázorňuje typický tok:

- Klient odešle požadavek a přijme odpověď HTTP 202 (přijato).
- Klient odešle požadavek HTTP GET na koncový bod stavu. Práce stále čeká na vyřízení, takže toto volání také vrátí HTTP 202.
- V určitém okamžiku je práce dokončena a koncový bod stavu vrátí 302 (nalezeno) přesměrování na prostředek.
- Klient načte prostředek na zadané adrese URL.
Problémy a důležité informace
Existuje několik možných způsobů, jak tento model implementovat přes protokol HTTP, ale ne všechny nadřazené služby mají stejnou sémantiku. Například většina služeb nevrátí odpověď HTTP 202 zpátky z metody GET, když se vzdálený proces nedokončil. Následující čistě sémantika REST by měla vrátit HTTP 404 (Nenalezeno). Tato odpověď dává smysl, pokud považujete výsledek volání ještě nepřítomný.
Odpověď HTTP 202 by měla indikovat umístění a četnost, které by měl klient dotazovat na odpověď. Měl by mít následující další hlavičky:
Hlavička Description Poznámky Umístění Adresa URL, kterou má klient dotazovat na stav odpovědi. Tato adresa URL může být token SAS se vzorem osobního klíče , který je vhodný v případě, že toto umístění potřebuje řízení přístupu. Vzor osobního klíče je platný i v případě, že dotazování odpovědi vyžaduje přesměrování na jiný back-end. Retry-After Odhad, kdy se zpracování dokončí Tato hlavička je navržená tak, aby zabránila cyklickému dotazování klientů při zahlcení back-endu s opakovanými pokusy. Je možné, že budete muset použít server pro zpracování nebo fasádu k manipulaci s hlavičkou nebo datovou částí v závislosti na používaných základních službách.
Pokud se po dokončení přesměrování koncového bodu stavu přesměruje, buď jsou http 302 nebo http 303 odpovídající návratové kódy, v závislosti na přesné sémantické sémantikě, kterou podporujete.
Po úspěšném zpracování by měl prostředek zadaný v hlavičce umístění vracet příslušný kód odpovědi HTTP, například 200 (OK), 201 (vytvořeno) nebo 204 (žádný obsah).
Pokud během zpracování dojde k chybě, zachovejte chybu v adrese URL prostředku popsané v hlavičce umístění a ideálně vraťte příslušný kód odpovědi klientovi z daného prostředku (4xx Code).
Ne všechna řešení budou implementovat tento model stejným způsobem a některé služby budou zahrnovat další nebo alternativní hlavičky. Například Azure Resource Manager používá upravenou variantu tohoto vzoru. Další informace najdete v tématu Azure Resource Manager asynchronní operace.
Starší verze klientů nemusí tento model podporovat. V takovém případě může být nutné umístit fasádu přes asynchronní rozhraní API pro skrytí asynchronního zpracování z původního klienta. například Azure Logic Apps podporuje tento vzor nativně lze použít jako integrační vrstvu mezi asynchronním rozhraním API a klientem, který provádí synchronní volání. Viz provádění dlouhotrvajících úloh pomocí vzoru akce Webhooku.
V některých scénářích můžete chtít klientům umožnit, aby zrušili dlouho běžící požadavek. V takovém případě musí back-end služba podporovat určitou formu pokynu pro zrušení.
Kdy se má tento model použít
Použít tento model pro:
Kód na straně klienta, například aplikace prohlížeče, kde je obtížné poskytnout koncové body zpětného volání, nebo použití dlouhotrvajících připojení přináší příliš mnoho dalších složitostí.
Volání služby, pokud je k dispozici pouze protokol HTTP a návratová služba nemůže kvůli omezením brány firewall na straně klienta vracet zpětná volání.
Volání služeb, která musí být integrovaná se staršími architekturami, které nepodporují moderní technologie zpětného volání, jako jsou webSockety nebo webhooky.
Tento model nemusí být vhodný v případě, že:
- Místo toho můžete použít službu vytvořenou pro asynchronní oznámení, například Azure Event Grid.
- Odpovědi musí do klienta streamovat v reálném čase.
- Klient musí shromáždit mnoho výsledků a přijetí latence těchto výsledků je důležité. Zvažte místo toho model sběrnice Service Bus.
- Můžete použít trvalá síťová připojení na straně serveru, jako jsou WebSockets nebo SignalR. Tyto služby lze použít k upozornění volajícího na výsledek.
- Návrh sítě umožňuje otevřít porty pro příjem asynchronních zpětných volání nebo webhooků.
Příklad
Následující kód ukazuje úryvek z aplikace, která používá Azure Functions k implementaci tohoto vzoru. Řešení obsahuje tři funkce:
- Asynchronní koncový bod rozhraní API.
- Koncový bod stavu.
- Back-endová funkce, která přebírá pracovní položky zařazené do fronty a spouští je.

Tato ukázka je k dispozici na GitHub.
<a name="asyncprocessingworkacceptor-function">Funkce AsyncProcessingWorkAcceptor
Funkce implementuje koncový bod, který přijímá práci z klientské aplikace a zařadit ji AsyncProcessingWorkAcceptor do fronty ke zpracování.
- Funkce vygeneruje ID požadavku a přidá ho jako metadata do zprávy fronty.
- Odpověď HTTP obsahuje hlavičku umístění odkazující na koncový bod stavu. ID požadavku je součástí cesty URL.
public static class AsyncProcessingWorkAcceptor
{
[FunctionName("AsyncProcessingWorkAcceptor")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] CustomerPOCO customer,
[ServiceBus("outqueue", Connection = "ServiceBusConnectionAppSetting")] IAsyncCollector<Message> OutMessage,
ILogger log)
{
if (String.IsNullOrEmpty(customer.id) || String.IsNullOrEmpty(customer.customername))
{
return new BadRequestResult();
}
string reqid = Guid.NewGuid().ToString();
string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{reqid}";
var messagePayload = JsonConvert.SerializeObject(customer);
Message m = new Message(Encoding.UTF8.GetBytes(messagePayload));
m.UserProperties["RequestGUID"] = reqid;
m.UserProperties["RequestSubmittedAt"] = DateTime.Now;
m.UserProperties["RequestStatusURL"] = rqs;
await OutMessage.AddAsync(m);
return (ActionResult) new AcceptedResult(rqs, $"Request Accepted for Processing{Environment.NewLine}ProxyStatus: {rqs}");
}
}
Funkce AsyncProcessingBackgroundWorker
Funkce vezme operaci z fronty, na základě datové části zprávy trochu pracuje a zapíše výsledek do umístění AsyncProcessingBackgroundWorker podpisu SAS.
public static class AsyncProcessingBackgroundWorker
{
[FunctionName("AsyncProcessingBackgroundWorker")]
public static void Run(
[ServiceBusTrigger("outqueue", Connection = "ServiceBusConnectionAppSetting")]Message myQueueItem,
[Blob("data", FileAccess.ReadWrite, Connection = "StorageConnectionAppSetting")] CloudBlobContainer inputBlob,
ILogger log)
{
// Perform an actual action against the blob data source for the async readers to be able to check against.
// This is where your actual service worker processing will be performed.
var id = myQueueItem.UserProperties["RequestGUID"] as string;
CloudBlockBlob cbb = inputBlob.GetBlockBlobReference($"{id}.blobdata");
// Now write the results to blob storage.
cbb.UploadFromByteArrayAsync(myQueueItem.Body, 0, myQueueItem.Body.Length);
}
}
Funkce AsyncOperationStatusChecker
Funkce AsyncOperationStatusChecker implementuje koncový bod stavu. Tato funkce nejprve zkontroluje, jestli byl požadavek dokončen.
- Pokud byl požadavek dokončen, vrátí funkce do odpovědi buď valet-key, nebo okamžitě přesměruje volání na adresu URL valet-key.
- Pokud požadavek stále čeká na vyřízení, měli bychom vrátit kód 202 přijatý s hlavičkou Location odkazující na sebe a do hlavičky http Retry-After požadavek na dokončenou odpověď.
public static class AsyncOperationStatusChecker
{
[FunctionName("AsyncOperationStatusChecker")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "RequestStatus/{thisGUID}")] HttpRequest req,
[Blob("data/{thisGuid}.blobdata", FileAccess.Read, Connection = "StorageConnectionAppSetting")] CloudBlockBlob inputBlob, string thisGUID,
ILogger log)
{
OnCompleteEnum OnComplete = Enum.Parse<OnCompleteEnum>(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
OnPendingEnum OnPending = Enum.Parse<OnPendingEnum>(req.Query["OnPending"].FirstOrDefault() ?? "Accepted");
log.LogInformation($"C# HTTP trigger function processed a request for status on {thisGUID} - OnComplete {OnComplete} - OnPending {OnPending}");
// Check to see if the blob is present.
if (await inputBlob.ExistsAsync())
{
// If it's present, depending on the value of the optional "OnComplete" parameter choose what to do.
return await OnCompleted(OnComplete, inputBlob, thisGUID);
}
else
{
// If it's NOT present, check the optional "OnPending" parameter.
string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{thisGUID}";
switch (OnPending)
{
case OnPendingEnum.Accepted:
{
// Return an HTTP 202 status code.
return (ActionResult)new AcceptedResult() { Location = rqs };
}
case OnPendingEnum.Synchronous:
{
// Back off and retry. Time out if the backoff period hits one minute
int backoff = 250;
while (!await inputBlob.ExistsAsync() && backoff < 64000)
{
log.LogInformation($"Synchronous mode {thisGUID}.blob - retrying in {backoff} ms");
backoff = backoff * 2;
await Task.Delay(backoff);
}
if (await inputBlob.ExistsAsync())
{
log.LogInformation($"Synchronous Redirect mode {thisGUID}.blob - completed after {backoff} ms");
return await OnCompleted(OnComplete, inputBlob, thisGUID);
}
else
{
log.LogInformation($"Synchronous mode {thisGUID}.blob - NOT FOUND after timeout {backoff} ms");
return (ActionResult)new NotFoundResult();
}
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnPending}");
}
}
}
}
private static async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, CloudBlockBlob inputBlob, string thisGUID)
{
switch (OnComplete)
{
case OnCompleteEnum.Redirect:
{
// Redirect to the SAS URI to blob storage
return (ActionResult)new RedirectResult(inputBlob.GenerateSASURI());
}
case OnCompleteEnum.Stream:
{
// Download the file and return it directly to the caller.
// For larger files, use a stream to minimize RAM usage.
return (ActionResult)new OkObjectResult(await inputBlob.DownloadTextAsync());
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnComplete}");
}
}
}
}
public enum OnCompleteEnum {
Redirect,
Stream
}
public enum OnPendingEnum {
Accepted,
Synchronous
}
Další kroky
Při implementaci tohoto modelu můžou být důležité tyto informace:
- Azure Logic Apps – Provádění dlouhotrajících úloh pomocí vzoru akce dotazování.
- Obecné osvědčené postupy při navrhování webového rozhraní API najdete v tématu Návrh webového rozhraní API.