一、介绍
RabbitMQ
MQ全称为Message Queue,即消息队列,RabbitMQ是由erlang语言开发,基于AMQP(高级消息队列)协议实现的消息队列,它是一种应用程序之间的通信方法,消息队列在分布式系统开发中应用非常广泛。
开发中消息队列通常用于如下应用场景。
(1)任务异步处理
将不需要同步进行的并且耗时常的操作由消息队列通知消息接收方进行异步处理。
(2)应用程序解耦
MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦合。
常见的消息队列还有
ActiveMQ、RabbitMQ、ZeroMQ、Kafka、MetaMQ、RokectMQ、Redis
使用RabbitMQ的好处
(1)使用简单、功能强大
(2)基于AMQP协议
(3)社区活跃,文档完善
(4)高并发性能好,这主要得益于Erlang语言。
(5)Spring Boot默认已集成RabbitMQ
JMS是
JMS是Java提供的一套消息服务API标准,主要目的是为所有的Java应用程序提供统一的消息通信标准,类似于Java的JDBC.
二、RabitMQ的工作原理
组成部分说明如下:
(1 )Broker : 消息队列服务进程,此进程包括两个部分:Exchange和Queue
(2)Exchange :消息队列交换机,按照一定的规则将消息路由转发到某个队列,对消息进行过滤
(3)Queue :消息队列,存储消息的队列,消息到达队列并转发给指定的消费方
(4)Producer :消费生产者,即生产客户端,生产客户端将消费发送到MQ
(5)Consumer :消息消费者,即消费客户端
消息发布接收流程
---------发送消息--------------
(1)生产者和Broker建立TCP连接
(2)生产者和Broker建立通道
(3)生产者通过消息发送Broker,由Exachange将消息进行转发
(4)Exchange将消息转发到指定的Queue(队列)
----------接收消息-----------------
(1)消费者和Broker建立TCP连接
(2)消费者和Broker建立通道
(3)消费者监听指定的Queue(队列)
(4)当有消息到达Queue时Broker默认将消息推送给消费者
(5)消费者接收消息
安装 省略
安装完成 打开浏览器输入地址:http://localhost:15672 初始账户与密码:guest guest
入门案例
生产者
package com.xuecheng.test;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
public class ProductTest {
private static final String QUEUE = "hello world";
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机 每个MQ可以设置多个虚拟机
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
/**
* queue, durable, exclusive, autoDelete, arguments
* 1、queue 队列名称
* 2、durable 是否持久化 如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接 ,如果Connection关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* 4、autoDelete 自动删除 队列不再使用就自动删除
* 5、arguments 此参数可以设置队列的扩展参数
*/
channel.queueDeclare(QUEUE, true, false, false, null);
/**
* exchange, routingKey, mandatory,immediate, props, body
* 1、exchange 交换机 如果不设置将使用mq的默认交换机
* 2、routingKey 路由key 交换机根据路由key分发消息到指定队列
* 3、props 消息属性
* 4、body 消息内容
*/
channel.basicPublish(" ", QUEUE, null, null);
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
消费者
package com.xuecheng.test;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RibbitMQCustomerTest {
private static final String QUEUE = "hello world";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机 每个MQ可以设置多个虚拟机
connectionFactory.setVirtualHost("/");
Connection connection = null;
connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
/**
* queue, durable, exclusive, autoDelete, arguments
* 1、queue 队列名称
* 2、durable 是否持久化 如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接 ,如果Connection关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* 4、autoDelete 自动删除 队列不再使用就自动删除
* 5、arguments 此参数可以设置队列的扩展参数
*/
channel.queueDeclare(QUEUE, true, false, false, null);
//实现消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
//当接收到消息之后此方法将被调用
/**
* @param consumerTag 用来标识消费者 在监听消费者的时候设置 channel.basicConsume()
* @param envelope 消息信封
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取交换机
String exchange = envelope.getExchange();
//消费ID mq在channel通道中标志消息的id 可以用来确认消息是否发送
long deliveryTag = envelope.getDeliveryTag();
//消息内容
String message = new String(body, "UTF-8");
}
};
//监听队列
channel.basicConsume(QUEUE,true,defaultConsumer);
}
}
三、工作模式
WorkQueue(工作队列模式)
特点如下:
(1)一个生产者将消息发给一个队列
(2)多个消费者共同监听一个队列消息
(3)消息不能被重复消费
(4)rabbit采用轮询的方式将消息是平均发给消费者的
Publish/subscribe (发布订阅模式)
(1)一个生产者将消息发给交换机
(2)与交换机绑定的有多个队列,每个消费者监听自己的队列
(3)生产者将消息发给交换机,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息
(4)如果消息发给没有绑定队列的交换机上消息将丢失
发布订阅模式与工作队列的区别
(1)发布订阅模式可以定义一个交换机绑定多个队列,一个消息可以发送给多个队列
(2)工作队列不需要定义交换机,一个消息一次只能发送给一个队列
(3)发布订阅模式的功能更强大,发布订阅可以将多个消费者监听到同一个队列实现工作队列模式
案例
用于充值成功或者转账成功系统通知yonghu,通知方式短信或者邮件等多种方法
Routing(路由模式)
(1)一个交换机绑定多个队列,每个队列设置routingKey,并且一个队列可以设置多个routingKey
(2)每个消费者监听自己的队列
(3)生产者将消息发给交换机,发送消息时需要指定routingKey的值,交换机来判断该routingKey和那个交换机的routingKey相等,如果相等则将消息转发给该队列
路由模式和发布订阅模式的区别
(1)发布订阅模式在绑定交换机的时候不需要指定routingKey,消息会发送到每个绑定交换机的队列
(2)路由模式要求在绑定交换机时要指定routingKey(这就是队列的routingKey)发送消息到routingKey相等的队列中,所以路由模式比发布订阅模式更强大
生产者
package com.xuecheng.test;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class Product_routing {
private static final String QUEUE_EMAIL = "email queue";
private static final String QUEUE_SMS = "sms queue";
private static final String EXCHANGE_ROUTING = "exchage routing";
private static final String ROUTINGKEY_ERROR = "routingKey error";
private static final String ROUTINGKEY_FALL = "routingKey fall";
public static void main(String[] args) {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机 每个MQ可以设置多个虚拟机
connectionFactory.setVirtualHost("/");
Connection connection = null;
Channel channel = null;
try {
connection = connectionFactory.newConnection();
channel = connection.createChannel();
/**
* queue, durable, exclusive, autoDelete, arguments
* 1、queue 队列名称
* 2、durable 是否持久化 如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接 ,如果Connection关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* 4、autoDelete 自动删除 队列不再使用就自动删除
* 5、arguments 此参数可以设置队列的扩展参数
*/
// 声明队列
channel.queueDeclare(QUEUE_EMAIL, true, false, false, null);
channel.queueDeclare(QUEUE_SMS, true, false, false, null);
//声明交换机
/**
* exchange : 交换机名称
* type : 交换机类型
* fanout : publish/subscribe
* direct : 路由工作模式
* topic :通配符工作模式
* header : header模式
* rpc :远程调用
*/
channel.exchangeDeclare(EXCHANGE_ROUTING, BuiltinExchangeType.FANOUT);
//交换机和队列进行绑定
/**
* queue :队列名称
* exchange : 交换机名称
* rutingKey : 路由key在发布订阅模式中 将key设置为null
*
*/
channel.exchangeBind(QUEUE_EMAIL,EXCHANGE_ROUTING,ROUTINGKEY_ERROR);
channel.exchangeBind(QUEUE_SMS,EXCHANGE_ROUTING,ROUTINGKEY_FALL);
/**
* exchange, routingKey, mandatory,immediate, props, body
* 1、exchange 交换机 如果不设置将使用mq的默认交换机
* 2、routingKey 路由key 交换机根据路由key分发消息到指定队列
* 3、props 消息属性
* 4、body 消息内容
*/
for(int i = 1;i < 5;i++){
String message = " hhhh";
channel.basicPublish(EXCHANGE_ROUTING,ROUTINGKEY_ERROR, null, message.getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
channel.close();
connection.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
消费者1
package com.xuecheng.test;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RibbitMQCustomer_Routing_Email {
private static final String QUEUE_EMAIL = "email queue";
private static final String EXCHANGE_ROUTING = "exchage routing";
private static final String ROUTINGKEY_ERROR = "routingKey error";
private static final String ROUTINGKEY_FALL = "routingKey fall";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机 每个MQ可以设置多个虚拟机
connectionFactory.setVirtualHost("/");
Connection connection = null;
connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
/**
* queue, durable, exclusive, autoDelete, arguments
* 1、queue 队列名称
* 2、durable 是否持久化 如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接 ,如果Connection关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* 4、autoDelete 自动删除 队列不再使用就自动删除
* 5、arguments 此参数可以设置队列的扩展参数
*/
channel.queueDeclare(QUEUE_EMAIL, true, false, false, null);
//声明交换机
/**
* exchange : 交换机名称
* type : 交换机类型
* fanout : publish/subscribe
* direct : 路由工作模式
* topic :通配符工作模式
* header : header模式
* rpc :远程调用
*/
channel.exchangeDeclare(EXCHANGE_ROUTING, BuiltinExchangeType.FANOUT);
//交换机和队列进行绑定
/**
* queue :队列名称
* exchange : 交换机名称
* rutingKey : 路由key在发布订阅模式中 将key设置为null
*
*/
channel.exchangeBind(QUEUE_EMAIL,EXCHANGE_ROUTING,ROUTINGKEY_ERROR);
channel.exchangeBind(QUEUE_EMAIL,EXCHANGE_ROUTING,ROUTINGKEY_FALL);
//实现消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
//当接收到消息之后此方法将被调用
/**
* @param consumerTag 用来标识消费者 在监听消费者的时候设置 channel.basicConsume()
* @param envelope 消息信封
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取交换机
String exchange = envelope.getExchange();
//消费ID mq在channel通道中标志消息的id 可以用来确认消息是否发送
long deliveryTag = envelope.getDeliveryTag();
//消息内容
String message = new String(body, "UTF-8");
}
};
//监听队列
channel.basicConsume(QUEUE_EMAIL,true,defaultConsumer);
}
}
消费者2
package com.xuecheng.test;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RibbitMQCustomer_Routing_SMS {
private static final String QUEUE_SMS = "sms queue";
private static final String EXCHANGE_ROUTING = "exchage routing";
private static final String ROUTINGKEY_ERROR = "routingKey error";
private static final String ROUTINGKEY_FALL = "routingKey fall";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory connectionFactory = new ConnectionFactory();
connectionFactory.setHost("127.0.0.1");
connectionFactory.setPort(5672);
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//设置虚拟机 每个MQ可以设置多个虚拟机
connectionFactory.setVirtualHost("/");
Connection connection = null;
connection = connectionFactory.newConnection();
Channel channel = connection.createChannel();
/**
* queue, durable, exclusive, autoDelete, arguments
* 1、queue 队列名称
* 2、durable 是否持久化 如果持久化,mq重启后队列还在
* 3、exclusive 是否独占连接 ,如果Connection关闭则自动删除,如果将此参数设置为true可用于临时队列的创建
* 4、autoDelete 自动删除 队列不再使用就自动删除
* 5、arguments 此参数可以设置队列的扩展参数
*/
channel.queueDeclare(QUEUE_SMS, true, false, false, null);
//声明交换机
/**
* exchange : 交换机名称
* type : 交换机类型
* fanout : publish/subscribe
* direct : 路由工作模式
* topic :通配符工作模式
* header : header模式
* rpc :远程调用
*/
channel.exchangeDeclare(EXCHANGE_ROUTING, BuiltinExchangeType.FANOUT);
//交换机和队列进行绑定
/**
* queue :队列名称
* exchange : 交换机名称
* rutingKey : 路由key在发布订阅模式中 将key设置为null
*
*/
channel.exchangeBind(QUEUE_SMS,EXCHANGE_ROUTING,ROUTINGKEY_ERROR);
channel.exchangeBind(QUEUE_SMS,EXCHANGE_ROUTING,ROUTINGKEY_FALL);
//实现消费方法
DefaultConsumer defaultConsumer = new DefaultConsumer(channel) {
//当接收到消息之后此方法将被调用
/**
* @param consumerTag 用来标识消费者 在监听消费者的时候设置 channel.basicConsume()
* @param envelope 消息信封
* @param properties 消息属性
* @param body 消息内容
* @throws IOException
*/
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
//获取交换机
String exchange = envelope.getExchange();
//消费ID mq在channel通道中标志消息的id 可以用来确认消息是否发送
long deliveryTag = envelope.getDeliveryTag();
//消息内容
String message = new String(body, "UTF-8");
}
};
//监听队列
channel.basicConsume(QUEUE_SMS,true,defaultConsumer);
}
}
Topics(通配符模式)
(1)一个交换机可以绑定多个队列,每个队列可以设置一个或者东哥带通配符的routingKey
(2)生产者将消息发送给交换机,交换机根据routingKey的值来匹配队列,匹配时采用通配符的方式将信息转发到指定的队列
Topic与Routing的区别
Topic和Routing的基本原理相同,但是匹配规则不同
符号 # : 匹配一个或者多个词
符号 * :匹配一个词
Header模式
header模式与routingKey不同的地方在于,header模式取消了routingKey,使用header中的key/value模式匹配队列
设置email用户只接受email 短信用户只接受sms短信
RPC
RPC即客户远程调用服务方法,使用MQ可以实现RPC的异步调用,基于Direct交换机实现,流程如下:
(1)客户端即是生产者也是消费者,向RPC请求队列发送RPC调用消息,同时监听RPC响应队列
(2)服务端监听RPC请求队列的信息,收到消息后执行服务daunt方法,返回结果
(3)服务端将RPC方法的结果发送到RPC响应队列