"> توسعه میکروسرویس ها با NET Core . و EF Core - بخش چهارم | ام اس پی سافت

توسعه میکروسرویس ها با NET Core . و EF Core – بخش چهارم

میکروسرویس ها با NET Core.

در این مقاله میخواهیم راجب توسعه میکروسرویس ها با NET Core 2.1. و EF Core و RabbitMQ, SignalR , Angular 6 صحبت کنیم. تا پایان این مقاله همراه ما باشید

میتوانید بخش های قبلی مقاله را در لینک‏ زیر مشاهده کنید.

The Merits of Building a Message Queuing Service

برای جلوگیری از ایجاد و مدیریت singleton Life time در برنامه Web API ، تصمیم گرفتیم یک برنامه کنسول چند رشته ای NET Core 2.1  جداگانه ایجاد کنیم که به عنوان یک سرویس message queuing برای هر میکروسرویس عمل کند و می تواند تمام اتصالات RabbitMQ پردازش پیام و کانال ها را مدیریت کند.

این برنامه های کنسول چندین موضوع را با هر threadsدر یک فاصله از پیش تعریف شده (۵ یا ۱۵ دقیقه) اجرا می کنند که هر موضوع با RabbitMQ و SQL-Server در تعامل است.

ایجاد سرویس های ارسال پیام دارای مزایای زیادی نسبت به ادغام پردازش message queuingدر برنامه API Webدارند زیرا می توانند موارد زیر را ارائه دهند:

  • شما می توانید یک queuing message service را به یک یا چند سرور برنامه جداگانه تقسیم کرده و پردازش queue message را خاموش کنید و منابع سرور وب را استفاده نکنید که در نتیجه باعث بهبود کارآیی API Webو در نهایت زمان پاسخگویی بهتر برنامه وب شوید.
  • بسیاری breakpoints در یک گردش کار میکروسرویس وجود دارد. به عنوان مثال ، چه اتفاقی می افتد که شما یک برنامه آنلاین دارید و کاربر روی دکمه ذخیره کلیک می کند و تغییرات به database منتقل می شوند و بلافاصله پس از انتقال، سعی می کنید تا یک پیام به queue message بفرستید اما message brokerکم است. چگونه می توان از این حالت نجات یافت؟ طراحی بهتر شامل ارتقاء پیام به database با بقیه معامله تجاری و داشتن queuing message service ، در مرحله بعد این پیام را انتخاب می کند.
  • یک queuing message service می تواند با استفاده مجدد و بازیابی عملکردی طراحی شود که بتواند به راحتی نقاط شکست را بدون تأثیرگذاری بر user نهایی ، مدیریت کند.
  • اجرای queuing message service مستقر در سرور جداگانه ، همیشه لازم نیست حتی در صورت داشتن user های آنلاین ، در دسترس باشد. مدیریت حافظه سرور و پیوندها و کانال های queue message ممکن است در ابتدا یک چالش باشد زیرا یاد می گیرید به درستی تنظیم و پیکربندی message brokerبا گذشت زمان چقدر مهم است، راه اندازی مجدد سرور تولید و رفع مشکلات سرور ، انعطاف پذیری زیادی را ایجاد می کند و در درازمدت به در دسترس بودن برنامه بهتر منجر می شود.

Implementing ASP.NET Core 2.1 SignalR

به عنوان یک طراح خوب ، می خواستم پیام ها را در زمان واقعی ارسال کنم.

بنابراین به روشی برای ارسال پیام به queuing message service نیاز داشتم تا در صورت نشستن بین فواصل ، پیام را برای پردازش پیام ها آماده کنم. این مرا به ASP.NET Core 2.1 SignalR سوق داد.

ASP.NET Core SignalR یک کتابخانه منبع باز است که اضافه کردن قابلیت وب در زمان واقعی به برنامه ها را ساده می کند.

قابلیت وب در زمان واقعی ، کد سمت سرور را قادر می سازد تا فوراً مطالب را به سمت مشتری هدایت کند.

SignalR اغلب برای تعامل با مشتریان JavaScript استفاده می شود. در این حالت مشتری یک برنامه کنسول است.

SignalR از هاب برای برقراری ارتباط بین مشتری و سرور استفاده می کند.

مدیریت اتصال را به طور اتوماتیک کنترل می کند.

در اتصالات قدیمی HTTP برای ارتباط کلاینت-سرور برای هر درخواست دوباره برقرار می شود، اما SignalR یک ارتباط پایدار بین کلاینت و سرور برقرار می کند.

در SignalR به جای مدل درخواست-پاسخ امروزی، کد سرور، کد کلاینت را با استفاده از Remote Procedure – RPC در مرورگر فراخوانی می کند.

SignalR یک API اپن سورس است و از طریق GitHub قابل دسترسی است.

برای ایجاد Hub ، فقط می توانید یک class را اضافه کنید که از Microsoft.AspNetCore.SignalR.Hub به ارث برسد و روش هایی را در کلاس Hub تعریف کنید که توسط مشتری ها قابل اجرا باشد.

از آنجا که برنامه مدیریت Web Inventory Web API فقط از SignalR برای ارسال پیام استفاده می کند ، کلاس MessageQueueHu هیچ روشی را تعریف نخواهد کرد.

namespace CodeProject.InventoryManagement.WebApi.SignalRHub
{
    public class MessageQueueHub : Hub
    {

    }
}

در ASP.NET Core SignalR ، از طریق تزریق وابستگی می توانید به نمونه ای از IHubContext دسترسی پیدا کنید.

نمونه ای از IHubContext در کلاس راه اندازی پیکربندی شده است و به کنترلر تزریق می شود و نمونه آن برای ارسال پیام به مشتری قابل استفاده است.

در این روش اقدام به، به روزرسانی UpdatesSalesOrderDetail میشود، بعد از سرویس تجاری مدیریت Inventory با موفقیت انجام انتقال موجودی انجام می شود.

بیانیه Clients.All.SendAynsc پیامی را برای کلیه مشتریانی که در URL “https: // localhost: 44340 / MessageQueueHub” هستند ، ارسال می کند.

در مورد API Web Management Management ، فقط سرویس ارسال پیام، پیام مدیریت Inventory در این URL گوش می دهد.

await _messageQueueContext.Clients.All.SendAsync(MessageQueueEndpoints.InventoryQueue, string.Empty);

Listening for ASP.NET Core SignalR Messages

برای گوش دادن به پیام های ASP.NET Core SignalR ، سرویس جستجوی پیام پرسشنامه مدیریت بسته بندی Microsoft.AspNetCore.SignalR.Client را پیاده سازی می کند.

کتابخانه مشتری ASP.NET Core SignalR .NET به شما امکان می دهد تا با مراکز SignalR از برنامه های NET ارتباط برقرار کنید.

Queuing message service وظایف جداگانه ای را برای ارسال ، دریافت و پردازش پیام ها آغاز می کند.

در شروع کار موضوع SendMessages ، ارتباطی با SignalR بر اساس URL API SignalR مدیریت موجودی “https: // localhost: 44340 / MessageQueueHub” ایجاد می شود.

در صورت عدم کارکرد هاب هنگام تلاش برای اتصال به آن ، منطق اتصال مجدد برای دوباره امتحان اتصال اضافه شده است.

پس از اتصال به هاب queuing message service در هر رویدادی که سرویس queueرا نشان می دهد ، برای بازیابی پیام ها و ارسال آنها به RabbitMQ ، با روش GetMessgaesInQueue تماس می گیرد.

/// <summary>
/// Start Process Interval
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
public Task StartAsync(CancellationToken cancellationToken)
{

    StartSignalRConnection();

    _timer = new Timer(GetMessagesInQueue, null, TimeSpan.Zero,
                       TimeSpan.FromSeconds(_appConfig.SendingIntervalSeconds));

    return Task.CompletedTask;
}

/// <summary>
/// Start SignalR Connection
/// </summary>
private async void StartSignalRConnection()
{
    if (string.IsNullOrEmpty(_appConfig.SignalRHubUrl))
    {
        return;
    }

    string url = _appConfig.SignalRHubUrl; /// "https://localhost:44340/MessageQueueHub",

    //
    //  Build Hub Connection
    //

    Boolean buildHubConnection = false;
    while (buildHubConnection  == false)
    {
        try
        {
            _signalRHubConnection = new HubConnectionBuilder().WithUrl(url).Build();
            buildHubConnection  = true;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            await Task.Delay(5000);
        }

    }
       
    //
    //   Listen for SignalR messages
    //      

    _signalRHubConnection.On<string>(_signalRQueue, (message) =>
    {
        this.GetMessagesInQueue(null);

    });

    //
    //   Listen for Hub Connection Closed Event
    //

    _signalRHubConnection.Closed += async (error) =>
    {
        Console.WriteLine("SignalR Connection Closed");
        await Task.Delay(10000);
        await _signalRHubConnection.StartAsync();
        Console.WriteLine("Restart SignalR");
    };

    //
    //  Start Hub Connection
    //

    connected = false;
    while (connected == false)
    {
         try
         {
               await _signalRHubConnection.StartAsync();
               connected = true;

        }
        catch (Exception ex)
        {
              await Task.Delay(10000);
        }

    }
        
}

