1. RabbitMQ介绍
1.1 引言
市面上比较火的几种MQ
ActiveMQ,RocketMQ,Kafka,RabbitMQ
1.语言的支持: ActiveMQ,RocketMQ只支持Java,Kafka,RabbitMQ支持多种语言
2.效率方面:ActiveMQ,RocketMQ,Kafka都是毫秒级别的,RabbitMQ是微秒级别的
3.消息丢失、消息重复问题:RabbitMQ针对消息的持久化,和重复问题都有成熟的解决方案
4.学习成本:RabbitMQ非常简单
RabbitMQ是由Rabbit公司研发和维护的,最终是在Pivotal公司
RabbitMQ严格的遵循AMQP协议,高级消息队列协议,帮助我们在进程之间传递异步信息。
2 安装RabbitMQ
3 RabbitMQ架构
3.1 简单的架构图
(1)Publisher-生产者,发布消息到RabbitMQ的Exchange中
(2)Consumer-消费者,监听Queue中的消息
(3)Exchange-交换机,和生产者建立连接,并且接收生产者的消息
(4)Queue-队列,Exchange会将消息发送到指定的Queue中,和消费者交互
(5)Routes-路由(交换机以什么样的策略将消息发布到Queue中)
3.2 RabbitMQ完整架构图
3.3 图形化界面
创建一个全新的用户和全新的Virtual Host,并且将test用户设置上可以操作/test的权限
4 RabbitMQ的使用
4.1 RabbitMQ的通讯方式
4.2 Java连接RabbitMQ
- 创建MVN项目
- 导入依赖
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.3</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
- 编写工具类
package com.learn.rabbitmq.config;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
/**
* 手动连接RabbitMq
*/
public class RabbitMqClient {
public static Connection getConnection(){
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("127.0.0.1");
factory.setPort(5672);
factory.setUsername("test");
factory.setPassword("test");
factory.setVirtualHost("/test");
// 1.创建Connection
try {
Connection connection = factory.newConnection();
return connection;
} catch (IOException e) {
e.printStackTrace();
return null;
} catch (TimeoutException e) {
e.printStackTrace();
return null;
}
}
}
4.3 Hello-World
一个生产者,一个默认的Exchange,一个队列,一个消费者
这里我们通过直接指定队列名称hello-world实现
- 创建生产者,发布消息到exchange,指定路由规则
package com.learn.rabbitdemo.helloworld;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
public class Publisher {
@Test
public void publish()throws Exception{
// 1.连接Rabbit、获取connection
Connection connection = RabbitMqClient.getConnection();
// 2.创建channel
Channel channel = connection.createChannel();
String msg = "Hello World";
// 3.发布消息到exchange,同时指定路由规则
// 参数1 指定exchange,这里使用空"",(默认)
// 参数2 指定路由规则,使用具体的队列名称
// 参数3 指定传递消息所携带的属性properties
// 参数4 指定发布的具体消息byte[]
channel.basicPublish("","hello-world",null,msg.getBytes());
// exchange是不会将消息持久化本地的,Queue才会帮你持久化消息
System.out.println("消息发布成功");
// 释放资源
channel.close();
connection.close();
}
}
- 创建消费者,常见一个channel,创建一个对流,并且去消费当前队列
package com.learn.rabbitdemo.helloworld;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 参数1:queue指定队列名称
// 参数2:durable 当前队列是否持久化(宕机以后是否重新发布)
// 参数3:exclusive 是否排外(conn.close()当前队列会被自动删除,当前队列只能被一个消费者消费)
// 参数4:autoDelete 如果这个队列没有消费者,队列是否被删除
// 参数5:arguments 指定当前队列的其他信息
channel.queueDeclare("hello-world",true,false,false,null);
// 开启监听
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("接收到消息" + new String(body,"utf-8"));
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("hello-world",true,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
4.4 Work
一个生产者,一个默认的Exchange,一个队列,两个消费者
这里我们也是同样直接指定队列名称,加上线程sleep和 channel.basicQos(1);设置消费者消费能力,演示rabbitMQ的消息分发
package com.learn.rabbitdemo.work;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer1 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 指定当前消费者,一次消费多少消息
channel.basicQos(1);
channel.queueDeclare("work",true,false,false,null);
// 开启监听
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("work",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.work;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer2 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
channel.queueDeclare("work",true,false,false,null);
channel.basicQos(1);
// 开启监听
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者2号接收到消息" + new String(body,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume("work",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.work;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
/**
* work工作模式
*/
public class Publisher {
@Test
public void publish()throws Exception{
// 1.连接Rabbit、获取connection
Connection connection = RabbitMqClient.getConnection();
// 2.创建channel
Channel channel = connection.createChannel();
// 3.发布消息到exchange,同时指定路由规则
// 参数1 指定exchange,这里使用空"",(默认)
// 参数2 指定路由规则,使用具体的队列名称
// 参数3 指定传递消息所携带的属性properties
// 参数4 指定发布的具体消息byte[]
for(int i =0;i<10;i++){
String msg = "work" + i;
channel.basicPublish("","work",null,msg.getBytes());
}
// exchange是不会将消息持久化本地的,Queue才会帮你持久化消息
System.out.println("消息发布成功");
// 释放资源
channel.close();
connection.close();
}
}
只需要在消费者端,添加Qos能力以及更改为手动ack即可让消费者,根据自己能力去消费指定的消息,而不是默认情况下由RabbitMQ平均分配了。
4.5 Publish/Subscribe
一个生产者,一个自己的Exchange,两个队列,两个消费者
这里我们通过指定exchange(FOUNT类型)的,然后queueBind(“队列名”,“交换机名”)进行绑定
声明一个Fanout类型的exchange,并且将exchange和queue绑定在一起,绑定的方式是直接绑定。
1.让生产者创建一个exchange并且指定fanout类型,和一个或者多个队列绑定在一起
2.消费者还是正常监听的某一个队列即可
channel.exchangeDeclare("publish-exchange", BuiltinExchangeType.FANOUT);
// 绑定队列(这里绑定两个队列)
channel.queueBind("publish-queue1","publish-exchange","");
channel.queueBind("publish-queue2","publish-exchange","");
package com.learn.rabbitdemo.publish;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
/**
* 生产者
*/
public class Publisher {
@Test
public void publish()throws Exception{
Connection connection = RabbitMqClient.getConnection();
Channel channel = connection.createChannel();
// 创建自己的Exchange,并且绑定某一个队列
// 参数1:Exchange的名称
// 参数2:指定exchange的类型,通过BuiltinExchangeType指定
// FANOUT -> publish,DIRECT -> Routing, TOPIC -> topics
channel.exchangeDeclare("publish-exchange", BuiltinExchangeType.FANOUT);
// 绑定队列(这里绑定两个队列)
channel.queueBind("publish-queue1","publish-exchange","");
channel.queueBind("publish-queue2","publish-exchange","");
for(int i =0;i<10;i++){
String msg = "publish" + i;
channel.basicPublish("publish-exchange","work",null,msg.getBytes());
}
System.out.println("消息发布成功");
// 释放资源
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.publish;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer1 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 指定当前消费者,一次消费多少消息
channel.basicQos(1);
// 创建队列
channel.queueDeclare("publish-queue1",true,false,false,null);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("publish-queue1",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.publish;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer2 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
channel.queueDeclare("publish-queue2",true,false,false,null);
channel.basicQos(1);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume("publish-queue2",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
4.6 Routing
一个生产者,一个交换机,两个队列,两个消费者
这里指定交换机(direct类型的),通过路由key绑定交换机和路由,queueBind(“queueName”,“exchangeName”,“routingName”)绑定
package com.learn.rabbitdemo.routing;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
public class Publisher {
@Test
public void publish()throws Exception{
// 1.连接Rabbit、获取connection
Connection connection = RabbitMqClient.getConnection();
// 2.创建channel
Channel channel = connection.createChannel();
// 3.创建exchange 绑定 routing-queue-error routing-queue-info
channel.exchangeDeclare("routing-exchange", BuiltinExchangeType.DIRECT);
// 4.通过路由key绑定交换机与队列
channel.queueBind("routing-queue-error","routing-exchange","error");
channel.queueBind("routing-queue-info","routing-exchange","info");
String msg = "routing";
channel.basicPublish("routing-exchange","info",null,"info".getBytes());
channel.basicPublish("routing-exchange","error",null,"error1".getBytes());
channel.basicPublish("routing-exchange","error",null,"error2".getBytes());
channel.basicPublish("routing-exchange","error",null,"error3".getBytes());
channel.basicPublish("routing-exchange","error",null,"error4".getBytes());
System.out.println("消息发布成功");
// 释放资源
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.routing;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer1 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 指定当前消费者,一次消费多少消息
channel.basicQos(1);
// 创建队列
channel.queueDeclare("routing-queue-info",true,false,false,null);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("routing-queue-info",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.routing;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer2 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
channel.queueDeclare("routing-queue-error",true,false,false,null);
channel.basicQos(1);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume("routing-queue-error",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
4.7 Topic
一个生产者,一个交换机,两个队列,两个消费者
生产者创建topic的exchange,并且绑定在队列中,这次绑定可以通过*和#关键字对指定routeKey内容
*表示一个占位符
#表示一个通配符
特别注意:在发送消息的时候必须指定具体的routeKey
package com.learn.rabbitdemo.topic;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
public class Publisher {
@Test
public void publish()throws Exception{
// 1.连接Rabbit、获取connection
Connection connection = RabbitMqClient.getConnection();
// 2.创建channel
Channel channel = connection.createChannel();
// 3.创建exchange 绑定 routing-queue-error routing-queue-info
channel.exchangeDeclare("topic-exchange", BuiltinExchangeType.TOPIC);
// 4.通过路由key绑定交换机与队列
// 动物信息<speed><color><what>
// *.red.* 红色的动物 -》 * 占位符
// fast.# 快速的动物 -》 # 通配符
// #.rabbit
channel.queueBind("topic-queue-1","topic-exchange","*.red.*");
channel.queueBind("topic-queue-2","topic-exchange","fast.#");
channel.queueBind("topic-queue-2","topic-exchange","#.rabbit");
channel.basicPublish("topic-exchange","fast.red.monkey",null,"红快猴子".getBytes());
channel.basicPublish("topic-exchange","slow.black.dog",null,"黑慢猴子".getBytes());
channel.basicPublish("topic-exchange","fast.white.cat",null,"快白猫".getBytes());
System.out.println("消息发布成功");
// 释放资源
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.topic;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer1 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 指定当前消费者,一次消费多少消息
channel.basicQos(1);
// 创建队列
channel.queueDeclare("topic-queue-1",true,false,false,null);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("topic-queue-1",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
package com.learn.rabbitdemo.topic;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer2 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
channel.queueDeclare("topic-queue-2",true,false,false,null);
channel.basicQos(1);
// 开启监听
DefaultConsumer consumer = 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,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}
};
channel.basicConsume("topic-queue-2",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
5.rabbitMQ整合Springboot
5.1 springboot整合RabbitMQ
1.创建springboot工程
2.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
3.配置文件
# rabbitMQ
spring.rabbitmq.host = 127.0.0.1
spring.rabbitmq.port = 5672
spring.rabbitmq.username=test
spring.rabbitmq.password=test
spring.rabbitmq.virtual-host= /test
#
server.port=8080
#mysql
spring.datasource.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
package com.learn.rabbitdemo.config;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* rabbitMQ配置类
*/
@Configuration
public class RabbitConfig {
// 1.创建topic 类型的exchange
@Bean
public TopicExchange getTopicExchange(){
return new TopicExchange("boot-topic-exchange",true,false);
}
// 2.创建队列
@Bean
public Queue getQueue(){
return new Queue("boot-topic-queue",true,false,false,null);
}
// 3.绑定在一起
@Bean
public Binding getBinding(TopicExchange topicExchange,Queue queue){
return BindingBuilder.bind(queue).to(topicExchange).with("*.red.*");
}
}
- 发送消息
package com.learn.rabbitdemo;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitdemoApplicationTests {
@Autowired
private RabbitTemplate rabbitTemplate;
@Test
void contextLoads() {
// 1指定exchange
rabbitTemplate.convertAndSend("boot-topic-exchange","slow.red.dog","红色的大狼狗!!!");
}
}
- 消费消息
package com.learn.rabbitdemo.config;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class Consumer {
@RabbitListener(queues = "boot-topic-queue")
public void getMessage(Object message){
System.out.println(message);
}
}
5.2 手动ACK
1.添加配置文件
spring.rabbitmq.listener.simple.acknowledge-mode=manual
2.监听消息的地方,手动ACK
@Component
public class Consumer {
@RabbitListener(queues = "boot-topic-queue")
public void getMessage(String msg, Channel channel, Message message) throws Exception{
System.out.println(msg);
// 手动ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
6 RabbitMQ的其他操作
6.1 消息的可靠性
1.如果消息到了RabbitMQ,但是它宕机了,消息是不是就丢了?
不会,RabbitMQ得到Queue有持久化机制
2.消费者在消费消息时,如果执行一般,消费者宕机了,手动ACK
3.生产者发送消息时,由于网络问题,导致消息没发到RabbitMQ
两种解决方案:1RabbitMQ提供了事务操作,和Comfirm
RabbitMQ事务:事务可以保证消息的百分百传递,可以通过事务的回滚去记录日志,后面定时再发送当前消息,但是效率太低,加了事务操作以后,效率低了一百倍以上
RabbitMQ除了事务还提供了Confirm机制,这个效率比事务高很多
1.普通Confirm方式
String msg = "Hello World";
// 开启Confirm
channel.confirmSelect();
channel.basicPublish("","hello-world",null,msg.getBytes());
// exchange是不会将消息持久化本地的,Queue才会帮你持久化消息
// 判断消息是否发送成功
if (channel.waitForConfirms()){
System.out.println("消息发布成功");
}else {
System.out.println("消息发布失败");
}
2.批量Confirm方式
// 开启Confirm
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String msg = "Hello World" + i;
channel.basicPublish("","hello-world",null,msg.getBytes());
}
// 演示批量Confirm,当你有一个失败的时候会抛出IOExpection
channel.waitForConfirmsOrDie();
3.异步Confirm方式
// 开启Confirm
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
String msg = "Hello World" + i;
channel.basicPublish("","hello-world",null,msg.getBytes());
}
// 异步
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("消息发送成功 标志" + l + "是否批量" + b);
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("消息发送失败 标志" + l + "是否批量" + b);
}
});
// 释放资源
System.in.read();
6.1.2Return 机制
confirm只能保证消息到达exchange,无法保证消息可以被exchange分发到指定的queue
而且exchange是不能持久化消息的,queue是可以持久化消息的
采用Return机制来监听消息是否从exchange送到了queue中
// 当消息没有送到queue时,才会执行
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int i, String s, String s1, String s2, AMQP.BasicProperties basicProperties, byte[] bytes) throws IOException {
// 当消息没有送到queue时,才会执行
System.out.println(new String(bytes,"utf-8") + "没有送达queue中!!!");
}
});
// 发送消息时,需要指定mandatory参数为true
channel.basicPublish("","publish-queue2",true,null,msg.getBytes());
6.1.3 springboot实现confirm以及Return
1.配置文件
spring.rabbitmq.publisher-confirm-type=simple
spring.rabbitmq.publisher-returns-type=true
2.指定rabbitTemplet开启Confirm和Return,并且编写回调方法
package com.learn.rabbitdemo.config;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
public class PublisherConfig implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback {
@Autowired
private RabbitTemplate rabbitTemplate;
@PostConstruct
public void init(){
rabbitTemplate.setReturnCallback(this);
rabbitTemplate.setConfirmCallback(this);
}
@Override
public void confirm(CorrelationData correlationData, boolean b, String s) {
if (b){
System.out.println("消息已经到达exchange");
}else {
System.out.println("消息没有到达exchange");
}
}
@Override
public void returnedMessage(Message message, int i, String s, String s1, String s2) {
System.out.println("消息没有到达queue");
}
}
6.2 重复消费问题
消费者没有给RabbitMQ一个ACK
重复消费消息,会对非幂等行操作造成问题
为了解决重复消费问题,可以使用redis帮忙,先将消息的id放到redis中
id-0 (正在执行业务)
id-1(执行业务成功)
如果ACK失败,在RabbitMQ将消息交给其他的消费者时,先执行setnx,如果key已经存在,获取它的值,如果是0说明此消息正被消费,当前消费者就什么都不做,如果是1,直接ACK
极端情况:第一个消费者在执行业务时,出现了死锁在setnx的基础上再设置一个生存时间。
- 生产者发送消息时,指定messageId
package com.learn.rabbitdemo.reply;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import org.junit.Test;
import java.util.UUID;
public class Publisher {
@Test
public void publish()throws Exception{
// 1.连接Rabbit、获取connection
Connection connection = RabbitMqClient.getConnection();
// 2.创建channel
Channel channel = connection.createChannel();
// 开启Confirm
channel.confirmSelect();
for (int i = 0; i < 10; i++) {
// 指定消息是否需要持久化 1-需要持久化,2-不需要持久化
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(1)
.messageId(String.valueOf(UUID.randomUUID()))
.build();
String msg = "Hello World" + i;
channel.basicPublish("","reply-info",true,properties,msg.getBytes());
System.out.println("消息发送成功");
}
// 演示批量Confirm,当你有一个失败的时候会抛出IOExpection
channel.waitForConfirmsOrDie();
// 释放资源
channel.close();
connection.close();
}
}
- 消费者,在消费消息时,根据具体逻辑去操作redis
package com.learn.rabbitdemo.reply;
import com.learn.rabbitdemo.config.RabbitMqClient;
import com.rabbitmq.client.*;
import redis.clients.jedis.Jedis;
import java.io.IOException;
/**
* 消费者
*/
public class Consumer1 {
public static void main(String[] args) throws Exception{
// 创建连接
Connection connection = RabbitMqClient.getConnection();
// 创建channel
Channel channel = connection.createChannel();
// 指定当前消费者,一次消费多少消息
channel.basicQos(1);
// 创建队列
channel.queueDeclare("reply-info",true,false,false,null);
// 开启监听
DefaultConsumer consumer = new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
Jedis jedis = new Jedis("127.0.0.1",6379);
String messageId = properties.getMessageId();
// 1.setnx到Redis,默认指定0
String result = jedis.set(messageId,"0","NX","EX",10);
if (result != null && "OK".equalsIgnoreCase(result)){
// 2.消费成功,set messageid 为1
jedis.setex(messageId,10,"1");
System.out.println(properties.getMessageId());
System.out.println("消费者1号接收到消息" + new String(body,"utf-8"));
// 手动ACK
channel.basicAck(envelope.getDeliveryTag(),false);
}else {
// 如果1中的setnx失败,获取key对应的value,如果是0,return 如果是1,ACK
String s = jedis.get(messageId);
if ("1".equalsIgnoreCase(s)){
channel.basicAck(envelope.getDeliveryTag(),false);
}
}
}
};
// 参数1:queue指定队列
// 参数2:autoAck 指定是否自动ACK,(true接收到消息后,会立即告诉RabbitMQ被消费的,不然就要手动通知)
// 参数3:指定消息回调
channel.basicConsume("reply-info",false,consumer);
System.out.println("消费者开始监听队列");
System.in.read();
channel.close();
connection.close();
}
}
6.2.2springboot如何实现
1.导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.2.RELEASE</version>
</dependency>
2.编写配置文件
#redis
spring.redis.host=localhost
spring.redis.port=6379
3.修改生产者
@RequestMapping("/sendReply")
public void send(){
CorrelationData messageId = new CorrelationData(UUID.randomUUID().toString());
// 1指定exchange
Message message = MessageBuilder
.withBody("asddas".getBytes())
.setContentType(MessageProperties.CONTENT_TYPE_BYTES)
.setContentEncoding("ute-8")
.setMessageId(UUID.randomUUID().toString())
.build();
rabbitTemplate.convertAndSend("boot-topic-exchange","fast.red.dog",message);
}
4.修改消费者
package com.learn.rabbitdemo.config;
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
@Component
public class Consumer {
@Autowired
private StringRedisTemplate redisTemplate;
@RabbitListener(queues = "boot-topic-queue")
public void getMessage(String msg, Channel channel, Message message) throws Exception{
String messageId = message.getMessageProperties().getMessageId();
if (redisTemplate.opsForValue().setIfAbsent(messageId,"0",10, TimeUnit.SECONDS)){
System.out.println(msg);
redisTemplate.opsForValue().set(messageId,"1",10, TimeUnit.SECONDS);
// 手动ACK
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}else {
// 如果1中的setnx失败,获取key对应的value,如果是0,return 如果是1,ACK
String s = redisTemplate.opsForValue().get(messageId);
if ("1".equalsIgnoreCase(s)){
channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}
}
}
}