RabbitMQ的简单使用
RabbitMQ的关键对象概念介绍
生产者(producer):负责生产消息,可以有多个生产者,可以理解为生成消息的那部分逻辑
消费者(consumer):从队列中获取消息,对消息处理的那部分逻辑
队列(queue):用于存放消息,可以理解为先进先出的一个对象
交换机(exchange):顾名思义,就是个中介的角色,将接收到的消息按不同的规则转发到其他交换机或者队列中
路由(route):就是交换机分发消息的规则,交换机可以指定路由规则,生产者在发布消息时也可以指定消息路由,比如交换机中设置A路由表示将消息转发到队列1,B路由表示将消息转发到队列2,那么当交换机接收到消息时,如果消息的路由满足A路由,则将消息转发到队列1,如果满足B路由则将消息转发到队列2
虚拟主机(virtual host):虚拟地址,用于进行逻辑隔离,一个虚拟主机里面可以有若干个 exchange 和 queue,但是里面不能有相同名称的 exchange 或 queue
RabbitMQ的工作模式
简单的来说,就是生产者将消息发布到rabbitmq上,然后消费者连接rabbitmq,获取到消息就消费,但是有几点说明一下
1、rabbitmq中的消息是可被多次消费的,因为rabbitmq提供了ack机制,当消费者在消费消息时,如果将自动ack设置成false,那么需要手动提交ack才能告诉rabbitmq消息已被使用,否则当通道关闭时,消息会继续呆在队列中等待消费
2、当存在多个消费者时,默认情况下,一个消费者获取一个消息,处理完成后再获取下一个,但是rabbitmq消费一次性获取多个,当然后当这些消息消费完成后,再获取下一批,这也就是rabbitmq的Qos机制
Qos机制:当生产者将消息发布到rabbitmq之后,如果在未配置QOS的情况下,rabbitmq尽可能快速地发送队列中的所有消息到消费者端,如果消息比较多,消费者来不及处理,就会缓存这些消息,当消息堆积过多,可能导致服务器内存不足而影响其他进程,rabbitmq的QOS可以很好的解决这类问题,QOS就是限制消费者一次性从rabbitmq中获取消息的个数,而不是获取所有消息。比如设置rabbitmq的QOS为10,也就是prefetch=10,就是说,哪怕rabbitmq中有100条消息,消费者也只是一次性获取10条,然后消费者消费这10条消息,剩下的交给其他消费者,当10条消息中的unacked个数少于prefetch * 消费者数目时,会继续从rabbitmq获取消息,如果在工作模式中,不使用QOS,你会发现,所有的消息都被一个消费者消费了
C#中使用RabbitMQ
首先,我们创建了两个Demo项目:RabbitMQReceived(消费者)和RabbitMQSend(生产者),分别使用使用nuget安装RabbitMQ.Client:
RabbitMQSend Program脚本
如下:
class Program { static void Main(string[] args) { //string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" }; string hosts = "localhost"; int port = 5672; string userName = "guest"; string password = "guest"; string virtualHost = "/"; //创建一个连接工厂 var factory = new ConnectionFactory(); factory.UserName = userName; factory.Password = password; factory.Port = port; factory.VirtualHost = virtualHost; //创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成 //一个连接可以创建多个通道 var connection = factory.CreateConnection(hosts); #region 普通模式、工作模式 //string queue = "queue1";//队列名称 ////1、创建一个通道 ////此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 //var channel = connection.CreateModel(); ////给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错 //var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; ////2、声明(创建)对列 //// 第一个参数,queueName:对列名称。数据类型:String //// 第二个参数,durable:是否持久化, 队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库。数据类型:boolean //// 第三个参数,exclusive:是否排外的。数据类型:boolean //// 第四个参数,autoDelete:是否自动删除。数据类型:boolean //// 第五个参数,arguments:参数。数据类型:Map<String, Object> ////channel.queueDeclare(queueName, true, false, false, null); //channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); #endregion //3、创建交换机 // 第一个参数,exchange:交换机名称。数据类型:String // 第二个参数,type:交换机的类型(direct/topic/fanout)。数据类型:String //channel.exchangeDeclare(exchange, type); //4、绑定交换机和队列 //第一个参数,queueName:对列名称。数据类型:String //第二个参数,exchange:交换机名称。数据类型:String //第三个参数,routingKey:队列跟交换机绑定的键值。数据类型:String //channel.queueBind(queueName, exchange, routingKey); #region 订阅模式 ////创建一个通道 ////此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 //var channel = connection.CreateModel(); //string exchange = "demo.fanout";//交换机名称 //string exchangeType = "fanout";//交换机类型 ////给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列 //var arguments = new Dictionary<string, object>() { }; //channel.QueueDeclare(queue: "queue2", durable: true, exclusive: false, autoDelete: false, arguments: arguments); //channel.ExchangeDeclare(exchange: exchange, type: exchangeType, durable: true, autoDelete: false, arguments: arguments); //channel.QueueBind("queue1", exchange, ""); //channel.QueueBind("queue2", exchange, ""); #endregion #region 路由模式 ////创建一个通道 ////此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 //var channel = connection.CreateModel(); //string exchange_dir = "demo.direct";//交换机名称 //string exchangeType_dir = "direct";//交换机类型 ////给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列 //var arguments_dir = new Dictionary<string, object>() { }; //channel.QueueDeclare(queue: "queue3", durable: true, exclusive: false, autoDelete: false, arguments: arguments_dir); //channel.QueueDeclare(queue: "queue4", durable: true, exclusive: false, autoDelete: false, arguments: arguments_dir); //channel.ExchangeDeclare(exchange: exchange_dir, type: exchangeType_dir, durable: true, autoDelete: false, arguments: arguments_dir); //string[] routes = new string[] { "apple", "banana" }; //channel.QueueBind("queue3", exchange_dir, routes[0]); //channel.QueueBind("queue4", exchange_dir, routes[1]); #endregion #region 主题模式 //创建一个通道 //此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 var channel = connection.CreateModel(); string exchange_top = "demo.topic";//交换机名称 string exchangeType_top = "topic";//交换机类型 //给通道绑定一个交换机,交换机如果不存在,则会创建新交换机,如果交换机已存在,那么参数一定要正确,特别是arguments参数,各参数类似队列 var arguments_dir = new Dictionary<string, object>() { }; channel.QueueDeclare(queue: "queue5", durable: true, exclusive: false, autoDelete: false, arguments: arguments_dir); channel.QueueDeclare(queue: "queue6", durable: true, exclusive: false, autoDelete: false, arguments: arguments_dir); channel.ExchangeDeclare(exchange: exchange_top, type: exchangeType_top, durable: true, autoDelete: false, arguments: arguments_dir); string[] routes = new string[] { "apple.", "banana." }; channel.QueueBind("queue5", exchange_top, routes[0]); channel.QueueBind("queue6", exchange_top, routes[1]); #endregion //发布10条消息 for (var i = 0; i < 10; i++) { var buffer = Encoding.UTF8.GetBytes("is my send msg " + i.ToString()); //普通模式、工作模式 //channel.BasicPublish("", queue, null, buffer); //订阅模式 //channel.BasicPublish(exchange, "", null, buffer); //路由模式 //channel.BasicPublish(exchange_dir, routes[i % 2], null, buffer); //主题模式 channel.BasicPublish(exchange_top, routes[i % 2], null, buffer); } channel.Close(); Console.ReadKey(); } }
简要说明:
1、分别包含普通模式、工作模式非使用交换机情景,另外使用交换机情景包含订阅模式、路由模式和主题模式,创建通道、队列、绑定通道队列,发送消息等等;
direct:路由模式,就是将消息的路由和交换机的绑定路由作比较,当两者一致时,则匹配成功,然后消息就会被转发到这个绑定路由后的队列或者交换机
fanout:订阅模式,这种类型的交换机是不需要指定路由的,当交换机接收到消息时,会将消息广播到所有绑定到它的所有队列或交换机中
topic:主题类型,类似direct类型,只不过在将消息的路由和绑定路由做比较时,是通过特定表达式去比较的,其中# 匹配一个或多个,* 匹配一个
2、普通模式、工作模式、订阅模式、路由模式和主题模式都带有说明,包括发布消息,每次只需要释放其中一块注释代码即可,当前脚本目前释放是主题模式;
3、普通模式和工作模式使用的queue1(10条),订阅模式queue1和queue2(各10条),路由模式queue3/key为apple和queue4/key为banana(各5条),主题模式queue5/key为apple.和queue6/key为banana.(各5条);
RabbitMQRecevied Program脚本
无论是使用交换机场景还是不使用交换机场景各种模式就是发布消息方式的不一样,消费者当然还是从队列获取消息消费的如下:
class Program { static void Main(string[] args) { //string[] hosts = new string[] { "192.168.209.133", "192.168.209.134", "192.168.209.135" }; string hosts = "localhost"; int port = 5672; string userName = "guest"; string password = "guest"; string virtualHost = "/"; //创建一个连接工厂 var factory = new ConnectionFactory(); factory.UserName = userName; factory.Password = password; factory.Port = port; factory.VirtualHost = virtualHost; //创建一个连接,此时可以在rabbitmq后台Web管理页面中的Connections中看到一个连接生成 //一个连接可以创建多个通道 var connection = factory.CreateConnection(hosts); #region 单消费者 //string queue = "queue1";//队列名称 ////创建一个通道 ////此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 //var channel = connection.CreateModel(); ////给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错 //var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; //channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); ////channel.BasicQos(2, 2, false);//设置QOS ////在通道中定义一个事件消费者 //EventingBasicConsumer consumer = new EventingBasicConsumer(channel); //consumer.Received += (sender, e) => //{ // var body = e.Body.ToArray(); // string message = Encoding.UTF8.GetString(body); // Console.WriteLine($"接收到消息:{message}"); // Thread.Sleep(500);//暂停一下 // //通知消息已被处理,如果没有,那么消息将会被重复消费 // channel.BasicAck(e.DeliveryTag, false); //}; ////ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息 //channel.BasicConsume(queue, false, consumer); #endregion #region 多消费者 //Consumer(connection, 1); //Consumer(connection, 2); #endregion #region 订阅模式 //Consumer(connection, "queue1");//消费者1 //Consumer(connection, "queue2");//消费者2 #endregion #region 路由模式 //Consumer(connection, "queue3");//消费者1 //Consumer(connection, "queue4");//消费者2 #endregion #region 主题模式 Consumer(connection, "queue5");//消费者1 Consumer(connection, "queue6");//消费者2 #endregion Console.ReadKey(); } static void Consumer(IConnection connection, ushort perfetch) { new Thread(() => { int threadId = Thread.CurrentThread.ManagedThreadId;//线程Id,用于区分消费者 string queue = "queue1";//队列名称 //创建一个通道 //此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 var channel = connection.CreateModel(); //给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错 var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); //设置消费者每次获取的消息数量,可以用来设置消费者消费的权重 //必须等获[取的消息都消费完成后才能重新获取 channel.BasicQos(0, perfetch, true);//设置QOS //在通道中定义一个事件消费者 EventingBasicConsumer consumer = new EventingBasicConsumer(channel); consumer.Received += (sender, e) => { var body = e.Body.ToArray(); string message = Encoding.UTF8.GetString(body); Console.WriteLine($"ThreadId:【{threadId}】——>接收到消息:{message}"); Thread.Sleep(500);//暂停一下 //通知消息已被处理,如果没有,那么消息将会被重复消费 channel.BasicAck(e.DeliveryTag, false); }; //ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息 channel.BasicConsume(queue, false, consumer); }).Start(); } static void Consumer(IConnection connection, string queue) { new Thread(() => { int threadId = Thread.CurrentThread.ManagedThreadId;//线程Id,用于区分消费者 //创建一个通道 //此时可以在rabbitmq后台Web管理页面中的Channels中看到一个新通道生成 var channel = connection.CreateModel(); //给通道绑定一个队列,队列如果不存在,则会创建新队列,如果队列已存在,那么参数一定要正确,特别是arguments参数,否则会报错 var arguments = new Dictionary<string, object>() { { "x-queue-type", "classic" } }; channel.QueueDeclare(queue: queue, durable: true, exclusive: false, autoDelete: false, arguments: arguments); //设置消费者每次获取的消息数量,可以用来设置消费者消费的权重 //必须等获[取的消息都消费完成后才能重新获取 //channel.BasicQos(0, perfetch, true);//设置QOS //在通道中定义一个事件消费者 EventingBasicConsumer consumer = new EventingBasicConsumer(channel); consumer.Received += (sender, e) => { var body = e.Body.ToArray(); string message = Encoding.UTF8.GetString(body); Console.WriteLine($"ThreadId:【{threadId}】——>接收到消息:{message}"); Thread.Sleep(500);//暂停一下 //通知消息已被处理,如果没有,那么消息将会被重复消费 channel.BasicAck(e.DeliveryTag, false); }; //ack设置成false,表示不自动提交,那么就需要在消息被消费后,手动调用BasicAck去提交消息 channel.BasicConsume(queue, false, consumer); }).Start(); } }
其中也是包含了各种模式的消费队列,每次只需要释放一种类型脚本即可,当前脚本目前释放是主题模式
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)