什么是MQ
消息队列(message queue),通过典型的生产者和消费者模型,生产者生产消息写入消息队列中,消费者从消息队列中取出消息消费。因为生产者和消费者是异步,且只关心消息发送和接受,没有业务逻辑的侵入,轻松实现系统间的解耦。
MQ有哪些呢
比较流行的消息中间件有,ActiveMQ、RabbitMQ、kafka、阿里巴巴的RocketMQ等
不同MQ的特点
- ActiveMQ
apache出品,完全支持JMS规范,丰富的API,多种架构集群模式让ActiveMQ成为业界老牌的消息中间件,在中小型企业很受欢迎 - kafka
apache出品,追求高吞吐量,一开始就是用于日志的收集和传输,0.8版本后支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合大数据开发 - RocketMQ
RocketMQ是阿里出品,它是纯java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用特点。它对消息的可靠传输及事务做了优化,目前在阿里被广泛用于交易、充值、流计算、消息推送等场景 - RabbitMQ
RabbitMQ使用erlang语言开发,基于AMQP协议实现,AMQP主要特征是面向消息、队列、路由、可靠性、安全,它对数据一致性、稳定性和可靠性具有很高的要求,对性能和吞吐量还是其次。
RabbitMQ在数据可靠性、一致性、稳定性方面比kafka更可靠,kafka更适合大数据开发,高吞吐量的处理
RabbitMQ的安装
因为rabbitmq是erlang语言写的,所以需要先安装erlang依赖
- 下载erlang, 下载地址
- 下载rabbitmq,下载地址
- erlang与rabbitmq对应版本要求,查看地址
- 上传erlang与rabbitmq安装包到服务器
- 安装erlang与rabbitmq
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
6. 修改配置文件名称
默认安装完后,配置文件在/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要将配置文件复制到/etc/rabbitmq目录中,并修改名称为rabbitmq.config
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config
7. 修改配置文件内容
vim /etc/rabbitmq/rabbitmq.config
去掉注释以及逗号
8. 启动rabbitmq插件管理
rabbitmq-plugins enable rabbitmq_management
9. 启动rabbitmq/关闭rabbitmq/查看状态rabbitmq
systemctl start rabbitmq-server
systemctl stop rabbitmq-server
systemctl status rabbitmq-server
systemctl restart rabbitmq-server
10. 访问rabbitmq的web页面
开启15672端口 ,并使之立即生效,访问服务器地址
http://192.168.0.120:15672/ #192.168.0.104是服务器地址
首次登陆,用户名和密码都是guest
引入依赖
<!--引入rabbitmq相关依赖 -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
rabbitmq的helloworld模型
工具类
没必要每次都去重新创建连接工厂和设置参数,使用静态代码块,随着类的加载而加载,只执行一次
public class RabbitmqUtils {
private static ConnectionFactory factory;
//没必要每次都去重新创建连接工厂和设置参数,创建一次,随着类的加载而创建一次就行
//静态代码块
static {
//创建连接工厂factory
factory = new ConnectionFactory();
//2.设置服务器地址
factory.setHost("192.168.0.120");
//3.设置连接rabbitmq的端口号
factory.setPort(5672);
//4.设置要连接哪台虚拟机
factory.setVirtualHost("/ems");
//5.设置连接的虚拟机用户名和密码
factory.setUsername("ems");
factory.setPassword("123456");
}
//得到连接
public static Connection getConnetion() throws IOException, TimeoutException {
return factory.newConnection();
}
//关闭连接、通道
public static void closeConneAndChann(Connection connection, Channel channel) throws IOException, TimeoutException {
//关闭通道、连接
channel.close();
connection.close();
}
}
消息生产者
//生产者
public class Producer {
@Test
public void produceMessage() throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列,消息不持久化
* 参数3:是否被通道或链接独占队列
* 参数4:消费完后是否自动删除队列
* 参数5:额外参数
*/
channel.queueDeclare("hello",false,false,false,null);
/**发布消息
* 参数1:交换机名称
* 参数2:队列名称
* 参数3:传递消息额外参数 MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
* 参数4:消息内容
*/
channel.basicPublish("","hello",null,"hello rabbitmq".getBytes());
//关闭通道、连接
RabbitmqUtils.closeConneAndChann(connection,channel);
}
}
消费者
这里需要用main方法,消费者消费完消息后并进行相关处理,比如:打印出消息
//消费者
public class Consumer {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除队列
* 参数5:额外参数
*/
channel.queueDeclare("hello",false,false,false,null);
/**消费消息
* 参数1:队列名称
* 参数2:开始消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume("hello",true,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
}
}
运行结果
注意事项
启动生产者生产消息放入队列中,需要关闭服务器防火墙,不然无法创建队列
work queue模型
定义
当生产消息的速度远大于消费消息的速度,长此以往,消息越来越多无法及时处理,用work queue模型,让多个消费者绑定一个队列,共同消费队列中的消息,队列中的消息一旦消费完,队列就会消失,因此任务不会被重复执行
生产者
public class Producer {
@Test
public void produceMessage() throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到通道
Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除
* 参数5:额外参数
*/
channel.queueDeclare("work",true,false,true,null);
for(int i=0;i<10;++i){
/**发布消息
* 参数1:交换机名称
* 参数2:队列名称
* 参数3:传递消息额外参数 MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
* 参数4:消息内容
*/
channel.basicPublish("","work",null,(i+"hello work queue").getBytes());
}
//关闭通道、连接
RabbitmqUtils.closeConneAndChann(connection,channel);
}
}
消费者1
//消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除
* 参数5:额外参数
*/
channel.queueDeclare("work",true,false,true,null);
/**消费消息
* 参数1:队列名称
* 参数2:开始消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume("work",true,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
消费者2
//消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除
* 参数5:额外参数
*/
channel.queueDeclare("work",true,false,true,null);
/**消费消息
* 参数1:队列名称
* 参数2:开始消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume("work",true,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2:"+new String(body));
}
});
}
}
运行结果
结论
work queue模型是按照平均方式消费消息,无论多少条消息,每个消费者收到都是相同数量的消息进行消费,这种方式成为循环。
改进work queue模型为能者多劳模型
消费者1
//消费者
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
final Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除
* 参数5:额外参数
*/
channel.queueDeclare("work",true,false,true,null);
channel.basicQos(1); //通道一次只传送一条消息
/**消费消息
* 参数1:队列名称
* 参数2:开始消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume("work",false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
System.out.println("消费者1:"+new String(body));
Thread.sleep(1000);
channel.basicAck(envelope.getDeliveryTag(),false);
} catch (InterruptedException e) {
e.printStackTrace();
}
/**开启手动确认消息机制
* 参数1:确认是队列中哪个消息
* 参数2:是否开启多个消息同时确认
*/
}
});
}
}
消费者2
//消费者
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
final Channel channel = connection.createChannel();
/**通道与队列进行 绑定
* 参数1:队列名称,如果队列不存在创建队列
* 参数2:是否持久化队列
* 参数3:是否被一个通道或链接独占队列
* 参数4:消费完后是否自动删除
* 参数5:额外参数
*/
channel.queueDeclare("work",true,false,true,null);
channel.basicQos(1); //通道一次只传送一条消息
/**消费消息
* 参数1:队列名称
* 参数2:开启消息的自动确认机制 改为false不开启自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume("work",false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者2:"+new String(body));
/**开启手动确认消息机制
* 参数1:确认是队列中哪个消息
* 参数2:是否开启多个消息同时确认
*/
channel.basicAck(envelope.getDeliveryTag(), false);
}
});
}
}
运行结果
消费者1每消费1次消息需要1s,消费者不需要花费1s,所以消费者2处理消费的效率更高,按照能者多劳机制,因此消费者2处理更多消息比消费者1
fanout(publish/subscribe)模型
广播模式工作流程
广播模式下可以有多个消费者,每个消费者都有自己的queue,每个queue绑定到exchange,生产者直接与交换机打交道,生产者生产的消息直接发送给exchange,由交换机决定将消息发送给哪个队列,生产者无法决定,交换机将消息发送给绑定的所有队列,实现一条消息被所有消费者消费
生产者
public class Producer {
@Test
public void produceMessage() throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 fanout---广播类型
*/
channel.exchangeDeclare("pub/subscri","fanout");
/**发布消息
* 参数1:交换机名称
* 参数2:
* 参数3:传递消息额外参数 MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久化
* 参数4:消息内容
*/
channel.basicPublish("pub/subscri","",null,"hello pub/subscri".getBytes());
//关闭通道、连接
RabbitmqUtils.closeConneAndChann(connection,channel);
}
}
消费者1
//消费者
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 fanout---广播类型
*/
channel.exchangeDeclare("pub/subscri","fanout");
//得到临时队列
String queueName = channel.queueDeclare().getQueue();
/**交换机与队列进行绑定
* 参数1:队列名
* 参数2:交换机名
* 参数3:额外参数
*/
channel.queueBind(queueName,"pub/subscri","");
/**消费消息
* 参数1:队列名称
* 参数2:是否开始消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
消费者2、消费者3如上
运行结果
生产者生产消息交给交换机,交换机将该消息分发给与它绑定的所有队列,于是一条消息,所有的消费者都拿到了
routing模型之direct模型(重点)
在广播模式中,一条消息会被所有消费者消费。但是在某些场景下,我们希望不同的消息被不同的消费者消费,这时就要用到direct类型的交换机。
routing模式下的工作流程
在routing模式中,队列与交换机的绑定不是随意绑定,而是需要指定一个routingkey,消息在向交换机发送时,也必须消息的routingkey,交换机不再把消息发送给绑定的所有队列,而是根据消息的routingkey进行判断,当消息的routingkey与队列的routingkey相同时,才会发送到指定队列
生产者
public class Producer {
@Test
public void produceMessage() throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 direct---路由模式
*/
channel.exchangeDeclare("routing-direct","direct");
//指定routingkey
String routingKey="info";
/**发布消息
* 参数1:交换机名称
* 参数2:指定routingKey标志
* 参数3:传递消息额外参数 MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久
* 参数4:消息内容
*/
channel.basicPublish("routing-direct",routingKey,null,"hello routing模式,我是direct类型".getBytes());
//关闭通道、连接
RabbitmqUtils.closeConneAndChann(connection,channel);
}
}
消费者1
//消费者
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 direct---路由模式
*/
channel.exchangeDeclare("routing-direct","direct");
//得到临时队列
String queueName = channel.queueDeclare().getQueue();
/**交换机与队列进行绑定
* 参数1:队列名
* 参数2:交换机名
* 参数3:指定routingkey
*/
channel.queueBind(queueName,"routing-direct","error");
/**消费消息
* 参数1:队列名称
* 参数2:是否开启消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
消费者2
//消费者
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 direct---路由模式
*/
channel.exchangeDeclare("routing-direct","direct");
//得到临时队列
String queueName = channel.queueDeclare().getQueue();
/**交换机与队列进行绑定
* 参数1:队列名
* 参数2:交换机名
* 参数3:指定routingkey
*/
//该队列可以接收多个routingkey类型的消息
channel.queueBind(queueName,"routing-direct","info");
channel.queueBind(queueName,"routing-direct","error");
channel.queueBind(queueName,"routing-direct","waring");
channel.queueBind(queueName,"routing-direct","debugger");
/**消费消息
* 参数1:队列名称
* 参数2:是否开启消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
运行结果
生产者发送消息的routingkey是info,消费者1是error,消费者2是info、error、waring、debgger,因此消费者1不会接收到消息,消费者2会接收到
routing模型之topic模型
topic类型的交换机与direct类型的交换机都是根据routingkey把消息发送到不同队列中,区别在于topic类型的交换机给队列指定routingkey时候可以使用通配符,如:item.insert.hello
通配符
*匹配1个
#匹配1个或多个
audit.* 可以匹配 audit.item
audit.# 可以匹配 audit.item.haha.hello
生产者
public class Producer {
@Test
public void produceMessage() throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 fanout---广播类型
*/
channel.exchangeDeclare("routing_topict","topic");
//指定routingkey
String routingKey="user.save.dynamic";
/**发布消息
* 参数1:交换机名称
* 参数2:指定routingKey标志
* 参数3:传递消息额外参数 MessageProperties.PERSISTENT_TEXT_PLAIN--消息持久
* 参数4:消息内容
*/
channel.basicPublish("routing_topict",routingKey,null,"hello routing_topic,这是动态routing模式".getBytes());
//关闭通道、连接
RabbitmqUtils.closeConneAndChann(connection,channel);
}
}
消费者1
//消费者
public class Consumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 direct---路由模式
*/
channel.exchangeDeclare("routing_topict","topic");
//得到临时队列
String queueName = channel.queueDeclare().getQueue();
/**交换机与队列进行绑定
* 参数1:队列名
* 参数2:交换机名
* 参数3:指定routingkey, 使用动态通配符形式
*/
channel.queueBind(queueName,"routing_topict","user.*");
/**消费消息
* 参数1:队列名称
* 参数2:是否开启消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
消费者2
//消费者
public class Consumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
//得到连接
Connection connection = RabbitmqUtils.getConnetion();
//得到连接通道
Channel channel = connection.createChannel();
/**通道与交换机进行 绑定
* 参数1:交换机名称
* 参数2:交换机类型 direct---路由模式
*/
channel.exchangeDeclare("routing_topict","topic");
//得到临时队列
String queueName = channel.queueDeclare().getQueue();
/**交换机与队列进行绑定
* 参数1:队列名
* 参数2:交换机名
* 参数3:指定routingkey, 使用动态通配符形式
*/
channel.queueBind(queueName,"routing_topict","user.#");
/**消费消息
* 参数1:队列名称
* 参数2:是否开启消息的自动确认机制
* 参数3:消费时的回调接口
*/
channel.basicConsume(queueName,false,new DefaultConsumer(channel){
@Override //最后一个参数:从消息队列中取出消息消费
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("消费者1:"+new String(body));
}
});
}
}
运行结果
生产者发送消息指定的routingkey是user.save.dynamic,交换机给消费者1指定的routingkey是动态通配符user.*给消费者2指定的routingkey是user.#,所以最后消费者1没能拿到消息,消费者2拿到了该消息进行消费
rabbitmq整合springboot
创建springboot项目时引入springweb、spring for rabbitmq依赖
application.properties配置文件
#给springboot应用取名字
spring.application.name=rabbitmq_springboot
#服务器地址
spring.rabbitmq.host=192.168.0.120
#端口号
spring.rabbitmq.port=5672
#哪台虚拟机
spring.rabbitmq.virtual-host=/ems
#用户名
spring.rabbitmq.username=ems
#密码
spring.rabbitmq.password=123456
helloworld模型
生产者
//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
@Autowired
private RabbitTemplate rabbitTemplate;
//hello world模型 生产者
@Test
public void helloWold(){
//参数1:队列名称 参数2:消息内容
rabbitTemplate.convertAndSend("hello","hello world");
}
消费者
@Component //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
//消费者监听hello队列,并进行持久化、是否独占、自动删除等配置
@RabbitListener(queuesToDeclare = @Queue(value = "hello",durable = "true",exclusive = "false",autoDelete ="true" ))
public class HelloConsumer{
//从队列中取出消息消费
@RabbitHandler
public void receive(String message){
System.out.println(message);
}
}
运行结果
workqueue模型
生产者
//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
@Autowired
private RabbitTemplate rabbitTemplate;
//workQueue模型 生产者
@Test
public void workQueue(){
for(int i=0;i<20;++i){
//参数1:队列名称 参数2:消息内容
rabbitTemplate.convertAndSend("work","hello workqueue");
}
}
}
消费者
@Component //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class WorkConsumer {
//消费者1
//@RabbitListener用在方法上,就省去@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(queuesToDeclare = @Queue(value = "work",durable = "true",exclusive = "false",autoDelete ="false" ))
public void receive1(String message){
System.out.println("消费者1:"+message);
}
//消费者2
@RabbitListener(queuesToDeclare = @Queue(value = "work",durable = "true",exclusive = "false",autoDelete ="false" ))
public void receive2(String message){
System.out.println("消费者2:"+message);
}
}
运行结果
采用workqueue模型每个消费者拿到的消息数量相等,采用循环方式消费
publish/subsucribe模型之fanout模型
生产者
//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
@Autowired
private RabbitTemplate rabbitTemplate;
//publish/subscribe模型 生产者
@Test
public void fanOut(){
//参数1:交换机名称 参数2:routingkey 参数3:消息内容
rabbitTemplate.convertAndSend("pub/sub","","广播模型,fanout_pub/sub");
}
}
消费者
@Component //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class FanOutConsumer {
//消费者1
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "pub/sub",type="fanout"))})
public void receive1(String message){
System.out.println("消费者1:"+message);
}
//消费者2
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "pub/sub",type="fanout"))})
public void receive2(String message){
System.out.println("消费者2:"+message);
}
}
运行结果
采用广播模式,生产者生产消息交给交换机,交换机将该消息分发给与它绑定的所有队列,实现一条消息,所有的消费者消费
routing模型之direct模型
生产者
//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
@Autowired
private RabbitTemplate rabbitTemplate;
//routing模型之direct 生产者
@Test
public void routing(){
//参数1:交换机名称 参数2:routingkey 参数3:消息内容
rabbitTemplate.convertAndSend("routing_direct","info","routing模型,routing_direct模型");
}
}
消费者
@Component //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class DirectConsumer {
//消费者1
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "routing_direct",type="direct"),
key = {
"info","error","waring","debugger"})}) //给队列指定routingkey
public void receive1(String message){
System.out.println("消费者1:"+message);
}
//消费者2
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "routing_direct",type="direct"),
key = {
"error","waring"})}) //给队列指定routingkey
public void receive2(String message){
System.out.println("消费者2:"+message);
}
}
运行结果
生产者发送消息的routingkey是info,消费者1的routingkey是"info",“error”,“waring”,“debugger”,消费者2是"error",“waring”,因此消费者1会接收到消息,消费者2不会接收到
routing模型之topict模型(动态路由)
生产者
//启动springboot测试,测试RabbitmqSpringbootApplication类
@SpringBootTest(classes = RabbitmqSpringbootApplication.class)
@RunWith(SpringRunner.class)
public class TestRabbitMQ {
//将rabbittemplate对象注入到springIOC核心容器的某个对象中,充当该对象的属性
@Autowired
private RabbitTemplate rabbitTemplate;
//routing模型之topic模型(动态路由) 生产者
@Test
public void routingTopic(){
//参数1:交换机名称 参数2:routingkey 参数3:消息内容
rabbitTemplate.convertAndSend("routing_topic","user.name.password","routing模型,动态路由模型,routing_topic模型");
}
}
消费者
@Component //创建consumer实例到springIOC核心容器中,默认bean的id就是类名consumer
public class TopicConsumer {
//消费者1
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "routing_topic",type="topic"),
key = {
"user.*"})}) //给队列指定动态routingkey
public void receive1(String message){
System.out.println("消费者1:"+message);
}
//消费者2
//@RabbitListener用在方法上,不需要使用@rabbithandler注解
//消费者监听work队列且从队列中取出消息消费,并进行持久化、是否独占、自动删除等配置
@RabbitListener(bindings={
@QueueBinding(
value=@Queue, //创建临时队列
//队列和交换机进行绑定, 参数1:交换机名 参数2:交换机类型
exchange=@Exchange(name = "routing_topic",type="topic"),
key = {
"user.#"})}) //给队列指定动态routingkey
public void receive2(String message){
System.out.println("消费者2:"+message);
}
}
运行结果
生产者发送消息指定的routingkey是user.name.password,交换机给消费者1指定的routingkey是动态通配符user.*给消费者2指定的routingkey是user.#,所以最后消费者1没能拿到消息,消费者2拿到了该消息进行消费
rabbitmq应用场景
异步处理
场景说明:用户注册后,需要发送注册邮件和短信,传统做法有两种:串行方式,并行方式
- 串行方式:将注册信息写入到数据库后,再发送注册邮件和注册短信,3个任务完成后才能返回给客户端。这有一个问题,邮件和短信并不是必须的,它只是一个通知,这种做法让客户端等待没有必要等待的东西
- 并行方式:将注册信息写入到数据库后,发送邮件和短信同时进行,三个任务完成后返回给客户端,并行的方式提高处理时间
- 消息队列:邮件和短信对我们正常使用网站没有任何影响,引入消息队列后,把发送邮件,短信不是必须的业务交给消息队列来处理
应用解耦
场景:双11购物,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统接口
这种做法有一个缺点:
当库存系统出现故障时,订单就会失败。订单系统和库存系统高度耦合,引入消息队列
- 订单系统:用户下单后,订单系统将消息写入到消息队列,返回给用户下单成功
- 库存系统:库存系统订阅下单的消息,进行库操作。
流量削峰
场景:秒杀业务,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前加个消息队列
作用:
- 可以控制活动人数,超过一定阈值的订单直接丢弃(我为什么没有秒杀成功)
- 可以缓解短时间高流量压垮应用
rabbitmq的集群
镜像集群
镜像队列机制就是将队列在三个节点之间设置主从关系,消息在三个节点之间进行自动同步,如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群整体高可用
镜像集群搭建
准备3个虚拟机,分别为192.168.0.120、192.168.0.121、192.168.0.122,分别在上面启动rabbitmq,添加一个策略,其中120是主节点,121和122是从节点,生产者发送消息hello world,可以发现所有节点通过镜像方式都接受到该消息,如果此时将主节点120宕机,集群会在剩下的从节点中选取一个作为主节点,如果原来的主节点120重新连接回来,只能作为集群从节点使用。
策略说明
rabbitmqctl set_policy 策略名 队列正则表达式(队列匹配模式) 镜像定义
镜像定义:
包括三个部分ha-mode,ha-params,ha-sync-mode
ha-mode:指定镜像队列的模式,有效值是如下:
------------all:在集群中所有节点上进行镜像
------------exactly:在指定个数的节点上进行镜像,节点的个数由ha-params指定
------------nodes:在指定节点上进行镜像,节点名称由ha-params指定
ha-params:ha-mode模式需要用到的参数
ha-sync-mode:进行队列中消息同步方式,automatic自动和manual手动
priority:可选参数,policy的优先级
查看已有策略
rabbitmqctl list_policies
添加策略
rabbitmqctl set_policy ha-all "^hello" "{"ha-mode":"all","ha-sync-mode":"automatic"}"
删除策略
rabbitmqctl clear_policy ha-all
节点宕机
rabbitmqctl stop_app