Leválaszthatja a háttérbeli feldolgozást az előtérbeli gazdagépről, ha a háttérbeli feldolgozásnak aszinkronnak kell lennie, de az előtér egyértelmű választ igényel.
Kontextus és probléma
A modern alkalmazásfejlesztés során az ügyfélalkalmazások – gyakran webügyfelek (böngészők) által futtatott kód – esetében megszokott, hogy távoli API-któl függenek, hogy üzleti logikát és írási funkciókat biztosítsanak. Ezek az API-k közvetlenül kapcsolódnak az alkalmazáshoz, vagy egy harmadik fél által nyújtott megosztott szolgáltatások lehetnek. Ezek az API-hívások általában a HTTP(S) protokollon keresztül történnek, és a REST szemantikát követik.
Az ügyfélalkalmazások API-jait a legtöbb esetben úgy tervezték, hogy gyorsan, 100 ms vagy annál kisebb sorrendben válaszoljanak. Számos tényező befolyásolhatja a válasz késését, például:
- Egy alkalmazás üzemelteti a vermet.
- Biztonsági összetevők.
- A hívó és a háttérrendszer relatív földrajzi helye.
- Hálózati infrastruktúra.
- Aktuális terhelés.
- A kérelem hasznos adatainak mérete.
- Az üzenetsor hosszának feldolgozása.
- A háttérrendszer számára a kérés feldolgozásának ideje.
Ezen tényezők bármelyike késést adhat a válaszhoz. Néhányat a háttérrendszer horizontális felskálázásával lehet enyhíteni. Mások, például a hálózati infrastruktúra, nagyrészt nem irányítják az alkalmazásfejlesztőt. A legtöbb API elég gyorsan tud válaszolni ahhoz, hogy a válaszok ugyanazon a kapcsolaton keresztül érkezzenek vissza. Az alkalmazáskód nem blokkoló módon tud szinkron API-hívást kezdeményezni, így aszinkron feldolgozás jelenik meg, ami az I/O-kötésű műveletekhez ajánlott.
Bizonyos esetekben azonban előfordulhat, hogy a háttérrendszer által végzett munka hosszú ideig fut, másodpercek sorrendjében, vagy lehet egy háttérfolyamat, amelyet percekben vagy akár órákban hajtanak végre. Ebben az esetben nem lehet megvárni, amíg a munka befejeződik, mielőtt válaszol a kérésre. Ez a helyzet potenciális probléma bármilyen szinkron kérés-válasz mintával kapcsolatban.
Egyes architektúrák úgy oldják meg ezt a problémát, hogy egy üzenetközvetítővel választják el a kérési és válaszszakaszokat. Ezt az elkülönítést gyakran a várólista-alapú terheléselegyenlítési minta használatával lehet elérni. Ez az elkülönítés lehetővé teszi, hogy az ügyfélfolyamat és a háttér API egymástól függetlenül skálázható legyen. Ez az elkülönítés azonban további összetettséget is eredményez, ha az ügyfél sikerértesítést igényel, mivel ennek a lépésnek aszinkronnak kell lennie.
Az ügyfélalkalmazások esetében tárgyalt szempontok közül sok az elosztott rendszerekben – például mikroszolgáltatás-architektúrában – a kiszolgáló–kiszolgáló KÖZÖTTI REST API-hívásokra is vonatkozik.
Megoldás
A probléma egyik megoldása a HTTP-lekérdezés használata. A lekérdezés hasznos az ügyféloldali kódhoz, mivel nehéz lehet visszahívási végpontokat biztosítani, vagy hosszú ideig futó kapcsolatokat használni. Még ha visszahívások is lehetségesek, a szükséges további kódtárak és szolgáltatások néha túl sok extra összetettséggel járnak.
Az ügyfélalkalmazás szinkron hívást kezdeményez az API-hoz, és elindít egy hosszú ideig futó műveletet a háttérrendszeren.
Az API a lehető leggyorsabban szinkron módon válaszol. Egy HTTP 202 (elfogadott) állapotkódot ad vissza, elismerve, hogy a kérés feldolgozásra érkezett.
Feljegyzés
Az API-nak a hosszú ideig futó folyamat megkezdése előtt ellenőriznie kell a kérést és a végrehajtandó műveletet is. Ha a kérelem érvénytelen, azonnal válaszolja meg a következő hibakódot: HTTP 400 (Hibás kérés).
A válasz egy olyan végpontra mutató helyhivatkozást tartalmaz, amelyet az ügyfél lekérdezhet a hosszú ideig futó művelet eredményének ellenőrzéséhez.
Az API kicsomagozza a feldolgozást egy másik összetevőre, például egy üzenetsorra.
Az állapotvégpont minden sikeres hívásához HTTP 200-et ad vissza. Amíg a munka még függőben van, az állapotvégpont egy erőforrást ad vissza, amely jelzi, hogy a munka még folyamatban van. Ha a munka befejeződött, az állapotvégpont visszaadhat egy befejezést jelző erőforrást, vagy átirányíthatja egy másik erőforrás URL-címére. Ha például az aszinkron művelet új erőforrást hoz létre, az állapotvégpont átirányítja az adott erőforrás URL-címére.
Az alábbi diagram egy tipikus folyamatot mutat be:
- Az ügyfél kérést küld, és HTTP 202 (Elfogadott) választ kap.
- Az ügyfél HTTP GET kérést küld az állapotvégpontnak. A munka még függőben van, ezért ez a hívás HTTP 200-et ad vissza.
- Egy ponton a munka befejeződött, és az állapotvégpont a 302-et adja vissza, amely átirányítja az erőforrást.
- Az ügyfél lekéri az erőforrást a megadott URL-címen.
Problémák és megfontolandó szempontok
Ezt a mintát többféleképpen is implementálhatja HTTP-en keresztül, és nem minden felsőbb rétegbeli szolgáltatásnak ugyanaz a szemantikája. A legtöbb szolgáltatás például nem ad vissza HTTP 202-választ a GET metódusból, ha egy távoli folyamat még nem fejeződött be. A tiszta REST szemantikát követve HTTP 404-et kell visszaadniuk (nem található). Ennek a válasznak akkor van értelme, ha úgy véli, hogy a hívás eredménye még nem jelenik meg.
A HTTP 202-válasznak azt a helyet és gyakoriságot kell jeleznie, amelyet az ügyfélnek le kell keresnie a válaszhoz. A következő további fejlécekkel kell rendelkeznie:
Fejléc Leírás Jegyzetek Hely Egy URL- cím, amely alapján az ügyfélnek le kell keresnie a válasz állapotát. Ez az URL-cím lehet SAS-jogkivonat, és a valet kulcsminta megfelelő, ha ehhez a helyhez hozzáférés-vezérlésre van szükség. Az értékkulcs minta akkor is érvényes, ha a válasz-lekérdezést ki kell kapcsolni egy másik háttérrendszerbe. Újrapróbálkozás után Becslés arra vonatkozóan, hogy mikor fejeződik be a feldolgozás Ez a fejléc célja, hogy megakadályozza, hogy a lekérdezési ügyfelek túlterhelik a háttérrendszert az újrapróbálkozással. A válasz tervezésekor figyelembe kell venni az ügyfél várt viselkedését. Bár az Ön felügyelete alatt álló ügyfél kódolt, hogy explicit módon tartsa tiszteletben ezeket a válaszértékeket, a nem Ön által létrehozott vagy nem vagy alacsony kódszámú megközelítést használó ügyfelek (például az Azure Logic Apps) szabadon használhatják saját HTTP 202-alapú logikai kezelésüket.
Előfordulhat, hogy a válaszfejlécek vagy a hasznos adatok kezeléséhez feldolgozási proxyt vagy homlokzatot kell használnia a használt mögöttes szolgáltatásoktól függően.
Ha az állapotvégpont a befejezéskor átirányít, akkor a HTTP 302 vagy a HTTP 303 megfelelő visszatérési kód, a támogatott szemantikától függően.
Sikeres feldolgozás esetén a Hely fejlécben megadott erőforrásnak megfelelő HTTP-válaszkódot kell visszaadnia, például 200 (OK), 201 (Létrehozva) vagy 204 -et (Nincs tartalom).
Ha a feldolgozás során hiba történik, őrizze meg a hibát a Hely fejlécben leírt erőforrás URL-címén, és ideális esetben adja vissza a megfelelő válaszkódot az ügyfélnek az adott erőforrásból (4xx kód).
Nem minden megoldás implementálja ezt a mintát ugyanúgy, és egyes szolgáltatások további vagy alternatív fejléceket is tartalmaznak. Az Azure Resource Manager például ennek a mintának egy módosított változatát használja. További információ: Azure Resource Manager Async Operations.
Előfordulhat, hogy a régi ügyfelek nem támogatják ezt a mintát. Ebben az esetben előfordulhat, hogy egy homlokzatot kell elhelyeznie az aszinkron API-ra, hogy elrejtse az aszinkron feldolgozást az eredeti ügyfél elől. Az Azure Logic Apps például natív módon támogatja ezt a mintát egy aszinkron API és egy szinkron hívásokat végző ügyfél integrációs rétegeként. Lásd: Hosszú ideig futó feladatok végrehajtása a webhook műveleti mintával.
Bizonyos esetekben érdemes lehet módot biztosítani az ügyfelek számára egy hosszú ideig futó kérelem megszakítására. Ebben az esetben a háttérszolgáltatásnak valamilyen lemondási utasítást kell támogatnia.
Mikor érdemes ezt a mintát használni?
Használja ezt a mintát a következőkhöz:
Az ügyféloldali kód, például a böngészőalkalmazások, ahol nehéz visszahívási végpontokat biztosítani, vagy a hosszú ideig futó kapcsolatok használata túl sok további összetettséggel jár.
Olyan szolgáltatáshívások, ahol csak a HTTP protokoll érhető el, és a visszatérési szolgáltatás nem tud visszahívásokat indítani az ügyféloldali tűzfalkorlátozások miatt.
Olyan szolgáltatáshívásokat kell integrálnunk az örökölt architektúrákkal, amelyek nem támogatják a modern visszahívási technológiákat, például a WebSocketeket vagy a webhookokat.
Ez a minta nem feltétlenül megfelelő, ha:
- Ehelyett használhat aszinkron értesítésekhez készült szolgáltatást, például az Azure Event Gridet.
- A válaszoknak valós időben kell streamelnie az ügyfélnek.
- Az ügyfélnek számos eredményt kell összegyűjtenie, és fontos, hogy az eredmények késése érkezik. Fontolja meg inkább a service bus-mintát.
- Használhat kiszolgálóoldali állandó hálózati kapcsolatokat, például WebSocketeket vagy SignalR-eket. Ezekkel a szolgáltatásokkal értesítheti a hívót az eredményről.
- A hálózati kialakítás lehetővé teszi portok megnyitását aszinkron visszahívások vagy webhookok fogadásához.
Számítási feladatok tervezése
Az tervezőknek értékelniük kell, hogyan használható az aszinkron kérés-válasz minta a számítási feladat kialakításában az Azure Well-Architected Framework pilléreiben foglalt célok és alapelvek kezelésére. Példa:
Pillér | Hogyan támogatja ez a minta a pillércélokat? |
---|---|
A teljesítményhatékonyság a skálázás, az adatok és a kód optimalizálásával segíti a számítási feladatok hatékony kielégítését . | Az azonnali választ nem igénylő folyamatokhoz tartozó interakciók kérési és válaszfázisainak leválasztása javítja a rendszerek válaszkészségét és méretezhetőségét. Aszinkron megközelítésként maximalizálhatja az egyidejűséget a kiszolgálóoldalon, és ütemezheti a kapacitás által lehetővé tevő munka befejezését. - PE:05 Skálázás és particionálás - PE:07 Kód és infrastruktúra |
Mint minden tervezési döntésnél, fontolja meg az ezzel a mintával bevezethető többi pillér céljaival szembeni kompromisszumokat.
Példa
Az alábbi kódrészletek egy olyan alkalmazásból származnak, amely az Azure Functions használatával implementálja ezt a mintát. A megoldásban három függvény található:
- Az aszinkron API-végpont.
- Az állapotvégpont.
- Egy háttérfüggvény, amely az üzenetsorba helyezett munkaelemeket veszi át, és végrehajtja őket.
Ez a minta a GitHubon érhető el.
AsyncProcessingWorkAcceptor függvény
A AsyncProcessingWorkAcceptor
függvény implementál egy végpontot, amely fogadja a munkát egy ügyfélalkalmazásból, és egy várólistára helyezi a feldolgozáshoz.
- A függvény létrehoz egy kérésazonosítót, és metaadatként hozzáadja az üzenetsor-üzenethez.
- A HTTP-válasz tartalmaz egy állapotvégpontra mutató helyfejlécet. A kérelem azonosítója az URL-elérési út része.
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<ServiceBusMessage> OutMessages,
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);
var message = new ServiceBusMessage(messagePayload);
message.ApplicationProperties.Add("RequestGUID", reqid);
message.ApplicationProperties.Add("RequestSubmittedAt", DateTime.Now);
message.ApplicationProperties.Add("RequestStatusURL", rqs);
await OutMessages.AddAsync(message);
return new AcceptedResult(rqs, $"Request Accepted for Processing{Environment.NewLine}ProxyStatus: {rqs}");
}
}
AsyncProcessingBackgroundWorker függvény
A AsyncProcessingBackgroundWorker
függvény felveszi a műveletet az üzenetsorból, elvégzi a munkát az üzenet hasznos adatai alapján, és az eredményt egy tárfiókba írja.
public static class AsyncProcessingBackgroundWorker
{
[FunctionName("AsyncProcessingBackgroundWorker")]
public static async Task RunAsync(
[ServiceBusTrigger("outqueue", Connection = "ServiceBusConnectionAppSetting")] BinaryData customer,
IDictionary<string, object> applicationProperties,
[Blob("data", FileAccess.ReadWrite, Connection = "StorageConnectionAppSetting")] BlobContainerClient inputContainer,
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 = applicationProperties["RequestGUID"] as string;
BlobClient blob = inputContainer.GetBlobClient($"{id}.blobdata");
// Now write the results to blob storage.
await blob.UploadAsync(customer);
}
}
AsyncOperationStatusChecker függvény
A AsyncOperationStatusChecker
függvény implementálja az állapotvégpontot. Ez a függvény először ellenőrzi, hogy a kérés befejeződött-e
- Ha a kérés befejeződött, a függvény vagy egy valet-key-t ad vissza a válasznak, vagy azonnal átirányítja a hívást a valet-key URL-címre.
- Ha a kérés még függőben van, akkor egy 200 kódot kell visszaadnunk, az aktuális állapotot is beleértve.
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")] BlockBlobClient inputBlob, string thisGUID,
ILogger log)
{
OnCompleteEnum OnComplete = Enum.Parse<OnCompleteEnum>(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
OnPendingEnum OnPending = Enum.Parse<OnPendingEnum>(req.Query["OnPending"].FirstOrDefault() ?? "OK");
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, then we need to back off. Depending on the value of the optional "OnPending" parameter, choose what to do.
string rqs = $"http://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{thisGUID}";
switch (OnPending)
{
case OnPendingEnum.OK:
{
// Return an HTTP 200 status code.
return new OkObjectResult(new { status = "In progress", 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 new NotFoundResult();
}
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnPending}");
}
}
}
}
private static async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, BlockBlobClient inputBlob, string thisGUID)
{
switch (OnComplete)
{
case OnCompleteEnum.Redirect:
{
// Redirect to the SAS URI to blob storage
return 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 new OkObjectResult(await inputBlob.DownloadContentAsync());
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnComplete}");
}
}
}
}
public enum OnCompleteEnum
{
Redirect,
Stream
}
public enum OnPendingEnum
{
OK,
Synchronous
}
Következő lépések
Az alábbi információk segíthetnek a minta megvalósításakor:
- Azure Logic Apps – Hosszú ideig futó feladatokat hajthat végre a lekérdezési műveleti mintával.
- A webes API tervezésekor általános ajánlott eljárásokért lásd a webes API-tervezést.