Configuring the Inventory Management Message Queuing Service

وقتی یک برنامه کنسول با استفاده از .NET Core ایجاد می کنید ، متوجه می شوید که هنگام ساختن برنامه به طور پیش فرض .NET Core DLL را ایجاد می کند که به عنوان یک برنامه قابل حمل ساخته شده است و باعث ایجاد exe نمی شود.

آنها توسط زمان اجرا مشترک NET Core اجرا می شوند.

شما فقط می توانید با اجرای دستور dotnet دستور برنامه را اجرا کنید. اما اگر واقعاً می خواهید exe تولید کنید ، دستور زیر را اجرا کنید:
dotnet منتشر -c Debug -r win10-x64 یا dotnet منتشر -c انتشار -r win10-x64

این یک برنامه کنسول مستقل مشابه برنامه های کنسول دات نت فعلی ایجاد می کند.

این به ما اجازه می دهد تا برنامه را بدون نیاز به زمان اجرا .NET Core بر روی دستگاه مورد نظر اجرا کنیم.

شروع با نسخه C # 7.1 ، می توانید برنامه های کنسولی را با یک نقطه ورود استاتیک به عنوان یک کار غیر ناهمزمان ایجاد کنید که به شما امکان می دهد یک برنامه کنسول چند رشته ای بسازید.

علاوه بر این ،NET Core 2.1. با ویژگی جدید مجموعه ای برای ساده سازی سرویس های مبتنی بر کنسول ارائه شده است.

این ویژگی های جدید شامل IHost و HostBuilder است.

برنامه های NET Core 2.1 2.1 پیکربندی و راه اندازی میزبان. مسئول راه اندازی برنامه ها و مدیریت طول عمر است.

با استفاده از .NET Core HostBuilder ، کارهای پس زمینه می توانند به عنوان خدمات میزبانی شده انجام شوند.

سرویس میزبانی شده کلاس با منطق وظیفه پس زمینه است که رابط IHostedService را پیاده سازی می کند.

برای سرویس مدیریت پیام پرسشنامه ، سه وظیفه پس زمینه ایجاد می شود که بر روی تایمر اجرا می شوند. یکی برای ارسال پیام ، دیگری برای دریافت پیام و دیگری برای ارسال پیام.

در روش اصلی برنامه کنسول ، می توانید با ایجاد یک HostBuilder شروع کرده و سپس از روشهای افزودنی برای ثبت خدمات با تزریق وابستگی استفاده کنید ، اطلاعات پیکربندی را بخوانید و گزارش های مورد نیاز خود را پیکربندی کنید.

برای برنامه کنسول صف ارسال پیام ، هر کار پس زمینه به عنوان سرویس با طول عمر با استفاده از روش service.AddTransient ثبت می شود

public static async Task Main(string[] args)
{
    //
    //    get configuration information
    //

    MessageQueueAppConfig messageQueueAppConfig = new MessageQueueAppConfig();
    ConnectionStrings connectionStrings = new ConnectionStrings();

    string environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
    string jsonFile = $"appsettings.{environment}.json";

    var configBuilder = new ConfigurationBuilder()
        .SetBasePath(Directory.GetCurrentDirectory())
        .AddJsonFile(jsonFile, optional: true, reloadOnChange: true);

    IConfigurationRoot configuration = configBuilder.Build();

    configuration.GetSection("MessageQueueAppConfig").Bind(messageQueueAppConfig);
    configuration.GetSection("ConnectionStrings").Bind(connectionStrings);

    //
    //    Sending Message
    //

    IHostedService sendInventoryManagementMessages = new SendMessages();

    //
    //   Receive Messages 
    //

    IHostedService receiveInventoryManagementMessages = new ReceiveMessages();
   
    //
    //    Message Processing
    //   

    IHostedService processMessages = new ProcessMessages();

    var builder = new HostBuilder().ConfigureAppConfiguration((hostingContext, config) => {})
        .ConfigureServices((hostContext, services) =>
        {
            services.AddTransient<IHostedService>(provider => processMessages);
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.AddTransient<IHostedService>(provider => sendInventoryManagementMessages);
        })
        .ConfigureServices((hostContext, services) =>
        {
            services.AddTransient<IHostedService>(provider => receiveInventoryManagementMessages);
        })
        .ConfigureLogging((hostingContext, logging) =>
        {
            logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
            logging.AddConsole();
        });

        await builder.RunConsoleAsync();
}

Getting started with RabbitMQ

برای شروع کار با RabbitMQ ، شما باید سرور RabbitMQ را بارگیری کرده و دستورالعمل های نصب را به شرح مفصل در وب سایت آنها در https://www.rabbitmq.com دنبال کنید.

وقتی نصب کننده RabbitMQ را برای ویندوز اجرا می کنید ، RabbitMQ را به عنوان یک سرویس Windows نصب می کند و آن را با استفاده از پیکربندی پیش فرض شروع می کند.

این سرویس با استفاده از تنظیمات پیش فرض خود بسیار خوب اجرا خواهد شد.

می توانید محیط RabbitMQ را شخصی سازی کرده و در صورت لزوم پیکربندی آن را تغییر دهید.

سرویس RabbitMQ بطور خودکار شروع می شود. می توانید سرویس RabbitMQ را از منوی شروع متوقف کرده یا مجدداً نصب کنید.

RabbitMQ ابزاری برای مدیریت و نظارت بر UI وب

برای سرور RabbitMQ شما فراهم می کند.

از رابط مدیریت می توانید همه صف های خود را کنترل ، ایجاد ، حذف و لیست کنید.

همچنین می توانید اتصالات و کانال های سرور را رصد کنید و طول صف را زیر نظر بگیرید و نرخ پیام و غیره را بررسی کنید

RabbitMQ Queues and Exchanges

قبل از شروع ارسال و دریافت پیام با RabbitMQ ، باید یک شیرجه عمیق RabbitMQ بگیرید و برخی مفاهیم AMQP و RabbitMQ را درک کنید. برخی از مفاهیم اصلی پیام RabbitMQ شامل موارد زیر است:

  • مبادلات – صرافی ها اشخاص AMQP هستند که پیام ها ارسال می شوند. صرافی ها یک پیام دریافت می کنند و آن را به صف های صفر یا بیشتر می رسند. الگوریتم مسیریابی مورد استفاده بستگی به نوع ارز و قوانین الزام آور دارد.
  • صف – صف در مدل AMQP بسیار شبیه به صف های سایر سیستم های ارسال پیام و وظیفه است: آنها پیام هایی را ذخیره می کنند که توسط برنامه ها مصرف می شوند. صف ها برخی از خواص را با مبادلات به اشتراک می گذارند ، اما برخی از خواص اضافی نیز دارند:
  • صحافی – صحافی نقشه مبادله را در صف قرار می دهد. صحافی قوانینی برای نحوه هدایت پیام از صرافی به یک یا چند صف است.

وقتی مبادله می شود ، چهار نوع صرافی در RabbitMQ وجود دارد:

  • مبادله مستقیم – یک مبادله مستقیم بر اساس کلید مسیریابی پیام ، پیام ها را به صف می رساند.
  •  تبادل Fanout – تبادل fanat برای همه صف هایی که به آن وصل شده اید پیام می دهد و کلید مسیریابی را نادیده می گیرد.
  •  تبادل موضوع – مبادله پیام های مسیر را به یک یا بسیاری از صف ها بر اساس تطابق بین کلید مسیریابی پیام و الگویی که برای اتصال صف به یک صرافی استفاده شده است ، تبادل می کند.

o Exchange Headers – مبادله سرصفحه برای مسیریابی روی چندین ویژگی طراحی شده است که به راحتی به عنوان هدر پیام از یک کلید مسیریابی بیان می شوند.

Sending RabbitMQ Messages With a Fanout Exchange

برای برنامه نمونه ، ارسال پیام با استفاده از Exchange Fanout بهترین انتخاب به نظر می رسید.

به عنوان مثال ، هنگام ایجاد محصولی در میکروسرویس مدیریت Inventory ، اطلاعات مربوط به محصول باید با میکروسرویس مدیریت سفارش فروش و میکروسرویس مدیریت خرید سفارش به اشتراک گذاشته شود.

علاوه بر این باید یک پیام برای نظارت و تأیید تکمیل موفقیت آمیز چرخه کامل زندگی یک پیام ارسال شود

با نگاهی به سرویس ارسال پیام مدیریت پرسشنامه ، مبادلات زیر تنظیم شده است.

با داشتن RabbitMQ ، باید یک استراتژی برای تعیین و طراحی مبادلات و صف های خود برنامه ریزی کنید.

بهترین شیوه ها و الگوهای طراحی برای طراحی مبادلات و صف ها در اینترنت وجود دارد که می توانید بخوانید اما برای نمونه نمونه ، رویکردی که من در نظر گرفته ام ایجاد یک مبادله جداگانه RabbitMQ برای هر نوع معاملات تجاری است.

