RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue )的开源实现。AMQP
的出现其实也是应了广大人民群众的需求,虽然在同步消息通讯的世界里有很多公开标准(如 COBAR的 IIOP ,或者是 SOAP
等),但是在异步消息处理中却不是这样,只有大企业有一些商业实现(如微软的 MSMQ ,IBM 的 Websphere MQ 等),因此,在
2006 年的 6 月,Cisco 、Redhat、iMatix 等联合制定了 AMQP 的公开标准。 RabbitMQ是由RabbitMQ Technologies Ltd开发并且提供商业支持的。该公司在2010年4月被SpringSource(VMWare的一个部门)收购。在2013年5月被并入Pivotal。其实VMWare,Pivotal和EMC本质上是一家的。不同的是VMWare是独立上市子公司,而Pivotal是整合了EMC的某些资源,现在并没有上市。
RabbitMQ的官网是http://www.rabbitmq.com
百度百科amqp协议介绍https://baike.baidu.com/item/AMQP/8354716?fr=aladdin
注意:RabbitMQ是采用erlang语言开发的,所以必须有erlang环境才可以运行
docker run -d -p 15672:15672 -p 5672:5672 -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin --name rabbitmq rabbitmq:3-management
RabbitMQ环境搭建好后浏览器访问http://127.0.0.1:15672即可看到如下界面:
简单描述下上图中中控制台的列表的作用:
RabbitMQ有一个Virtual Host概念:
它表示运行在rabbit service上的一个独立的空间,每个Virtual Host都有一个名字,我们连接使用RabbitMQ消息服务时,需要指定Virtual Host名来连接;
可能还是不太明白,下面我举个例子吧:
其实就类似于MySQL中数据库的概念,一个MySQL服务可以创建多个数据库(同样一个rabbit service也可以创建多个Virtual Host),每个数据库都有一个名字(每个Virtual Host也有一个名字,以/
开始),连接MySQL时需要指定数据库的名字(连接ribbit service时也需要指定一个Virtual Host的名字);
创建用户,为用户分配权限
设置用户能访问哪些Virtual Host
,一个用户可以分配多个Virtual Host,用逗号隔开
创建Virtual Host(注意:以/
开头表示一个路径):
为用户分配访问Virtual Host权限:
点击表格中的用户名,打开页面在Permissions下的VirtualHost下拉列表中选择即可。
使用Virtual Host的好处:
RabbitMQ的五种模式:
1. 简单队列(点对点模式):
如图:
伪代码:
生产者:
消费者:
2. 工作队列:
应答模式
):使用basicQos(1)
方法,设置每次发送给同一消费者一条消息,消费者消费成功应答后,再发送下一条消息,这样就保证了应答快的消费者消费的消息多些。为了确保消息不会丢失,RabbitMQ支持消息应答。
消费者发送一个消息应答,告诉RabbitMQ这个消息已经接收并且处理完毕,RabbitMQ就可以删除它了。 如果一个消费者挂掉却没有发送应答,RabbitMQ会理解为这个消息没有处理完全,然后交给另一个消费者去重新处理。消息应答是默认打开的。
我们通过显示的设置autoAsk=true关闭这种机制。现即自动应答开,一旦我们完成任务,消费者会自动发送应答。通知RabbitMQ消息已被处理,可以从内存删除。如果消费者因宕机或链接失败等原因没有发送ACK(不同于ActiveMQ,在RabbitMQ里,消息没有过期的概念),则RabbitMQ会将消息重新发送给其他监听在队列的下一个消费者。如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。当然还是会有一些小概率事件会导致消息丢失。
队列持久化:
我们就hello队列持久化,在声明队列名称时,持久化队列
,生产端和消费端都要
注意:此时只是做到了消息队列的持久化,消息还未持久化
channel.queue_declare(queue='hello', durable=True)
消息持久化:
上面队列持久化仅仅是消息队列持久化,但是消息并非持久化
channel.basic_publish(exchange='',
routing_key='hello',
body='hello',
properties=pika.BasicProperties(
delivery_mode=2, # make message persistent
))
# 增加properties,这个properties 就是消费端 callback函数中的properties
# delivery_mode = 2 持久化消息
设置队列持久化和消息持久化后,重启RabbitMQ服务后,队列和消息都还在。
总结:
生产者与队列之间多了一个交换机:
注:交换机不做缓存,只做转发
交换机不会保存消息
),队列在将消息以推送或者拉取方式给消费者进行消费,这和我们之前学习Nginx有点类似。 交换机的作用根据具体的路由策略分发到不同的队列中。交换机有四种类型:
直连交换机
)是根据消息携带的路由键(routing key)
将消息投递给对应队列的扇型交换机
)将消息路由给绑定到它身上的所有队列 (默认)主题交换机
)队列通过路由键
绑定到交换机上,然后,交换机根据消息里的路由值,将消息路由给一个或多个绑定队列头交换机
)类似主题交换机,但是头交换机使用多个消息属性来代替路由键
建立路由规则。通过判断消息头的值能否与指定的绑定相匹配来确立路由规则。/**
* pub/sub模式 生产者
* 交换机类型:扇形交换机 fanout
* 注意:交换机若没有绑定的队列,则消息会丢失(交换机不做缓存,只做转发)
* 只要队列创建了,并与交换机绑定,队列就有缓存功能。
*/
public class Producer {
//交换机名称
private static final String DESTINATION_NAME = "my_fanout_destination";
public static void main(String[] args) throws Exception {
System.out.println("生产者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 生产者绑定交换机 args1:交换机名称 args2: 交换机类型
channel.exchangeDeclare(DESTINATION_NAME,"fanout");
//4. 创建要发送的消息
String msg = "my_fanout_destination_msg!!!";
System.out.println("发送消息:"+msg);
//5. 发送消息
channel.basicPublish(DESTINATION_NAME,"",null,msg.getBytes());
//6. 关闭通道和连接
channel.close();
connection.close();
}
}
/**
*邮件消费者
*/
public class ConsumerEmailFanout {
//交换机名称
private static final String DESTINATION_NAME = "my_fanout_destination";
//队列名称
private static final String EMAIL_QUEUE = "email_queue";
public static void main(String[] args) throws Exception {
System.out.println("邮件消费者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 消费者声明队列
channel.queueDeclare(EMAIL_QUEUE,false,false,false,null);
//4. 消费者队列绑定交换机
channel.queueBind(EMAIL_QUEUE,DESTINATION_NAME,"");
//5. 消费者监听消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("邮件消费者获取生产消息:"+msg);
}
};
channel.basicConsume(EMAIL_QUEUE,defaultConsumer);
}
}
/**
*短信消费者
*/
public class ConsumerSmsFanout {
//交换机名称
private static final String DESTINATION_NAME = "my_fanout_destination";
//队列名称
private static final String SMS_QUEUE = "sms_queue";
public static void main(String[] args) throws Exception {
System.out.println("短息消费者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 消费者声明队列
channel.queueDeclare(SMS_QUEUE,false,false,false,null);
//4. 消费者队列绑定交换机
channel.queueBind(SMS_QUEUE,DESTINATION_NAME,"");
//5. 消费者监听消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("短信消费者获取生产消息:"+msg);
}
};
channel.basicConsume(SMS_QUEUE,defaultConsumer);
}
}
MqConnectionUtils工具类:
/**
* RabbitMQ连接工具类
*/
public class MqConnectionUtils {
public static Connection newConnection() throws Exception{
//1. 定义连接工厂
ConnectionFactory factory = new ConnectionFactory();
//2. 设置服务器地址
factory.setHost("127.0.0.1");
//3. 设置协议端口号
factory.setPort(5672);
//4. 设置Virtual Host(类似于数据库名)
factory.setVirtualHost("/admin_vhost");
//5. 设置用户名
factory.setUsername("admin");
//6. 设置密码
factory.setPassword("admin");
//7. 创建新的连接
Connection connection = factory.newConnection();
return connection;
}
}
/**
* 路由模式(RoutingKey) 生产者
* 交换机类型:扇形交换机 direct
*/
public class Producer {
//交换机名称
private static final String Direct_NAME = "direct_exchange";
public static void main(String[] args) throws Exception {
System.out.println("路由模式生产者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 生产者绑定交换机 args1:交换机名称 args2: 交换机类型
channel.exchangeDeclare(Direct_NAME,"direct");
//路由键
String routingKey = "sms";
//4. 创建要发送的消息
String msg = "my_fanout_destination_msg!!! "+routingKey;
//5. 发送消息
channel.basicPublish(Direct_NAME,routingKey,null,msg.getBytes());
System.out.println("发送消息:"+msg);
//6. 关闭通道和连接
channel.close();
connection.close();
//注意:如果消费者没有绑定交换机和队列,则消息会丢失
}
}
邮件消费者:
/**
*邮件消费者
*/
public class ConsumerEmailDirect {
//交换机名称
private static final String Direct_NAME = "direct_exchange";
//队列名称
private static final String EMAIL_QUEUE = "email_queue";
public static void main(String[] args) throws Exception {
System.out.println("邮件消费者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 消费者声明队列
channel.queueDeclare(EMAIL_QUEUE,false,false,false,null);
//4. 消费者队列 路由键绑定交换机(可以绑定多个路由键)
channel.queueBind(EMAIL_QUEUE,Direct_NAME,"email");
channel.queueBind(EMAIL_QUEUE,Direct_NAME,"sms");
//5. 消费者监听消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("邮件消费者获取生产消息:"+msg);
}
};
//true 应答
channel.basicConsume(EMAIL_QUEUE,true,defaultConsumer);
}
}
短信消费者:
/**
*短信消费者
*/
public class ConsumerSmsDirect {
//交换机名称
private static final String Direct_NAME = "direct_exchange";
//队列名称
private static final String SMS_QUEUE = "sms_queue";
public static void main(String[] args) throws Exception {
System.out.println("短息消费者启动...");
//1. 建立MQ链接
Connection connection = MqConnectionUtils.newConnection();
//2. 创建通道
Channel channel = connection.createChannel();
//3. 消费者声明队列
channel.queueDeclare(SMS_QUEUE,false,false,false,null);
//4. 消费者队列 路由键绑定交换机(可以绑定多个路由键)
channel.queueBind(SMS_QUEUE,Direct_NAME,"sms");
//5. 消费者监听消息
DefaultConsumer defaultConsumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
String msg = new String(body,"utf-8");
System.out.println("短信消费者获取生产消息:"+msg);
}
};
//true 应答
channel.basicConsume(SMS_QUEUE,true,defaultConsumer);
}
}
参见:SpringBoot整合RabbitMQ
待续…