Saya memigrasikan jutaan pengguna dari on-prem AD ke Azure AD B2C menggunakan MS Graph API untuk membuat pengguna di B2C. Saya telah menulis aplikasi konsol .Net Core 3.1 untuk melakukan migrasi ini. Untuk mempercepat, saya melakukan panggilan bersamaan ke Graph API. Ini bekerja dengan baik - semacam.
Selama pengembangan saya mengalami kinerja yang dapat diterima saat menjalankan dari Visual Studio 2019, tetapi untuk pengujian saya menjalankan dari baris perintah di Powershell 7. Dari Powershell kinerja panggilan bersamaan ke HttpClient sangat buruk. Tampaknya ada batas jumlah panggilan bersamaan yang diizinkan HttpClient saat berjalan dari Powershell, jadi panggilan dalam kumpulan bersamaan yang lebih besar dari 40 hingga 50 permintaan mulai menumpuk. Tampaknya menjalankan 40 hingga 50 permintaan bersamaan sekaligus memblokir sisanya.
Saya tidak mencari bantuan dengan pemrograman async. Saya sedang mencari cara untuk memecahkan masalah perbedaan antara perilaku run-time Visual Studio dan perilaku run-time baris perintah Powershell. Berjalan dalam mode rilis dari tombol panah hijau Visual Studio berperilaku seperti yang diharapkan. Menjalankan dari baris perintah tidak.
Saya mengisi daftar tugas dengan panggilan async dan kemudian menunggu Task.WhenAll (tugas). Setiap panggilan membutuhkan antara 300 dan 400 milidetik. Saat menjalankan dari Visual Studio berfungsi seperti yang diharapkan. Saya melakukan batch 1000 panggilan secara bersamaan dan masing-masing individu selesai dalam waktu yang diharapkan. Seluruh blok tugas hanya membutuhkan beberapa milidetik lebih lama dari panggilan individu terlama.
Perilaku berubah ketika saya menjalankan build yang sama dari baris perintah Powershell. 40 hingga 50 panggilan pertama mengambil 300 hingga 400 milidetik yang diharapkan, tetapi kemudian waktu panggilan individu masing-masing tumbuh hingga 20 detik. Saya pikir panggilan serialisasi, jadi hanya 40 hingga 50 sedang dieksekusi sekaligus sementara yang lain menunggu.
Setelah berjam-jam mencoba-coba, saya bisa mempersempitnya ke HttpClient. Untuk mengisolasi masalah, saya mengejek panggilan ke HttpClient.SendAsync dengan metode yang melakukan Task.Delay (300) dan mengembalikan hasil tiruan. Dalam hal ini menjalankan dari konsol berperilaku identik dengan berjalan dari Visual Studio.
Saya menggunakan IHttpClientFactory dan saya bahkan sudah mencoba menyesuaikan batas koneksi pada ServicePointManager.
Ini kode registrasi saya.
public static IServiceCollection RegisterHttpClient(this IServiceCollection services, int batchSize)
{
ServicePointManager.DefaultConnectionLimit = batchSize;
ServicePointManager.MaxServicePoints = batchSize;
ServicePointManager.SetTcpKeepAlive(true, 1000, 5000);
services.AddHttpClient(MSGraphRequestManager.HttpClientName, c =>
{
c.Timeout = TimeSpan.FromSeconds(360);
c.DefaultRequestHeaders.Add("User-Agent", "xxxxxxxxxxxx");
})
.ConfigurePrimaryHttpMessageHandler(() => new DefaultHttpClientHandler(batchSize));
return services;
}
Inilah DefaultHttpClientHandler.
internal class DefaultHttpClientHandler : HttpClientHandler
{
public DefaultHttpClientHandler(int maxConnections)
{
this.MaxConnectionsPerServer = maxConnections;
this.UseProxy = false;
this.AutomaticDecompression = System.Net.DecompressionMethods.GZip | System.Net.DecompressionMethods.Deflate;
}
}
Inilah kode yang mengatur tugas.
var timer = Stopwatch.StartNew();
var tasks = new Task<(UpsertUserResult, TimeSpan)>[users.Length];
for (var i = 0; i < users.Length; ++i)
{
tasks[i] = this.CreateUserAsync(users[i]);
}
var results = await Task.WhenAll(tasks);
timer.Stop();
Inilah cara saya mengejek HttpClient.
var httpClient = this.httpClientFactory.CreateClient(HttpClientName);
#if use_http
using var response = await httpClient.SendAsync(request);
#else
await Task.Delay(300);
var graphUser = new User { Id = "mockid" };
using var response = new HttpResponseMessage(HttpStatusCode.OK) { Content = new StringContent(JsonConvert.SerializeObject(graphUser)) };
#endif
var responseContent = await response.Content.ReadAsStringAsync();
Berikut adalah metrik untuk pengguna 10k B2C yang dibuat melalui GraphAPI menggunakan 500 permintaan bersamaan. 500 permintaan pertama lebih panjang dari biasanya karena koneksi TCP sedang dibuat.
Berikut tautan ke metrik jalankan konsol .
Berikut tautan ke metrik menjalankan Visual Studio .
Waktu blok dalam metrik VS menjalankan berbeda dari apa yang saya katakan dalam posting ini karena saya memindahkan semua akses file sinkron ke akhir proses dalam upaya untuk mengisolasi kode bermasalah sebanyak mungkin untuk menjalankan tes.
Proyek ini dikompilasi menggunakan Net Core 3.1. Saya menggunakan Visual Studio 2019 16.4.5.