به عنوان مثال ، هنگام ایجاد یک محصول در میکروسرویس مدیریت Inventory ، یک مبادله اختصاصی RabbitMQ صرفاً جهت ردیابی پیام های ایجاد محصول و بروزرسانی محصول ایجاد شده است.

برای پاسخگویی به الزامات مربوط به برنامه نمونه ، نوع تبادل fanout به کلیه صف های پیوند شده به آن پیغام می دهد.

در میکروسرویس مدیریت موجودی سه معاملات تجاری وجود دارد و بنابراین من سه مبادله RabbitMQ برای ارسال و مسیریابی پیام ها به شرح زیر ایجاد کردم:

  • تبادل ایجاد و به روزرسانی محصولات – پیام هایی را به صف سفارش سفارش ، صف سفارش فروش و صف ورود به سیستم ارسال می کند
  • موجودی دریافت ارز – پیام هایی را به صف سفارش سفارش ، صف سفارش و صف ورود ارسال می کند
  •  موجودی بورس اوراق بهادار – پیام هایی را به صف سفارش فروش و صف ورود به سیستم ارسال می کند

سایر مبادلات موجود در برنامه نمونه عبارتند از:

  •  Purchase Order Submitted Exchange – پیامهایی را به صف مدیریت موجودی و صف ورود به سیستم ارسال می کند
  • Sales Order Submitted Exchange – پیامهایی را به صف مدیریت موجودی و صف ورود به سیستم ارسال می کند
  • Logging Exchange – پیامها را مستقیماً به صف ورود به سیستم ارسال می کند

Sending an Inventory Shipped Message

برای ارسال queue message در برنامه نمونه ، یک کلاس SendMessages عمومی ایجاد شده است که کلیه سرویس های ارسال پیام پیاده سازی خواهد شد.

عملکرد اساسی این کلاس شامل موارد زیر است:

اتصال SignalR شروع میشود.

برای بازیابی پیام ها در جدول TransactionQueueOutBound در SQL-Server ، از پیش تعیین شده داخلی استفاده کنید.

گوش دادن به SignalR در رویدادها و بازیابی پیام ها در زمان واقعی در جدول TransactionQueueOutBound در SQL-Server
یکی از تصمیمات طراحی من برای صف ارسال پیام در برنامه نمونه این بود که اطمینان حاصل کنیم که تمام معاملات تجاری بصورت متوالی پردازش می شوند زیرا معاملات تجاری به پایگاه داده متصل هستند.

در محیطی که صدها کاربر همزمان بر روی سیستم وجود داشته باشد ، سرویس جستجوی مدیریت Inventory چندین درخواست پیام در زمان واقعی به طور همزمان دریافت می کند و شاید بر همان قطعات داده تأثیر بگذارد

using CodeProject.Shared.Common.Interfaces;
using Microsoft.Extensions.Hosting;
using System;
using System.Collections.Generic;
using System.Text;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using CodeProject.Shared.Common.Models;
using CodeProject.MessageQueueing;
using Microsoft.AspNetCore.SignalR.Client;
using RabbitMQ.Client;

namespace CodeProject.MessageQueueing
{
    public class SendMessages : IHostedService, IDisposable
    {
        private readonly List<IMessageQueueConfiguration> _messageQueueConfigurations;
        private readonly IMessageQueueConnection _messageQueueConnection;
        private readonly IMessageQueueProcessing _messageProcessor;
        private readonly MessageQueueAppConfig _appConfig;
        private readonly ConnectionStrings _connectionStrings;
        private readonly string _signalRQueue;

        private HubConnection _signalRHubConnection;
        private Timer _timer;

        /// <summary>
        /// Send Messages
        /// </summary>
        /// <param name="messageQueueConnection"></param>
        /// <param name="messageProcessor"></param>
        /// <param name="appConfig"></param>
        /// <param name="connectionStrings"></param>
        /// <param name="messageQueueConfigurations"></param>
        public SendMessages(IMessageQueueConnection messageQueueConnection, 
                            IMessageQueueProcessing messageProcessor, 
                            MessageQueueAppConfig appConfig, 
                            ConnectionStrings connectionStrings, 
                            List<IMessageQueueConfiguration> messageQueueConfigurations, 
                            string signalRQueue)
        {
            _messageQueueConnection = messageQueueConnection;
            _messageQueueConfigurations = messageQueueConfigurations;
            _connectionStrings = connectionStrings;
            _messageProcessor = messageProcessor;
            _appConfig = appConfig;
            _signalRQueue = signalRQueue;
        }

        /// <summary>
        /// Start Process Interval
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {

            StartSignalRConnection();

            _timer = new Timer(GetMessagesInQueue, null, TimeSpan.Zero, 
                               TimeSpan.FromSeconds(_appConfig.SendingIntervalSeconds));

            return Task.CompletedTask;
        }

        /// <summary>
        /// Start SignalR Connection
        /// </summary>
        private async void StartSignalRConnection()
        {
            _signalRHubConnection = new HubConnectionBuilder().WithUrl(url).Build();
           
            _signalRHubConnection.On<string>(_signalRQueue, (message) =>
            {
                this.GetMessagesInQueue(null);

            });

            _signalRHubConnection.Closed += async (error) =>
            {
                await Task.Delay(10000);
                await _signalRHubConnection.StartAsync();
            };

            await _signalRHubConnection.StartAsync();
                  
        }
        
        /// <summary>
        /// Get Messages In Queue
        /// </summary>
        /// <param name="state"></param>
        private async void GetMessagesInQueue(object state)
        {
            ResponseModel<List<MessageQueue>> messages = 
                 await _messageProcessor.SendQueueMessages(_messageQueueConfigurations,
                                                           _appConfig.OutboundSemaphoreKey, 
                                                           _connectionStrings);
            
            Console.WriteLine("total messages " + messages.Entity.Count.ToString() + 
                              " sent at " + DateTime.Now);

        }

        /// <summary> 
        /// Stop Process  
        /// </summary>
        public Task StopAsync(CancellationToken cancellationToken)
        {
            _timer?.Change(Timeout.Infinite, 0);

            return Task.CompletedTask;
        }

        /// <summary>
        /// Dispose Timer
        /// </summary>
        public void Dispose()
        {
            _timer?.Dispose();
        }

    }

}
برای تضمین پردازش پی در پی معاملات تجاری ، روش SendQueueMessages یک دستور قفل را اجرا می کند تا چندین بار از ارسال پیام به طور همزمان مسدود شود.
اولین درخواست از طریق یک قفل ردیف منحصر به فرد SQL-Server به دست می آید و به خواندن کلیه معاملات در حال تعلیق در جدول TransactionQueueOutbound در معاملات پی در پی ترتیب سفارش می پردازد و مقدار بار پیام صف را برای هر تراکنش استخراج می کند و پیامی برای هر تراکنش ارسال می شود.
تبادل مناسب RabbitMQ هنگام اجرای روش SendMessage. پس از اتمام این چرخه ، درخواست پیام بعدی از طریق مرحله بعدی پردازش معاملات تجاری در حال انجام خواهد بود
/// <summary>
/// Send Queue Messages
/// </summary>
/// <param name="messageQueueConfigurations"></param>
/// <param name="outboundSemaphoreKey"></param>
/// <param name="connectionStrings"></param>
/// <returns></returns>
public async Task<ResponseModel<List<MessageQueue>>> SendQueueMessages(
             List<IMessageQueueConfiguration> messageQueueConfigurations, 
             string outboundSemaphoreKey, 
             ConnectionStrings connectionStrings)
{
    ResponseModel<List<MessageQueue>> returnResponse = new ResponseModel<List<MessageQueue>>();
    returnResponse.Entity = new List<MessageQueue>();

    Console.WriteLine("sending = " + _sending);

    lock (_sendingLock)
    {
        if (_sending)
        {
            Console.WriteLine("Aborted iteration still sending");
            return returnResponse;
        }

        _sending = true;

    }

    Console.WriteLine("Start sending");

    Boolean getMessages = true;

    while (getMessages==true)
    {
        ResponseModel<List<MessageQueue>> response = 
                                          await GetMessagesToSend(messageQueueConfigurations,
                                                                  outboundSemaphoreKey, 
                                                                  connectionStrings);

        foreach (MessageQueue message in response.Entity)
        {
            returnResponse.Entity.Add(message);
        }

        if (response.Entity.Count == 0)
        {
            _sending = false;
            getMessages = false;
        }
    }

    return returnResponse;

}
        
