RabbitMQ有五种模式:
a、简单模式:消息生产者将消息放入队列,消息的消费者监听消息队列;消息可能没有被消费者正确处理,就已经从队列中消失了,造成消息的丢失;一个消息生产者对应一个消费者;应用场景:聊天
b、工作队列模式:一个消息生产者对应多个消费者,存在资源竞争;高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(syncronize,与同步锁的性能不一样)保证一条消息只能被一个消费者使用;应用场景:抢红包
c、publish/subscribe发布订阅:消息产生者将消息放入交换机,交换机发布订阅把消息发送到所有消息队列中,对应消息队列的消费者拿到消息进行消费,可以共享资源;应用场景:邮件群发、群聊天、广播
d、路由模式:消息生产者将消息发送给交换机按照路由判断,路由是字符串,当前产生的消息携带路由字符,交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;应用场景:错误通知、客户通知
e、topic主题模式(路由模式的一种):星号*井号#代表通配符,*匹配不多不少恰好1个词,#匹配一个或多个词;相当于在路由模式上添加了模糊匹配
本次主要学习工作队列模式,本次案列假定你已经在本地或服务器安装了RabbitMQ服务端(安装方法可自行百度)
1、新建一个控制台应用(.NET Framework),用的框架是:.NET Framework 4.5.1,然后在NuGet程序包中下载RabbitMQ.Client,本案列用的版本是5.2.0,并在项目中引入程序集
2、创建生产者
using System.Text;
using RabbitMQ.Client;
namespace OcvProducer
{
class Program
{
private static readonly string queueName = "OCV_TEST_QUEUE1";
static void Main(string[] args)
{
Producer();
}
public static void Producer()
{
//实列化连接工厂
var factory = new ConnectionFactory();
//主机IP
factory.HostName = "localhost";
//端口,5672是安装后默认的端口
factory.Port = 5672;
//登录用户名
factory.UserName = "123abc";
//登录密码
factory.Password = "123456";
//虚拟主机,默认没有
factory.VirtualHost = "/";
//调用工厂建立连接
using (var connection = factory.CreateConnection())
{
//创建通道
using (var channel = connection.CreateModel())
{
/*
声明队列
QueueDeclare的各参数涵义:
1、第一个参数queue:队列名
2、第二个参数durable:队列是否持久化
队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失队列(即便队列中有数据),如果想重启之后还存在就要使队列持久化(设置durable为true),保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库
3、第三个参数exclusive:是否排外
有两个作用:
a、当连接关闭时,该队列会自动删除;列如:如果一个排外队列由生产者声明, 如果没有消费者或者没有消费完,在队列中存在数据的情况下,生产者断开连接的话,队列中的数据和队列依旧会删除,会丢数据
b、exclusive参数声明为true的排他队列是基于连接(Connection)可见的,是该Connection私有的,同个连接Connection的不同管道Channel是可以访问同一个排他队列的;会对当前队列加锁,非同一个Connection的通道channel是不能访问的;一般来说 ,exclusive等于true的话用于一个队列只能有一个消费者来消费的场景
4、第四个参数autoDelete:当最后一个消费者断开连接之后队列是否自动被删除
特殊情况:如果生产者声明了一个queue,此队列从来没有消费者连接过,那么即使consumers = 0,队列也不会自动删除的
5、第五个参数arguments:常用的有x-message-ttl:设置队列中消息的过期时间;x-expires:设置队列的过期时间;x-max-length:设置队列最多可以容纳的消息数量,超出部分从头部开始删除等等
*/
channel.QueueDeclare(queueName, true, false, false, null);
//循环发送
for (int i = 0; i < 100; i++)
{
string data = i.ToString();
var body = Encoding.UTF8.GetBytes(data);
//消息持久化
var props = channel.CreateBasicProperties();
props.Persistent = true;
//发布消息,工作队列模式不需要交换机exchange
channel.BasicPublish("", queueName, props, body);
}
}
}
}
}
}
RabbitMQ的持久化分为两个步骤:
a、队列持久化:channel.queueDeclare的durable的值设为true;如果只设置该值,那么重启rabbitmq时队列将保存而消息消失;
b、消息持久化:在发送消息的时候配置;如果只设置消息持久化,那么重启rabbitmq时队列和消息都会消失,只有同时设置了队列持久化和消息持久化,消息才会保存。就算是这样消息也不是100%持久化成功的。
2、创建消费者
using System;
using System.Linq;
using System.Text;
using System.Threading;
using RabbitMQ.Client;
using RabbitMQ.Client.Events;
namespace OcvConsumer
{
class Program
{
private static string queueName = "OCV_TEST_QUEUE1";
static void Main(string[] args)
{
//使用多线程开启多个消费者
Thread thread1 = new Thread(() => Consumer(1));
Thread thread2 = new Thread(() => Consumer(2));
thread1.Start();
thread2.Start();
}
public static void Consumer(int no)
{
//实列化连接工厂
var factory = new ConnectionFactory();
//主机IP
factory.HostName = "localhost";
//端口,5672是安装后默认的端口
factory.Port = 5672;
//登录用户名
factory.UserName = "123abc";
//登录密码
factory.Password = "123456";
//虚拟主机,默认没有
factory.VirtualHost = "/";
//调用工厂建立连接
using (var connection = factory.CreateConnection())
{
//创建通道
using (var channel = connection.CreateModel())
{
/*
声明队列
注意:
1、消费者和生产者的队列名需要一致
2、如果生产者声明了队列持久化(第二个参数durable=true),那么消费者也要声明队列持久化,不然会异常
*/
channel.QueueDeclare(queueName, true, false, false, null);
/*
Qos--不公平分发和预取值(Quality of Service,服务质量)
消息应答和 QoS 预取值对用户吞吐量有重大影响;通常,增加预取值将提高向消费者传递消息的速度,但是,在这种情况下已传递但尚未处理的消息的数量也会增加,从而增加了消费者的RAM(随机存取存储器)消耗
预取值设置的太低:信道传输消息效率太低
预取值设置的太高:消费者来不及确认消息导致消息积累问题,内存消耗不断增大
预取值:设置消费者信道最大传输信息数
不公平分发:能者多劳 channel.BasicQos(1);
第一个参数prefetchSize:预取大小服务器将传递的最大内容量(以八位字节为单位),默认为0
第二个参数prefetchCount:服务器一次请求将传递的最大信息数,必填,默认0
第三个参数global:是否将设置应用于整个频道(true),而不是每个消费者(false)
*/
channel.BasicQos(0, 1, false);
//创建消费者
var consumers = new EventingBasicConsumer(channel);
//注册时间
consumers.Received += (model, ea) =>
{
var message = Encoding.UTF8.GetString(ea.Body.ToArray());
Console.WriteLine(no + "msg:" + message);
//具体业务耗时
Thread.Sleep(1000);
//手动消息应答,确认消息
channel.BasicAck(ea.DeliveryTag, false);
};
//绑定队列和消费者,autoAck:true-自动确认,false-手动确认(需要BasicAck)
channel.BasicConsume(queueName, false, consumers);
Console.ReadLine();
}
}
}
}
}
效果图: