头图

This article was first published on network - " .NET 5/.NET Core application using the message queue middleware RabbitMQ sample tutorial "

Preface

In today's Internet era, message queue middleware has become an important component in distributed systems. It can solve application scenarios such as application coupling, asynchronous messaging, and traffic peaking. With the help of message queues, we can achieve the high availability, high performance, and scalability of the architecture. It is an indispensable middleware in the architecture of large distributed systems.

Currently the more popular message queue middleware mainly include RabbitMQ, NATS, Kafka, ZeroMQ, Amazon SQS, ServiceStack, Apache Pulsar, RocketMQ, ActiveMQ, IBM MQ and so on.

This article mainly shares for you a sample tutorial of using the message middleware RabbitMQ in a .NET 5 application.

Ready to work

Before starting the actual combat in this article, please prepare the following environment:

  • Message middleware: RabbitMQ
  • Development tools: Visual Studio 2019 or VS Code or Rider

The development tool I used is Rider 2021.1.2 .

Prepare solutions and projects

Create project

Open Rider, create a RabbitDemo , and then create three projects based on .NET 5, namely: RabbitDemo.Shared , RabbitDemo.Send and RabbitDemo.Receive .

  • The RabbitDemo.Shared project is mainly used to store the connection-related classes of the shared RabbitMQ;
  • The RabbitDemo.Send project is mainly used to simulate producers (publishers);
  • The RabbitDemo.Receive project is mainly used to simulate consumers (subscribers)

Install dependencies

First, use the package management tool or the command line tool to install the RabbitMQ.Client dependency package in the three projects created above, as follows:

Write RabbitDemo.Shared project

RabbitDemo.Shared The project is mainly used to store the connection-related classes of the shared RabbitMQ. Here we create a RabbitChannel class, and then add in it to create some connection methods related to RabbitMQ, including initializing the RabbitMQ connection, closing the RabbitMQ connection, etc. The code is as follows:

using RabbitMQ.Client;

namespace RabbitDemo.Shared
{
    public class RabbitChannel
    {
        public static IModel Channel;
        private static IConnection _connection;
        public static IConnection Connection => _connection;

        public static void Init()
        {
            _connection = new ConnectionFactory
            {
                HostName = "xxxxxx",     // 你的RabbitMQ主机地址
                UserName = "xxx",        // RabbitMQ用户名
                VirtualHost = "xxx",    // RabbitMQ虚拟主机    
                Password = "xxxxxx"        // RabbitMQ密码
            }.CreateConnection();

            Channel = _connection.CreateModel();
        }

        public static void CloseConnection()
        {
            if (Channel != null)
            {
                Channel.Close();
                Channel.Dispose();
            }

            if (_connection != null)
            {
                _connection.Close();
                _connection.Dispose();
            }
        }
    }
}

Write a message producer

In the project RabbitDemo.Send , reference the project RabbitDemo.Shared , and then create a Send.cs , and write the producer's code in it, as follows:

using System;
using System.Text;
using System.Threading;
using RabbitDemo.Shared;
using RabbitMQ.Client;

namespace RabbitDemo.Send
{
    public class Send
    {
        public static void Run()
        {
            for (var i = 0; i < 5; i++)
            {
                Publish(i);
                Thread.Sleep(500);
            }
        }

        private static void Publish(int index)
        {
            RabbitChannel.Channel.QueueDeclare(queue: "hello",
                durable: false,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            var message = $"Hello World from sender({index})!";
            var body = Encoding.UTF8.GetBytes(message);

            RabbitChannel.Channel.BasicPublish(exchange: "",
                routingKey: "hello",
                basicProperties: null,
                body: body);
            Console.WriteLine(" [x] Sent {0}", message);
        }
    }
}

The above example simulates that a producer produces 5 messages at a time and stores the messages in the message queue of RabbitMQ.

Modify the Program.cs code in this project as follows:

using System;
using RabbitDemo.Shared;

namespace RabbitDemo.Send
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("按任意键退出.");
            RabbitChannel.Init();
            Send.Run();
            Console.ReadKey();
            Console.WriteLine("正在关闭连接...");
            RabbitChannel.CloseConnection();
            Console.WriteLine("连接已关闭,退出程序.");
        }
    }
}

Write a message consumer

In the project RabbitDemo.Receive , reference the project RabbitDemo.Shared , and then create a Receive.cs , and write the consumer code in it, as follows:

using System;
using System.Linq;
using System.Text;
using RabbitDemo.Shared;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace RabbitDemo.Receive
{
    public class Receive
    {
        public static void Run()
        {
            RabbitChannel.Channel.QueueDeclare(queue: "hello",
                durable: false,
                exclusive: false,
                autoDelete: false,
                arguments: null);

            var consumer = new EventingBasicConsumer(RabbitChannel.Channel);
            consumer.Received += (model, ea) =>
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine(" [x] Received {0}", message);
                RabbitChannel.Channel.BasicAck(deliveryTag:ea.DeliveryTag,multiple:false);
            };
            RabbitChannel.Channel.BasicConsume(queue: "hello",
                autoAck: false,
                consumer: consumer);
        }
    }
}

Modify the Program.cs code in this project as follows:

using System;
using RabbitDemo.Shared;