/// <summary>
/// Get Messages To Send
/// </summary>
/// <param name="messageQueueConfigurations"></param>
/// <param name="outboundSemaphoreKey"></param>
/// <param name="connectionStrings"></param>
/// <returns></returns>
private async Task<ResponseModel<List<MessageQueue>>> GetMessagesToSend(
     List<IMessageQueueConfiguration> messageQueueConfigurations, 
     string outboundSemaphoreKey, ConnectionStrings connectionStrings)
{
    TransactionQueueSemaphore transactionQueueSemaphore = null;

    ResponseModel<List<MessageQueue>> returnResponse = new ResponseModel<List<MessageQueue>>();
    returnResponse.Entity = new List<MessageQueue>();

    try
    {
        _inventoryManagementDataService.OpenConnection(
                                        connectionStrings.PrimaryDatabaseConnectionString);

        _inventoryManagementDataService.BeginTransaction((int)IsolationLevel.Serializable);

         //
         // get all pending outbound transactions
         //

         List<TransactionQueueOutbound> transactionQueue = 
              await _inventoryManagementDataService.GetOutboundTransactionQueue();

         foreach (TransactionQueueOutbound transactionQueueItem in transactionQueue)
         {
            MessageQueue message = new MessageQueue();
            message.ExchangeName = transactionQueueItem.ExchangeName;
            message.TransactionQueueId = transactionQueueItem.TransactionQueueOutboundId;
            message.TransactionCode = transactionQueueItem.TransactionCode;
            message.Payload = transactionQueueItem.Payload;

            //
            //  the message queue configurations object has a list of the all exchange/queue
            //  configurations - the where clause finds the configration needed for the 
            //  particular transaction being processed
            //

            IMessageQueueConfiguration messageQueueConfiguration = messageQueueConfigurations
                     .Where(x => x.TransactionCode == message.TransactionCode).FirstOrDefault();
            if (messageQueueConfiguration == null)
            {
                break;
            }

            //
            //  The SendMessage method will send a message to RabbitMQ
            //

            ResponseModel<MessageQueue> messageQueueResponse = 
                                        messageQueueConfiguration.SendMessage(message);

            if (messageQueueResponse.ReturnStatus == true)
            {
                transactionQueueItem.SentToExchange = true;
                transactionQueueItem.DateSentToExchange = DateTime.UtcNow;
                await _inventoryManagementDataService
                      .UpdateOutboundTransactionQueue(transactionQueueItem);

                returnResponse.Entity.Add(message);
            }
            else
            {
                break;
            }

        }

        await _inventoryManagementDataService.UpdateDatabase();

        _inventoryManagementDataService.CommitTransaction();
        _inventoryManagementDataService.CloseConnection();

    }
    catch (Exception ex)
    {
        _inventoryManagementDataService.RollbackTransaction();
        returnResponse.ReturnStatus = false;
        returnResponse.ReturnMessage.Add(ex.Message);
    }
    finally
    {
        _inventoryManagementDataService.CloseConnection();
    }

    return returnResponse;
}
Creating a RabbitMQ Connection
برای نوشتن کد سی شارپ با استفاده از RabbitMQ ، باید کتابخانه .NET RabbitMQ.Client را نصب کنید.
مشتری RabbitMQ .NET یک کتابخانه منبع باز است و اجرای کتابخانه مشتری AMQP برای زبانهای C # و سایر زبانهای NET است.
اولین کاری که برای ارسال و دریافت پیام با RabbitMQ باید انجام دهید ایجاد ارتباط با RabbitMQ است. در حالت توسعه ، اتصال فقط به ویژگیهای زیر با پیش فرض توسعه به شرح زیر نیاز دارد:
HostName = localhost
نام کاربری = مهمان
رمز عبور = مهمان
برای برنامه نمونه ، هر کار / موضوع غیر همزمان در حال اجرا در هر سرویس صف ارسال پیام ، یک ارتباط جداگانه با RabbitMQ ایجاد و حفظ می کند.
using CodeProject.Shared.Common.Interfaces;
using CodeProject.Shared.Common.Models;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;

namespace CodeProject.MessageQueueing
{
    public class MessageQueueConnection  : IMessageQueueConnection
    {
    
        private ConnectionFactory _connectionFactory;
        private MessageQueueAppConfig _messageQueueAppConfig;
        private IConnection _connection;

        public MessageQueueConnection(MessageQueueAppConfig messageQueueAppConfig)
        {
            _messageQueueAppConfig = messageQueueAppConfig;
        }

        /// <summary>
        /// Create RabbitMQ Connection
        /// </summary>
        public void CreateConnection()
        {
            _connectionFactory = new ConnectionFactory();

            _connectionFactory.HostName = _messageQueueAppConfig.MessageQueueHostName;
            _connectionFactory.UserName = _messageQueueAppConfig.MessageQueueUserName;
            _connectionFactory.Password = _messageQueueAppConfig.MessageQueuePassword;

            _connection = _connectionFactory.CreateConnection();

        }

        public IConnection GetConnection()
        {
            return _connection;
        }

    }
}

Declaring, Creating and Configuring RabbitMQ Exchanges and Queues

از ویزگی های پروتکل AMQP میتوان به پیام گرایی، صف بندی و مسیر یابی اشاره کرد به این صورت که در مسیر یابی یک فرایند تصمیم گیری رخ میدهد تا برای قرار دادن پیام از کدام صف استفاده کند.

همچنین این استاندارد از امنیت و قابلیت اطمینان بالایی نیز برخوردار است.

AMQP را میتوان به عنوان یک سیستم پست الکترونیکی در صنعت درنظر گرفت تا به جای افراد به عمل ارسال ایمیل را برعهده گیرد زیرا قادر است به عنوان یک پیام رسان میان میلیون ها سازمان مختلف ارتباط برقرار کند.

