一.:入门(1~12)
1:MQ 相关概念:
1)什么是 MQ:(Message Queue)
-1:MQ,从字面意思上来看,本质是个队列,FIFO 先进先出。只不过队列中存放的内容是 Message 而已。
-2:还是一种跨进程的通信机制,用于上下游传递信息。
-3::在互联网架构中,MQ 是一种非常常见的,上下游 “逻辑解耦 + 物理解耦” 的消息通信服务。
-4::使用了 MQ 之后,消息发送上游,只需要依赖MQ,不用依赖其它服务。
2)为什么要用 MQ:(3)(流量削峰、应用解耦、异步处理)
-1:流量削峰:
-2:应用解耦:
-3:异步处理:
3)MQ 的分类:
-1:ActiveMQ:(老)
优点:单机吞吐量 万级,实效性 ms 级,可用性高。基于主从架构可实现高可用性。消息可靠性高,丢失数据概率较低。
缺点:官方社区现在对 ActiveMQ 5.x 维护越来越少,高吞吐量场景较少使用。
-2:Kafka:
-3:Rocket MQ:
-4:RabbitMQ:
1、介绍:是一个消息中间件(类似于快递站),用于 接收、存储、转发数据。 2007 年发布,是一个在 AMQP(高级消息队列协议)基础上完成的,可复用企业消息系统, 是当前最主流的消息中间件之一。
2、优点:erlang 语言,性能较好,吞吐量万级。 RabbitMQ 功能比较完备、健壮、稳定、易用、跨平台。支持多种语言。文档较全和更新活跃。
3、缺点:商业版需要收费,学习成本较高。
4)MQ 的 选择:
2:RabbitMQ:
1)RabbitMQ 的概念:见上
2)四大核心概念:
-1:生产者:产生数据,发送消息的程序。
-2:交换机:
a:接收来自生产者的消息,并将消息推送到队列中。
b:交换机必须确切的知道,如何处理它接收到的消息。是将这些消息推送到多个队列,还是把消息丢弃。由交换机类型来决定。
-3:队列:
a:队列是 RabbitMQ 内部使用的一种数据结构。尽管消息流经 RabbitMQ 和 应用程序,但是它们只能存储在队列中。
b:队列仅受 主机的内存 和 磁盘限制 的约束,本质上是一个大的消息缓冲区。
-4:消费者:
a:消费与接收,具有相同的含义。
b:消费者大多时候,是一个等待接收消息的程序。
c:同一个应用,既可以是生产者,也可以是消费者。
3)RabbitMQ 核心部分:(六大模式)(后面介绍)
4)各个名词介绍:
-1:Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker。
-2:Virtual Host:出于多租户和安全因素设计,把 AMQP 的基本组件,划分到一个虚拟分组中。 类似网络中的 namespace 概念。 当多个不用的用户,使用用一个 RabbitMQ Server 提供服务时,可以划分出多个 vhost,每个用户在自己的 vhost 创建 exchange / queue 等。
-3:Connection:publisher / consumer 和 broker 之间的 TCP 连接。
-4:Channel:
a:如果每一次访问 RabbitMQ,都建立一个 Connection,在消息量大的时候建立 TCP Connection 的开销将是巨大的,效率也低。
b:Channel 是在 Connection 内部建立的逻辑连接。 如应用程序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客户端 和 message broker 识别 channel,所以 channel 之间是完全隔离的。
c:Channel 作为轻量级的 Connection,极大减少了操作系统建立在 TCP Connection 的开销。
-5:ExChange:
a:message 到达 broker 的第一站。根据分发规则,匹配查询表中的 routing key,分发到 queue 中去。
b:通常的类型有:
direct(point-to-point)
topic(publish-subscribe)
fanout(multicast)
-6:Queue:消息最终被送到这里,等待 consumer 取走。
-7:Bingding:Exchange 和 Queue 之间的虚拟连接。binding 中,可以包含 routing key。binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据。
3:安装:
(安装参考文档)
1)下载 & 上传:
-1:下载地址 :
a:Erlang:(https://www.erlang.org/downloads)
b:RabbitMQ:(https://www.rabbitmq.com/download.html)
c:版本对应关系:(https://www.erlang.org/downloads)
-2:文件上传到:(/usr/local/software/rabbitmq/)
2)安装:erlang:
// 安装依赖
yum -y install gcc glibc-devel make ncurses-devel openssl-devel xmlto perl wget gtk2-devel binutils-devel
// 解压 erlang
tar -zxvf otp_src_24.2.2.tar.gz
// 新建 erlang 安装目录(bin 目录)
mkdir /usr/local/bin/erlang
// 回到解压后目录,编译安装
./configure --prefix=/usr/local/bin/erlang
make & make install
// 配置环境变量 & 刷新
vim /etc/profile
“export PATH=$PATH:/usr/local/bin/erlang/bin”
source /etc/profile
// 测试
erl :(进入)
halt(). :(退出)
3)安装 RabbitMQ:
// 解压
xz -d rabbitmq-server-generic-unix-3.9.13.tar.xz
tar -xvf rabbitmq-server-generic-unix-3.9.13.tar
// 由于是tar.xz格式的所以需要用到xz,没有的话就先安装
yum install -y xz
// 配置环境
"export PATH=$PATH:/usr/local/rabbitmq/rabbitmq/sbin"
4)启动:
// 启动
./rabbitmq-server detached // ./rabbitmq-server start
// 停止
./rabbitmqctl stop
// 状态
./rabbitmqctl status
5)开启web插件 & 访问:
./rabbitmq-plugins enable rabbitmq_management
// 访问
http://192.168.124.11:15672/。(默认 guest,权限不足不可登陆)
6)用户管理:(添加用户并设置权限)
// 查看所有用户
./rabbitmqctl list_users
// 添加一个用户
./rabbitmqctl add_user admin 123
// 设置用户权限
./rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"
// 表示:用户 admin ,具有 vhost1 这个 virtual host 中所有资源的 (配置、写、读)权限。
// 查看用户权限
./rabbitmqctl list_user_permissions admin
// 设置tag
./rabbitmqctl set_user_tags admin administrator
// 删除用户(安全起见,删除默认用户)
./rabbitmqctl delete_user admin
二.:核心部分(13~48)
Hello World:(简单模式)
Work queues:(工作队列模式)
Publish / Subscribe:(发布/订阅模式)
Routing:(路由模式)
Topics:(主题模式)
Publisher Confirms:(发布确认模式)
1:Hello World:(简单模式):
1)说明:
2)依赖:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.4.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
3)消息生产者:
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 3:09 下午
*/
public class Producer01 {
public static final String QUEUE_NAME = "hello_world";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.124.11");
factory.setUsername("admin");
factory.setPassword("123");
// 创建连接
Connection connection = factory.newConnection();
System.out.println("连接:" + connection);
// 创建信道
Channel channel = connection.createChannel();
// 生成一个队列(exclusive:true可以多个消费者消费,false只能一个消费者消费)
Map<String, Object> map = new HashMap<>();
AMQP.Queue.DeclareOk declareOk = channel.queueDeclare(QUEUE_NAME, true, false, true, map);
// 发消息
String message = "hello world";
channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
System.out.println("消息发送完毕");
}
}
4)消息消费者:
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 3:29 下午
*/
public class comsumer01 {
public static final String QUEUE_NAME = "hello_world";
public static void main(String[] args) throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.124.11");
factory.setUsername("admin");
factory.setPassword("123");
// 创建新的连接
Connection connection = factory.newConnection();
// 创建信道
Channel channel = connection.createChannel();
/**
* 消费者 接收消息
*/
// 声明接收消息
DeliverCallback deliverCallback = (consumerTag, message) -> {
System.out.println("--message:" + new String(message.getBody()));
};
// 取消消息时回掉
CancelCallback cancelCallback = consumerTag -> {
System.out.println("consumerTag:" + consumerTag);
};
channel.basicConsume(QUEUE_NAME, true, deliverCallback, cancelCallback);
}
}
2:Work queues:(工作队列模式)
1):轮询分发消息:(1 生产者、2消费者,查看他们如何工作)
-1:抽取工具类:
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 4:17 下午
*/
public class Utils {
public static Channel getChannel() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("192.168.124.11");
factory.setUsername("admin");
factory.setPassword("123");
Connection connection = factory.newConnection();
return connection.createChannel();
}
}
-2:启动两个消费者线程:
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 4:20 下午
*/
public class Consumer01 {
public static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) {
Channel channel = Utils.getChannel();
new Thread(() -> {
try {
channel.basicConsume(QUEUE_NAME, false,
(consumerTag, message) -> System.out.println(Thread.currentThread().getName() + ":消费者 接收消息:" + new String(message.getBody())),
consumerTag -> System.out.println("消息被取消回掉逻辑:" + consumerTag));
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
-3:启动一个发送线程:
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 4:19 下午
*/
public class Produce01 {
public static final String QUEUE_NAME = "work_queue";
public static void main(String[] args) throws IOException {
Channel channel = Utils.getChannel();
// 生成一个队列(exclusive:true可以多个消费者消费,false只能一个消费者消费)
Map<String, Object> map = new HashMap<>();
channel.queueDeclare(QUEUE_NAME, true, false, true, map);
// 发消息
String message = "work_queue:";
for (int i = 0; i < 100; i++) {
channel.basicPublish("", QUEUE_NAME, null, String.valueOf(message + i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发送完毕");
}
}
-4:结果展示:(两个消费者,轮询消费)
2)消息应答:
-1:概念:
-2:自动应答:
-3:消息应答方法:
-4:Multiple 解释:(批量)(建议 false)
-5:消息自动重新入队:
-6:消息手动应答代码:(消息在手动应答中是不丢失的)(1生产者 & 2消费者)
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/7 4:19 下午
*/
public class Produce01 {
public static final String QUEUE_NAME = "work_queue1";
public static void main(String[] args) throws IOException {
Channel channel = Utils.getChannel();
// 生成一个队列(exclusive:true可以多个消费者消费,false只能一个消费者消费)
Map<String, Object> map = new HashMap<>();
channel.queueDeclare(QUEUE_NAME, false, false, false, map);
// 发消息
String message = "work_queue:";
for (int i = 0; i < 100; i++) {
channel.basicPublish("", QUEUE_NAME, null, String.valueOf(message + i).getBytes(StandardCharsets.UTF_8));
}
System.out.println("消息发送完毕");
}
}
/**
* @author zhangxudong@chunyu.me
* @date 2022/3/8 10:24 上午
*/
public class Consumer02 {
public static final String QUEUE_NAME = "work_queue1";
public static void main(String[] args) {
Channel channel =