MongoDB
 sql >> Teknologi Basis Data >  >> NoSQL >> MongoDB

System.TimeoutException:Timeout terjadi setelah 30000ms memilih server menggunakan CompositeServerSelector

Ini adalah masalah yang sangat rumit terkait dengan Perpustakaan Tugas. Singkatnya, ada terlalu banyak tugas yang dibuat dan dijadwalkan sehingga salah satu tugas yang ditunggu oleh driver MongoDB tidak akan dapat diselesaikan. Saya membutuhkan waktu yang sangat lama untuk menyadari bahwa ini bukanlah jalan buntu meskipun kelihatannya memang demikian.

Berikut adalah langkah untuk mereproduksi:

  1. Unduh kode sumber driver CSharp MongoDB .
  2. Buka solusi itu dan buat proyek konsol di dalamnya dan rujuk proyek driver.
  3. Dalam fungsi Utama, buat System.Threading.Timer yang akan memanggil TestTask tepat waktu. Atur timer untuk segera memulai sekali. Di akhir, tambahkan Console.Read().
  4. Dalam TestTask, gunakan for loop untuk membuat 300 tugas dengan memanggil Task.Factory.StartNew(DoOneThing). Tambahkan semua tugas itu ke daftar dan gunakan Task.WaitAll untuk menunggu semuanya selesai.
  5. Dalam fungsi DoOneThing, buat MongoClient dan lakukan beberapa kueri sederhana.
  6. Sekarang jalankan.

Ini akan gagal di tempat yang sama yang Anda sebutkan:MongoDB.Driver.Core.Clusters.Cluster.WaitForDescriptionChangedHelper.HandleCompletedTask(Task completedTask)

Jika Anda meletakkan beberapa break point, Anda akan tahu bahwa WaitForDescriptionChangedHelper membuat tugas batas waktu. Kemudian menunggu salah satu tugas DescriptionUpdate atau tugas batas waktu untuk diselesaikan. Namun, DescriptionUpdate tidak pernah terjadi, tetapi mengapa?

Sekarang, kembali ke contoh saya, ada satu bagian yang menarik:Saya memulai pengatur waktu. Jika Anda memanggil TestTask secara langsung, itu akan berjalan tanpa masalah. Dengan membandingkannya dengan jendela Tasks Visual Studio, Anda akan melihat bahwa versi timer akan membuat lebih banyak tugas daripada versi non-timer. Biarkan saya menjelaskan bagian ini sedikit nanti. Ada perbedaan penting lainnya. Anda perlu menambahkan baris debug di Cluster.cs :

    protected void UpdateClusterDescription(ClusterDescription newClusterDescription)
    {
        ClusterDescription oldClusterDescription = null;
        TaskCompletionSource<bool> oldDescriptionChangedTaskCompletionSource = null;

        Console.WriteLine($"Before UpdateClusterDescription {_descriptionChangedTaskCompletionSource?.Task.Id}, {_descriptionChangedTaskCompletionSource?.Task?.GetHashCode().ToString("F8")}");
        lock (_descriptionLock)
        {
            oldClusterDescription = _description;
            _description = newClusterDescription;

            oldDescriptionChangedTaskCompletionSource = _descriptionChangedTaskCompletionSource;
            _descriptionChangedTaskCompletionSource = new TaskCompletionSource<bool>();
        }

        OnDescriptionChanged(oldClusterDescription, newClusterDescription);
        Console.WriteLine($"Setting UpdateClusterDescription {oldDescriptionChangedTaskCompletionSource?.Task.Id}, {oldDescriptionChangedTaskCompletionSource?.Task?.GetHashCode().ToString("F8")}");
        oldDescriptionChangedTaskCompletionSource.TrySetResult(true);
        Console.WriteLine($"Set UpdateClusterDescription {oldDescriptionChangedTaskCompletionSource?.Task.Id}, {oldDescriptionChangedTaskCompletionSource?.Task?.GetHashCode().ToString("F8")}");
    }

    private void WaitForDescriptionChanged(IServerSelector selector, ClusterDescription description, Task descriptionChangedTask, TimeSpan timeout, CancellationToken cancellationToken)
    {
        using (var helper = new WaitForDescriptionChangedHelper(this, selector, description, descriptionChangedTask, timeout, cancellationToken))
        {
            Console.WriteLine($"Waiting {descriptionChangedTask?.Id}, {descriptionChangedTask?.GetHashCode().ToString("F8")}");
            var index = Task.WaitAny(helper.Tasks);
            helper.HandleCompletedTask(helper.Tasks[index]);
        }
    }