به این ترتیب با استفاده از یک فرمت مشترک در پیام رسانی، مابین سیستم های گوناکون و برنامه های کاربردی متفاوت اتصال برقرار میکند.
یک واسطه‌ی پیام به عنوان یک فرد میانی برای سرویس‌های مختلف عمل کند (برای نمونه یک برنامه تحت وب، می‌توانند جهت کاهش بار و زمان تحویل توسط سرور برنامه‌های تحت وب استفاده شوند از آنجاییکه وظایف، که بطور معمول مقدار زمانی را جهت پردازش صرف می‌کنند، می‌توانند به یک شخص ثالث/ واسطه که تنها کار آن اجرای آنها است، محول شوند.

در این سناریویی را دنبال می‌کنیم که در آن یک برنامه‌ی تحت وب به کاربران اجازه می‌دهد تا اطلاعات را بر روی یک وب‌سایت بارگذاری/آپلود کنند.

سایت این اطلاعات را مدیریت کرده و یک PDF را تولید و مجدد به کاربر ایمیل خواهد کرد.

مدیریت اطلاعات، تولید PDF و ارسال ایمیل در مورد این مثال چندین ثانیه زمان خواهد برد و این یکی از دلایل چرایی استفاده از یک صف پیام است.

هنگامیکه کاربر اطلاعات کاربری را در رابط وب وارد کرد، برنامه تحت وب یک وظیفه “پردازش/فرآیند PDF” و تمامی اطلاعات را در یک پیام خواهد گذاشت و پیام در یک صف تعریف شده در RabbitMQ قرار خواهد گرفت.

  •  پیام های مداوم – پیام های مداوم به محض رسیدن به صف به دیسک نوشته خواهند شد ، در حالی که پیام های گذرا فقط روی دیسک نوشته می شوند تا در صورت کم شدن حافظه از حافظه جدا شوند. پیام های مداوم نیز در صورت امکان در حافظه ذخیره می شوند. فقط وقتی حافظه کم می شود از حافظه حذف می شود.
  • صف های پایدار و غیر پایدار-صف های ماندگار بر روی دیسک ادامه می یابند و بنابراین از راه اندازی مجدد کارگزار زنده می مانند. مسائلی که ماندگار نیستند گذرا نامیده می شوند. اگر کارگزار پایین بیاید و دوباره به عقب برگردد ، یک صف ماندگار در هنگام راه اندازی کارگزار دوباره اعلام می شود ، با این وجود ، فقط پیام های مداوم بازیابی می شوند.
  • حذف خودکار – در صورت تنظیم صف برای حذف خودکار ، هنگامی که آخرین مصرف کننده مشترک خود را لغو کرد ، صف حذف می شود.
using CodeProject.Shared.Common.Interfaces;
using CodeProject.Shared.Common.Models;
using Newtonsoft.Json;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.MessagePatterns;

namespace CodeProject.MessageQueueing
{
    public class MessageQueueConfiguration : IMessageQueueConfiguration
    {
        
        private string _exchangeName;
        private List<string> _boundedQueues;
        private MessageQueueAppConfig _messageQueueAppConfig;
        private readonly IMessageQueueConnection _messageQueueConnection;
        private Subscription _subscription;
        private IBasicProperties _basicProperties;
        private IModel _channel;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="exchangeName"></param>
        /// <param name="messageQueueAppConfig"></param>
        /// <param name="messageQueueConnection"></param>
        public MessageQueueConfiguration(string exchangeName, 
                                         List<string> boundedQueues, 
                                         MessageQueueAppConfig messageQueueAppConfig, 
                                         IMessageQueueConnection messageQueueConnection)
        {
            _messageQueueAppConfig = messageQueueAppConfig;
            _messageQueueConnection = messageQueueConnection;
            _exchangeName = exchangeName;
            _boundedQueues = boundedQueues;
        }
      
        /// <summary>
        /// Initialize Initialize RabbitMQ Exchange
        /// </summary>
        public void InitializeRabbitMQExchange()
        {
            _channel = _messageQueueConnection.GetConnection().CreateModel();

            _basicProperties = _channel.CreateBasicProperties();
            _basicProperties.Persistent = true;

            string exchangeName = _exchangeName + "_" + _messageQueueAppConfig.MessageQueueEnvironment;

            _channel.ExchangeDeclare(exchangeName, "fanout", true, false);

            foreach (string queueName in _boundedQueues)
            {
                string queue = queueName + "_" + _messageQueueAppConfig.MessageQueueEnvironment;

                _channel.QueueDeclare(queue, true, false, false);
                _channel.QueueBind(queue, exchangeName, _messageQueueAppConfig.RoutingKey);
            }
        }
    
    }

}

ارسال پیام به RabbitMQ Exchange

هنگامی که Inventory Management Message Queuing Service معاملات در حال انتظار را از جدول TransactionQueueOutbound در databaseمدیریت Inventory ، اطلاعات بارگیری را برای هر تراکنش به صورت جدا از هم استخراج کرده ، بارگذاری را به روش SendMessage زیر ارسال کرده و پیام را از طریق InventoryShipped RabbitMQ منتشر می کند.

RabbitMQ از الگوی message queuing انتشار / اشتراک مشترک پشتیبانی می کند.

الگو، انتشارمشترک شدن درقسمتی است که ارسال کنندگان پیام (ناشران یا تولید کنندگان) پیام هایی را بدون اطلاع از مشترکین در صورت وجود ، منتشر می کنند.

به همین ترتیب ، مشترکین یا مصرف کنندگان ، فقط پیام هایی را که لازم دارند دریافت می کنند ، بدون آنکه آگاه باشند چه ناشرانی آن ها را تهیه کرده اند.

برای انتشار پیام با RabbitMQ ، ابتدا یک نمونه PublicationAddress ایجاد می کنید و خصوصیات و نوع exchangeرا تعیین می کنید.

برای ارسال پیام بهexchange ، با استفاده از روش BasicPublish از یک کانال RabbitMQ استفاده میشود.

آدرس انتشار ، خصوصیات اساسی و میزان بارگذاری پیام برای انتقال در این روش اجرا می شود. هنگام ارسال پیام ، مبلغ پرداختی به عنوان UTF8 ارسال می شود.

در روش SendMessage در زیر ، یک بلوک try / catch کدی را که در حال ارسال پیام به RabbitMQ است محاصره می کند و در صورت بروز خطا در ارسال پیام ، خطا به سرویس پرسشنامه مدیریت پیام موجودی باز می گردد و در آنجا معامله تجاری را ترک می کند.

در TransactionQueueOutbound اگر خطایی در ارسال پیام به RabbitMQ رخ دهد ، در واقع به معنای پایین آمدن سرور RabbitMQ است.

اجرای جداول TransactionQueueInbound و TransactionQueueOutbound واسطه ای در SQL-Server باعث می شود کل فرایند message queue با پذیرفتن خطا آسان تر عمل کرده و عملکرد بازیابی و آزمایش مجدد را تسهیل می بخشد.

اجرای صف پیغام بدون جداول صف پیام ، پیام واسطه ای که در یک معامله SQL-Server commit / backback شرکت می کند را بازیابی کرده و تلاش مجدد برای جلوگیری از بروز خطا را بسیار دشوارتر می کند.

Hide   Shrink    Copy Code
/// <summary>
/// Send Message
/// </summary>
/// <param name="entity"></param>
public ResponseModel<MessageQueue> SendMessage(MessageQueue entity)
{
    ResponseModel<MessageQueue> response = new ResponseModel<MessageQueue>();
    response.Entity = new MessageQueue();

    try
    {
        string output = JsonConvert.SerializeObject(entity);

        byte[] payload = Encoding.UTF8.GetBytes(output);

        string exchangeName = _exchangeName + "_" + _messageQueueAppConfig.MessageQueueEnvironment;

        PublicationAddress address = new PublicationAddress(ExchangeType.Fanout, 
                                                            exchangeName,
                                                            _messageQueueAppConfig.RoutingKey);

        _channel.BasicPublish(address, _basicProperties, payload);

        response.Entity.Payload = output;

        response.ReturnStatus = true;
    }
    catch (Exception ex)
    {
        response.ReturnStatus = false;
        response.ReturnMessage.Add(ex.Message);
    }

    return response;

}

ایجاد و پیکربندی اشتراک RabbitMQ

توصیه و راحت ترین راه برای دریافت پیام ، تنظیم اشتراک است.

در RabbitMQ یک گزینه پیکربندی مختلف برای تنظیم اشتراک وجود دارد.

برای نمونه برنامه های اشتراک با استفاده از object اشتراک RabbitMQ ایجاد می شوند.

پس از ایجاد ، اشتراک از یک queueمصرف می کند.سفارش های دریافت شده را می توان با تماس با Next () یا با استفاده از object به عنوان IEnumerator در یک حلقه foreach بازیابی کرد.

در روش InitializeRabbitMQSubscription یک object اشتراک ایجاد می شود و با اعلام اولین queueبه همان روش هنگام اعلام صف برای اتصال به مبادله ، پیکربندی می شود.

پس از اعلام به اشتراک اختصاص داده می شود و این اشتراک به یک کانال RabbitMQ اختصاص می یابد.

using CodeProject.Shared.Common.Interfaces;
using CodeProject.Shared.Common.Models;
using Newtonsoft.Json;
using RabbitMQ.Client;
using System;
using System.Collections.Generic;
using System.Text;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.MessagePatterns;

namespace CodeProject.MessageQueueing
{
    public class MessageQueueConfiguration : IMessageQueueConfiguration
    {
        
        private string _exchangeName;
        private List<string> _boundedQueues;
        private MessageQueueAppConfig _messageQueueAppConfig;
        private readonly IMessageQueueConnection _messageQueueConnection;
        private Subscription _subscription;
        private IBasicProperties _basicProperties;
        private IModel _channel;
        private string _originatingQueueName;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="exchangeName"></param>
        /// <param name="messageQueueAppConfig"></param>
        /// <param name="messageQueueConnection"></param>
        public MessageQueueConfiguration(string exchangeName, 
                                         MessageQueueAppConfig messageQueueAppConfig, 
                                         IMessageQueueConnection messageQueueConnection)
        {
            TransactionCode = exchangeName;

            _messageQueueAppConfig = messageQueueAppConfig;
            _messageQueueConnection = messageQueueConnection;
        }

        /// <summary>
        /// Initialize RabbitMQ Subscription
        /// </summary>
        /// <param name="queueName"></param>
        public void InitializeRabbitMQSubscription(string queueName)
        {
            _channel = _messageQueueConnection.GetConnection().CreateModel();

            string queue = queueName + "_" + _messageQueueAppConfig.MessageQueueEnvironment;

 _          _channel.QueueDeclare(queue: queue, 
                                  durable: true,
                                  exclusive: false,
                                  autoDelete: false, 
                                  arguments: null); 

            _subscription = new Subscription(_channel, queue, false);

        }
       
    }

}

Consuming and Receiving Messages from a RabbitMQ Queue

برای برنامه نمونه ، یک ReceiveMessages عمومی ایجاد شده است که همه سرویس های ارسال پیام برای مصرف پیام از یک queue استفاده می کنند.

کلاس ReceiveMessages به عنوان یک سرویس میزبان ایجاد می شود و به یک هماهنگ کننده جداگانه در هرmessage queuing services برای هر میکرو سرویس ، اجرا می شود .

از آنجا که Inventory Management Message Queuing Service موجودی را به Inventory Shipment exchange, ارسال می کند ، سرویس مدیریت Sales Order به طور همزمان در queueسفارش فروش، که برای مبادله حمل و نقل موجودی است مشترک است.

Sales Order Management Message Queuing Service ، برای دریافت پیام ، کلاس ReceiveMessages را پیاده سازی می کند.

در روش GetMessagesInQueue ، پس از ایجاد موفقیت در فرآیند شروع اتصال و اشتراک ، مرجعی به موضوع اشتراک بدست می آید
در Sales Order Management Message Queuing Service ، اشتراک ایجاد شده همان sales order queue می باشد.

تحویل های دریافت شده (Received deliveries) با استفاده از اشتراک به عنوان IEnumerator در یک حلقه foreach بازیابی می شوند.

با هر تکرار ، یک هدف BasicDeliverEventArg از اشتراک برگردانده می شود.

BasicDeliverEventArg شامل تمام اطلاعات مربوط به پیام ارسال شده از یک کارگزار AMQP است.

حلقه foreach به طور مداوم تکرار می شود.

هنگامی که تعداد بیشتری پیام برای پردازش وجود ندارد ، بیکار می ماند.

وقتی پیام های بیشتری وارد صف می شوند ، حلقه foreach به طور خودکار شروع به تکرار می کند.

ReceiveMessages به یک جزء پردازنده پیام سفارشی اشاره دارد که شامل روش هایی برای پردازش معاملات داخلی و به روزرسانی جداول بانک اطلاعاتی است.

The Sales Order Management Message Queuing Service ، پیام ورودی RabbitMQ را وارد کرده و آن را به صورت deserialize در یک Object MessageQueue می فرستد و deserialized را به روش CommitInBoundMessage پردازنده پیام منتقل می کند و این روش پیام را وارد جدول TransactionQueueInbound در بانک اطلاعات sales order managmentمی کند.

هنگامی که به جدول TransactionQueueInbound وصل شدید ، یک پیام تأیید به سرور RabbitMQ ارسال می شود و به سرور اطلاع می دهد که می تواند پیام را از صف پیام sales orderخارج کند.

سرانجام پردازنده پیام مجدداً پردازش می شود تا پیام ها را در جدول TransactionQueueInbound پردازش کند و مقدار ارسال شده در سفارش فروش را به روز کند.

using System;
using System.Collections.Generic;
using System.Text;
using System.Reactive.Subjects;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using CodeProject.Shared.Common.Models;
using CodeProject.Shared.Common.Interfaces;
using CodeProject.Shared.Common.Models.MessageQueuePayloads;
using Newtonsoft.Json;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
using RabbitMQ.Client.MessagePatterns;

namespace CodeProject.MessageQueueing
{

    public class ReceiveMessages : IHostedService, IDisposable
    {

        private readonly List<IMessageQueueConfiguration> _messageQueueConfigurations;
        private readonly IMessageQueueConnection _messageQueueConnection;
        private readonly IMessageQueueProcessing _messageProcessor;
        private readonly MessageQueueAppConfig _appConfig;
        private readonly ConnectionStrings _connectionStrings;

        private Timer _timer;
        private Boolean _running = false;

        public ReceiveMessages(IMessageQueueConnection messageQueueConnection, 
                               IMessageQueueProcessing messageProcessor, 
                               MessageQueueAppConfig appConfig, ConnectionStrings connectionStrings, 
                               List<IMessageQueueConfiguration> messageQueueConfigurations)
        {
            _messageQueueConnection = messageQueueConnection;
            _messageQueueConfigurations = messageQueueConfigurations;
            _connectionStrings = connectionStrings;
            _messageProcessor = messageProcessor;
            _appConfig = appConfig;
        }

        /// <summary>
        /// Start
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StartAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine("Starting Receiving Messages");

            _timer = new Timer(GetMessagesInQueue, null, TimeSpan.Zero, 
                               TimeSpan.FromSeconds(_appConfig.ReceivingIntervalSeconds));

            return Task.CompletedTask;
        }

        /// <summary>
        /// Get Messages In Queue
        /// </summary>
        /// <param name="state"></param>
        private async void GetMessagesInQueue(object state)
        {
    
            if (_running == true)
            {
                return;
            }

            _running = true;

            Console.WriteLine("Receiving Messages at " + DateTime.Now);

            Subscription subscription = _messageQueueConfigurations[0].GetSubscription();

            foreach (BasicDeliverEventArgs e in subscription)
            {
                string message = Encoding.UTF8.GetString(e.Body);

                MessageQueue messageQueue = JsonConvert.DeserializeObject<MessageQueue>(message);

                ResponseModel<MessageQueue> responseMessage = 
                              await _messageProcessor.CommitInboundMessage(messageQueue, 
                                                                           _connectionStrings);

                if (responseMessage.ReturnStatus == true)
                {
                   
                    Console.WriteLine($"Message Committed: {messageQueue.TransactionQueueId}");

                    subscription.Ack(e);
                    
                    await _messageProcessor.ProcessMessages(_appConfig.InboundSemaphoreKey, 
                                                            _connectionStrings);

                }

            }

        }
        
        /// <summary>
        /// Stop Async
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public Task StopAsync(CancellationToken cancellationToken)
        {
            Console.WriteLine("Stopping.");

            return Task.CompletedTask;
        }

        public void Dispose()
        {

        }
    }
}

