مكتبة مفتوحة المصدر لمعالجة الخلفية البسيطة والموزعة لـ .NET ، مدعومة من وسيط رسائل RabbitMQ. ستكون الوثائق ، بمجرد الانتهاء ، متاحة على موقع MassiveJobs.net.
إذا لم يكن لديك تثبيت موجود لـ RabbitMQ ، فإن أبسط طريقة هي بدء تشغيله في حاوية. سيبدأ الأمر التالي RabbitMQ في حاوية ستتم إزالتها على الفور عند إيقافها .
docker run -- rm -- hostname rabbit - test -- name rabbit - test - d - p 15672 : 15672 - p 5672 : 5672 rabbitmq:managementالآن ، يجب أن تكون قادرًا على الوصول إلى واجهة مستخدم إدارة RabbitMQ في متصفحك على: http: // localhost: 15672 عنوان. يمكنك تسجيل الدخول مع ضيف اسم المستخدم وكلمة المرور ، إذا كنت ترغب في مراقبة الاتصالات ، قوائم الانتظار ، إلخ .
سنستخدم .NET Core 3.1 CLI لهذه البداية السريعة ، ولكن يمكنك أيضًا القيام بذلك في Visual Studio ، مع .NET Core أو .NET Framework 4.6.1 أو أحدث.
إنشاء مجلد للمشروع.
mkdir MassiveJobs.QuickStart
cd MassiveJobs.QuickStartإنشاء مشروع تطبيق وحدة تحكم جديدة.
dotnet new consoleاختبار المشروع السقالة.
dotnet run يجب أن ترى Hello World! بعد بضع ثوان.
إضافة مرجع الحزمة إلى MassiveJobs.RabbitMqBroker .
dotnet add package MassiveJobs.RabbitMqBrokerاستخدم المحرر المفضل لديك لفتح program.cs وإدخال هذا الرمز. يجب أن تكون التعليقات في الكود كافية لإعطائك فكرة أساسية عما يجري.
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 ) ;
}
}
}
}ابدأ ثلاثة مطالبات أوامر مختلفة (أو قذائف الطاقة). سيتم استخدام اثنين كعمال ، وسيتم استخدام واحد كناشر.
لبدء التطبيق ، انتقل إلى مجلد المشروع وتشغيله:
dotnet run اكتب 1 واضغط على Enter لبدء عامل ، اكتب 2 واضغط على Enter لبدء الناشر. عند إدخال الرسائل في وحدة التحكم الناشر ، ستلاحظ معالجتها في واحد أو عامل آخر ، ولكن ليس كلاهما. وذلك لأن الوظائف موزعة بين العمال.
لاحظ أنه يمكنك بدء العديد من الناشرين أيضًا.
يمكن للعمال والناشرين أن يكونوا على أجهزة مختلفة ، طالما أنهم يستطيعون الوصول إلى خادم RabbitMQ.
لتوزيع العمال عبر العديد من الآلات ، سيتعين عليك تكوين المعلومات حول خادم RabbitMQ. على الأقل ، يعني ذلك اسم المستخدم ، كلمة المرور ، اسم المضيف (أو عنوان IP) ، ورقم المنفذ (إذا تم تكوين خادم RabbitMQ للاستماع إلى الاتصالات على منفذ غير قياسي). في المثال أعلاه ، لم نقم بتكوين أي منها لأن الإعدادات الافتراضية كانت كافية - اسم المستخدم: guest ، كلمة المرور: guest ، اسم المضيف: localhost ، المنفذ: -1 (= استخدم المنفذ الافتراضي).
على سبيل المثال ، إذا كان خادم RabbitMQ يعمل على جهاز مع اسم المضيف rabbit.example.local ، والاستماع إلى رقم المنفذ القياسي ، وقمت بإنشاء مستخدم massive في RabbitMQ مع كلمة المرور: d0ntUseTh!sPass ثم تقوم بتهيئة RabbitMqJobs مثل هذا.
JobsBuilder . Configure ( )
. WithRabbitMqBroker ( s =>
{
s . HostNames = new [ ] { "rabbit.example.com" } ;
s . Username = "massive" ;
s . Password = "d0ntUseTh!sPass" ;
} )
. Build ( ) ;أو ، إذا كنت لا ترغب في بدء تشغيل مؤشرات ترابط العمال (أي لاستخدام العملية فقط لنشر وظائف):
JobsBuilder . Configure ( )
. WithRabbitMqBroker ( s =>
{
//...
} )
. Build ( false ) ;يمكنك الآن نشر العمال (والناشرين) على آلات متعددة وتشغيلهم. إذا كان اتصال الشبكة يعمل (جدران الحماية مفتوحة وما إلى ذلك) يجب أن يعمل كل شيء. سيتم توجيه الوظائف إلى العمال بطريقة مستديرة. ضع في اعتبارك أنه ، بشكل افتراضي ، يبدأ كل تطبيق MassiveJobs موضوعين للعامل. هذا يعني ، إذا كان لديك 3 آلات ، كل منها يدير تطبيقًا ضخمًا ، فإن توزيع الوظائف سيبدو شيئًا من هذا القبيل:
ربما تكون قد لاحظت ، في المثال السريع ، عندما ندير تطبيقين MassiveJobs في نوافذ posershell ، ستذهب اثنان من الرسائل إلى نافذة واحدة ، والثاني التاليين إلى النافذة الأخرى وما إلى ذلك. الآن أنت تعرف السبب.
تخطي هذا القسم إذا كان التطبيق الخاص بك يعمل في بيئة .NET Core المستضافة (ASP.NET Core Web Application أو خدمة العمال).
من المهم للغاية تكوين تسجيل الدخول في التطبيق الخاص بك الذي يعمل على تشغيل MassiveJobs لأن هذه هي الطريقة الوحيدة لرؤية أخطاء وقت تشغيل MassiveJobs في التطبيق الخاص بك. الأمر بسيط مثل تثبيت حزمة مناسبة وإعداد JobLoggerFactory عند التهيئة ، إذا كنت تستخدم إحدى مكتبات المسجلات التالية:
MassiveJobs.Logging.Log4Net )MassiveJobs.Logging.NLog )MassiveJobs.Logging.Serilog ) على سبيل المثال ، إذا كنت ترغب في إضافة تسجيل الدخول إلى LOG4NET إلى مثال سريع البداية ، فقم أولاً بتثبيت حزمة MassiveJobs.Logging.Log4Net في مشروعك. بعد ذلك ، قم بتهيئة مكتبة Log4Net ، وأخيراً 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 ( ) ;
}
}يجب عليك تنفيذ "initializeLogging" بنفسك ، كما تفعل عادة التهيئة لمكتبة التسجيل الخاصة بك. على سبيل المثال ، بالنسبة إلى log4net ، من شأنه تكوين وحدة التحكم في وحدة التحكم فقط.
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 ;
}الآن عندما تبدأ تطبيق العامل ، يجب أن ترى رسائل التسجيل في وحدة التحكم:
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.ستلاحظ ، أنه إذا بدأت تطبيق الناشر ، فلا يحاول الاتصال بـ RabbitMQ حتى تحاول إرسال الرسائل الأولى. هذا هو أن كل تطبيقات MassiveJobs يحافظ على اتصالين بـ RabbitMQ ، أحدهما للنشر والآخر لاستهلاك الرسائل. في الناشر ، نحن لا نبدأ العمال ، لذلك لا يتم تهيئة الاتصال.
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 لاستخدام MassiveJobs.RabbitMqBroker في بيئة مستضافة .NET Core (ASP.NET Core ، خدمات العمال) قم بتثبيت الحزمة التالية في تطبيقك:
dotnet add package MassiveJobs.RabbitMqBroker.Hosting ثم ، في فئة بدء التشغيل ، عند تكوين الخدمات ، Call 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 ( ) ;
}
//...
}
} سيؤدي ذلك إلى تسجيل خدمات MassiveJobs المطلوبة ، وبدء خدمة استضافة خلفية لتشغيل عمال الوظائف. الآن يمكنك نشر وظائف من وحدة تحكم. على سبيل المثال ، إذا كان لديك كيان Customer وترغب في إرسال بريد إلكتروني مرحب به إلى عميل تم إنشاؤه حديثًا ، فقد يكون لديك شيء مثل هذا:
// 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 ) ;
} من المهم جدًا أن تضع في اعتبارك أن SendWelcomeEmailJob.Publish لا يشارك في المعاملة _. RabbitMqbroker لـ MassiveJobs لا يدعم المعاملات. لكن طريقة Publish ستقوم بإلقاء استثناء إذا فشل النشر (فقط النشر - لا ترسل بالفعل البريد ، والذي يتم بشكل غير متزامن). إذا فشل النشر ، فسيتم طرح الاستثناء ، ولن يتم استدعاء trans.Commit() أبدًا ، وسيتم تشغيل المعاملة على التخلص منها.
بدليًا ، يتم استخدام الوظيفة هنا كمورد آخر ارتكاب .
يمكن أن يبدو SendWelcomeEmailJob شيء من هذا القبيل:
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 ) ;
}
}
}هناك عدة أشياء يجب ملاحظتها هنا:
customer.IsEmailSent قبل القيام بأي شيء. إذا تم ضبطه على صحيح ، فإننا لا نفعل أي شيء (لا يتم طرح أي استثناء ، لأن الاستثناء سيجعل مكتبة MassiveJobs تحدد المهمة لإعادة المحاكاة)SaveChanges() على سياق DB قبل إرسال البريد الإلكتروني فعليًا حتى يتمكن من إلقاء استثناءات التزامن والتي ستعيد جدولة المهمة في وقت لاحق (ولكن يجب عليك تكوين خصائص التزامن على askes to to to to to to work). ومع ذلك ، في هذه الحالة بالذات ، فئة العمل لدينا ليست معتدل تمامًا. قد لا يزال يتم إرسال البريد الإلكتروني مرتين لأن خادم البريد الإلكتروني لا يشارك في المعاملة. إذا كان client.Send يلقي استثناء مهلة ، فمن غير المؤكد ما إذا كان البريد الإلكتروني قد تم إرساله بالفعل أم لا. ربما يكون خادم البريد قد تلقى الطلب ، وقام بتصوير الرسالة للتسليم ، لكننا لم نحصل أبدًا على الاستجابة بسبب مشكلة شبكة مؤقتة. بعبارة أخرى ، مرة واحدة على الأقل يتم ضمان التسليم في هذه الحالة ، وليس مرة واحدة بالضبط .
إذا كانت تغييرات قاعدة البيانات فقط متورطة في الوظيفة ، فيمكننا أن نحصل على ضمانات بالضبط . ولكن حتى ذلك الحين ، يمكن استدعاء طريقة Perform الوظيفة مرتين ، لذا يجب عليك التأكد من أن المهمة معلقة في طريقة Perform (على غرار ما فعلناه مع IsEmailSent ).