文章目录
rabbitmq相关介绍
最近写的东西比较散,也是工作需要啥就写啥,工作中相关技术用完之后就没再更新了.这里就从自己感兴趣的地方开始写的,会更新到贴近企业运用吧
RabbitMQ: 功能介绍应该不需要多说,是消息中间件的一种,通常会与kafka,RocketMQ做对比.说白了就是服务间的数据传输.消息中间件的主要优点就是流量削峰,服务解耦,异步调用,缺点是增加的系统的复杂性
我会从他的几种工作模式开始讲解.到死信队列,到消息防丢失等等,最后会专门出一篇帖子说他和SpringBoot整合后的使用
首先先得了解一下他的相关名词
Broker: mq的服务器用于接收和分发消息,包含了交换机Exchange和队列Queue
Exchange: 交换机,按照一定的规则讲消息发送到绑定交换机的某个队列
Queue:存储消息的队列
Producer:消息的生产者
Consumer:消息的消费者
Connection:生产者和消费者和对Broker的连接
channel:信道,Connection里包含了多个信道,减少了connection的创建
rabbitmq常用模式和使用
基本概念了解完之后,在了解一下他的常用工作模式
常用的也就前五种,最后一个暂不研究
相关依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>rabbitMQ</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.8.0</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
第一种:Hello World 简单模式
生产者生产消息到队列上,注意哦,没有使用交换机,但是底层会默认一个交换机,由一个消费者接收
开始编写生产者代码
package com.ax.product;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class HelloProduct {
public static void main(String[] args) throws IOException, TimeoutException {
//队列名
final String QUERY_NAME="hello";
//创建连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("1.116.130.252");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//创建连接
Connection connection=connectionFactory.newConnection();
//获取信道
Channel channel = connection.createChannel();
//生成队列
// 队列名称 是否持久化 是否允许多个消费者消费 是否自动删除 参数
channel.queueDeclare(QUERY_NAME,false,false,false,null);
String message="hello world";
//交换机 队列 消息持久化保存在磁盘上 消息体
channel.basicPublish("",QUERY_NAME,null,message.getBytes());
System.out.println("消息发送完毕");
}
}
消费者代码
package com.ax.consumer;
import com.rabbitmq.client.*;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class HelloConsumer {
public static void main(String[] args) throws IOException, TimeoutException {
String QUEUE_NAME="hello";
//创建工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setPassword("guest");
connectionFactory.setUsername("guest");
connectionFactory.setHost("1.116.130.252");
//获取连接
Connection connection = connectionFactory.newConnection();
//获取信道
Channel channel = connection.createChannel();
//接受消息的回调
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println(new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback=consumerTag->{
System.out.println("消息消费被中断");
};
//消费队列 消费成功之后是否自动应答 消费者没有成功消费回调 消费者取消消费回调
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
小结一下:小结的都是重点
生产者步骤说明
1:创建连接工厂
2:获取连接
3:获取信道
4:由信道声明队列,绑定队列名,在企业中,会有相关的枚举类来声明队列名和交换机名
channel.queueDeclare(QUERY_NAME,false,false,false,null);
简单说明一下声明队列需要的几个参数
参数名 | 解释 |
---|---|
String queue | 队列名称 |
boolean durable | 是否持久化 |
boolean exclusive | 是否共享 |
boolean autoDelete | 是否自动删除消息 |
Map<String, Object> arguments | 其他参数,后面说明使用 |
5:指定消息内容
6;发送消息
channel.basicPublish("",QUERY_NAME,null,message.getBytes());
发送消息参数有
参数 | 解释 |
---|---|
String exchange | 交换机名 |
String routingKey | 此处是队列名 |
BasicProperties props | 消息做什么处理 |
byte[] body | 消息体.字节数组 |
消费者的步骤说明
1创建工厂
2获取连接
3.获取信道
4.消息消费
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
参数 | 解释 |
---|---|
String queue | 队列名 |
boolean autoAck | 是否自动应答 |
DeliverCallback deliverCallback | 接受成功的消息回调 |
CancelCallback cancelCallback | 取消回调 |
小结结束
这里给一个工具类,方便后面使用
package com.ax.utils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class MQUtils {
public static Channel MQUtil() throws IOException, TimeoutException {
//创建连接工厂
ConnectionFactory connectionFactory=new ConnectionFactory();
connectionFactory.setHost("1.116.130.252");
connectionFactory.setUsername("guest");
connectionFactory.setPassword("guest");
//创建连接
Connection connection=connectionFactory.newConnection();
//获取信道
return connection.createChannel();
}
}
第二种:Work queues 工作队列模式
一个生产者发送消息到一个队列上,由多个消费者消费
生产者代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class WorkProduct {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
String QUEUE_NAME="hello work";
Channel channel = MQUtils.MQUtil();
//声明队列 队列名称 是否持久化 是否允许多个消费者消费 是否自动删除 参数
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发布消息
for (int i = 0; i < 10; i++) {
String message=i+"号种子选手";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
}
System.out.println("发送消息完成");
}
}
消费者代码模拟多个消费者
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class WorkConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
String QUEUE_NAME="hello work";
Channel channel = MQUtils.MQUtil();
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println(new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
System.out.println("1号等待消息");
//消费队列 消费成功之后是否自动应答 消息消费回调 消费者取消消费回调
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//对应着WorkConsumer1和2
public class WorkConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
String QUEUE_NAME="hello work";
Channel channel = MQUtils.MQUtil();
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println(new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
System.out.println("2号等待消息");
//消费队列 消费成功之后是否自动应答 消息消费回调 消费者取消消费回调
channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
}
}
首先启动两个消费者,会报错,因为队列还没被创建.先启动生产者,等启动完一个消费者之后他会消费掉所所有消息之后.在重启两个消费者,再重启生产者
小结:工作队列模式是采用轮循的方式来消费的,那么如何能者多劳呢,后面基本工作模式讲完再做解答
第三种: Publish/Subscribe 广播模式/也有叫扇出模式
他是由一个生产者发送消息到绑定交换机的队列上.每个队列收到的消息都是一样的.每个消费者消费的消息也自然一样
生产者代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ExchangeFanoutProduct {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = MQUtils.MQUtil();
//声明交换机 交换机名 交换机类型
channel.exchangeDeclare("fanout","fanout");
for (int i = 0; i < 10; i++) {
String message="消息"+i;
channel.basicPublish("fanout","",null,message.getBytes());
}
}
}
说明,生产者不在直接绑定队列,而是绑定交换机
channel.exchangeDeclare("fanout","fanout");
参数 | 解释 |
---|---|
String exchange | 交换机名 |
String type | 交换机类型,有枚举类BuiltinExchangeType |
channel.basicPublish("fanout","",null,message.getBytes());
参数 | 解释 |
---|---|
String exchange | 交换机名 |
String routingKey | 路由key.此处不指定 |
BasicProperties props | 消息如何处理 |
byte[] body | 消息内容 |
消费者代码多个消费者
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ExchangeFanoutConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = MQUtils.MQUtil();
//声明交换机 交换机名 交换机类型
channel.exchangeDeclare("fanout","fanout");
//声明一个临时队列 队列名随机 当消费者断开连接,队列自动删除
String queue = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queue,"fanout","");
//接受消息
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("接受消息"+new String(message.getBody()));
};
channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
}
}
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ExchangeFanoutConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = MQUtils.MQUtil();
//声明交换机 交换机名 交换机类型
channel.exchangeDeclare("fanout","fanout");
//声明一个临时队列 队列名随机 当消费者断开连接,队列自动删除
String queue = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queue,"fanout","");
//接受消息
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("接受消息"+new String(message.getBody()));
};
channel.basicConsume(queue,true,deliverCallback,consumerTag -> {});
}
}
说明:消费者的从队列上消费消息,但是队列需要绑定交换机
channel.queueBind(queue,"fanout","");
参数 | 解释 |
---|---|
String queue | 队列名 |
String exchange | 交换机名 |
String routingKey | 路由key |
第四种:Routing 路由模式
生产者代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RoutingExchangeProduct {
public static void main(String[] args) throws IOException, TimeoutException {
Channel channel = MQUtils.MQUtil();
channel.exchangeDeclare("routing_exchange", BuiltinExchangeType.DIRECT);
for (int i = 0; i < 10; i++) {
String message="消息"+i;
channel.basicPublish("routing_exchange","123",null,message.getBytes());
}
}
}
参数 | 解释 |
---|---|
String exchange | 交换机名 |
String type | 交换机类型,有枚举类BuiltinExchangeType |
channel.basicPublish("routing_exchange","123",null,message.getBytes());
参数 | 解释 |
---|---|
String exchange | 交换机名 |
String routingKey | 路由key,转发到相符合的队列上 |
BasicProperties props | 消息如何处理 |
byte[] body | 消息内容 |
消费者代码多个消费者
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RoutingExchangeConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
String EXCHANGE_NAME="routing_exchange";
Channel channel = MQUtils.MQUtil();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare("duilie1",false,false,false,null);
//绑定
channel.queueBind("duilie1",EXCHANGE_NAME,"123");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消息1号线"+new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
channel.basicConsume("duilie1",true,deliverCallback,cancelCallback);
}
}
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class RoutingExchangeConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
String EXCHANGE_NAME="routing_exchange";
Channel channel = MQUtils.MQUtil();
//声明交换机
channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
//声明队列
channel.queueDeclare("duilie",false,false,false,null);
//绑定
channel.queueBind("duilie",EXCHANGE_NAME,"345");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消息1号线"+new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
channel.basicConsume("duilie",true,deliverCallback,cancelCallback);
}
}
消费者需要将交换机和队列做个绑定,并指定路由key.相同的路由key的队列才能接受到消息
第五种 Topics 主题模式
主题模式在于routingKey的区别.不同的routingKey也能接受到相同的消息
生产者代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicExchangeProduct {
public static void main(String[] args) throws IOException, TimeoutException {
String TOPIC_EXCHANGE = "topic";
Channel channel = MQUtils.MQUtil();
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
for (int i = 0; i < 10; i++) {
String message="消息"+i;
channel.basicPublish(TOPIC_EXCHANGE, "topic.exchange.hah", null, message.getBytes());
}
}
}
消费者代码也是多个消费者
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicExchangeConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
String TOPIC_EXCHANGE = "topic";
Channel channel = MQUtils.MQUtil();
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
channel.queueDeclare("d1",true,false,false,null);
channel.queueBind("d1",TOPIC_EXCHANGE,"*.exchange.*");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消息"+new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
channel.basicConsume("d1",true,deliverCallback,cancelCallback);
}
}
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class TopicExchangeConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
String TOPIC_EXCHANGE = "topic";
Channel channel = MQUtils.MQUtil();
channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);
channel.queueDeclare("d2",true,false,false,null);
channel.queueBind("d2",TOPIC_EXCHANGE,"topic.#");
DeliverCallback deliverCallback=(consumerTag, message)->{
System.out.println("消息"+new String(message.getBody()));
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
channel.basicConsume("d2",true,deliverCallback,cancelCallback);
}
}
两个消费者的区别是routingKey的区别
*主题交换机.routingKey的编写规则
多个单词.号隔开,不能超过255字节
号代表一个
#代表零个或者多个单词
五种模式到此搞定
面试相关
消息队列,如何能者多劳
生产者代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
//消息手动应答生产者 对应 ManualAnswerConsumer1和2
public class ManualAnswerProduct {
public static void main(String[] args) throws IOException, TimeoutException {
String ACK_QUEUE_NAME="ack_queue";
Channel channel = MQUtils.MQUtil();
//声明队列 队列名称 是否队列持久化 是否允许多个消费者消费 是否自动删除 参数
channel.queueDeclare(ACK_QUEUE_NAME,true,false,false,null);
//发布消息
for (int i = 0; i < 10; i++) {
String message=i+"线路";
channel.basicPublish("",ACK_QUEUE_NAME,null,message.getBytes());
}
System.out.println("发送消息完成");
}
}
消费者代码
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ManualAnswerConsumer1 {
public static void main(String[] args) throws IOException, TimeoutException {
final String ACK_QUEUE_NAME="ack_queue";
Channel channel = MQUtils.MQUtil();
DeliverCallback deliverCallback=(consumerTag, message)->{
try {
//沉睡30秒
Thread.sleep(30000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消息1号线"+new String(message.getBody()));
//参数: 消息标记 是否批量应答 该代码就是手动应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
System.out.println("1号等待消息");
//不公平分发,不写默认轮询
channel.basicQos(1);
//消费队列 消费成功之后是否自动应答 消息消费回调 消费者取消消费回调
channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);
}
}
package com.ax.consumer;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class ManualAnswerConsumer2 {
public static void main(String[] args) throws IOException, TimeoutException {
final String ACK_QUEUE_NAME="ack_queue";
Channel channel = MQUtils.MQUtil();
DeliverCallback deliverCallback=(consumerTag, message)->{
try {
//沉睡1秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消息2号线"+new String(message.getBody()));
//手动应答
//参数: 消息标记 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
};
//取消消息时的回调
CancelCallback cancelCallback= consumerTag->{
System.out.println(consumerTag+"消息消费被中断");
};
//不公平分发,不写默认轮询
channel.basicQos(1);
System.out.println("2号等待消息");
//消费队列 消费成功之后是否自动应答 消息消费回调 消费者取消消费回调
channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);
}
}
说明:两个消费者沉睡的时间不一样,用来模拟性能.一个消费的快.一个消费的慢.
channel.basicQos(1);
上面是能者多劳的核心代码.basicQos的值是0,表示轮循分发,是默认的,改成1.则是能者多劳模式
消息防丢失
首先消息防止丢失.可以开启队列持久化和消息持久化
生产者端在声明队列和发送消息的时候可以设置
//声明队列 第二个参数 durable改为true
channel.queueDeclare(ACK_QUEUE_NAME,true,false,false,null);
//发送消息时 第三个参数BasicProperties props传MessageProperties.PERSISTENT_TEXT_PLAIN
channel.basicPublish("",ACK_QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
说明:队列消息是依托队列来发送的,为了避免rabbitmq宕机恢复后,队列消息,故而开启队列持久化
消息持久化则是将发送到队列的消息保存在硬盘中,.反正宕机后消息消失于内存之中
假设:当我生产者发送消息给broker时,发生了网络抖动,或者mq服务器宕机.那么我消息来不及保存,不还是丢失了吗.
所以这时可以在生产者开始一个发布确认模式.当我mq服务器收到消息,并且保存下来后,给生产者说我保存了.那么这条消息就过去了.
生产者端代码
package com.ax.product;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class WorkProduct {
public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
String QUEUE_NAME="hello work";
Channel channel = MQUtils.MQUtil();
//开启发布确认
channel.confirmSelect();
//声明队列 队列名称 是否持久化 是否允许多个消费者消费 是否自动删除 参数
channel.queueDeclare(QUEUE_NAME,false,false,false,null);
//发布消息
for (int i = 0; i < 10; i++) {
String message=i+"号种子选手";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
//等待确认 返回值是boolean
channel.waitForConfirms();
}
System.out.println("发送消息完成");
}
}
//开启发布确认
channel.confirmSelect();
//等待确认 返回值是boolean
channel.waitForConfirms();
confirm确认机制有三种模式.
1 同步单条确认,上述代码就是,效率最低,安全性最高
2 同步批量确认,效率比较高,某个消息出现问题无法确认
3.异步批量确认 效率最高他会在信道中发送map集合.key是消息的序号.value是消息(message),比较复杂
需要消息监听器,监听成功和失败的消息
问题接着来了,如果我消费者在消费消息时,网络抖动或者服务器宕机.那么我消息还是丢失了啊.所以需要在消费者端设置手动应答方式,
channel.basicConsume(ACK_QUEUE_NAME,false,deliverCallback,cancelCallback);
上面代码,第二个参数autoAck如果是true,那么这是自动应答.自动应答中,消费者接收到消息就会给mq发出响应,我已经接收到消息了.此时mq可以删除了.但是此时消息还没被处理啊,所以手动应答是有风险存在的.这里可以改成false,手动应答
手动应答的三种方式
1 Channel.basicAck(肯定确认) mq知道消息成功处理.将其丢失 可批量应答
2 Channel.basicNack(否定确认) mq知道消息未成功处理.将其丢失 可批量应答
3 Channel.basicReject(否定确认) mq知道消息未成功处理.将其丢失,同比上面少了批量的参数
批量应答的说明:
multiple的true和false
true:假设三条未处理,一条消息处理成功 都会给出mq确认的应答
false:假设三条未处理,一条消息处理成功 那么着三条未处理的消息,不会给出mq应答,成功的会给出
通常采用false
//参数一 消息的标记 参数二 是否批量应答
channel.basicAck(message.getEnvelope().getDeliveryTag(),false);
总结:消息防丢失 消费者端开启ACK确认机制 生产者端开启confirm确认机制 队列持久化.消息持久化
避免消息重复消费,保障消息的幂等性
原因 消费了消息给mq服务器做出应答时,网络波动导致应答没能收到,mq服务器会再发一条相同的消息.这就是重复消费的原因
解决 :
1当我消费完一条消息后,将其插入数据库,当重复消费时会插入不进去,也是保障了消息的唯一性
2在写入消息队列的数据做唯一标识,当消费消息时,根据唯一标识判断是否消费过,可以缓存在redis中
消息积压
1.修复消费者代码,是否给出ack确认
2,关闭无关紧要的消息持久化
3扩大mq集群,提高消息吞吐量.增加消费者家掳爱处理
消息过期
消息过期是设置了过期时间,可以配置死信队列用于接收过期消息
如何保障消息数据处理的一致性
最简单的模型 一个生产者对应一个队列对应一个消费者,问题是吞吐量低,容错性低
高可用
本人在这块也不是很熟悉,但是知道两种解决方案,一是搭建普通集群,二是镜像集群.镜像比较高可用
死信队列
生产者代码
package com.ax.delete;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
public class DeadLetterProduct {
public static void main(String[] args) throws IOException, TimeoutException {
//普通交换机
String NORMAL_EXCHANGE="normal_exchange";
//获取信道
Channel channel = MQUtils.MQUtil();
//设置过期时间
AMQP.BasicProperties properties=new AMQP.BasicProperties().builder().expiration("5000").build();
for (int i = 0; i < 10; i++) {
String message="死信消息"+i;
channel.basicPublish(NORMAL_EXCHANGE,"456",properties,message.getBytes());
}
}
}
消费者代码,一个是正常消费者,一个是死信消息消费
package com.ax.delete;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列:无法被消费的消息
* 队列中的消息无法被消费者消费就成了死信,有死信就有了死信队列,专门用来处理死信消息的
*
* 死信的来源
* 1,消息TTL过期
* 2,队列达到最大长度,无法再添加数据到mq
* 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
*
*
* 流程梳理
* 生产者---> 交换机1 ----> 队列1 -----> 消费者1
* |
* 1,消息TTL过期
* 2,队列达到最大长度,无法再添加数据到mq
* 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
* |
* |
* 死信交换机----->死信队列-------->消费者2
*
*
*
*
*
*/
//TODO 这是普通消息消费
public class DeadLetterQueue {
public static void main(String[] args) throws IOException, TimeoutException {
//普通队列
String NORMAL_QUEUE="normal_queue";
//死信队列
String DEAD_LETTER_QUEUE="dead_letter_queue";
//普通交换机
String NORMAL_EXCHANGE="normal_exchange";
//死信交换机
String DEAD_LETTER_EXCHANGE="dead_letter_exchange";
//获取信道
Channel channel = MQUtils.MQUtil();
//声明普通交换机
channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);
//声明死信交换机
channel.exchangeDeclare(DEAD_LETTER_EXCHANGE,BuiltinExchangeType.DIRECT);
//设置参数.正常队列设置死信队列,设置路由key
Map<String, Object> arguments=new HashMap<>();
//arguments.put("x-message-ttl",3000); //TODO 过期时间可以在生产方设置.生产方优先
arguments.put("x-dead-letter-exchange",DEAD_LETTER_EXCHANGE);
arguments.put("x-dead-letter-routing-key","123");
//arguments.put("x-max-length",6); //TODO 超出最大长度,生产者可以取消过期时间
//声明普通队列
channel.queueDeclare(NORMAL_QUEUE,true,false,false,arguments);
//声明死信队列
channel.queueDeclare(DEAD_LETTER_QUEUE,true,false,false,null);
//绑定普通交换机和队列
channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"456");
//绑定死信交换机和队列
channel.queueBind(DEAD_LETTER_QUEUE,DEAD_LETTER_EXCHANGE,"123");
System.out.println("正常消费者等待接受消息");
//接收成功回调
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println(new String(message.getBody()));
//消息拒收
// String msg = new String(message.getBody());
// if(msg.equals("死信消息0")){
// //参数说明 标签号 是否重新放回队列
// channel.basicReject(message.getEnvelope().getDeliveryTag(),false);
// }else {
// System.out.println(new String(message.getBody()));
// }
};
//接受消息
//channel.basicConsume(NORMAL_QUEUE,true, deliverCallback,consumerTag -> {});
}
}
package com.ax.delete;
import com.ax.utils.MQUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeoutException;
/**
* 死信队列:无法被消费的消息
* 队列中的消息无法被消费者消费就成了死信,有死信就有了死信队列,专门用来处理死信消息的
*
* 死信的来源
* 1,消息TTL过期
* 2,队列达到最大长度,无法再添加数据到mq
* 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
*
*
* 流程梳理
* 生产者---> 交换机1 ----> 队列1 -----> 消费者1
* |
* 1,消息TTL过期
* 2,队列达到最大长度,无法再添加数据到mq
* 3,消息被拒绝(basic.reject或者basic.nack)并且requeue(重新排队)=false
* |
* |
* 死信交换机----->死信队列-------->消费者2
*
*
*
*
*
*/
//TODO 死信消费
public class DeadLetterQueue2 {
public static void main(String[] args) throws IOException, TimeoutException {
//死信队列
String DEAD_LETTER_QUEUE="dead_letter_queue";
//获取信道
Channel channel = MQUtils.MQUtil();
System.out.println("死信消费者等待接受消息");
//接收成功回调
DeliverCallback deliverCallback=(consumerTag,message)->{
System.out.println(new String(message.getBody()));
};
//接受消息
channel.basicConsume(DEAD_LETTER_QUEUE,true, deliverCallback,consumerTag -> {});
}
}
码云地址:: https://gitee.com/an-xing/rabbitmq.git
下一遍会讲解和springboot的整合