Microservices Logging Best Practices

معماری میکروسرویس مزایای بسیار زیادی از قبیل امکان استفاده از پشته های مختلف فن آوری ، استقرار برنامه ها به طور مستقل ، حل مشکلات کوچک را یک باره و موارد دیگر ارائه می دهد.

اما استفاده از میکروسرویس با توجه به پیچیده بودن آن ها هزینه بالایی دارد.

نه تنها در نحوه برقراری ارتباط با یکدیگر بلکه در نحوه مدیریت آنها. و در صورت عدم موفقیت یک یا چند سرویس ، پیچیده تر می شوند. عیب یابی میکروسرویس بدون مکانیسم ورود به سیستم بی معنیست.

برای برنامه نمونه ، یک بانک اطلاعاتی (database) ورود به سیستم جداگانه ای ایجاد شده است.

همه پیام های صف ارسال شده و دریافت شده از طریق RabbitMQ نیز به صف پیام ورود به سیستم هدایت می شوند.

یک سرویس تنظیم صفحات ورود به سیستم برای ورود پیام ها به یک جدول MessagesSent یا یک جدول MessagesReceived در پایگاه داده ورود به سیستم متمرکز ایجاد شد.

به عنوان مثال در ساخت میکروسرویس مدیریت Inventory ، محصولی را ایجاد می کند.

هنگامی که این معامله تجاری رخ می دهد ، پیامی به ProductUpdate ارسال می شود و این پیام به sales orderهدایت می شود.

با موفقیت پردازش شده توسط تمام سرویس های صف پیام ، سه ردیف به بانک اطلاعاتی ورود اضافه می شود.

یکی برای پیام ارسالی اصلی که در جدول MessagesSent وارد شده و دو ردیف در جدول MessagesReceived وارد می شوند.

هرکدام این ها برای پیامهای دریافت شده توسط صف sales order هستند .

سرویس Queue Messaging Queue تعداد صف های متصل به هر تبادل RabbitMQ را حفظ می کند.

این تعداد برای پیوند پیام ها استفاده می شود.

هنگامی که پیام ها پیوند داده شده اند ، سرویس ارسال پیام، پیام های ورود به سیستم و پیام های تصدیق را از طریق RabbitMQ ارسال می کند.

در این مورد مثال تولید محصول ، صف موجودی یک پیام تأییدیه دریافت می کند و سرویس ارسال پیام مدیریت پرسشنامه پیام و بایگانی ردیف های TransactionQueueOutbound را به یک جدول TransactionQueueOutboundHistory پردازش می کند.

علاوه بر این ، کلیه استثنائات خطای برنامه باید در یک بانک اطلاعاتی ورود به سیستم متمرکز وارد شود. متمرکز کردن پیام های خطا می تواند به همان روشی انجام شود که هر نوع پیام دیگر از طریق صف ارسال پیام RabbitMQ انجام می شود.

برخی از اطلاعات برای خطاهای ورود به سیستم عبارتند از:

  • تاریخ و زمان – توصیه می شود از تاریخ و ساعت UTC استفاده کنید ، مخصوصاً اگر سرورهای شما در مناطق مختلف در سناریوی cloudy در حال اجرا هستند.
  • Stack errors – می تواند objectاستثناء را به عنوان یک پارامتر برای ورود به سیستم و libraryمنتقل کند.
  • Microservice Name – این به شما کمک می کند تا مشخص شود که کدام logs مربوطه از کدام میکروسرویس است.
  • توابع ، نام کلاس و روش – عملکرد ، کلاس یا نام روش که خطا در آنجا رخ داده است ، بنابراین نیازی نیست حدس بزنید مشکل کجاست.
  • آدرس IP – آدرس IP سرور و درخواست مشتری. این اطلاعات باعث می شود تا شناسایی سرورهای ناسالم را آسان شود.
  • User-agent – برنامه کاربردی است به گونه ای که می فهمید مرورگرها یا کاربران با چه مشکلی روبرو هستند.

Contextualizing ورود به سیستم متمرکز باعث صرفه جویی در وقت شما در صورت نیاز به عیب یابی مشکلات در سیستم می شود

Microservices and Shared Libraries

در برنامه نمونه ، یک library مشترک ایجاد شده است که همه میکروسرویس ها به آن مراجعه می کنند.

library مشترک در برنامه نمونه شامل کلیه مدل ها و واسط های پردازش پیام ها است.

این libraryمشترک همچنین شامل توابع و روشهای کاربردی برای سریال سازی ، مدیریت نشانه ها ، هشدارها و دیگر عملکردهای زیرساخت عمومی است. library مشترک هیچگونه منطق کسب و کاری در اختیار ندارد.

همانطور که ما پیشرفت را از یکپارچگی به سمت یک معماری مبتنی بر میکروسرویس ها انجام سوق دادیم، موضوع )

library های مشترک در میکروسرویس ها همچنان مورد بحث است. یکی از اهداف اصلی خدمات میکروسرویس ایجاد خدمات کاملاً مشترک است که می تواند بطور مستقل از سایر میکروسرویس ها تغییر یابد.

