Perpustakaan open-source untuk pemrosesan latar belakang yang sederhana dan terdistribusi untuk .NET, ditenagai oleh broker pesan RabbitMQ. Dokumentasi, setelah selesai, akan tersedia di situs web massiveJobs.net.
Jika Anda tidak memiliki instalasi RabbitMQ yang ada, cara paling sederhana adalah memulainya dalam wadah. Perintah berikut akan memulai RabbitMQ dalam wadah yang akan segera dilepas saat dihentikan .
docker run -- rm -- hostname rabbit - test -- name rabbit - test - d - p 15672 : 15672 - p 5672 : 5672 rabbitmq:managementSekarang, Anda harus dapat mengakses RabbitMQ Management UI di browser Anda di: http: // localhost: 15672 alamat. Anda dapat masuk dengan tamu nama pengguna dan tamu kata sandi, jika Anda ingin memantau koneksi, antrian, dll.
Kami akan menggunakan .NET Core 3.1 CLI untuk awal yang cepat ini, tetapi Anda juga dapat melakukannya di Visual Studio, dengan .NET Core atau dengan .NET Framework 4.6.1 atau yang lebih baru.
Buat folder untuk proyek.
mkdir MassiveJobs.QuickStart
cd MassiveJobs.QuickStartBuat proyek aplikasi konsol baru.
dotnet new consoleUji proyek perancah.
dotnet run Anda harus melihat Hello World! Setelah beberapa detik.
Tambahkan referensi paket ke MassiveJobs.RabbitMqBroker .
dotnet add package MassiveJobs.RabbitMqBrokerGunakan editor favorit Anda untuk membuka program.cs dan masukkan kode ini. Komentar dalam kode harus cukup untuk memberi Anda ide dasar tentang apa yang sedang terjadi.
using System ;
using MassiveJobs . Core ;
using MassiveJobs . RabbitMqBroker ;
namespace MassiveJobs . QuickStart
{
/// <summary>
/// This is a "job" class.
/// It will be instantiated every time a message is received, and Perform will be called.
/// It inherits from Job<TJob, TArgs> generic class, where TJob specifies the type of the job,
/// and TArgs specifies the type of the parameter expected by the Perform method.
///
/// In the example below, TArgs is a string, but it can be a custom class with multiple properties.
/// TArgs instances will be serialized (System.Text.Json by default) as a part of the job,
/// before it gets sent to the RabbitMQ.
/// </summary>
public class MessageReceiver : Job < MessageReceiver , string >
{
public override void Perform ( string message )
{
Console . WriteLine ( "Job performed: " + message ) ;
}
}
class Program
{
private static void Main ( )
{
Console . WriteLine ( "1: Worker" ) ;
Console . WriteLine ( "2: Publisher" ) ;
Console . Write ( "Choose 1 or 2 -> " ) ;
var startWorkers = Console . ReadLine ( ) != "2" ;
// We are not starting job workers if '2' is selected.
// This is not mandatory, an application can run job workers
// and publish jobs using the same MassiveJobs instance.
JobsBuilder . Configure ( )
. WithRabbitMqBroker ( )
. Build ( startWorkers ) ;
if ( startWorkers )
{
RunWorker ( ) ;
}
else
{
RunPublisher ( ) ;
}
JobsBuilder . DisposeJobs ( ) ;
}
private static void RunWorker ( )
{
Console . WriteLine ( "Initialized job worker." ) ;
Console . WriteLine ( "Press Enter to end the application." ) ;
Console . ReadLine ( ) ;
}
private static void RunPublisher ( )
{
Console . WriteLine ( "Initialized job publisher" ) ;
Console . WriteLine ( "Write the job name and press Enter to publish it (empty job name to end)." ) ;
while ( true )
{
Console . Write ( "> " ) ;
var message = Console . ReadLine ( ) ;
if ( string . IsNullOrWhiteSpace ( message ) ) break ;
// notice that Publish is a static method on our MessageReceiver class
// it is available because MessageReceiver inherits from Job<TJob, TArgs>
MessageReceiver . Publish ( message ) ;
}
}
}
}Mulai tiga prompt perintah yang berbeda (atau shell daya). Dua akan digunakan sebagai pekerja, dan satu akan digunakan sebagai penerbit.
Untuk memulai aplikasi, buka folder proyek dan jalankan:
dotnet run Tipe 1 dan tekan Enter untuk memulai pekerja, Tipe 2 dan tekan Enter untuk memulai penerbit. Saat Anda memasukkan pesan di konsol penerbit, Anda akan melihat mereka diproses dalam satu atau pekerja lainnya, tetapi tidak keduanya. Ini karena pekerjaan didistribusikan di antara para pekerja.
Perhatikan bahwa Anda juga dapat memulai beberapa penerbit.
Pekerja dan penerbit dapat berada di mesin yang berbeda, selama mereka dapat mengakses server RabbitMQ.
Untuk mendistribusikan pekerja di beberapa mesin, Anda harus mengkonfigurasi informasi tentang server RabbitMQ. Minimal, itu berarti nama pengguna, kata sandi, nama host (atau alamat IP), dan nomor port (jika server RabbitMQ Anda dikonfigurasi untuk mendengarkan koneksi pada port non-standar). Dalam contoh di atas kami tidak mengkonfigurasi semua itu karena standarnya cukup - nama pengguna: guest , kata sandi: guest , nama host: localhost , port: -1 (= gunakan port default).
Misalnya, jika server RabbitMQ Anda berjalan pada mesin dengan hostname rabbit.example.local , mendengarkan pada nomor port standar, dan Anda telah membuat pengguna massive di rabbitmq dengan kata sandi: d0ntUseTh!sPass maka Anda akan menginisialisasi RabbitMqJobs seperti ini.
JobsBuilder . Configure ( )
. WithRabbitMqBroker ( s =>
{
s . HostNames = new [ ] { "rabbit.example.com" } ;
s . Username = "massive" ;
s . Password = "d0ntUseTh!sPass" ;
} )
. Build ( ) ;Atau, jika Anda tidak ingin memulai utas pekerja (yaitu. Untuk menggunakan proses hanya untuk pekerjaan penerbitan):
JobsBuilder . Configure ( )
. WithRabbitMqBroker ( s =>
{
//...
} )
. Build ( false ) ;Sekarang Anda dapat menggunakan pekerja (dan penerbit) di banyak mesin dan menjalankannya. Jika konektivitas jaringan berfungsi (firewall terbuka dll.) Semuanya harus berfungsi. Pekerjaan akan dialihkan ke pekerja dengan cara round-robin. Ingatlah bahwa, secara default, setiap aplikasi MassiveJobs memulai dua utas pekerja. Itu berarti, jika Anda memiliki 3 mesin, masing -masing menjalankan satu aplikasi MassiveJobs, maka distribusi pekerjaan akan terlihat seperti ini:
Anda mungkin telah memperhatikan, dalam contoh cepat-awal, ketika kami telah menjalankan dua aplikasi Massivejobs di dua jendela posershell, dua pesan akan masuk ke satu jendela, dua berikutnya ke jendela lain dan sebagainya. Sekarang Anda tahu alasannya.
Lewati bagian ini jika aplikasi Anda berjalan di lingkungan yang di -host inti (ASP.NET Core Web Application atau layanan pekerja).
Sangat penting untuk mengonfigurasi pencatatan dalam aplikasi Anda yang menjalankan MASSIVEJOBS karena itulah satu-satunya cara untuk melihat kesalahan run-time massiveJobs di aplikasi Anda. Ini sesederhana memasang paket yang sesuai dan mengatur JobLoggerFactory pada inisialisasi, jika Anda menggunakan salah satu dari pustaka logger berikut:
MassiveJobs.Logging.Log4Net )MassiveJobs.Logging.NLog )MassiveJobs.Logging.Serilog ) Misalnya, jika Anda ingin menambahkan log4net logging ke contoh cepat-awal, pertama-tama instal paket MassiveJobs.Logging.Log4Net dalam proyek Anda. Setelah itu, inisialisasi perpustakaan Log4net, dan akhirnya MASSIVEJOBS.
//...
using MassiveJobs . Logging . Log4Net ;
//...
private static void Main ( )
{
InitializeLogging ( ) ;
Console . WriteLine ( "1: Worker" ) ;
Console . WriteLine ( "2: Publisher" ) ;
Console . Write ( "Choose 1 or 2 -> " ) ;
var startWorkers = Console . ReadLine ( ) != "2" ;
// We are not starting job workers if '2' is selected.
// This is not mandatory, an application can run job workers
// and publish jobs using the same MassiveJobs instance.
JobsBuilder . Configure ( )
. WithLog4Net ( )
. WithRabbitMqBroker ( )
. Build ( startWorkers ) ;
if ( startWorkers )
{
RunWorker ( ) ;
}
else
{
RunPublisher ( ) ;
}
}Anda harus menerapkan "inisialisasi" sendiri, karena Anda biasanya melakukan inisialisasi untuk perpustakaan logging Anda. Misalnya, untuk log4net ini hanya akan mengkonfigurasi Console Appender.
private static void InitializeLogging ( )
{
var patternLayout = new PatternLayout ( ) ;
patternLayout . ConversionPattern = "%date [%thread] %-5level %logger - %message%newline" ;
patternLayout . ActivateOptions ( ) ;
var hierarchy = ( Hierarchy ) LogManager . GetRepository ( Assembly . GetExecutingAssembly ( ) ) ;
hierarchy . Root . AddAppender ( new ConsoleAppender { Layout = patternLayout } ) ;
hierarchy . Root . Level = Level . Warn ;
hierarchy . Configured = true ;
}Sekarang ketika Anda memulai aplikasi pekerja, Anda akan melihat pesan logging di konsol:
PS > dotnet run
1 : Worker
2 : Publisher
Choose 1 or 2 - > 1
2020 - 11 - 10 10 : 25 : 22 , 251 [ 1 ] WARN MassiveJobs.RabbitMqBroker.RabbitMqMessageConsumer - Connected
Initialized job worker.
Press Enter to end the application.Anda akan melihat, bahwa jika Anda memulai aplikasi penerbit, itu tidak mencoba untuk terhubung ke RabbitMQ sampai Anda mencoba mengirim pesan pertama. Ini karena setiap aplikasi MassiveJobs mempertahankan dua koneksi ke RabbitMQ, satu untuk penerbitan dan yang lainnya untuk mengkonsumsi pesan. Di penerbit, kami tidak memulai pekerja, sehingga mengonsumsi koneksi tidak diinisialisasi.
PS > dotnet run
1 : Worker
2 : Publisher
Choose 1 or 2 - > 2
Initialized job publisher
Write the job name and press Enter to publish it (empty job name to end ).
> Hello
2020 - 11 - 10 10 : 27 : 22 , 954 [ 4 ] WARN MassiveJobs.RabbitMqBroker.RabbitMqMessagePublisher - Connected Untuk menggunakan MassiveJobs.RabbitMqBroker di lingkungan yang di -host inti (ASP.NET Core, Layanan Pekerja) Instal paket berikut dalam aplikasi Anda:
dotnet add package MassiveJobs.RabbitMqBroker.Hosting Kemudian, di kelas startup Anda, saat mengonfigurasi layanan, panggilan services.AddMassiveJobs() .
//...
using MassiveJobs . RabbitMqBroker . Hosting ;
namespace MassiveJobs . Examples . Api
{
public class Startup
{
public Startup ( IConfiguration configuration )
{
Configuration = configuration ;
}
public IConfiguration Configuration { get ; }
public void ConfigureServices ( IServiceCollection services )
{
//...
services . AddMassiveJobs ( )
. UseRabbitMqBroker ( ) ;
}
//...
}
} Ini akan mendaftarkan layanan MassiveJobs yang diperlukan, dan memulai layanan yang di -host latar belakang untuk menjalankan pekerja pekerjaan. Sekarang Anda dapat mempublikasikan pekerjaan dari pengontrol. Misalnya, jika Anda memiliki entitas Customer dan ingin mengirim email selamat datang ke pelanggan yang baru dibuat, Anda mungkin memiliki sesuatu seperti ini:
// POST: api/Customers
[ HttpPost ]
public ActionResult < Customer > PostCustomer ( Customer customer )
{
using var trans = _context . Database . BeginTransaction ( ) ;
_context . Customers . Add ( customer ) ;
_context . SaveChanges ( ) ;
if ( ! string . IsNullOrWhiteSpace ( customer . Email ) )
{
// send a welcome email after 5 seconds
SendWelcomeEmailJob . Publish ( customer . Id , TimeSpan . FromSeconds ( 5 ) ) ;
}
// do this last. If Job publishing to RabbitMq fails, we will rollback
trans . Commit ( ) ;
return CreatedAtAction ( "GetCustomer" , new { id = customer . Id } , customer ) ;
} Sangat penting untuk diingat bahwa SendWelcomeEmailJob.Publish tidak berpartisipasi dalam transaksi _. Rabbitmqbroker untuk MASSIVEJOBS tidak mendukung transaksi. Tapi, metode Publish akan melempar pengecualian jika penerbitan gagal (hanya penerbitan - tidak benar -benar mengirim surat, yang dilakukan dengan asyncron). Jika penerbitan gagal, pengecualian akan dilemparkan, dan trans.Commit() tidak akan pernah dipanggil, dan transaksi akan digulung saat dibuang.
Pada dasarnya, menerbitkan pekerjaan di sini digunakan sebagai sumber daya komitmen terakhir .
The SendWelcomeEmailJob bisa terlihat seperti ini:
public class SendWelcomeEmailJob : Job < SendWelcomeEmailJob , int >
{
private readonly ExamplesDbContext _context ;
public SendWelcomeEmailJob ( ExamplesDbContext context )
{
_context = context ;
}
public override void Perform ( int customerId )
{
using var trans = _context . Database . BeginTransaction ( ) ;
var customer = _context . Customers . Find ( customerId ) ;
if ( customer . IsEmailSent ) return ; // make the job idempotent
customer . IsEmailSent = true ;
// Do this before sending email, to lessen the chance of an exception on commit.
// Also, if optimistic concurrency is enabled, we will fail here, before sending the email.
// This way we avoid sending the email to the customer twice.
_context . SaveChanges ( ) ;
SendEmail ( customer ) ;
// Do this last. In case the SendEmail method fails, the transaction will be rolled back.
trans . Commit ( ) ;
}
private static void SendEmail ( Customer customer )
{
var mailMessage = new MailMessage
{
From = new MailAddress ( "[email protected]" ) ,
Body = $ "Welcome customer { customer . FirstName } { customer . LastName } " ,
Subject = "Welcome to examples.com"
} ;
mailMessage . To . Add ( customer . Email ) ;
using ( var client = new SmtpClient ( "smtp.examples.com" ) )
{
client . UseDefaultCredentials = false ;
client . Credentials = new NetworkCredential ( "username" , "password" ) ;
client . Send ( mailMessage ) ;
}
}
}Ada beberapa hal yang perlu diperhatikan di sini:
customer.IsEmailSent diperiksa sebelum melakukan apa pun. Jika diatur ke True, kami tidak melakukan apa -apa (tidak ada pengecualian yang dilemparkan, karena pengecualian akan membuat Perpustakaan MassiveJobs menjadwalkan pekerjaan untuk coba lagi)SaveChanges() pada konteks DB sebelum benar -benar mengirim email sehingga dapat melempar pengecualian konkurensi yang akan menjadwal ulang pekerjaan untuk nanti (tetapi Anda harus mengkonfigurasi properti konkurensi pada bagian masuk Anda agar berfungsi). Namun, dalam kasus khusus ini, kelas pekerjaan kami tidak sepenuhnya idempot. Masih mungkin terjadi bahwa email dikirim dua kali karena server email tidak berpartisipasi dalam transaksi. Jika client.Send Lemparkan Pengecualian Timeout , tidak pasti apakah email tersebut benar -benar dikirim atau tidak. Server Mail mungkin telah menerima permintaan, mengantri pesan untuk pengiriman, tetapi kami tidak pernah mendapat tanggapan karena masalah jaringan sementara. Dengan kata lain, setidaknya sekali pengiriman dijamin dalam kasus ini, tidak tepat sekali .
Jika hanya perubahan database yang terlibat dalam pekerjaan, maka kami dapat memiliki jaminan yang tepat . Tetapi bahkan kemudian, metode Perform pekerjaan dapat dipanggil dua kali sehingga Anda harus memastikan bahwa pekerjaan itu idempoten dalam metode Perform (mirip dengan apa yang kami lakukan dengan IsEmailSent ).