RabbitMQ消息中间件学习
一、主流消息中间件介绍
1、ActiveMQ
ActiveMQ是Apache产品的最流行能力强劲的开源消息总线。并且它是一个完全支持JMS规范的消息中间件。
具有丰富的API,多种集群构建模式使得ActiveMQ成为业界老牌消息中间件,在小型企业中广泛应用
MQ的衡量指标:服务性能,数据存储,集群架构
1.1、ActiveMQ的集群架构模式
Master-Slave模式 、NetWork模式
2、Kafka
Kafka是LinkedIn开源的分布式发布-订阅消息系统,目前属于Apache顶级项目。Kafak的主要特点是基于Pull的模式来处理消息消费,追求高吞吐量,其一开始的目的就是就是用于日志收集。0.8版本开始支持复制,不支持事物,对消息的重复,丢失,错误没有严格的要求,适合阐释大量数据的互联网服务的数据收集业务。
2.1 Kafka的集群模式
3、RocketMQ
RocketMQ是阿里开源的消息中间件,目前也已经孵化为Apache顶级项目,他是纯java开发,具有高吞吐量,高可用性,适合大规模分布式系统应用的特点。RocketMQ思路起源于Kafak,他对消息的可靠性传输及事物性做了优化,目前在阿里集团被广泛应用于交易,充值,流计算,消息推送,日志流式处理,binglog分发等场景。
RocketMQ集群模式
4、RabbitMQ
RabbitMQ是使用Erlang语言开发的开源消息队列系统, 基于AMQP协议来实现。AMQP的主要特征是面向消息,队列,路由(包括点对点和发布/订阅)、可靠性,安全。AMQP协议更多应用在企业系统内,对数据一致性,稳定性,和可靠性要求很高的场景,对性能和吞吐量的要求还在其次。
4.1 RabbitMQ的集群架构
镜像集群模式
二、RabbitMQ的核心概念
1、为什么互联网大厂使用RabbitMQ?
开源 性能优秀 稳定性搞
提供可靠性消息投递模式(Confirm)、返回模式(Return)
可以SpringAMQP完美整合,API丰富
集群模式丰富, 表达式配置,HA模式,镜像队列模式
保证数据不丢失的情况下做到高可用性,高可靠性
2、RabbitMQ高性能的原因
使用Erlang语言,Erlang语言最初是在交换机领域使用的架构模式,这样就使得RabbitMQ在Broker之间进行数据交互的性能是非常优秀的。Erlang语言有着和原生Socket一样的延迟
3、什么是AMQP高级消息队列协议
AMQP全称:Advance Message Queue Protocol
AMQP是具有现代特征的二进制协议。是一个提供统一消息服务的应用层标准高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。
AMQP协议模型
4、AMQP具体的核心概念
Server:又称Broker,接收客户端的连接,实现AMQP实体服务
Connection:连接,应用程序与Broker的网络连接
Channel:网络信道,几乎所有的操作都是在Channel中进行的,Channel是进行消息读写的通道。客户端可以建立多个Channel,每个Channel代表一个会话任务。
Message:消息,服务器和应用程序之间传递的数据,由Properties和Body组成。Properties可以对消息进行修饰,比如消息的优先级,延迟等高级特性。Body就是消息体的内容。
Virtual Host:虚拟主机,用于进行逻辑隔离,最上层的消息路由。一个VirtualHost里可以有若干戈Exchange和Queue,同一个Virtual Host里面不能有相同名称的Exchange或Queue。
Exchange:交换机,接收消息,根据路由键转发消息到绑定的队列
Queue:也成MessageQueue,消息队列,保存消息并将消息转发给消费者。
Binding:Exchange和Queue之间的虚拟连接,binding中可以包含routing key
Routing Key : 路由规则,虚拟机可以用来确定如何路由一个特定的消息
5、RabbitMQ的整体架构是什么样子的?
6、RabbitMQ的消息流转
三、RabbitMQ的安装和使用
1、下载
在官网上下载你需要的RabbitMQ安装包
https://www.rabbitmq.com/
erlang-18.3-1.el7.centos.x86_64.rpm
socat-1.7.3.2-5.el7.lux.x86_64.rpm
rabbitmq-server-3.6.5-1.noarch.rpm
也可以在linux系统上使用命令下载
wget www.rabbitmq.com/releases/erlang/erlang-18.3-1.el7.centos.x86_64.rpm
wget http://repo.iotti.biz/CentOS/7/x86_64/socat-1.7.3.2-5.el7.lux.x86_64.rpm
wget www.rabbitmq.com/releases/rabbitmq-server/v3.6.5/rabbitmq-server-3.6.5-1.noarch.rpm
2、安装llinux的必要依赖包
yum install
build-essential openssl openssl-devel unixODBC unixODBC-devel
make gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz
3、安装
3.1 安装Erlang
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm
3.2 安装socat
安装过程有可能出现如下情况:
rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
上一步安装socat出现问题 需要执行下面的命令
rpm -ivg socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
3.3 安装RabbitMQ
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm
4、配置文件修改
修改RabbitMQ的配置文件
比如修改密码、配置等等,例如:loopback_users 中的 <<“guest”>>,只保留guest
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
{loopback_users, [guest]}
5、使用RabbitMQ
5.1 启动RabbitMQ
rabbitmq-server start &
5.2 停止RabbitMQ
rabbitmqctl stop
5.3 RabbitMQ管理插件(可视化管理)
rabbitmq-plugins enable rabbitmq_management
插件安装成功之后访问
http://192.168.139.128:15672
如果RabbitMQ是安装在远程机器上的,在本地打开RabbitMQ管理台使用guest用户是登录不了的。默认情况下,RabbitMQ不允许guest用于远程登录。如果需要guest用户远程登录,则需要修改配置文件。将
loopback_users后的配置置空。
{loopback_users, []}
6 命令行与管控台-基础操作
关闭应用
rabbitmqctl stop_app
启动应用
rabbitmqctl start_app
查询节点状态
rabbitmqctl status
添加用户
rabbitmqctl add_user username password
username:用户名
password:用户密码
查询出所有用户
rabbitmqctl list_users
删除用户
rabbitmqctl delete_user username
username:用户名
清除用户权限
rabbitmqctl clear_permissions -p vhostpath username
vhostpath: 虚拟主机
username: 用户名
查询用户权限
rabbitmqctl list_user_permissions username
修改用户密码
rabbitmqctl change_password username password
设置用户权限
rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"
创建虚拟主机
rabbitmqctl add_vhost vhostpath
查询出所有虚拟主机
rabbitmqctl list_vhosts
查询虚拟主机的所有权限
rabbitmqctl list_permissions -p vhostpath
删除虚拟主机
rabbitmqctl delete_vhost vhostpath
查看所有队列信息
rabbitmqctl list_queues
清除队列里的消息
rabbitmqctl -p vhostpath purge_queue blue
移除所有数据
rabbitmqctl reset
查看集群状态
rabbitmqctl cluster_status
四、RabbitMQ代码实操
1、简单的生产者和消费者
消息的生产者
package com.hyc.quickstart;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消息生产者.
*
* @className: Producer
* @author: hyc
* @date: 2021-10-26 20:07
*/
public class Producer {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// RabbitMQ服务地址
factory.setHost("127.0.0.1");
// RabbitMQ服务端口
factory.setPort(5672);
// 设置访问的虚拟主机
factory.setVirtualHost("/");
// 连接RabbitMQ的用户
factory.setUsername("guest");
// 连接RabbitMQ用户的密码
factory.setPassword("guest");
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 通估计channel发送消息
for (int i = 0; i < 10; i++) {
String body = "Hello RabbitMQ";
channel.basicPublish("", "test001", null, body.getBytes());
}
// 关闭相关的链接
channel.close();
connection.close();
}
}
消息的消费者
package com.hyc.quickstart;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 消息消费者.
*
* @className: Consumer
* @author: hyc
* @date: 2021-10-26 20:07
*/
public class Consumer {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// RabbitMQ服务地址
factory.setHost("127.0.0.1");
// RabbitMQ服务端口
factory.setPort(5672);
// 设置访问的虚拟主机
factory.setVirtualHost("/");
// 连接RabbitMQ的用户
factory.setUsername("guest");
// 连接RabbitMQ用户的密码
factory.setPassword("guest");
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 创建一个队列
// 参数一 队列名称
// 参数二 是否持久化
// 参数三 是否独占
// 参数四 是否自动删除
// 参数五 扩展参数
String queueName = "test001";
channel.queueDeclare(queueName, true, false, false, null);
// 创建消息消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
// 设置channel
channel.basicConsume(queueName, true, consumer);
while (true) {
// 获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息: " + msg);
}
}
}
POM文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.6</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.hyc</groupId>
<artifactId>rabbitmq-api</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>rabbitmq-api</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.6.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2、Exchange交换机详解
Exchange:接收消息,并根据路由键转发消息到所绑定的队列
交换机的属性
name:交换机的名称
type:交换机类型,direct、topic、fanout、headers
durability:是否持久化 true-持久化 false-不持久哈
autoDelete:当最后一个绑定到Exchange上的队列删除后,自动删除该Exchange
internal:当前Exchange是否用于RabbitMQ内部使用,默认为false
argumens:扩展参数,用于扩展AMQP协议自制定化使用
Direct Exchange
所有发送到Direct Exchange的消息都会被转发到RouteKey中指定的Queue。
注意:Direct模式可以使用RabbitMQ自带的Exchange 即default Exchange,所以不需要讲Exchange进行任何绑定(binding)操作,消息传递时,RouteKey必须完全匹配才会被队列接收,否则该消息会被抛弃。
Direct Exchange代码示例
消息生产者
package com.hyc.rabbitmqapi.exchange.direct;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消息生产者.
*
* @className: ProducerDirectExchange
* @author: hyc
* @date: 2021-10-26 22:32
*/
public class ProducerDirectExchange {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// RabbitMQ服务的地址
factory.setHost("127.0.0.1");
// RabbitMQ服务端口
factory.setPort(5672);
// RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ用户的密码
factory.setPassword("guest");
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机名称
String exchangeName = "test_direct_exchange";
// 路由键
String routingKey = "test.direct";
String msg = "Hello RabbitMQ This is Direct Exchange Message";
// 发送消息
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
// 关闭连接
channel.close();
connection.close();
}
}
消息消费者
package com.hyc.rabbitmqapi.exchange.direct;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 消息消费者.
*
* @className: ConsumerDirectExchange
* @author: hyc
* @date: 2021-10-26 22:32
*/
public class ConsumerDirectExchange {
public static void main(String[] args) throws Exception {
// 创建连接工程
ConnectionFactory factory = new ConnectionFactory();
// RabbitMQ服务的地址
factory.setHost("127.0.0.1");
// RabbitMQ服务端口
factory.setPort(5672);
// RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ用户的密码
factory.setPassword("guest");
// 是否支持自动重新连接
factory.setAutomaticRecoveryEnabled(true);
// 每3秒钟重连一次
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机名称
String exchangeName = "test_direct_exchange";
// 交换机类型
String exchangeType = "direct";
// 队列名称
String queueName = "test_direct_queue";
// 路由键
String routingKey = "test.direct";
// 通过channel声明一个exchange
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
// 通过channel声明一个queue
channel.queueDeclare(queueName, false, false, false, null);
// 将queue与exchange进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer consumer = new QueueingConsumer(channel);
// 参数 队列名称 是否自动ACK consumer
channel.basicConsume(queueName, true, consumer);
while (true) {
// 获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("收到消息: " + msg);
}
}
}
Topic Exchange
所用发送到Topic Exchange的消息都会被转发到所有关心 RouteKey中指定Topic的queue上。
Exchange将RouteKey和某个Topic进行模糊匹配,此时队列需要绑定一个Topic。
注意:可以使用通配符进行模糊匹配
符号 # 匹配一个或多个词
符号 * 只匹配一个词
例如:“log.#” 能够匹配到 “log.info.o”’
“log.*” 只能匹配到 “log.error”
Topic Exchange代码示例
生产者
package com.hyc.rabbitmqapi.exchange.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 这里是类说明.
*
* @className: ProducerTopicExchange
* @author: hyc
* @date: 2021-10-28 18:42
*/
public class ProducerTopicExchange {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ的用户密码
factory.setPassword("guest");
// 设置自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间3秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机的名称
String exchangeName = "test_topic_exchange";
// 路由键
String routingKey1 = "user.save";
String routingKey2 = "user.update";
String routingKey3 = "user.delete.abc";
String msg = "Hello RabbitMQ This is Topic Exchange Msg ";
channel.basicPublish(exchangeName, routingKey1, null, (msg + routingKey1).getBytes());
channel.basicPublish(exchangeName, routingKey2, null, (msg + routingKey2).getBytes());
channel.basicPublish(exchangeName, routingKey3, null, (msg + routingKey3).getBytes());
// 关闭连接
channel.close();
connection.close();
}
}
消费者
package com.hyc.rabbitmqapi.exchange.topic;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 这里是类说明.
*
* @className: ConsumerTopicExchange
* @author: hyc
* @date: 2021-10-28 18:59
*/
public class ConsumerTopicExchange {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ的用户密码
factory.setPassword("guest");
// 设置自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间3秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机的名称
String exchangeName = "test_topic_exchange";
// 交换机的类型
String exchangeType = "topic";
// 队列的名称
String queueName = "test_topic_queue";
// 路由键
String routingKey = "user.*";
// String routingKey = "user.#";
// 通过channel声明一个交换机
channel.exchangeDeclare(exchangeName, exchangeType, true ,false, false, null);
// 通过channel声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
// 交换机和队列建立绑定关系
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
// 获取消息
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("接收到的消息: " + msg);
}
}
}
Fanout Exchange
不处理路由键,只需要简单的将队列绑定到交换机上
发送到交换机的消息都会被转发到与该交换机绑定的所有队列上
该类型的交换机转发消息是最快的
Fanout Exchange 示例代码
生产者
package com.hyc.rabbitmqapi.exchange.fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 这里是类说明.
*
* @className: ProducerFanoutExchange
* @author: hyc
* @date: 2021-10-28 19:30
*/
public class ProducerFanoutExchange {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ的用户密码
factory.setPassword("guest");
// 设置自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间3秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机的名称
String exchangeName = "test_fanout_exchange";
String msg = "Hello RabbitMQ This is Fanout Msg";
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchangeName, "", null, msg.getBytes());
}
channel.close();
connection.close();
}
}
消费者
package com.hyc.rabbitmqapi.exchange.fanout;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 这里是类说明.
*
* @className: ConsumerFanoutExchange
* @author: hyc
* @date: 2021-10-28 19:30
*/
public class ConsumerFanoutExchange {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ的用户
factory.setUsername("guest");
// 访问RabbitMQ的用户密码
factory.setPassword("guest");
// 设置自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间3秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机的名称
String exchangeName = "test_fanout_exchange";
// 交换机类型
String exchangeType = "fanout";
// 队列名称
String queueName = "test_fanout_queue";
// 路由键
String routingKey = "";
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
channel.queueDeclare(queueName, false, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("接收消息: " + msg);
}
}
}
自定义消息发送
生产者
package com.hyc.rabbitmqapi.message;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
public class ProducerCustomMessage {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// RabbitMQ服务地址
factory.setHost("127.0.0.1");
// RabbitMQ服务端口
factory.setPort(5672);
// RabbitMQ虚拟主机
factory.setVirtualHost("/");
// 访问RabbitMQ服务的用户
factory.setUsername("guest");
// 访问RabbitMQ用户的密码
factory.setPassword("guest");
// 是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机名称
String exchangeName = "properties_direct_exchange";
String routingKey = "properties_key";
Map<String, Object> header = new HashMap<>();
header.put("hello", "world");
header.put("RabbitMQ", "Message");
// 自定义消息属性
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.contentEncoding("utf-8")
// 消息是否持久化 1-不持久化 2-持久化
.deliveryMode(2)
// 消息过期时间 单位毫秒
.expiration("10000")
// 自定义消息
.headers(header)
.build();
// 消息内容
String message = "Hello RabbitMQ This Customer Message";
// 发送消息
channel.basicPublish(exchangeName, routingKey, properties, message.getBytes());
// 关闭连接
channel.close();
connection.close();
}
}
消费者
package com.hyc.rabbitmqapi.message;
import com.rabbitmq.client.*;
import java.util.Map;
/**
* 这里是类说明.
*
* @className: ConsumerCustomMessage
* @author: hyc
* @date: 2021-10-29 15:01
*/
public class ConsumerCustomMessage {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ的服务端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机的名称
String exchangeName = "properties_direct_exchange";
// 交换机的类型
String exchangeType = "direct";
// 队列的名称
String queueName = "properties_message_queue";
// 路由键
String routingKey = "properties_key";
// 声明一个交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, false, null);
// 声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
// 将队列与交换机通过路由键进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 创建消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String body = new String(delivery.getBody());
AMQP.BasicProperties properties = delivery.getProperties();
Map<String, Object> headers = properties.getHeaders();
System.out.println("接收到的消息: " + body);
System.out.printf("headers: " + headers);
}
}
}
Binding 绑定
Binding是Exchange和Exchange、Queue之间的连接关系
Binding中可以包含RoutingKey或参数
Queue 消息队列
消息队列,实际存储消息
属性:
Durability:是否持久化 Durable 持久化 Transient 不持久化
Auto Delete:是否自动删除。代表当最后一个监听被移除后,该Queue会自动删除
Message 消息
服务与服务之间传递的数据
本质上就是一段数据,由Properties和Payload组成
常用属性:
delivery mode、headers(自定义属性)、content_type、content_encoding、priority
virtual host 虚拟主机
虚拟主机,用于逻辑隔离,是最上层的消息路由
五、RabbitMQ的高级特性
1、如何保证消息的100%投递成功
什么是生产端的可靠性投递
- 保证消息可以发送出去
- 保证MQ节点的成功接收
- 发送端收到MQ节点(Broker)的确认应答
- 完善的消息补偿机制
生产端可靠性投递(一)
BAT/TMD 互联网大厂的解决方案
- 消息落库,对消息的状态进行打标
- 消息的延迟投递,做二次确认,回调检查
消费端-幂等性保障
-
唯一id + 指纹码 机制
唯一Id + 指纹码 机制,利用数据库主键去重
SELECT COUNT(1) FROM T_ORDER WHERE Id = 唯一Id + 指纹码
优点:实现简单
缺点:高并发下有数据库写入的性能瓶颈
解决方案:根据ID进行分库分表进行路由算法 -
利用redis的原子性实现
使用redis幂等性需要考虑的问题 -
1、我们是否需要进行数据落库,如果落库的话,关键解决的问题是数据库和缓存如何做到原子性
-
2、如果不进行落库,那么都存储到缓存中,如何设置定时同步的策略
Confirm 确认消息
消息的确认是指生产者投递消息后,如果Broker收到消息则会给我们消息的生产者一个应答。生产者接收应答用来确定这条消息是否正常发送到Broker,这种方式也是消息的可靠性投递的核心保障。
如何实现Confirm确认消息
- 1、在channel上开启确认模式channel.confirmSelect();
- 2、在channel上添加监听 channel.addConfrimListener,监听成功和失败的返回结果,根据具体的结果对消息进行重新发送或记录日志等后续处理。
Confirm消息代码示例
消息生产者
package com.hyc.rabbitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.ConfirmListener;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.io.IOException;
/**
* confirm模式的消息.
*
* @className: ProducerConfirm
* @author: hyc
* @date: 2021-11-01 20:05
*/
public class ProducerConfirm {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务的用户密码
factory.setPassword("guest");
// 是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 设置消息的确认模式
channel.confirmSelect();
// 交换机名称
String exchangeName = "confirm_exchange";
// 路由键
String routingKey = "confirm.save";
String msg = "Hello RabbitMQ This is Confirm Msg";
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
// 添加监听
channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long l, boolean b) throws IOException {
System.out.println("---------------ACK--------------");
}
@Override
public void handleNack(long l, boolean b) throws IOException {
System.out.println("--------------No ACK------------");
}
});
}
}
消息消费者
package com.hyc.rabbitmqapi.confirm;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 这里是类说明.
*
* @className: ConsumerConfirm
* @author: hyc
* @date: 2021-11-01 20:05
*/
public class ConsumerConfirm {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的服务地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务端口
factory.setPort(5672);
// 设置RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务的用户密码
factory.setPassword("guest");
// 是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 交换机名称
String exchangeName = "confirm_exchange";
// 交换机类型
String exchangeType = "topic";
// 队列名称
String queueName = "confirm_queue";
// 路由键
String routingKey = "confirm.#";
// 通过channel声明一个交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
// 通过channel声明一个队列
channel.queueDeclare(queueName, false, false, false, null);
// 将队列和叫环境通过路由键进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 创建一个消费者
QueueingConsumer consumer = new QueueingConsumer(channel);
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
String msg = new String(delivery.getBody());
System.out.println("接收消息: " + msg);
}
}
}
Return消息机制
Return Listener用于处理一些不可路由的消息
消息的生产者,通过指定一个Exchange和RoutingKey把消息发送到某一个消息队列中,然后我们的消费者监听队列进行消费处理。
在某些情况下,如果我们在发送消息的时候,当前的exchange不存在或者指定的路由key路由不到,这个时候如果我们需要监听这种不可达的消息,就需要使用Return Listener。
Mandatory:如果为true,则监听器会接收到路由不可达的消息,然后进行后续处理,如果为false,那么broker端会自动删除消息
Return消息机制流程
Return 消息代码示例
消息生产者
package com.hyc.rabbitmqapi.returnlistener;
import com.rabbitmq.client.*;
import java.io.IOException;
/**
* 这里是类说明.
*
* @className: ProducerListener
* @author: hyc
* @date: 2021-11-02 19:20
*/
public class ProducerListener {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的地址
factory.setHost("127.0.0.1");
// 是指RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连的时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 声明交换机名称
String exchangeName = "return_exchange";
// 声明路由键
String routingKey = "return.save";
String msg = "Hello RabbitMQ This is Return Listener Msg";
// mandatory 设置为 true 则会监听到无法路由到的消息
channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());
channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("-------------return listener--------------");
System.out.println("replyCode: " + replyCode);
System.out.println("replyText: " + replyText);
System.out.println("exchange: " + exchange);
System.out.println("routingKey: " + routingKey);
System.out.println("properties: " + properties);
System.out.println("body: " + new String(body));
}
});
}
}
消息消费者
package com.hyc.rabbitmqapi.returnlistener;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import com.rabbitmq.client.QueueingConsumer;
/**
* 这里是类说明.
*
* @className: ConsumerListener
* @author: hyc
* @date: 2021-11-02 19:20
*/
public class ConsumerListener {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置访问RabbitMQ服务的地址
factory.setHost("127.0.0.1");
// 设置访问RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务的用户密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "return_exchange";
// 定义交换机的类型 直连模式
String exchangeType = "direct";
// 定义路由键
String routingKey = "return.*";
// 定义队列名称
String queueName = "return_queue";
// 通过channel声明交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
// 通过channel声明队列
channel.queueDeclare(queueName,true, false, false, null);
// 将队列与交换机通过路由键进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 创建消费之
QueueingConsumer consumer = new QueueingConsumer(channel);
// 消费消息
channel.basicConsume(queueName, true, consumer);
while (true) {
QueueingConsumer.Delivery delivery = consumer.nextDelivery();
System.out.println("接收消息: " + new String(delivery.getBody()));
}
}
}
2、自定义消息消费者
通过继承DefaultConsumer
自定义消息消费者代码示例
消息生产者
package com.hyc.rabbitmqapi.customer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 自定义消费者的消息生产者.
*
* @className: ProducerCustomer
* @author: hyc
* @date: 2021-11-02 19:52
*/
public class ProducerCustomer {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "test_customer_exchange";
// 定义路由键的名称
String routingKey = "customer.key";
String msg = "Hello RabbitMQ This is Customer Msg";
for (int i = 0; i < 5;i ++) {
// 发送消息
channel.basicPublish(exchangeName, routingKey, true, null, msg.getBytes());
}
// 关闭连接
channel.close();
connection.close();
}
}
消息的消费者
package com.hyc.rabbitmqapi.customer;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 自定义消费者的消息消费者.
*
* @className: ConsumerCustomer
* @author: hyc
* @date: 2021-11-02 19:52
*/
public class ConsumerCustomer {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "test_customer_exchange";
String exchangeType = "topic";
// 定义路由键的名称
String routingKey = "customer.#";
// 定义队列的名称
String queueName = "test_customer_queue";
// 通过channel声明交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
// 通过channel声明队列
channel.queueDeclare(queueName, true, false, false, null);
// 将队列与交换机通过路由键绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 消费消息
channel.basicConsume(queueName, true, new MyCustomerConsumer(channel));
}
}
自定义的消费者
package com.hyc.rabbitmqapi.customer;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
/**
* 这里是类说明.
*
* @className: MyCustomerConsumer
* @author: hyc
* @date: 2021-11-02 20:00
*/
public class MyCustomerConsumer extends DefaultConsumer {
public MyCustomerConsumer(Channel channel) {
super(channel);
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("----------customer consumer------------");
System.out.println("consumerTag: " + consumerTag);
System.out.println("envelope: " + envelope);
System.out.println("properties: " + properties);
System.out.println("body: " + new String(body));
}
}
3、消费端限流
什么是消费端限流
假设一个场景,首先我们的RabbitMQ服务器上有上万条未处理的消息,我们随便打开一个消费者都会出现巨量的消息全部推送过来,但是我们的单个消费端无法处理这么多数据,造成服务器宕机。
RabbitMQ提供了一种qos(服务质量保证)功能,即在非自动确认的消息的前提下,如果一定数量的消息(基于通过consumer或者channel设置qos的值)未被确认前,不进行消费新的消息。
void BasicQos(uint prefetchSize, ushort prefetchCount, bool global);
- prefetchSize:0 消息的大小限制,0 表示消息大小不收限制
- prefetchCount:会告诉RabbitMQ不要同时给一个消费者推送多于N个消息,即一旦有N个消息还没有ACK,则该consumer将block掉,直到有消息ACK。
- global:true/false 是否将上面设置应用于channel,简单点说,就是上面限制是channel级别还是consumer级别。
prefetchSize和global这两项,RabbitMQ没有实现,暂且不研究prefetchCount在no_ask=false的情况下,即在自动应答的情况下这两个值是不生效的
消费端限流代码示例
生产者
package com.hyc.rabbitmqapi.limit;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消费端消息限流 生产者.
*
* @className: ProducerLimit
* @author: hyc
* @date: 2021-11-05 16:05
*/
public class ProducerLimit {
public static void main(String[] args) throws Exception {
// 床架你连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务的用户密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间 单位毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机名称
String exchangeName = "limit_exchange";
// 定义路由键的名称
String routingKey = "limit.key";
String msg = "Hello RabbitMQ This is Limit Msg";
for (int i = 0; i < 10; i++) {
channel.basicPublish(exchangeName, routingKey, null, msg.getBytes());
}
channel.close();
connection.close();
}
}
消费者
package com.hyc.rabbitmqapi.limit;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 消费端消息限流 消费者.
*
* @className: ConsumerLimit
* @author: hyc
* @date: 2021-11-05 16:06
*/
public class ConsumerLimit {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的服务的地址
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 是指是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间 单位 毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "limit_exchange";
// 定义交换机的类型
String exchangeType = "topic";
// 定义队列的名称
String queueName = "limit_queue";
// 定义路由键
String routingKey = "limit.*";
// 通过channel声明交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
// 通过channel声明队列
channel.queueDeclare(queueName, true, false, false, null);
// 将队列与交换机通过路由键绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 每次只消费1条消息
channel.basicQos(0, 1, false);
// 消费端限流的关键 将自动ack设置为手动ack
// 消费消息 设置为手动ack
channel.basicConsume(queueName, false, new MyCustomerConsumer(channel));
}
}
自定义消费者
package com.hyc.rabbitmqapi.limit;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
/**
* 这里是类说明.
*
* @className: MyCustomerConsumer
* @author: hyc
* @date: 2021-11-05 16:31
*/
public class MyCustomerConsumer extends DefaultConsumer {
private Channel channel;
public MyCustomerConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("----------customer consumer------------");
System.out.println("consumerTag: " + consumerTag);
System.out.println("envelope: " + envelope);
System.out.println("properties: " + properties);
System.out.println("body: " + new String(body));
// ack确认收到消息 multiple: 是否批量签收消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
4、消费端ACK与重回队列
消费端的手工ACK和NACK
消费端进行消费的时候,如果由于业务异常我们可以进行业务的记录然后进行补偿。
由于服务器宕机等严重问题,我们需要进行手工ACK,保障消费端消费成功。
消费端重回队列
消费端重回队列是为了处理没有消费成功的消息。把消息冲洗递给broker。
一般实际应用中,我们都会关闭消息重回队列,也就是设置为false。
重回队列代码示例
生产者
package com.hyc.rabbitmqapi.noack;
import com.rabbitmq.client.*;
import java.util.HashMap;
import java.util.Map;
/**
* NoAck消息重回队列 生产者.
*
* @className: ProducerAckReturnQueue
* @author: hyc
* @date: 2021-11-05 17:09
*/
public class ProducerAckReturnQueue {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间 单位 毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机名称
String exchangeName = "ack_return_exchange";
// 定义路由键
String routingKey = "ack.return";
String msg = "Hello RabbitMQ This is Ack Or NoAck Return Queue Msg";
for (int i = 0; i < 5; i++) {
Map<String, Object> headers = new HashMap<>();
headers.put("num", i);
AMQP.BasicProperties basicProperties = new AMQP.BasicProperties().builder()
.contentEncoding("utf-8")
.deliveryMode(2)
.headers(headers)
.build();
// 发送消息
channel.basicPublish(exchangeName, routingKey, basicProperties, msg.getBytes());
}
}
}
消费者
package com.hyc.rabbitmqapi.noack;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* NoAck消息重回队列 消费者.
*
* @className: ConsumerAckReturnQueue
* @author: hyc
* @date: 2021-11-05 17:09
*/
public class ConsumerAckReturnQueue {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("guest");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("guest");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 设置自动重连时间 单位 毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机名称
String exchangeName = "ack_return_exchange";
// 定义交换机的类型
String exchangeType = "direct";
// 定义队列名称
String queueName = "ack_return_queue";
// 定义路由键
String routingKey = "ack.return";
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
// 消费消息 autoAck:false 设置自动ack为false 使用手工签收消息
channel.basicConsume(queueName, false, new MyCustomerConsumer(channel));
}
}
自定义消费者
package com.hyc.rabbitmqapi.noack;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DefaultConsumer;
import com.rabbitmq.client.Envelope;
import java.io.IOException;
/**
* 这里是类说明.
*
* @className: MyCustomerConsumer
* @author: hyc
* @date: 2021-11-05 16:31
*/
public class MyCustomerConsumer extends DefaultConsumer {
private Channel channel;
public MyCustomerConsumer(Channel channel) {
super(channel);
this.channel = channel;
}
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("----------customer consumer------------");
int num = (int)properties.getHeaders().get("num");
System.out.println("body: " + new String(body) + " " + num);
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num == 0) {
// requeue 是否重回队列 true-重回队列 false-不重回队列
channel.basicNack(envelope.getDeliveryTag(), false, true);
} else {
// ack确认收到消息 multiple: 是否批量签收消息
channel.basicAck(envelope.getDeliveryTag(), false);
}
}
}
5、死信队列
死信队列 DLX (Dead-Letter-Exchange)。一个消息没有被消费者消费(由于某种原因)。
利用DLX,当消息在一个队列中变成死信(Dead Message)之后,它能重新被publish到另一个Exchange,这个Exchange就是DLX。
DLX也是一个正常的Exchange,和一般的Exchange没有区别,它能在任何的队列上被指定,实际上就是设置某个队列的属性。当这个队列中有死信时,RabbitMQ就会自动的将这个消息重新发布到设置的Exchange上进而被路由到另一个队列。
可以监听这个队列中的消息然后做相应的处理,这个特性可以弥补RabbitMQ3.0以前支持的immediate参数的功能
消息变成死信的几种情况
- 消息被拒绝(basic.reject/basic.nack)并且requeue=false
- 消息TTL过期
- 队列达到最大的长度
死信队列的设置
首先要设置死信队列的exchange和queue,然后尽心绑定。
- Exchange:dlx.exchange
- Queue:dlx.queue
- RoutingKey:#
然后我们正常的声明交换机和队列,然后进行绑定。只不过需要在队列上加上一个参数即可:arguments.put(“x-dead-letter-exchange”,“dlx.exchange”);
死信队列场景代码演示
场景:发一个有过期时间10秒的消息到一个队列中,然后不消费。看10秒之后消息是否被publish到了死信队列中去了吗。
生产者
package com.hyc.rabbitmqapi.dlx;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
/**
* 这里是类说明.
*
* @className: ProducerDlx
* @author: hyc
* @date: 2021-11-08 18:24
*/
public class ProducerDlx {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("hyc");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("hyc");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间 单位毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "dlx_exchange";
String routingKey = "dlx.test";
String msg = "Hello RabbitMQ This is Test DLX msg";
// 自定义消息属性
AMQP.BasicProperties properties = new AMQP.BasicProperties().builder()
.deliveryMode(2)
// TTL 消息过期时间10秒
.expiration("10000")
.contentType("utf-8")
.build();
// 发送消息
channel.basicPublish(exchangeName, routingKey, true, properties, msg.getBytes());
channel.close();
connection.close();
}
}
消费者
package com.hyc.rabbitmqapi.dlx;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
import java.util.HashMap;
import java.util.Map;
/**
* 这里是类说明.
*
* @className: ConsumerDlx
* @author: hyc
* @date: 2021-11-08 18:24
*/
public class ConsumerDlx {
public static void main(String[] args) throws Exception {
// 创建连接工厂
ConnectionFactory factory = new ConnectionFactory();
// 设置RabbitMQ服务的主机
factory.setHost("127.0.0.1");
// 设置RabbitMQ服务的端口
factory.setPort(5672);
// 设置访问RabbitMQ服务的虚拟主机
factory.setVirtualHost("/");
// 设置访问RabbitMQ服务的用户
factory.setUsername("hyc");
// 设置访问RabbitMQ服务用户的密码
factory.setPassword("hyc");
// 设置是否自动重连
factory.setAutomaticRecoveryEnabled(true);
// 自动重连时间 单位毫秒
factory.setNetworkRecoveryInterval(3000);
// 通过连接工厂创建连接
Connection connection = factory.newConnection();
// 通过连接创建channel
Channel channel = connection.createChannel();
// 定义交换机的名称
String exchangeName = "dlx_exchange";
// 定义交换机的类型
String exchangeType = "topic";
// 定义队列的名称
String queueName = "dlx_queue";
// 定义路由键
String routingKey = "dlx.#";
// 通过channel声明一个交换机
channel.exchangeDeclare(exchangeName, exchangeType, true, false, null);
// 定义扩展参数
Map<String, Object> arguments = new HashMap<>();
// 在扩展参数里定义死信队列 名称为: dlx.exchange
arguments.put("x-dead-letter-exchange", "dlx.exchange");
// 通过channel声明一个队列 同时携带死信队列
channel.queueDeclare(queueName, true, false, false, arguments);
// 将交换机和队列进行绑定
channel.queueBind(queueName, exchangeName, routingKey);
// 通过channel声明死信队列
channel.exchangeDeclare("dlx.exchange", "topic", true, false, null);
channel.queueDeclare("dlx.queue", true, false, false, null);
channel.queueBind("dlx.queue", "dlx.exchange", "#");
channel.basicConsume(queueName, true, new MyCustomerConsumer(channel));
}
}