Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Memisahkan pemrosesan back-end dari host antarmuka depan ketika pemrosesan back-end perlu berjalan secara asinkron, tetapi antarmuka depan membutuhkan respons yang jelas.
Konteks dan masalah
Dalam pengembangan aplikasi modern, aplikasi klien sering mengandalkan API jarak jauh untuk menyediakan logika bisnis dan menyusun fungsionalitas. Banyak aplikasi menjalankan kode di browser web, dan lingkungan lain juga menghosting kode klien. API mungkin berhubungan langsung dengan aplikasi atau beroperasi sebagai layanan bersama dari layanan eksternal. Sebagian besar panggilan API menggunakan HTTP atau HTTPS dan mengikuti semantik REST.
Dalam kebanyakan kasus, API untuk aplikasi klien merespons dalam sekitar 100 milidetik (ms) atau kurang. Banyak faktor dapat memengaruhi latensi respons:
- Lapisan hosting aplikasi
- Komponen keamanan
- Lokasi geografis relatif pemanggil dan sisi belakang
- Infrastruktur jaringan
- Beban saat ini
- Ukuran payload permintaan
- Panjang antrean pemrosesan
- Waktu yang dibutuhkan untuk back end memproses permintaan
Faktor-faktor ini dapat menambahkan latensi ke respons. Anda dapat mengurangi beberapa faktor risiko dengan meningkatkan kapasitas pada backend. Faktor lain, seperti infrastruktur jaringan, berada di luar kontrol pengembang aplikasi. Sebagian besar API merespons dengan cukup cepat agar respons kembali melalui koneksi yang sama. Kode aplikasi dapat melakukan panggilan API sinkron dengan cara nonblokir untuk memberikan tampilan pemrosesan asinkron. Kami merekomendasikan pendekatan ini untuk operasi terikat input dan output (I/O).
Dalam beberapa skenario, back end melakukan pekerjaan yang berlangsung lama dan memakan waktu beberapa detik. Pada skenario lain, bagian belakang melakukan pekerjaan latar belakang yang berjalan lama selama beberapa menit atau periode yang diperpanjang. Dalam kasus ini, Anda tidak dapat menunggu pekerjaan selesai sebelum mengirim respons. Situasi ini dapat membuat masalah untuk pola balasan permintaan sinkron.
Beberapa arsitektur memecahkan masalah ini dengan menggunakan perantara pesan untuk memisahkan tahap permintaan dan respons. Banyak sistem mencapai pemisahan ini melalui pola Queue-Based Load Leveling. Pemisahan ini memungkinkan proses klien dan API back-end untuk meningkatkan skala secara mandiri. Ini juga memperkenalkan kompleksitas ekstra ketika klien memerlukan pemberitahuan keberhasilan karena langkah itu juga harus menjadi asinkron.
Banyak pertimbangan yang sama yang berlaku untuk aplikasi klien juga berlaku untuk panggilan REST API server-ke-server dalam sistem terdistribusi, seperti dalam arsitektur layanan mikro.
Solusi
Salah satu solusi untuk masalah ini adalah dengan menggunakan polling HTTP. Polling berfungsi dengan baik untuk kode sisi klien ketika titik akhir panggilan balik tidak tersedia atau ketika koneksi yang berjalan lama menambahkan terlalu banyak kompleksitas. Bahkan ketika panggilan balik dimungkinkan, pustaka dan layanan tambahan yang mereka butuhkan dapat meningkatkan kompleksitas.
Langkah-langkah berikut menjelaskan solusi:
Aplikasi klien melakukan panggilan sinkron ke API untuk memicu operasi yang berjalan lama di ujung belakang.
API merespons secara sinkron secepat mungkin. Ini mengembalikan kode status HTTP 202 (Diterima) untuk mengakui bahwa ia menerima permintaan pemrosesan.
Nota
API memvalidasi permintaan dan tindakan yang akan dilakukan sebelum memulai proses yang berjalan lama. Jika permintaan tidak valid, segera balas dengan kode kesalahan seperti HTTP 400 (Permintaan Buruk).
Respon mencakup referensi lokasi yang menunjuk ke alamat tujuan yang dapat diakses klien untuk memeriksa hasil operasi jangka panjang.
API memindahkan pemrosesan ke komponen lain, seperti antrian pesan.
Untuk suatu panggilan yang berhasil ke titik akhir status, titik akhir tersebut akan mengembalikan HTTP 200 (OK). Saat pekerjaan sedang berlangsung, titik akhir mengembalikan sumber daya yang menunjukkan status tersebut. Setelah pekerjaan selesai, titik akhir mengembalikan sumber daya yang menunjukkan penyelesaian atau pengalihan ke URL sumber daya lain. Misalnya, jika operasi asinkron membuat sumber daya baru, titik akhir status akan dialihkan ke URL untuk sumber daya tersebut.
Diagram berikut menunjukkan alur umum.
Klien mengirim permintaan dan menerima respons HTTP 202.
Klien mengirimkan permintaan GET HTTP ke titik akhir status. Pekerjaan tertunda, sehingga panggilan ini mengembalikan HTTP 200.
Pekerjaan selesai dan endpoint status mengembalikan HTTP 302 (Found) untuk mengarahkan ulang ke resource.
Klien mengambil sumber daya di URL yang ditentukan.
Masalah dan pertimbangan
Pertimbangkan poin-poin berikut saat Anda memutuskan cara menerapkan pola ini:
Beberapa cara ada untuk menerapkan pola ini melalui HTTP, dan layanan hulu tidak selalu menggunakan semantik yang sama. Misalnya, sebagian besar layanan mengembalikan HTTP 404 (Tidak Ditemukan) dari metode GET ketika proses jarak jauh tidak selesai, bukan HTTP 202. Menurut semantik REST standar, HTTP 404 adalah respons yang benar karena hasil panggilan belum ada.
Respons HTTP 202 menunjukkan di mana klien melakukan polling dan seberapa sering. Ini termasuk header berikut.
Header Deskripsi Catatan LocationURL yang diperiksa klien secara berkala untuk status respons URL ini dapat menjadi token tanda tangan akses bersama. Pola Kunci Valet berfungsi dengan baik ketika lokasi ini membutuhkan kontrol akses. Pola ini juga berlaku ketika polling respons perlu dialihkan ke back end yang lain. Retry-AfterPerkiraan waktu pemrosesan akan selesai Header ini mencegah klien polling mengirim terlalu banyak permintaan ke back end. Pertimbangkan perilaku klien yang diharapkan saat Anda merancang respons ini. Klien yang Anda kontrol dapat mengikuti nilai respons ini dengan tepat. Klien yang ditulis orang lain, termasuk klien yang dibangun dengan menggunakan alat tanpa kode atau kode rendah seperti Azure Logic Apps, dapat menerapkan penanganan mereka sendiri untuk HTTP 202.
Anda mungkin perlu menggunakan proksi pemrosesan untuk menyesuaikan header respons atau payload, tergantung pada layanan mendasar yang Anda gunakan.
Jika titik akhir status dialihkan setelah selesai, HTTP 302 atau HTTP 303 (Lihat Lainnya) adalah kode pengembalian yang valid, tergantung pada semantik yang Anda dukung.
Setelah server memproses permintaan, sumber daya yang
Locationditentukan header mengembalikan kode status HTTP seperti 200, 201 (Dibuat), atau 204 (Tanpa Konten).Jika terjadi kesalahan selama pemrosesan, kesalahan tersebut dipertahankan pada URL sumber daya yang ditentukan oleh header
Locationdan kembalikan kode status HTTP 4xx dari sumber daya yang sesuai dengan kegagalan tersebut.Solusi tidak semuanya menerapkan pola ini dengan cara yang sama, dan beberapa layanan menyertakan header tambahan atau alternatif. Misalnya, Azure Resource Manager menggunakan varian pola ini yang dimodifikasi. Untuk informasi selengkapnya, lihat operasi asinkron pada Resource Manager.
Klien lama mungkin tidak mendukung pola ini. Dalam hal ini, Anda mungkin perlu menempatkan proksi pemrosesan melalui API asinkron untuk menyembunyikan pemrosesan asinkron dari klien asli. Misalnya, Logic Apps mendukung pola ini secara asli, dan Anda dapat menggunakannya sebagai lapisan integrasi antara API asinkron dan klien yang melakukan panggilan sinkron. Untuk informasi lebih lanjut, lihat Melaksanakan tugas jangka panjang dengan pola aksi webhook.
Dalam beberapa skenario, Anda mungkin ingin menyediakan cara bagi klien untuk membatalkan permintaan yang sudah berjalan lama. Dalam hal ini, layanan back-end harus mendukung beberapa bentuk instruksi pembatalan.
Kapan menggunakan pola ini
Gunakan pola ini ketika:
Anda bekerja dengan kode sisi klien, seperti aplikasi browser, dan batasan tersebut membuat titik akhir panggilan balik sulit disediakan, atau koneksi yang berjalan lama menambah terlalu banyak kompleksitas.
Anda memanggil layanan yang hanya menggunakan protokol HTTP dan layanan pengembalian tidak dapat mengirim panggilan balik karena pembatasan firewall di sisi klien.
Anda berintegrasi dengan arsitektur warisan yang tidak mendukung mekanisme panggilan balik modern seperti WebSocket atau webhook.
Pola ini mungkin tidak cocok ketika:
Anda dapat menggunakan layanan yang dibuat untuk pemberitahuan asinkron, seperti Azure Event Grid.
Respons harus mengalir secara real time ke klien.
Klien perlu mengumpulkan banyak hasil dan latensi hasil tersebut penting. Pertimbangkan pola bus layanan sebagai gantinya.
Koneksi jaringan persisten sisi server seperti WebSockets atau SignalR tersedia. Anda dapat menggunakan koneksi ini untuk memberi tahu pemanggil hasilnya.
Desain jaringan mendukung port terbuka untuk menerima panggilan balik atau webhook asinkron.
Desain beban kerja
Arsitek harus mengevaluasi bagaimana mereka dapat menggunakan pola Permintaan-Tanggapan Asinkron dalam desain beban kerja mereka untuk memenuhi tujuan dan prinsip yang tercakup dalam pilar kerangka kerja Azure Well-Architected.
| Pilar | Bagaimana pola ini mendukung tujuan pilar |
|---|---|
| Efisiensi Performa membantu beban kerja Anda memenuhi permintaan secara efisien melalui pengoptimalan dalam penskalaan, data, dan kode. | Anda meningkatkan responsivitas dan skalabilitas dengan memisahkan fase permintaan dan balasan untuk proses yang tidak memerlukan respons segera. Pendekatan asinkron meningkatkan tingkat konkurensi dan memungkinkan server untuk menjadwalkan pekerjaan saat kapasitas tersedia. - PE:05 Penskalaan dan pemartisian - Pe:07 Kode dan infrastruktur |
Seperti halnya keputusan desain apa pun, pertimbangkan kompromi terhadap tujuan pilar lain yang diperkenalkan oleh pola ini.
Example
Kode berikut menunjukkan kutipan dari aplikasi yang menggunakan Azure Functions untuk menerapkan pola ini. Solusi ini memiliki tiga fungsi:
- Titik akhir API asinkron
- Titik akhir status
- Fungsi back-end yang menangani item pekerjaan dalam antrean dan menjalankannya
Sampel ini tersedia di GitHub.
Fungsi AsyncProcessingWorkAcceptor
Fungsi ini AsyncProcessingWorkAcceptor mengimplementasikan titik akhir yang menerima pekerjaan dari aplikasi klien dan mengantrekannya untuk diproses:
Fungsi ini menghasilkan ID permintaan dan menambahkannya sebagai metadata ke pesan antrean.
Respons HTTP menyertakan
Locationheader yang menunjuk ke titik akhir status. ID permintaan muncul di jalur URL.
public class AsyncProcessingWorkAcceptor(ServiceBusClient _serviceBusClient)
{
[Function("AsyncProcessingWorkAcceptor")]
public async Task<IActionResult> RunAsync([HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req, [FromBody] CustomerPOCO customer)
{
if (string.IsNullOrEmpty(customer.id) || string.IsNullOrEmpty(customer.customername))
{
return new BadRequestResult();
}
var reqid = Guid.NewGuid().ToString();
string scheme = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development" ? "http" : "https";
var rqs = $"{scheme}://{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);
var sender = _serviceBusClient.CreateSender("outqueue");
await sender.SendMessageAsync(message);
return new AcceptedResult(rqs, $"Request Accepted for Processing{Environment.NewLine}ProxyStatus: {rqs}");
}
}
Fungsi "AsyncProcessingBackgroundWorker"
Fungsi AsyncProcessingBackgroundWorker membaca operasi dari antrean, memprosesnya berdasarkan muatan pesan, dan menulis hasilnya ke akun penyimpanan data.
public class AsyncProcessingBackgroundWorker(BlobContainerClient _blobContainerClient)
{
[Function(nameof(AsyncProcessingBackgroundWorker))]
public async Task Run([ServiceBusTrigger("outqueue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message)
{
var requestGuid = message.ApplicationProperties["RequestGUID"].ToString();
string blobName = $"{requestGuid}.blobdata";
await _blobContainerClient.CreateIfNotExistsAsync();
var blobClient = _blobContainerClient.GetBlobClient(blobName);
using (MemoryStream memoryStream = new MemoryStream())
using (StreamWriter writer = new StreamWriter(memoryStream))
{
writer.Write(message.Body.ToString());
writer.Flush();
memoryStream.Position = 0;
await blobClient.UploadAsync(memoryStream, overwrite: true);
}
}
}
Fungsi AsyncOperationStatusChecker
Fungsi AsyncOperationStatusChecker mengimplementasikan titik akhir status. Fungsi ini memeriksa status permintaan:
Jika permintaan selesai, fungsi mengembalikan kunci valet ke respons atau mengalihkan panggilan segera ke URL kunci valet.
Jika permintaan tertunda, fungsi mengembalikan kode HTTP 200 yang menyertakan status saat ini.
public class AsyncOperationStatusChecker(ILogger<AsyncOperationStatusChecker> _logger)
{
[Function("AsyncOperationStatusChecker")]
public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "RequestStatus/{thisGUID}")] HttpRequest req,
[BlobInput("data/{thisGUID}.blobdata", Connection = "DataStorage")] BlockBlobClient inputBlob, string thisGUID)
{
OnCompleteEnum OnComplete = Enum.Parse<OnCompleteEnum>(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
OnPendingEnum OnPending = Enum.Parse<OnPendingEnum>(req.Query["OnPending"].FirstOrDefault() ?? "OK");
_logger.LogInformation($"C# HTTP trigger function processed a request for status on {thisGUID} - OnComplete {OnComplete} - OnPending {OnPending}");
// Check whether the blob exists.
if (await inputBlob.ExistsAsync())
{
// If the blob exists, the function uses the OnComplete parameter to determine the next action.
return await OnCompleted(OnComplete, inputBlob, thisGUID);
}
else
{
// If the blob doesn't exist, the function uses the OnPending parameter to determine the next action.
string scheme = Environment.GetEnvironmentVariable("AZURE_FUNCTIONS_ENVIRONMENT") == "Development" ? "http" : "https";
string rqs = $"{scheme}://{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 back-off period reaches one minute.
int backoff = 250;
while (!await inputBlob.ExistsAsync() && backoff < 64000)
{
_logger.LogInformation($"Synchronous mode {thisGUID}.blob - retrying in {backoff} ms");
backoff = backoff * 2;
await Task.Delay(backoff);
}
if (await inputBlob.ExistsAsync())
{
_logger.LogInformation($"Synchronous Redirect mode {thisGUID}.blob - completed after {backoff} ms");
return await OnCompleted(OnComplete, inputBlob, thisGUID);
}
else
{
_logger.LogInformation($"Synchronous mode {thisGUID}.blob - NOT FOUND after timeout {backoff} ms");
return new NotFoundResult();
}
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnPending}");
}
}
}
}
private async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, BlockBlobClient inputBlob, string thisGUID)
{
switch (OnComplete)
{
case OnCompleteEnum.Redirect:
{
// The typical way to generate a shared access signature token in code requires the storage account key.
// If you need to use a managed identity to control access to your storage accounts in code, which is a recommended best practice, you should do so when possible.
// In this scenario, you don't have a storage account key, so you need to find another way to generate the shared access signatures.
// To generate shared access signatures, use a user delegation shared access signature. This approach lets you sign the shared access signature by using Microsoft Entra ID credentials instead of the storage account key.
BlobServiceClient blobServiceClient = inputBlob.GetParentBlobContainerClient().GetParentBlobServiceClient();
var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(7));
// Redirect the shared access signature uniform resource identifier (URI) to blob storage.
return new RedirectResult(inputBlob.GenerateSASURI(userDelegationKey));
}
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
}
Kelas berikut CloudBlockBlobExtensions menyediakan metode ekstensi yang digunakan pemeriksa status untuk menghasilkan pengidentifikasi sumber daya seragam (URI) tanda tangan akses bersama delegasi pengguna untuk blob hasil.
public static class CloudBlockBlobExtensions
{
public static string GenerateSASURI(this BlockBlobClient inputBlob, UserDelegationKey userDelegationKey)
{
BlobServiceClient blobServiceClient = inputBlob.GetParentBlobContainerClient().GetParentBlobServiceClient();
BlobSasBuilder blobSasBuilder = new BlobSasBuilder()
{
BlobContainerName = inputBlob.BlobContainerName,
BlobName = inputBlob.Name,
Resource = "b",
StartsOn = DateTimeOffset.UtcNow,
ExpiresOn = DateTimeOffset.UtcNow.AddMinutes(10)
};
blobSasBuilder.SetPermissions(BlobSasPermissions.Read);
var blobUriBuilder = new BlobUriBuilder(inputBlob.Uri)
{
Sas = blobSasBuilder.ToSasQueryParameters(userDelegationKey, blobServiceClient.AccountName)
};
// Generate the shared access signature on the blob, which sets the constraints directly on the signature.
Uri sasUri = blobUriBuilder.ToUri();
// Return the URI string for the container, including the shared access signature token.
return sasUri.ToString();
}
}