namespace RabbitDemo.Receive
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("按任意键退出.");
            RabbitChannel.Init();
            Receive.Run();
            Console.ReadKey();
            Console.WriteLine("正在关闭连接...");
            RabbitChannel.CloseConnection();
            Console.WriteLine("连接已关闭,退出程序.");
        }
    }
}

run

Generate and run the producer and consumer projects respectively, and the running effects are as follows:

As can be seen from the above figure, during the whole demonstration process, RabbitMQ messages are very instant, and consumers can consume messages produced by producers almost in real time.

Message queue that needs to be confirmed

In the above producer/consumer example, RabbitMQ will immediately remove the message from the queue once the message is consumed by the consumer. But in some scenarios, we need consumers to confirm that the message is correctly consumed before removing it from the queue. RabbitMQ provides the function of consumption confirmation. Let's use an example to demonstrate.

First create Worker.cs RabbitDemo.Send , and write the following code:

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
using RabbitDemo.Shared;
using RabbitMQ.Client;

namespace RabbitDemo.Send
{
    public class Worker
    {
        public static void Run()
        {
            for (var i = 0; i < 5; i++)
            {
                Thread.Sleep(1000);
                Publish(i);
            }
        }

        private static void Publish(int index)
        {
            var args = new Dictionary<string, object>
            {
                {"x-max-priority", 0}
            };
            RabbitChannel.Channel.QueueDeclare(queue: "task_queue",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: args);

            var message = $"Hello({index}) at {DateTime.Now.ToString(CultureInfo.InvariantCulture)}";
            var body = Encoding.UTF8.GetBytes(message);

            var properties = RabbitChannel.Channel.CreateBasicProperties();
            properties.Persistent = true;
            properties.Headers = new Dictionary<string, object>
            {
                {"order-no", $"1001{index}"}
            };
            RabbitChannel.Channel.BasicPublish(exchange: "",
                routingKey: "task_queue",
                basicProperties: properties,
                body: body);
            Console.WriteLine(" [x] Sent {0}", message);
        }
    }
}

This example simulated life and the five messages, and store the message to RabbitMQ of task_queue queue, which we also adopted QueueDeclare() method arguments set parameters queue priorities, but also by basicProperties adding a header (Header custom parameter ) Parameter order-no .

Next, create a Task.cs in the project RabbitDemo.Receive project, and write the following consumer code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using RabbitDemo.Shared;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;

namespace RabbitDemo.Receive
{
    public class Task
    {
        public static void Run()
        {
            Console.WriteLine(" [*] Waiting for messages.");
            Consume();
        }

        private static void Consume()
        {
            var args = new Dictionary<string, object>
            {
                {"x-max-priority", 0}
            };
            RabbitChannel.Channel.QueueDeclare(queue: "task_queue",
                durable: true,
                exclusive: false,
                autoDelete: false,
                arguments: args);
            RabbitChannel.Channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
            var consumer = new EventingBasicConsumer(RabbitChannel.Channel);
            consumer.Received += (sender, ea) =>
            {
                var body = ea.Body.ToArray();
                var message = Encoding.UTF8.GetString(body);
                Console.WriteLine(" [x] Received {0}", message);
                var messageBuilder = new StringBuilder();
                foreach (var headerKey in ea.BasicProperties.Headers.Keys)
                {
                    var value = ea.BasicProperties.Headers[headerKey] as byte[];
                    messageBuilder.Append("Header key: ")
                        .Append(headerKey)
                        .Append(", value: ")
                        .Append(Encoding.UTF8.GetString(value))
                        .Append("; ");
                }
                Console.WriteLine($"Customer properties:{messageBuilder.ToString()}");
                var sleep = 6;
                Thread.Sleep(sleep * 1000);
                Console.WriteLine(" [x] Done");
                ((EventingBasicConsumer)sender)?.Model.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
            };
            RabbitChannel.Channel.BasicConsume(queue: "task_queue",
                autoAck: false,
                consumer: consumer);
        }
    }
}

In this consumer code, our main concern is how to confirm the correct consumption of the message to RabbitMQ. Compared with the Receive.cs consumer above, this example sets

RabbitChannel.Channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);
The setting here means: a consumer can only pull 1 message at a time

with

((EventingBasicConsumer)sender)?.Model.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
This sentence indicates that the message has been confirmed to be consumed normally.

Modify Program.cs producer:

using System;
using RabbitDemo.Shared;

namespace RabbitDemo.Send
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("按任意键退出.");
            RabbitChannel.Init();
            Worker.Run();
            Console.ReadKey();
            Console.WriteLine("正在关闭连接...");
            RabbitChannel.CloseConnection();
            Console.WriteLine("连接已关闭,退出程序.");
        }
    }
}

Modify the consumed Program.cs :

using System;
using RabbitDemo.Shared;

namespace RabbitDemo.Receive
{
    static class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("按任意键退出.");
            RabbitChannel.Init();
            Task.Run();
            Console.ReadKey();
            Console.WriteLine("正在关闭连接...");
            RabbitChannel.CloseConnection();
            Console.WriteLine("连接已关闭,退出程序.");
        }
    }
}

The effect is as follows:


RECTOR
666 声望173 粉丝

计算机科学与技术专业,全栈工程师,码友网创建者、维护者,千星开源项目(DncZeus)项目开发者,专注.NET/.NET Core及相关开发。