ایجاد کتابخانه های “مشترک” خود ما بین این پروژه ها است.

به عنوان یک قانون اساسی ، نیازی به ایجاد منطق تجارت در یک کتابخانه مشترک نیست.

اگر این کار را انجام می دهید ، پس زمینه محدود دامنه های خدمات میکروسرویس شما به احتمال زیاد نادرست است ، و یا یک میکروسرویس را گم کرده اید.

توسعه دهندگان خدمات میکرو باید این واقعیت را بپذیرند که کپی کردن کد بین میکروسرویس ها در واقع خوب است. کپی کردن کد در یک میکروسرویس خاص اشکالی ندارد.

البته واقعیت این است که در یک معماری میکروسرویس نیاز به یک کتابخانه مشترک وجود خواهد داشت.

مانند کتابخانه مشترک در برنامه نمونه ، کد در کتابخانه های مشترک در یک معماری میکروسرویس معمولاً فقط باید شامل توابع و class هایی باشد که از زیرساخت های مشترک پشتیبانی می کنند.

چندین روش برای مدیریت کتابخانه های مشترک در یک معماری میکروسرویس وجود دارد.

در دنیای مایکروسافت ، کتابخانه های مشترک می توانند به صورت بسته های Nuget نسخه در سراسر خدمات میکروسرویس مستقر شوند و به میکروسرویس های مختلف امکان می دهند تا در صورت دلخواه جدیدترین نسخه از یک کتابخانه مشترک را پیاده سازی کنند.

مراقبت برای جلوگیری از ایجاد تغییر در کتابخانه مشترک گزینه دیگری است. استفاده از توابع اضافه بار می تواند به جلوگیری از ایجاد تغییر در یک کتابخانه مشترک کمک کند.

پیروی از اصول طراحی SOLID نیز می تواند کمک کند. SOLID یکی از محبوب ترین مجموعه های اصول طراحی در توسعه نرم افزار objectگرا است.

این یک اصطلاح اختصاری برای پنج اصل طراحی زیر است

  •  اصل مسئولیت منفرد
  • اصل باز / بسته
  • اصل جایگزینی لیسکوف
  • اصل تفکیک رابط
  •  وارونگی وابستگی

Installing the Sample Application

بین Angular 6 ، .NET Core 2.1 و RabbitMQ ، قسمت های متحرک زیادی برای نصب و پیکربندی وجود دارد تا بتوانید نمونه کار را اجرا و کنترل کنید. برنامه نمونه همچنین از نه پروژه ویژوال استودیو ۲۰۱۷ تشکیل شده است.

به همان اندازه که این فناوری های جدید جالب هستند ، می تواند یک کابوس درstreet Elm باشد که سعی در بروزرسانی به آخرین نسخه این فناوری ها و مقابله با تمام وابستگی های این فناوری ها از جمله برخورد با ناسازگاری های نسخه دارد.

اگر تا به حال به جدیدترین نسخه از نسخه های ویژوال استودیو ارتقا یافته اید ، احتمالاً می دانید که ارتقاء چقدر می تواند سخت باشد.

تا حدی که حتی از ارتقاء پشیمان می شوید. ماهیت شکننده ارتقاء ویژوال استودیو کار را سخت تر میکند.

در تلاش برای ساختن برنامه نمونه و اجرا در محیط توسعه محلی ، موارد زیر را در مورد پیش نیازها ذکر کردم و مراحل لازم را برای بلند شدن و اجرای آن نصب کردم.

پیش نیازهای نصب نرم افزار:

  • SQL Server Management Studio و SQL Server Express 2014 یا بیشتر
  • ویژوال استودیو ۲۰۱۷ حرفه ای یا جامعه نسخه
  • .NET هسته ۲.۱
  • RabbitMQ 3.7.9
  • NodeJS 10.13.0 یا بیشتر
  • Angular CLI 6

نصب سرور RabbitMQ – RabbitMQ به نسخه ۶۴ بیتی پشتیبانی شده ارلانگ برای نصب ویندوز نیاز دارد. یک نصب کننده Windows برای Erlang در http://www.erlang.org/downloads وجود دارد.

نکته مهم: نصب Erlang باید با استفاده از یک حساب کاربری اداری اجرا شود ، در غیر این صورت یک کلید رجیستری که توسط RabbitMQ انتظار می رود وجود نخواهد داشت.

پس از نصب ارلانگ ، برنامه نصب RabbitMQ rabbitmq-server-3.7.9.exe را بارگیری کنید ، در https://www.rabbitmq.com/install-windows.html قابل بارگیری است.

این برنامه RabbitMQ را به عنوان یک سرویس Windows نصب می کند و آن را با استفاده از پیکربندی پیش فرض شروع می کند.

نصب ابزار مدیریت RabbitMQ Web UI Management – برای نصب ابزار مدیریت RabbitMQ Web UI Management ، یک افزونه مدیریت باید به شرح زیر نصب شود:

پنجره فرمان Windows را در حالت سرپرست باز کنید

تغییر فهرست به C: \ File Files \ RabbitMQ Server \ rabbitmq_server-3.7.9 \ sbin

از خط فرمان اجرای: افزونه های rabbitmq را فعال کنید rabbitmq_management

به خدمات Windows بروید و سرویس RabbitMQ را مجدداً راه اندازی کنید.

به مرورگر بروید و آدرس اینترنتی را وارد کنید: http: // localhost: 15672 و ورود پیش فرض نام کاربر خواهد بود: مهمان ، رمز ورود: مهمان

بارگیری نمونه کد منبع برنامه کاربردی – کد منبع برنامه نمونه را می توانید از لینک دریافت کد منبع در بالای این مقاله بارگیری کنید.

بارگیری شامل دستورالعمل بارگیری می باشد.

پس از پیروی از دستورالعمل ها ، می توانید فایل کد پستی کد منبع را از مخزن GitHub من بارگیری کنید و همه پرونده ها را در پوشه مورد نظر خود استخراج کنید.

نمونه بانک اطلاعات برنامه ها – پس از بارگیری کد منبع ، SQL را به عنوان سرپرست اجرا کنید و بانکهای اطلاعاتی زیر را که در پوشه دیتابیس های بارگیری شده قرار دارد وصل کنید:

MS_AccountManagement_DEV

MS_InventoryManagement_DEV

MS_LoggingManagement_DEV

MS_PurchaseOrderManagement_DEV

MS_SalesOrderManagement_DEV

NET Core 2.1 – وقتی Visual Studio 2017 Professional یا Community Edition را بارگیری و نصب می کنید ، NET Core 2.1 باید بطور خودکار با ویژوال استودیو نصب شود.

اگر از قبل ویژوال استودیو ۲۰۱۷ دارید ، می توانید نصب خود را با رفتن به منوی Tools و انتخاب گزینه Tools and Properties تأیید کنید و این برنامه نصب ویژوال استودیو را شروع می کند. از گزینه های نصب می توانید تأیید کنید که .NET Core 2.1 نصب شده است.

ساخت و اجرای Sample Application Web API Projects – برای تأیید صحت نصب ، چهار پروژه API Webزیر را برای برنامه نمونه وارد کنید.

حتماً هنگام باز کردن و ساخت این پروژه ها با Visual Studio 2017 یک یا دو دقیقه صبر کنید زیرا Visual Studio هنگام باز شدن ، پروژه نیاز به بازگرداندن بسته های مورد نیاز برای تهیه این پروژه ها دارد.

InventoryManagement -> CodeProject.InventoryManagement.WebApi.sln

فروش مدیریت -> CodeProject.SalesOrderManagement.WebApi.sln

سفارش مدیریت -> CodeProject. خرید یا سفارش مدیریت مدیریت.WebApi.sln

حسابداری مدیریت -> CodeProject.AccountManagement.WebApi.sln

این پروژه های API Web برای استفاده از SSL تنظیم شده اند.

برای جلوگیری از مشکلات SSL ، باید با انتخاب پروفایل IISExpress و انتخاب دکمه run و ASP.NET Core ، پروژه را امتحان کنید و آن را اجرا کنید.

ویژوال استودیو از شما سؤال می کند که آیا مایلید به گواهی خود به امضایی که ASP.NET Core ایجاد کرده است اعتماد کنید.

برای اعتماد به گواهی ، بله را انتخاب کنید.

از آنجا که ویژوال استودیو است ، ممکن است مجبور شوید پروژه را برای بار دوم یا سوم اجرا کنید یا از ویژوال استودیو خارج شوید و مجدداً بارگیری کنید تا تأیید کنید که همه چیز با این پروژه کار می کند.

هنگام اجرای پروژه از Visual Studio ، مرورگر باید یک رشته اتصال دیتابیس را از طریق کنترلر Values یک رشته اتصال دیتابیس را در مرورگر راه اندازی و نمایش دهد.

Build All Projects با استفاده از .NET Core CLI – رابط خط فرمان .NET Core (CLI) یک زنجیره ابزار جدید متقاطع برای توسعه برنامه های دات نت است.

CLI بنیادی است که از آن می توان از ابزارهای سطح بالاتر مانند محیطهای توسعه پیشرفته IDE و ویرایشگرها استفاده کرد.

