MQ概念
消息队列,通过典型的生产者和消费者模型,生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息。因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的侵入,轻松的实现系统间解耦。
常用MQ
- ActiveMQ:ActiveMQ 是Apache出品,最流行的,能力强劲的开源消息总线。它是一个完全支持JMS规范的的消息中间件。丰富的API,多种集群架构模式让ActiveMQ在业界成为老牌的消息中间件,在中小型企业颇受欢迎!
- Kafka:Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前归属于Apache顶级项目。Kafka主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,一开始的目的就是用于日志收集和传输。0.8版本开始支持复制,不支持事务,对消息的重复、丢失、错误没有严格要求,适合产生大量数据的互联网服务的数据收集业务(大数据方面应用,高性能)。
- RocketMQ:RocketMQ是阿里开源的消息中间件,它是纯Java开发,具有高吞吐量、高可用性、适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafka,但并不是Kafka的一个Copy,它对消息的可靠传输及事务性做了优化,目前在阿里集团被广泛应用于交易、充值、流计算、消息推送、日志流式处理、binglog分发等场景(一些业务要收费)。
- RabbitMQ:RabbitMQ是使用Erlang语言开发的开源消息队列系统,基于AMQP协议来实现。AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。AMQP协议更多用在企业系统内对数据一致性、稳定性和可靠性要求很高的场景,对性能和吞吐量的要求还在其次(无缝集成spring)。
安装步骤
# 1.将rabbitmq安装包上传到linux系统中
erlang-22.0.7-1.el7.x86_64.rpm
rabbitmq-server-3.7.18-1.el7.noarch.rpm# 2.安装Erlang依赖包
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpmrpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
# 3.安装RabbitMQ安装包(需要联网)
yum install -y rabbitmq-server-3.7.18-1.el7.noarch.rpm
注意:默认安装完成后配置文件模板在:/usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example目录中,需要
将配置文件复制到/etc/rabbitmq/目录中,并修改名称为rabbitmq.configfind / -name rabbitmq.config.example(查找文件)
# 4.复制配置文件
cp /usr/share/doc/rabbitmq-server-3.7.18/rabbitmq.config.example /etc/rabbitmq/rabbitmq.config# 5.查看配置文件位置
ls /etc/rabbitmq/rabbitmq.config# 6.修改配置文件(参见下图:)
vim /etc/rabbitmq/rabbitmq.config (将上图中配置文件中红色部分去掉`%%`,以及最后的`,`逗号)# 7.执行如下命令,启动rabbitmq中的插件管理 rabbitmq-plugins enable rabbitmq_management
# 8.启动RabbitMQ的服务
systemctl start rabbitmq-server
systemctl restart rabbitmq-server
systemctl stop rabbitmq-server
开机自启动:systemctl enable rabbitmq-server
# 9.查看服务状态
systemctl status rabbitmq-server
# 10.开放端口
firewall-cmd --add-port=15672/tcp --permanentfirewall-cmd --add-port=5672/tcp --permanent
firewall-cmd --reload
firewall-cmd --list-ports
# 11.访问web管理界面
http://主机地址:15672/# 12.登录管理界面
username: guest
password: guest
connections:无论生产者还是消费者,都需要与RabbitMQ建立连接后才可以完成消息的生产和消费,在这里可以查看连接情况`
channels:通道,建立连接后,会形成通道,消息的投递获取依赖通道。
Exchanges:交换机,用来实现消息的路由
Queues:队列,即消息队列,消息存放在队列中,等待消费,消费后被移除队列。
添加用户授权(可通过界面添加)
rabbitmqctl add_user admin admin
abbitmqctl set_user_tags admin administrator
流程图
RabbitMQ支持的消息模型
第一种模型(直连)
引入依赖
<dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.7.2</version> </dependency>
生产者
package first; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import org.junit.Test; public class provider { @Test public void sendMessage() throws Exception{ //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //参数1:队列名称,没有自动创建 参数2: 队列是否持久化 参数3:是否独占队列(能否被其它通道绑定) 参数4:是否自动删除 参数5:其他属性 channel.queueDeclare("hello",true,false,false,null); //参数1:交换机名称 参数2: 队列名称 参数3:传递消息额外设置(消息持久化) 参数4:传递的消息 channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,"hello rabbitmq".getBytes()); channel.close(); connection.close(); } }
消费者
package first; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 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 quene)
Work queues,也被称为(Task queues),任务模型。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以使用work 模型:让多个消费者绑定到一个队列,共同消费队列中的消息。队列中的消息一旦消费,就会消失,因此任务是不会被重复执行的。
总结:默认情况下,RabbitMQ将按顺序将每个消息发送给下一个使用者。平均而言,每个消费者都会收到相同数量的消息。这种分发消息的方式称为循环。消息自动确认机制(能者多劳)
package secend; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import org.junit.Test; public class provider { @Test public void sendMessage() throws Exception{ //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //参数1:队列名称,没有自动创建 参数2: 队列是否持久化 参数3:是否独占队列(能否被其它通道绑定) 参数4:是否自动删除 参数5:其他属性 channel.queueDeclare("hello",true,false,false,null); //参数1:交换机名称 参数2: 队列名称 参数3:传递消息额外设置(消息持久化) 参数4:传递的消息 for (int i = 0; i <20 ; i++) { channel.basicPublish("","hello", MessageProperties.PERSISTENT_TEXT_PLAIN,(i+"hello rabbitmq").getBytes()); } channel.close(); connection.close(); } }
package secend; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 final Channel channel = connection.createChannel(); channel.basicQos(1);//一次只接受一条未确认的消息 //参数1:队列名称,没有自动创建 参数2: 是否持久化 参数3:是否独占队列 参数4:是否自动删除 参数5:其他属性 channel.queueDeclare("hello",true,false,false,null); //参数1:队列名称,没有自动创建 参数2: 自动确认机制 参数3:消费时回调接口 channel.basicConsume("hello",false,new DefaultConsumer(channel){ @Override public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException { System.out.println(new String(body)); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } channel.basicAck(envelope.getDeliveryTag(),false);//手动确认消息 } }); } }
第三种模型(fanout)(广播模型)
在广播模式下,消息发送流程:
- 可以有多个消费者
- 每个消费者有自己的queue(临时队列)
- 每个队列都要绑定到Exchange(交换机)
- 生产者发送的消息,只能发送到交换机,交换机来决定要发给哪个队列,生产者无法决定。
- 交换机把消息发送给绑定过的所有队列
- 队列的消费者都能拿到消息。实现一条消息被多个消费者消费
package third; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import org.junit.Test; public class provider { @Test public void sendMessage() throws Exception { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //声明交换机 channel.exchangeDeclare("logs", "fanout");//广播 一条消息多个消费者同时消费 第二个参数:交换机类型且只能为fanout //参数1:交换机名称 参数2: 队列名称 参数3:传递消息额外设置(消息持久化) 参数4:传递的消息 channel.basicPublish("logs", "", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes()); channel.close(); connection.close(); } }
package third; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //绑定交换机 channel.exchangeDeclare("logs","fanout"); //创建临时队列 String queue = channel.queueDeclare().getQueue(); //将临时队列绑定exchange 第三个参数:路由key channel.queueBind(queue,"logs",""); //参数1:队列名称,没有自动创建 参数2: 自动确认机制 参数3:消费时回调接口 channel.basicConsume(queue,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)); } }); } }
第四种模型(Routing)
Routing 之订阅模型-Direct(直连)
在Direct模型下:
- 队列与交换机的绑定,不能是任意绑定了,而是要指定一个RoutingKey(路由key)
- 消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey。
- Exchange不再把消息交给每一个绑定的队列,而是根据消息的Routing Key进行判断,只有队列的Routingkey与消息的 Routing key完全一致,才会接收到消息
package four; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import com.rabbitmq.client.MessageProperties; import org.junit.Test; public class provider { @Test public void sendMessage() throws Exception { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //声明交换机 参数1:交换机名称 参数2:交换机类型 基于指令的Routing key转发 channel.exchangeDeclare("logs_direct","direct"); String routeKey = "error"; //发布消息 参数1:交换机名称 参数2: 路由key 参数3:传递消息额外设置(消息持久化) 参数4:传递的消息 channel.basicPublish("logs_direct", routeKey, null ,"hello rabbitmq".getBytes()); channel.close(); connection.close(); } }
package four; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //绑定交换机 channel.exchangeDeclare("logs_direct","direct"); //创建临时队列 String queue = channel.queueDeclare().getQueue(); //将临时队列绑定exchange 第三个参数:路由key channel.queueBind(queue,"logs_direct","info"); //参数1:队列名称,没有自动创建 参数2: 自动确认机制 参数3:消费时回调接口 channel.basicConsume(queue,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)); } }); } }
package four; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer1 { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //绑定交换机 channel.exchangeDeclare("logs_direct","direct"); //创建临时队列 String queue = channel.queueDeclare().getQueue(); //将临时队列绑定exchange 第三个参数:路由key channel.queueBind(queue,"logs_direct","error"); channel.queueBind(queue,"logs_direct","info"); channel.queueBind(queue,"logs_direct","logs"); //参数1:队列名称,没有自动创建 参数2: 自动确认机制 参数3:消费时回调接口 channel.basicConsume(queue,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)); } }); } }
Routing 之订阅模型-Topic(区别direct可以通配符匹配)
统配符
- * 匹配不多不少恰好1个词
- # .匹配一个或多个词
package five; import com.rabbitmq.client.Channel; import com.rabbitmq.client.Connection; import com.rabbitmq.client.ConnectionFactory; import org.junit.Test; public class provider { @Test public void sendMessage() throws Exception { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //声明交换机 参数1:交换机名称 参数2:交换机类型 基于指令的Routing key转发 channel.exchangeDeclare("logs_topic","topic"); String routeKey = "user"; //发布消息 参数1:交换机名称 参数2: 路由key 参数3:传递消息额外设置(消息持久化) 参数4:传递的消息 channel.basicPublish("logs_topic", routeKey, null ,"hello rabbitmq".getBytes()); channel.close(); connection.close(); } }
package five; import com.rabbitmq.client.*; import java.io.IOException; import java.util.concurrent.TimeoutException; public class Consumer { public static void main(String[] args) throws IOException, TimeoutException { //创建连接工厂对象 ConnectionFactory connectionFactory = new ConnectionFactory(); connectionFactory.setHost("192.168.6.133"); connectionFactory.setPort(5672); connectionFactory.setUsername("root"); connectionFactory.setPassword("root"); connectionFactory.setVirtualHost("jms"); //创建连接对象 Connection connection = connectionFactory.newConnection(); //创建通道 Channel channel = connection.createChannel(); //绑定交换机 channel.exchangeDeclare("logs_topic","topic"); //创建临时队列 String queue = channel.queueDeclare().getQueue(); //将临时队列绑定exchange 第三个参数:路由key channel.queueBind(queue,"logs_topic","user.#"); //参数1:队列名称,没有自动创建 参数2: 自动确认机制 参数3:消费时回调接口 channel.basicConsume(queue,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)); } }); } }
SpringBoot整合RabbitMQ
引入依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency>
配置配置文件
spring: application: name: springboot_rabbitmq rabbitmq: host: 192.168.6.133 port: 5672 username: root password: root virtual-host: ems
使用
五种模型使用
消费者
package com.wl.springbootmq.Test; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class TopCustomer { @RabbitListener(bindings = {@QueueBinding(value = @Queue, key = {"user.*"}, exchange = @Exchange(type = "topic",name = "topics"))}) public void receive1(String message){ System.out.println("message1 = " + message); } @RabbitListener(bindings = {@QueueBinding(value = @Queue, key = {"user.#"}, exchange = @Exchange(type = "topic",name = "topics"))}) public void receive2(String message){ System.out.println("message2 = " + message); } }
开发消费者
@Component @RabbitListener(queuesToDeclare = @Queue("hello")) //@Queue(value="hello",durable="true,autoDelete="true) public class HelloCustomer { @RabbitHandler public void receive1(String message){ System.out.println("message = " + message); } }
默认
package com.wl.springbootmq.Test; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class WorkCustomer { @RabbitListener(queuesToDeclare = @Queue("work")) public void receive1(String message){ System.out.println("work message1 = " + message); } @RabbitListener(queuesToDeclare = @Queue("work")) public void receive2(String message){ System.out.println("work message2 = " + message); } }
package com.wl.springbootmq.Test; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class FanoutCustomer { @RabbitListener(bindings = @QueueBinding(value = @Queue, exchange = @Exchange(name="logs",type = "fanout") )) public void receive1(String message){ System.out.println("message1 = " + message); } @RabbitListener(bindings = @QueueBinding(value = @Queue, //创建临时队列 exchange = @Exchange(name="logs",type = "fanout") //绑定交换机类型 )) public void receive2(String message){ System.out.println("message2 = " + message); } }
package com.wl.springbootmq.Test; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class DirectCustomer { @RabbitListener(bindings ={ @QueueBinding(value = @Queue(), key={"info","error"}, exchange = @Exchange(type = "direct",name="directs") )}) public void receive1(String message){ System.out.println("message1 = " + message); } @RabbitListener(bindings ={ @QueueBinding(value = @Queue(), key={"error"}, exchange = @Exchange(type = "direct",name="directs") )}) public void receive2(String message){ System.out.println("message2 = " + message); } }
package com.wl.springbootmq.Test; import org.springframework.amqp.rabbit.annotation.Exchange; import org.springframework.amqp.rabbit.annotation.Queue; import org.springframework.amqp.rabbit.annotation.QueueBinding; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.stereotype.Component; @Component public class TopCustomer { @RabbitListener(bindings = {@QueueBinding(value = @Queue, key = {"user.*"}, exchange = @Exchange(type = "topic",name = "topics"))}) public void receive1(String message){ System.out.println("message1 = " + message); } @RabbitListener(bindings = {@QueueBinding(value = @Queue, key = {"user.#"}, exchange = @Exchange(type = "topic",name = "topics"))}) public void receive2(String message){ System.out.println("message2 = " + message); } }
消息的确认机制
整个流程
生产端
可靠抵达-ConfirmCallback
可靠抵达-ReturnCallback
发送消息代码
配置代码
package com.huse.gulishop.order.config; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; @Configuration public class MyRabbitConfig { private RabbitTemplate rabbitTemplate; @Primary @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); this.rabbitTemplate = rabbitTemplate; rabbitTemplate.setMessageConverter(messageConverter()); initRabbitTemplate(); return rabbitTemplate; } // 指定发送的数据类型,接送格式 @Bean public MessageConverter messageConverter() { return new Jackson2JsonMessageConverter(); } /** * 定制RabbitTemplate * 1、服务收到消息就会回调 * 1、spring.rabbitmq.publisher-confirms: true * 2、设置确认回调 * 2、消息正确抵达队列就会进行回调 * 1、spring.rabbitmq.publisher-returns: true * spring.rabbitmq.template.mandatory: true * 2、设置确认回调ReturnCallback * * 3、消费端确认(保证每个消息都被正确消费,此时才可以broker删除这个消息) * */ // @PostConstruct //MyRabbitConfig对象创建完成以后,执行这个方法 public void initRabbitTemplate() { /** * 1、只要消息抵达Broker就ack=true * correlationData:当前消息的唯一关联数据(这个是消息的唯一id) * ack:消息是否成功收到 * cause:失败的原因 */ //设置确认回调 rabbitTemplate.setConfirmCallback((correlationData,ack,cause) -> { System.out.println("confirm...correlationData["+correlationData+"]==>ack:["+ack+"]==>cause:["+cause+"]"); }); /** * 只要消息没有投递给指定的队列,就触发这个失败回调 * message:投递失败的消息详细信息 * replyCode:回复的状态码 * replyText:回复的文本内容 * exchange:当时这个消息发给哪个交换机 * routingKey:当时这个消息用哪个路邮键 */ rabbitTemplate.setReturnCallback((message,replyCode,replyText,exchange,routingKey) -> { System.out.println("Fail Message["+message+"]==>replyCode["+replyCode+"]" + "==>replyText["+replyText+"]==>exchange["+exchange+"]==>routingKey["+routingKey+"]"); }); } }
消费端
可靠抵达-Ack消息确认机制
默认自动、手动确认
![]()
消息的TTL(Time To Live)
• 消息的 TTL 就是 消息的存活时间 。• RabbitMQ 可以对 队列 和 消息 分别设置 TTL 。• 对队列设置就是队列没有消费者连着的保留时间, 也可以对每一个单独的消息做单独的设置。超过了这个时间,我们认为这个消息就死了,称之为死信 。• 如果队列设置了,消息也设置了,那么会 取小的 。所以一个消息如果被路由到不同的队列中,这个消息死亡的时间有可能不一样(不同的队列设置)。这里单讲单个消息的 TTL,因为它才是实现延迟任务的关键。可以通过 设置消息的 expiration 字段或者 x- message-ttl属性来设置时间 ,两者是一样的效果。
Dead Letter Exchanges(DLX)
• 一个消息在满足如下条件下,会进 死信路由 ,记住这里是路由而不是队列,一个路由可以对应很多队列。(什么是死信)• 一个消息被 Consumer 拒收了,并且 reject 方法的参数里 requeue 是 false。也就是说不 会被再次放在队列里,被其他消费者使用。( basic.reject/ basic.nack)requeue=false• 上面的消息的 TTL 到了,消息过期了。• 队列的长度限制满了。排在前面的消息会被丢弃或者扔到死信路由上• Dead Letter Exchange 其实就是一种普通的 exchange ,和创建其他 exchange没有两样。只是在某一个设置 Dead Letter Exchange 的队列中有消息过期了,会自动触发消息的转发,发送到Dead Letter Exchange 中去。• 我们既可以控制消息在一段时间后变成死信,又可以控制变成死信的消息被路由到某一个指定的交换机,结合二者,其实就可以实现一个延时队列• 手动 ack& 异常消息统一放在一个队列处理建议的两种方式• catch异常后, 手动发送到指定队列 ,然后使用 channel 给 rabbitmq 确认消息已消费 • 给Queue 绑定死信队列,![]()
创建绑定关系两种方式
import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.Exchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.core.TopicExchange; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; @Configuration public class MyRabbitMQConfig { /* 容器中的Queue、Exchange、Binding 会自动创建(在RabbitMQ)不存在的情况下 ,存在时不会 覆盖原有的*/ /** * 死信队列 * * @return */@Bean public Queue orderDelayQueue() { /* Queue(String name, 队列名字 boolean durable, 是否持久化 boolean exclusive, 是否排他 boolean autoDelete, 是否自动删除 Map<String, Object> arguments) 属性 */ HashMap<String, Object> arguments = new HashMap<>(); arguments.put("x-dead-letter-exchange", "order-event-exchange"); arguments.put("x-dead-letter-routing-key", "order.release.order"); arguments.put("x-message-ttl", 60000); // 消息过期时间 1分钟 Queue queue = new Queue("order.delay.queue", true, false, false, arguments); return queue; } /** * 普通队列 * * @return */ @Bean public Queue orderReleaseQueue() { Queue queue = new Queue("order.release.order.queue", true, false, false); return queue; } /** * TopicExchange * * @return */ @Bean public Exchange orderEventExchange() { /* * String name, * boolean durable, * boolean autoDelete, * Map<String, Object> arguments * */ return new TopicExchange("order-event-exchange", true, false); } @Bean public Binding orderCreateBinding() { /* * String destination, 目的地(队列名或者交换机名字) * DestinationType destinationType, 目的地类型(Queue、Exhcange) * String exchange, * String routingKey, * Map<String, Object> arguments * */ return new Binding("order.delay.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.create.order", null); } @Bean public Binding orderReleaseBinding() { return new Binding("order.release.order.queue", Binding.DestinationType.QUEUE, "order-event-exchange", "order.release.order", null); } }
import lombok.extern.slf4j.Slf4j; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.core.*; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import java.util.Date; import java.util.HashMap; import java.util.UUID; @Slf4j @RunWith(SpringRunner.class) @SpringBootTest public class GulimallOrderApplicationTests { @Autowired private AmqpAdmin amqpAdmin; @Autowired private RabbitTemplate rabbitTemplate; @Test public void sendMessageTest() { OrderReturnReasonEntity reasonEntity = new OrderReturnReasonEntity(); reasonEntity.setId(1L); reasonEntity.setCreateTime(new Date()); reasonEntity.setName("reason"); reasonEntity.setStatus(1); reasonEntity.setSort(2); String msg = "Hello World"; //1、发送消息,如果发送的消息是个对象,会使用序列化机制,将对象写出去,对象必须实现Serializable接口 //2、发送的对象类型的消息,可以是一个json rabbitTemplate.convertAndSend("hello-java-exchange","hello2.java", reasonEntity,new CorrelationData(UUID.randomUUID().toString())); log.info("消息发送完成:{}",reasonEntity); } /** * 1、如何创建Exchange、Queue、Binding * 1)、使用AmqpAdmin进行创建 * 2、如何收发消息 */ @Test public void createExchange() { Exchange directExchange = new DirectExchange("hello-java-exchange",true,false); amqpAdmin.declareExchange(directExchange); log.info("Exchange[{}]创建成功:","hello-java-exchange"); } @Test public void testCreateQueue() { Queue queue = new Queue("hello-java-queue",true,false,false); amqpAdmin.declareQueue(queue); log.info("Queue[{}]创建成功:","hello-java-queue"); } @Test public void createBinding() { Binding binding = new Binding("hello-java-queue", Binding.DestinationType.QUEUE, "hello-java-exchange", "hello.java", null); amqpAdmin.declareBinding(binding); log.info("Binding[{}]创建成功:","hello-java-binding"); } }
消息可靠性
如何保证消息可靠性-消息丢失
![]()
如何保证消息可靠性-消息重复
如何保证消息可靠性-消息积压
MQ的应用场景
异步处理
场景说明:用户注册后,需要发注册邮件和注册短信,传统的做法有两种 1.串行的方式 2.并行的方式
串行方式: 将注册信息写入数据库后,发送注册邮件,再发送注册短信,以上三个任务全部完成后才返回给客户端。 这有一个问题是,邮件,短信并不是必须的,它只是一个通知,而这种做法让客户端等待没有必要等待的东西.
并行方式: 将注册信息写入数据库后,发送邮件的同时,发送短信,以上三个任务完成后,返回给客户端,并行的方式能提高处理的时间。
消息队列:假设三个业务节点分别使用50ms,串行方式使用时间150ms,并行使用时间100ms。虽然并行已经提高的处理时间,但是,前面说过,邮件和短信对我正常的使用网站没有任何影响,客户端没有必要等着其发送完成才显示注册成功,应该是写入数据库后就返回.,引入消息队列后,把发送邮件,短信不是必须的业务逻辑异步处理
应用解耦
场景:双11是购物狂节,用户下单后,订单系统需要通知库存系统,传统的做法就是订单系统调用库存系统的接口.
这种做法有一个缺点: 当库存系统出现故障时,订单就会失败。 订单系统和库存系统高耦合. 引入消息队列
订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户订单下单成功。
库存系统:订阅下单的消息,获取下单消息,进行库操作。 就算库存系统出现故障,消息队列也能保证消息的可靠投递,不会导致消息丢失.
流量削峰
场景: 秒杀活动,一般会因为流量过大,导致应用挂掉,为了解决这个问题,一般在应用前端加入消息队列。
作用: 1.可以控制活动人数,超过此一定阀值的订单直接丢弃(我为什么秒杀一次都没有成功过呢^^) 2.可以缓解短时间的高流量压垮应用(应用程序按自己的最大处理能力获取订单)
1.用户的请求,服务器收到之后,首先写入消息队列,加入消息队列长度超过最大值,则直接抛弃用户请求或跳转到错误页面. 2.秒杀业务根据消息队列中的请求信息,再做后续处理.
RabbitMQ的集群
普通集群(副本集群):不会对队列的数据进行备份
核心解决问题: 当集群中某一时刻master节点宕机,对Quene中信息进行备份
镜像集群
镜像队列机制就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,且如果其中一个节点不可用,并不会导致消息丢失或服务不可用的情况,提升MQ集群的整体高可用性。