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 bergantung pada 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. Untuk panduan tentang merancang pemrosesan back-end, lihat Pekerjaan latar belakang.
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 harus 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 setiap panggilan yang berhasil ke titik akhir status, titik akhir mengembalikan HTTP 200 (OK). Saat pekerjaan sedang berlangsung, endpoint status mengembalikan sumber daya yang menunjukkan status tersebut. Isi respons status harus menyertakan informasi yang cukup bagi klien untuk memahami status operasi saat ini.
Ketika pekerjaan selesai, titik akhir status 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 mengirimkan permintaan dan menerima respons HTTP 202 (Accepted).
Klien mengirimkan permintaan GET HTTP ke titik akhir status. Panggilan ini mengembalikan HTTP 200 karena pekerjaan tertunda.
Pada titik tertentu, pekerjaan selesai dan endpoint status mengembalikan HTTP 303 (Lihat yang Lain) untuk mengarahkan ke sumber daya.
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, beberapa implementasi tidak menggunakan titik akhir status terpisah. Sebagai gantinya, klien melakukan polling URL sumber daya target secara langsung dan menerima HTTP 404 (Tidak Ditemukan) hingga sumber daya dibuat. Respons ini dihasilkan karena sumber daya belum ada. Namun, pendekatan ini tidak jelas karena ID permintaan yang tidak valid juga mengembalikan HTTP 404. Titik akhir status khusus yang mengembalikan HTTP 200 dengan isi status, seperti yang dijelaskan dalam pola ini, menghindari kebingungan ini.
Respons HTTP 202 menunjukkan di mana klien melakukan polling dan seberapa sering. Ini harus mencakup tajuk berikut.
Header Deskripsi Catatan LocationURL yang diperiksa klien secara berkala untuk status respons URL ini dapat menjadi token tanda tangan akses bersama (SAS). 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 penyelesaian untuk pemrosesan Header ini membantu klien polling menghindari pengiriman 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.
Pertimbangkan untuk menyertakan bidang berikut dalam respons titik akhir status.
Ladang Deskripsi Catatan statusStatus operasi saat ini, seperti Tertunda, Berjalan, Berhasil, Gagal, atau Dibatalkan Menggunakan sekumpulan nilai terminal dan nonterminal yang konsisten dan terdokumentasi createdAtWaktu ketika operasi diterima Membantu klien mendeteksi operasi kedaluarsa atau ditinggalkan lastUpdatedAtWaktu status terakhir diperbarui Membantu klien membedakan antara operasi yang terhenti dan sedang berlangsung percentCompleteIndikator kemajuan opsional Berguna ketika back end dapat memperkirakan kemajuan errorObjek kesalahan terstruktur saat status Gagal Untuk konsistensi, pertimbangkan untuk menggunakan format RFC 9457 . 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, gunakan HTTP 303 (Lihat Lainnya). 303 menginstruksikan klien untuk mengeluarkan permintaan GET ke URL pengalihan, terlepas dari metode permintaan asli. Perilaku ini adalah semantik yang benar untuk pola ini karena klien mengambil sumber daya hasil yang berbeda, bukan mengirim ulang operasi asli. HTTP 302 (Ditemukan) tidak menjamin perubahan metode. Beberapa klien memutar ulang metode asli saat dialihkan. Perilaku ini dapat menyebabkan efek samping yang tidak diinginkan, seperti permintaan POST duplikat.
Setelah server berhasil memproses permintaan, sumber daya yang
Locationditentukan header mengembalikan kode status HTTP seperti 200, 201 (Dibuat), atau 204 (Tanpa Konten).Jika terjadi kesalahan selama pemrosesan, simpan kesalahan pada URL sumber daya yang ditentukan oleh header
Locationdan kembalikan kode status 4xx dari sumber daya yang sesuai dengan kegagalan. Gunakan format kesalahan terstruktur, seperti RFC 9457 (Detail Masalah untuk API HTTP), sehingga klien dapat mengurai dan menangani kegagalan secara terprogram.Sumber daya status dan hasil yang disimpan menggunakan penyimpanan dan komputasi. Tentukan kebijakan penyimpanan untuk membersihkannya setelah periode yang wajar. Untuk memberi tahu klien tentang jendela retensi, Anda dapat menambahkan
Expiresheader ke respons status.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 Resource Manager operasi asinkron.
Klien lama mungkin tidak mendukung pola ini. Dalam hal ini, Anda mungkin perlu menempatkan fasad di atas 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 selengkapnya, lihat Perilaku respons permintaan asinkron di Logic Apps.
Untuk memberikan cara bagi klien untuk membatalkan permintaan yang berjalan lama, sediakan operasi DELETE pada sumber daya endpoint status. Permintaan ini harus meneruskan instruksi pembatalan ke komponen pemrosesan back-end. Setelah back end menangani pembatalan, back end harus memperbarui sumber daya status untuk mencerminkan status yang dibatalkan. Proses ini membantu mencegah pekerjaan yang tidak lengkap mengkonsumsi sumber daya tanpa batas waktu. Tentukan apakah operasi mendukung pembatalan parsial atau memerlukan transaksi kompensasi.
Anda dapat mengharuskan klien untuk menyediakan kunci idempotensi, misalnya di
Idempotency-Keyheader permintaan, ketika mereka mengirimkan permintaan awal. Jika back end menerima kunci duplikat, backend harus mengembalikan sumber daya status yang sudah ada alih-alih mengantrekan item kerja kedua. Pendekatan ini melindungi dari kegagalan jaringan yang menyebabkan klien mencoba kembali POST yang sudah diterima server. Ini sangat penting dalam pola ini karena klien tidak dapat membedakan antara respons yang hilang dan permintaan yang tidak pernah diterima.
Nota
Pola ini menjelaskan polling HTTP, di mana klien secara berkala mengeluarkan permintaan baru untuk memeriksa status. Dalam long polling, klien mengirim permintaan dan server menahan koneksi terbuka hingga data baru tersedia atau terjadi batas waktu. Long polling mengurangi latensi respons dibandingkan dengan polling berkala, tetapi memperkenalkan kompleksitas dalam manajemen koneksi dan batas waktu.
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 beban kerja 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. Pertimbangkan untuk menggunakan Server-Sent Events (SSEs), yang menyediakan saluran pendorongan ringan, asli HTTP, dan unidirectional dari server ke klien tanpa mengharuskan klien untuk melakukan polling.
Klien perlu mengumpulkan banyak hasil, dan latensi hasil tersebut penting. Pertimbangkan untuk menggunakan broker pesan 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 Asynchronous Request-Reply dalam desain beban kerja mereka untuk mencapai tujuan dan prinsip yang tercakup dalam pilar Azure Well-Architected Framework.
| 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.
Implementasi menggunakan identitas terkelola untuk mengautentikasi dengan Azure Service Bus dan Azure Blob Storage, yang menghindari penyimpanan string koneksi atau kunci akun. Dependensi didaftarkan di Program.cs dengan menggunakan DefaultAzureCredential dan disuntikkan melalui konstruktor primer.
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 danRetry-Afterheader yang menyarankan interval polling. ID permintaan muncul di jalur URL.
public class AsyncProcessingWorkAcceptor(ServiceBusClient _serviceBusClient)
{
[Function("AsyncProcessingWorkAcceptor")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = null)] HttpRequest req,
[FromBody] CustomerPOCO customer)
{
if (string.IsNullOrEmpty(customer.id) || string.IsNullOrEmpty(customer.customername))
{
return new BadRequestResult();
}
string requestId = Guid.NewGuid().ToString();
string statusUrl = $"https://{Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME")}/api/RequestStatus/{requestId}";
var messagePayload = JsonConvert.SerializeObject(customer);
var message = new ServiceBusMessage(messagePayload);
message.ApplicationProperties.Add("RequestGUID", requestId);
message.ApplicationProperties.Add("RequestSubmittedAt", DateTime.UtcNow);
message.ApplicationProperties.Add("RequestStatusURL", statusUrl);
var sender = _serviceBusClient.CreateSender("outqueue");
await sender.SendMessageAsync(message);
req.HttpContext.Response.Headers["Retry-After"] = "5";
return new AcceptedResult(statusUrl, null);
}
}
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("AsyncProcessingBackgroundWorker")]
public async Task Run(
[ServiceBusTrigger("outqueue", Connection = "ServiceBusConnection")] ServiceBusReceivedMessage message)
{
// Perform an action against the blob data source for the async readers to check against.
// This is where your service worker processing will be performed.
var requestGuid = message.ApplicationProperties["RequestGUID"].ToString();
string blobName = $"{requestGuid}.blobdata";
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 HTTP 303 (Lihat Lainnya) dan mengalihkan klien ke URL kunci valet untuk hasilnya.
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/{requestId}")] HttpRequest req,
[BlobInput("data/{requestId}.blobdata", Connection = "DataStorage")] BlockBlobClient inputBlob, string requestId)
{
OnCompleteEnum OnComplete = Enum.Parse<OnCompleteEnum>(req.Query["OnComplete"].FirstOrDefault() ?? "Redirect");
OnPendingEnum OnPending = Enum.Parse<OnPendingEnum>(req.Query["OnPending"].FirstOrDefault() ?? "OK");
_logger.LogInformation("Received status request for {RequestId} - OnComplete {OnComplete} - OnPending {OnPending}",
requestId, OnComplete, 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, requestId, req);
}
else
{
// If the blob doesn't exist, the function uses the OnPending parameter to determine the next action.
switch (OnPending)
{
case OnPendingEnum.OK:
{
// Return an HTTP 200 status code.
return new OkObjectResult(new { status = "In progress", Location = rqs });
}
case OnPendingEnum.Synchronous:
{
// Long polling example: hold the connection open and check for completion
// using exponential backoff. Time out after approximately one minute.
int backoff = 250;
while (!await inputBlob.ExistsAsync() && backoff < 64000)
{
_logger.LogInformation("Synchronous mode {RequestId} - retrying in {Backoff} ms", requestId, backoff);
backoff = backoff * 2;
await Task.Delay(backoff);
}
if (await inputBlob.ExistsAsync())
{
_logger.LogInformation("Synchronous mode {RequestId} - completed after {Backoff} ms", requestId, backoff);
return await OnCompleted(OnComplete, inputBlob, requestId, req);
}
else
{
_logger.LogInformation("Synchronous mode {RequestId} - NOT FOUND after timeout {Backoff} ms", requestId, backoff);
return new NotFoundResult();
}
}
default:
{
throw new InvalidOperationException($"Unexpected value: {OnPending}");
}
}
}
}
private async Task<IActionResult> OnCompleted(OnCompleteEnum OnComplete, BlockBlobClient inputBlob, string requestId, HttpRequest req)
{
switch (OnComplete)
{
case OnCompleteEnum.Redirect:
{
// Generate a user delegation SAS URI by using managed identity credentials.
BlobServiceClient blobServiceClient = inputBlob.GetParentBlobContainerClient().GetParentBlobServiceClient();
var userDelegationKey = await blobServiceClient.GetUserDelegationKeyAsync(DateTimeOffset.UtcNow, DateTimeOffset.UtcNow.AddDays(7));
// Return 303 (See Other) to redirect the client to the result resource.
// GenerateUserDelegationSasUri is a custom helper. See the full implementation on GitHub.
req.HttpContext.Response.Headers.Location = GenerateUserDelegationSasUri(inputBlob, userDelegationKey);
return new StatusCodeResult(StatusCodes.Status303SeeOther);
}
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
}