در برنامه نمونه نه پروژه NET. هسته وجود دارد که نیاز به ساخت شدن دارند.

پس از ساختن دستی پروژه های API Web، بقیه پروژه ها با استفاده از یک پرونده دسته ای DOS به نام _BuildAllProjects.bat که می توانید در پوشه پشتیبانی پیدا کنید ، ساخته شده اند این پرونده دسته ای DOS دستور ساخت .NET Core CLI را برای هر پروژه اجرا می کند:

dotnet build SpawnProcesses\SpawnProcesses
dotnet build ..\AccountManagement\CodeProject.AccountManagement.WebApi
dotnet build ..\InventoryManagement\CodeProject.InventoryManagement.MessageQueueing
dotnet build ..\InventoryManagement\CodeProject.InventoryManagement.WebApi
dotnet build ..\LoggingManagement\CodeProject.LoggingManagement.MessageQueueing
dotnet build ..\PurchaseOrderManagement\CodeProject.PurchaseOrderManagement.MessageQueueing
dotnet build ..\PurchaseOrderManagement\CodeProject.PurchaseOrderManagement.WebApi
dotnet build ..\SalesOrderManagement\CodeProject.SalesOrderManagement.MessageQueueing
dotnet build ..\SalesOrderManagement\CodeProject.SalesOrderManagement.WebApi

Angular CLI 6.0.8 – برنامه جلویی Angular 6 ساخته شده و از طریق Angular CLI استفاده می شود.

می توانید با اجرای دستور Angular CLI: ng نسخه نصب Angular CLI خود را تأیید کنید. اگر Angular CLI نصب نشده است می توانید با تایپ npm install -g @ angular / cli @ 6.0.8 آن را از پنجره فرمان نصب کنید.

ساخت برنامه جلویی Angular 6 – ، برنامه جلویی Angular 6 به ماژول های گره بستگی دارد که باید در پوشه node_modules پروژه نصب شوند.

ایجاد همه ماژول های گره با مراجعه به پوشه Portal و باز کردن فایل راه حل پروژه پروژه ویژوال استودیو ۲۰۱۷ CodeProject.Portal.sln انجام می شود.

پس از باز کردن پروژه ، بر روی فایل pack.json کلیک راست کرده و Restore Packages را انتخاب کنید.

اگر پرونده ای برای بستن قفل وجود دارد ، آن را حذف کنید ، در غیر این صورت بسته ها بازیابی نخواهند شد.

پس از نصب بسته ها ، می توانید پروژه Angular 6 را با استفاده از Angular 6 CLI در یک پنجره فرمان DOS بسازید و به پوشه Portal -> CodeProject.Portal بروید و اجرای آن را انجام دهید: ng build

Running the Sample Application Back-End Services and Front-End Portal

اجرای Sample Application Back-End Services و Front-End Portal

هنگامی که همه چیز را ساخته اید ، می توانید برنامه نمونه را اجرا کنید.

ابتدا با اجرای پرونده DOS دسته ای _StartAllDevelopmentWebServersAndQueues.bat از پوشه پشتیبانی ، تمام برنامه های پشتیبان وب API و سرویس های ارسال پیام را راه اندازی کنید.

این پرونده یک برنامه داخلی دات نت ساخته شده به نام SpawnProcesses را اجرا می کند که تمام مراحل بازگشت را مرحله به مرحله برای برنامه نمونه شروع می کند.

برنامه SpawnProcesses فرآیند جدیدی را برای هر یک از هشت برنامه پشتیبان آغاز خواهد کرد. با تنظیم خاصیت CreateNoWindow به اشتباه برای هر فرآیند ، هر فرآیند را مجبور به اجرا در همان پنجره DOS می کند. این خوب است زیرا با باز شدن هشت ویندوز جداگانه ، محیط شما را آلوده نمی کند.

اگر نیاز به اجرای یک یا چند سرویس پشتیبان از ویژوال استودیو دارید ، می توانید یک ویژگی یا واقعی را در پرونده appsettings.development.json در پروژه SpawnProcesses تنظیم کنید تا به برنامه بگویید که مراحل خاص باید انجام شود.

اگر تنظیماتی را تغییر داهید ، کافیست پروژه SpawnProcesses را مجدداً بازسازی کنید تا تنظیمات به پوشه بن اضافه شوند

if (startUpProcesses.InventoryManagementWebApi == true)
{
        Console.WriteLine("Starting Inventory Management Web Api");

        Process process1 = new Process();
        process1.StartInfo.CreateNoWindow = false;
        process1.StartInfo.UseShellExecute = false;
        process1.StartInfo.RedirectStandardOutput = false;
        process1.StartInfo.FileName = runningPath + @"Support\StartInventoryManagementWebApi.bat";
        process1.StartInfo.Arguments = runningPath;
        process1.Start();

}

if (startUpProcesses.SalesOrderManagementWebApi == true)
{
        Console.WriteLine("Starting Sales Order Management Web Api");
        Process process2 = new Process();
        process2.StartInfo.CreateNoWindow = false;
        process2.StartInfo.UseShellExecute = false;
        process2.StartInfo.RedirectStandardOutput = false;
        process2.StartInfo.FileName = runningPath + @"Support\StartSalesOrderManagementWebApi.bat";
        process2.StartInfo.Arguments = runningPath;
        process2.Start();

}

هر فرآیند برای راه اندازی هر برنامه با یک پرونده دسته ای DOS تماس می گیرد که دستور اجرای NET Core CLI را اجرا می کند. قطعه کد زیر برنامه Web API Management Inventory Management را راه اندازی می کند.

dotnet run --verbosity m --launch-profile CodeProject.InventoryManagement.WebApi --no-build 

اکنون با تمام خدمات پشتیبان گیری و اجرا ، اکنون می توانیم برنامه کاربردی Angular 6 وب جلوی برنامه نمونه را ارائه دهیم.

از پنجره فرمان DOS ، به پوشه Portal -> CodeProject.Portal بروید و فرمان Angular CLI را اجرا کنید: ng service را اجرا کنید. با این کار سرور وب Node.js Express در localhost شروع می شود: ۴۲۰۰. برای دسترسی به برنامه Microsoftervice Portal ، در مرورگر خود به http: // localhost: 4200 بروید

Angular 6 Web Application – Points of Interest

برای برنامه نمونه خیلی وارد قسمت  جلوی Angular 6 نشدیم ، اما در زیر لیستی مختصر از نقاط مورد علاقه شما وجود دارد که ممکن است بخواهید کشف کنید:

  • ماژول های مدیریت موجودی ، مدیریت سفارش و فروش و مدیریت سفارشات خرید به صورت ماژول های جداگانه بارگیری می شوند.
    برنامه Angular 6 از اجزای طراحی Material Angular استفاده می کند که ظاهری تازه و متفاوت را نسبت به قالب و بوت استرپ سنتی ارائه می دهد.
  • ماژول Angular 6  به نام auth0 / angular-jwt پیاده سازی شد و از JWT Helper Service ماژول برای استخراج اطلاعات مربوط به مشتری از سمت وب سایت JSON کاربر استفاده می کند ، از جمله استخراج تاریخ انقضا Token که برای خروج خودکار کاربر هنگام استفاده از آن استفاده می شود.
  • برای اطلاعات بیشتر در مورد Angular 6 و .NET Core 2.1 ، مقاله Code Project من را با استفاده از یک برنامه Angular 6 با ASP.NET Core 2 بررسی کنید

خلاصه:

در فیلم کوبریک ۲۰۰۱: یک ادیسه فضایی ، یک شخصیت داستانی به نام رایانه HAL 9000 وجود داشت.

HAL در ابتدا یک عضو قابل اعتماد خدمه محسوب می شود ، عملکردهای کشتی را حفظ کرده و با همرزمان خدمه انسانی خود به طور مساوی درگیر می شود.

در فیلم ، هوش مصنوعی HAL به راحتی پیروز می شود.

با این حال ، با پیشرفت زمان ، HAL به روش های ظریف شروع به نقص می کند و در نتیجه تصمیم می گیرد HAL را خاموش کنیم تا از بروز نقص های جدی تر جلوگیری شود.

معماری میکروسرویس مهیج است و مانند HAL ، به نظر می رسد قابل اعتماد و جذاب باشد. اما به همان اندازه که خدمات میکروسرویس ، بدون چالش ها قابلیت اجرا ندارند، خدمات میکروسرویس باید به خوبی برنامه ریزی ، توسعه و مدیریت شوند.

ارتباطات بین فرآیند باید تسهیل شود ، داده ها باید به اشتراک گذاشته شوند و یا تکراری نشوند و برای ردیابی رفتارهای غیرطبیعی و خرابی های احتمالی ، باید تمام قسمت های اکوسیستم میکروسرویس نظارت شود.

  • پسورد: www.mspsoft.com
برچسب‌ها:
زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.

کدیشن ! مارکت پروژه های برنامه نویسی راه اندازی شدیه توکه پا بریم ببینم