Hangfire adalah pustaka untuk .net (inti), yang memungkinkan eksekusi beberapa kode secara asinkron dengan prinsip "fire and forget". Contoh kode seperti itu dapat mengirim E-Mail, pemrosesan video, sinkronisasi dengan sistem lain, dll. Selain "jalankan dan lupakan" ada dukungan untuk tugas yang ditangguhkan, serta tugas yang dijadwalkan dalam format Cron.
Saat ini, ada banyak perpustakaan seperti itu. Beberapa manfaat Hangfire adalah:
- Konfigurasi sederhana, API yang nyaman
- Keandalan Hangfire menjamin bahwa tugas yang dibuat akan dieksekusi setidaknya sekali
- Kemampuan untuk melakukan tugas secara paralel dan kinerja luar biasa
- Ekstensibilitas (di sini kita akan menggunakannya di bawah)
- Dokumentasi yang cukup lengkap dan mudah dipahami
- Dasbor tempat Anda dapat melihat semua statistik tentang tugas
Saya tidak akan membahas terlalu banyak detail, karena ada banyak artikel bagus tentang Hangfire dan cara menggunakannya. Pada artikel ini saya akan membahas cara menggunakan dukungan dari beberapa antrian (atau kumpulan tugas), cara memperbaiki fungsi coba lagi standar dan membuat setiap antrian memiliki konfigurasi individual.
Dukungan yang ada untuk antrian (semu)
Catatan penting: dalam judul, saya menggunakan istilah pseudo-queue karena Hangfire tidak menjamin bahwa tugas akan dilakukan dalam urutan tertentu. Yaitu prinsip "First In First Out" tidak berlaku dan kami tidak akan bergantung padanya. Selain itu, penulis perpustakaan merekomendasikan membuat tugas idempoten, yaitu stabil terhadap beberapa eksekusi yang tidak terduga. Selanjutnya saya hanya akan menggunakan kata "antrian", karena Hangfire menggunakan istilah "Antrian".
Hangfire memiliki dukungan antrian sederhana. Meskipun tidak menawarkan fleksibilitas Sistem Antrian Pesan seperti rabbitMQ atau Azure Service Bus, seringkali cukup untuk menyelesaikan berbagai tugas.
Setiap tugas memiliki properti "Antrian", yaitu nama antrian di mana ia harus dieksekusi. Secara default, tugas dikirim ke antrian dengan nama "default", kecuali ditentukan lain. Dukungan untuk beberapa antrian diperlukan untuk mengelola secara terpisah pelaksanaan tugas dari berbagai jenis. Misalnya, kami mungkin ingin tugas pemrosesan video masuk ke antrean "video_queue", dan mengirimkan E-Mail ke antrian "email_queue". Dengan demikian, kami dapat melakukan dua jenis tugas ini secara independen. Jika kami ingin memindahkan pemrosesan video ke server khusus, kami dapat dengan mudah melakukan ini dengan menjalankan server Hangfire terpisah sebagai aplikasi konsol yang akan memproses antrian "video_queue".
Mari kita lanjutkan berlatih
Menyiapkan server Hangfire di inti asp.net adalah sebagai berikut:
public void Configure(IApplicationBuilder app) { app.UseHangfireServer(new BackgroundJobServerOptions { WorkerCount = 2, Queues = new[] { "email_queue", "video_queue" } }); }
Masalah 1 - Memutar ulang tugas termasuk dalam antrian standar
Seperti yang saya sebutkan di atas, ada antrian default di Hangfire yang disebut "default". Jika tugas ditempatkan dalam antrian, misalnya, "video_queue", gagal dan perlu dicoba lagi, maka akan dikirim ke antrian "default" lagi dan bukan "video_queue" dan, akibatnya, tugas kami tidak akan dilakukan sama sekali contoh dari server Hangfire yang kami inginkan, jika sama sekali. Perilaku ini dibuat oleh saya secara eksperimental dan mungkin merupakan bug di Hangfire sendiri.
Filter pekerjaan
Hangfire memberi kami kemampuan untuk memperluas fungsionalitas dengan bantuan filter yang disebut ( Filter Pekerjaan ), yang pada prinsipnya mirip dengan Filter Tindakan di ASP.NET MVC. Faktanya adalah bahwa logika internal Hangfire diimplementasikan sebagai Mesin Negara. Ini adalah mesin yang secara berurutan mentransfer tugas-tugas di kumpulan dari satu keadaan ke keadaan lain (misalnya, dibuat -> enqueued -> pemrosesan -> berhasil), dan filter memungkinkan kita untuk "mencegat" tugas yang dieksekusi setiap kali keadaannya berubah dan untuk memanipulasinya. Filter diimplementasikan sebagai atribut yang dapat diterapkan ke metode tunggal, kelas, atau global.
Parameter pekerjaan
Objek ElectStateContext dilewatkan sebagai argumen ke metode filter. Objek ini berisi informasi lengkap tentang tugas saat ini. Antara lain, ia memiliki metode GetJobParameter <> (...) dan SettJobParameter <> (...). Parameter Pekerjaan memungkinkan Anda untuk menyimpan informasi yang terkait dengan tugas dalam database. Dalam Parameter Pekerjaan, nama antrian tempat tugas semula dikirim disimpan, hanya karena alasan tertentu informasi ini diabaikan selama coba lagi berikutnya.
Solusi
Jadi, kami memiliki tugas yang berakhir dengan kesalahan dan harus dikirim untuk dieksekusi kembali dalam antrian yang benar (dalam tugas yang sama dengan yang ditugaskan pada saat pembuatan awal). Pengulangan tugas yang dilengkapi dengan kesalahan adalah transisi dari keadaan "gagal" ke keadaan "enqueued". Untuk mengatasi masalah, buat filter yang, ketika tugas memasuki keadaan "enqueued", akan memeriksa di mana antrian tugas yang dikirim pada awalnya dan meletakkan parameter "QueueName" di nilai yang diinginkan:
public class HangfireUseCorrectQueueFilter : JobFilterAttribute, IElectStateFilter { public void OnStateElection(ElectStateContext context) { if (context.CandidateState is EnqueuedState enqueuedState) { var queueName = context.GetJobParameter<string>("QueueName"); if (string.IsNullOrWhiteSpace(queueName)) { context.SetJobParameter("QueueName", enqueuedState.Queue); } else { enqueuedState.Queue = queueName; } } } }
Untuk menerapkan filter default ke semua tugas (mis. Secara global), tambahkan kode berikut ke konfigurasi kami:
GlobalJobFilters.Filters.Add(new HangfireUseCorrectQueueFilter { Order = 1 });
Hasil tangkapan kecil lainnya adalah bahwa koleksi GlobalJobFilters secara default berisi turunan dari kelas AutomaticRetryAttribute. Ini adalah filter standar yang bertanggung jawab untuk menjalankan kembali tugas yang gagal. Dia juga mengirim tugas ke antrian "default", mengabaikan antrian asli. Agar sepeda kami dapat mengendarai, Anda harus menghapus filter ini dari koleksi dan membiarkan filter kami bertanggung jawab atas tugas yang diulang. Akibatnya, kode konfigurasi akan terlihat seperti ini:
var defaultRetryFilter = GlobalJobFilters.Filters .FirstOrDefault(f => f.Instance is AutomaticRetryAttribute); if (defaultRetryFilter != null && defaultRetryFilter.Instance != null) { GlobalJobFilters.Filters.Remove(defaultRetryFilter.Instance); } GlobalJobFilters.Filters.Add(new HangfireUseCorrectQueueFilter { Order = 1 });
Perlu dicatat bahwa AutomaticRetryAttribute mengimplementasikan logika secara otomatis meningkatkan interval antara upaya (interval meningkat dengan setiap upaya berikutnya), dan menghapus AutomaticRetryAttribute dari koleksi GlobalJobFilters, kami meninggalkan fungsi ini (lihat penerapan metode ScheduleAgainLater )
Jadi, kami telah mencapai bahwa tugas kami dapat dilakukan dalam antrian yang berbeda, dan ini memungkinkan kami untuk mengelola eksekusi secara mandiri, termasuk memproses antrian yang berbeda pada mesin yang berbeda. Hanya sekarang kita tidak tahu berapa kali dan berapa interval tugas kita akan diulang jika terjadi kesalahan, karena kita menghapus AutomaticRetryAttribute dari koleksi filter.
Masalah 2 - Pengaturan Individual untuk Setiap Antrian
Kami ingin dapat mengkonfigurasi interval dan jumlah pengulangan secara terpisah untuk setiap antrian, dan juga, jika untuk beberapa antrian kami tidak menentukan nilai secara eksplisit, kami ingin nilai default diterapkan. Untuk melakukan ini, kami menerapkan filter lain dan menyebutnya HangfireRetryJobFilter
.
Idealnya, kode konfigurasi akan terlihat seperti ini:
GlobalJobFilters.Filters.Add(new HangfireRetryJobFilter { Order = 2, ["email_queue"] = new HangfireQueueSettings { DelayInSeconds = 120, RetryAttempts = 3 }, ["video_queue"] = new HangfireQueueSettings { DelayInSeconds = 60, RetryAttempts = 5 } });
Solusi
Untuk melakukan ini, pertama-tama tambahkan kelas HangfireQueueSettings
, yang akan berfungsi sebagai wadah untuk pengaturan kami.
public sealed class HangfireQueueSettings { public int RetryAttempts { get; set; } public int DelayInSeconds { get; set; } }
Kemudian kami menambahkan implementasi dari filter itu sendiri, yang, ketika tugas diulang setelah kesalahan, akan menerapkan pengaturan tergantung pada konfigurasi antrian dan memantau jumlah percobaan ulang:
public class HangfireRetryJobFilter : JobFilterAttribute, IElectStateFilter, IApplyStateFilter { private readonly HangfireQueueSettings _defaultQueueSettings = new HangfireQueueSettings { RetryAttempts = 3, DelayInSeconds = 10 }; private readonly IDictionary<string, HangfireQueueSettings> _settings = new Dictionary<string, HangfireQueueSettings>(); public HangfireQueueSettings this[string queueName] { get { return _settings.TryGetValue(queueName, out HangfireQueueSettings queueSettings) ? queueSettings : _defaultQueueSettings; } set { _settings[queueName] = value; } } public void OnStateElection(ElectStateContext context) { if (!(context.CandidateState is FailedState failedState)) {
Catatan untuk kode: ketika menerapkan kelas HangfireRetryJobFilter
, kelas AutomaticRetryAttribute
dari HangfireRetryJobFilter
diambil sebagai dasar, oleh karena itu implementasi beberapa metode sebagian bertepatan dengan metode yang sesuai dari kelas ini.
Masalah 3 - Bagaimana cara mengirim tugas ke antrian tertentu?
Saya berhasil menemukan dua cara untuk menetapkan tugas ke antrian: didokumentasikan dan - tidak.
Metode 1 - gantung atribut yang sesuai pada metode
[Queue("video_queue")] public void SomeMethod() { } BackgroundJob.Enqueue(() => SomeMethod());
http://docs.hangfire.io/en/latest/background-processing/configuring-queues.html
Metode 2 (tidak berdokumen) - gunakan kelas BackgroundJobClient
var client = new BackgroundJobClient(); client.Create(() => MyMethod(), new EnqueuedState("video_queue"));
Keuntungan dari metode kedua adalah tidak membuat dependensi yang tidak perlu pada Hangfire dan memungkinkan Anda untuk memutuskan selama proses mana tugas harus pergi. Sayangnya, dalam dokumentasi resmi, saya tidak menemukan penyebutan tentang kelas BackgroundJobClient
dan bagaimana menerapkannya. Saya menggunakan metode kedua dalam solusi saya, jadi itu diuji dalam praktek.
Kesimpulan
Di artikel ini, kami menggunakan dukungan beberapa antrian di Hangfire untuk memisahkan pemrosesan berbagai jenis tugas. Kami menerapkan mekanisme kami untuk mengulangi tugas yang tidak selesai dengan kemungkinan konfigurasi individual untuk setiap antrian, memperluas fungsionalitas Hangfire menggunakan Filter Pekerjaan, dan juga belajar bagaimana mengirim tugas ke antrian yang diinginkan untuk dieksekusi.
Semoga artikel ini bermanfaat bagi seseorang. Saya akan senang berkomentar.
Tautan yang bermanfaat
Dokumentasi Hangfire
Kode Sumber Hangfire
Scott Hanselman - Cara menjalankan Tugas Latar Belakang di ASP.NET