Dengan menambahkan baris ini, Anda juga akan mengetahui bahwa versi non-timer akan diperbarui dua kali tetapi versi timer hanya akan diperbarui sekali. Dan yang kedua berasal dari "MonitorServerAsync" di ServerMonitor.cs. Ternyata, dalam versi timer, MontiorServerAsync dieksekusi tetapi setelah melewati ServerMonitor.HeartbeatAsync, BinaryConnection.OpenAsync, BinaryConnection.OpenHelperAsync dan TcpStreamFactory.CreateStreamAsync, akhirnya mencapai TcpStreamFactory.ResolveEndPointsAsync. Hal buruk terjadi di sini:Dns.GetHostAddressesAsync . Yang ini tidak pernah dieksekusi. Jika Anda sedikit memodifikasi kode dan mengubahnya menjadi:

    var task = Dns.GetHostAddressesAsync(dnsInitial.Host).ConfigureAwait(false);

    return (await task)
        .Select(x => new IPEndPoint(x, dnsInitial.Port))
        .OrderBy(x => x, new PreferredAddressFamilyComparer(preferred))
        .ToArray();

Anda akan dapat menemukan id tugas. Dengan melihat ke jendela Tasks Visual Studio, cukup jelas bahwa ada sekitar 300 tugas di depannya. Hanya beberapa dari mereka yang mengeksekusi tetapi diblokir. Jika Anda menambahkan Console.Writeline dalam fungsi DoOneThing, Anda akan melihat bahwa penjadwal tugas memulai beberapa di antaranya hampir pada saat yang bersamaan, tetapi kemudian melambat menjadi sekitar satu per detik. Jadi, ini berarti, Anda perlu menunggu sekitar 300 detik sebelum tugas menyelesaikan dns mulai berjalan. Itu sebabnya melebihi batas waktu 30 detik.

Sekarang, inilah solusi cepat jika Anda tidak melakukan hal-hal gila:

Task.Factory.StartNew(DoOneThing, TaskCreationOptions.LongRunning);

Ini akan memaksa ThreadPoolScheduler untuk segera memulai utas alih-alih menunggu satu detik sebelum membuat utas baru.

Namun, ini tidak akan berhasil jika Anda melakukan hal yang benar-benar gila seperti saya. Mari kita ubah for loop dari 300 menjadi 30000, bahkan solusi ini mungkin juga gagal. Alasannya adalah bahwa itu membuat terlalu banyak utas. Ini adalah sumber daya dan memakan waktu. Dan itu mungkin mulai memulai proses GC. Secara keseluruhan, mungkin tidak dapat menyelesaikan pembuatan semua utas tersebut sebelum waktu habis.

Cara sempurna adalah berhenti membuat banyak tugas dan menggunakan penjadwal default untuk menjadwalkannya. Anda dapat mencoba membuat item kerja dan memasukkannya ke dalam ConcurrentQueue lalu membuat beberapa utas sebagai pekerja untuk menggunakan item tersebut.

Namun, jika Anda tidak ingin terlalu mengubah struktur aslinya, Anda dapat mencoba cara berikut:

Buat ThrottledTaskScheduler yang berasal dari TaskScheduler.

  1. ThrottledTaskScheduler ini menerima TaskScheduler sebagai yang mendasari yang akan menjalankan tugas yang sebenarnya.
  2. Buang tugas ke penjadwal yang mendasarinya, tetapi jika melebihi batas, masukkan ke dalam antrean.
  3. Jika ada tugas yang selesai, periksa antrean dan coba masukkan ke penjadwal yang mendasari dalam batas.
  4. Gunakan kode berikut untuk memulai semua tugas baru yang gila itu:

·

var taskScheduler = new ThrottledTaskScheduler(
    TaskScheduler.Default,
    128,
    TaskCreationOptions.LongRunning | TaskCreationOptions.HideScheduler,
    logger
    );
var taskFactory = new TaskFactory(taskScheduler);
for (var i = 0; i < 30000; i++)
{
    tasks.Add(taskFactory.StartNew(DoOneThing))
}
Task.WaitAll(tasks.ToArray());

Anda dapat menggunakan System.Threading.Tasks.ConcurrentExclusiveSchedulerPair.ConcurrentExclusiveTaskScheduler sebagai referensi. Ini sedikit lebih rumit daripada yang kita butuhkan. Itu untuk tujuan lain. Jadi, jangan khawatir tentang bagian-bagian yang bolak-balik dengan fungsi di dalam kelas ConcurrentExclusiveSchedulerPair. Namun, Anda tidak dapat menggunakannya secara langsung karena tidak lulus TaskCreationOptions.LongRunning saat membuat tugas pembungkus.

Ini bekerja untuk saya. Semoga berhasil!

P.S.:Alasan memiliki banyak tugas dalam versi pengatur waktu mungkin terletak di dalam TaskScheduler.TryExecuteTaskInline. Jika berada di utas utama tempat ThreadPool dibuat, ia akan dapat menjalankan beberapa tugas tanpa memasukkannya ke dalam antrean.




  1. Redis
  2.   
  3. MongoDB
  4.   
  5. Memcached
  6.   
  7. HBase
  8.   
  9. CouchDB
  1. MongoDB:Menghapus bidang dari SEMUA subdokumen dalam bidang array

  2. find_by_sql setara untuk mongoid?

  3. luwak:mendeteksi jika dokumen yang dimasukkan adalah duplikat dan jika demikian, kembalikan dokumen yang ada

  4. pemulihan mongodb menghapus catatan

  5. Bagaimana cara mencocokkan elemen agregat ($graphLookup) di MongoDB?