RabbitMQ
概述
原始操作系统的TCP/IP协议满足不了项目需求
添加中间件 添加头 -> 满足要求
中间件
- 特点
- 高可用
- 可靠性
- 分布式消息中间件
- ActiveMQ:老派
- RabbitMQ:spring同源 支持度高
- Kafka:开源 性能最高 最接近底层
- RocketMQ:慎选
- 场景
- 消息中间件监控数据
- 异步数据传输场景
- 削峰填谷场景
- 任务调度场景
- 海量数据同步场景
- 分布式事务场景
- 日记管理场景
- 大数据分析场景
- 考量
- AMQP
- MQTT
- 持久化设计
- Kafka协议
- 消息分发设计
- 高可用
- 可靠性
- 容错
- 负载均衡中间件
- Nginx
- LVS负载均衡软件
- KeepAlive -> 高可用
- CDN -> 加速
- 缓存中间件
- MemCache
- Redis
- 数据库中间件
- Mycat
- Shardingjdbc
- 案例分析
- 异步数据保存
- 订单数据的消息分发
- 分布式事务
- 消息的容错
- 分布式锁
- 分布式会话
- 分库分表
架构
- 单体架构
- 耦合度太高
- 运维成本过高
- 不易维护
- 服务器的成本太高
- 升级架构的复杂度变高
- 分布式架构:一个请求由多个系统来处理
- 学习成本高 技术栈过多
- 运维成本 服务器成本高
- 人员成本
- 项目的复杂度上升
- 错误和容错率
- 占用的端口和通讯的选择成本
- 安全性的考虑被迫得选择RMI/MQ服务端通讯
MQ消息队列:负责消息的接受、存储和传递,它的性能要高于普通的服务和技术
消息队列协议
-
AMQP:Erlang(底层C) 开发
- 1.分布式事务支持
- 2.消息的持久化支持
- 高性能和高可靠性的消息处理优势
-
MQTT:
- 特点:
- 轻量
- 结构简单
- 传输块、不支持事务
- 没有持久化设计
- 应用场景:适用于计算能力有限、低带宽、网络不稳定的场景
- 特点:
-
OpenMessage:
- 结构简单
- 解析速度快
- 支持事务和持久化设计
-
KafKa:
- 结构简单
- 解析速度快
- 无事务支持
- 有持久化设计
消息队列的持久化
数据不存在内存中 -> 写入磁盘中 持久化保存
消息的分发策略
角色:
- 1.生产者
- 2.存储消息
- 3.消费者
RabbitMQ的角色分类
1:none
- 不能访问management plugin
2:management:查看自己相关节点信息
- 列出自己可以通过AMQP登入的虚拟机
- 查看自己的虚拟机节点 virtual hosts的queues,exchanges和bindings信息
- 查看和关闭自己的channels和connections
- 查看有关自己的虚拟机节点virtual hosts的统计信息。包括其他用户在这个节点virtual hosts 中的活动信息。
3:Policymaker
- 包含management所有权限
- 查看和创建和删除自己的virtual hosts所属的policies和parameters信息。
4:Monitoring
- 包含所有management所有权限
- 罗列出所有的virtual hosts 包括不能登录的virtual hosts
- 查看其他用户的connections和channel信息
- 查看节点级别的数据如clustering和memory使用情况
- 查看所有的virtual hosts的全局统计信息
5:Administrator
- 最高权限
- 可以创建和删除virtual hosts
- 可以查看,创建和删除users
- 查看创建permissions
- 关闭所有用户的connections
模式
- Simple:
- 1.创建连接工程
- 2.创建连接Connection
- 3.通过连接获取通道Channel
- 4.通过通道创建交换机、声明队列、绑定关系、路由key、发送消息和接受消息
- 5.准备消息内容
- 6.发送消息给队列queue
- 7.关闭连接
- 8.关闭通道
(1)yum 包更新到最新
> yum update
(2)安装需要的软件包, yum-util 提供yum-config-manager功能,另外两个是devicemapper驱动依赖的
> yum install -y yum-utils device-mapper-persistent-data lvm2
(3)设置yum源为阿里云
> yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
(4)安装docker
> yum install docker-ce -y
(5)安装后查看docker版本
> docker -v
(6) 安装加速镜像
sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://0wrdwnn6.mirror.aliyuncs.com"]
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker
(7) 获取rabbit镜像:
> docker pull rabbitmq:management
(8)创建并运行容器
> docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management
(9)查看日志
> docker logs -f myrabbit
(10)查看服务
> docker ps -a
(11)关闭容器
> docker 08d03ae27334 stop
为什么RabbitMq是基于通道而不是基于连接?
- Connection表示到消息代理的真实TCP连接,而Channel是其中的虚拟连接(AMQP连接)。这样,您可以在应用程序内部使用任意数量的(虚拟)连接,而不会使TCP连接使代理过载
- 您可以为所有内容使用一个Channel。但是,如果您有多个线程,建议为每个线程使用不同的Channel
可以存在没有交换机的队列么?
- 不可以,没有为队列指定交换机时,同一指向默认交换机
RabbitMq核心组件
- 每一个virtual Host里还有routinkey -> 条件 分发给不同的消费者 (对应非广播场景) -> 过滤
- RabbitMQ 消息传递模型的核心思想是生产者从不直接向队列发送任何消息 实际上,生产者经常甚至根本不知道消息是否会被传送到任何队列
- 生产者只能将消息发送到交换。交换是一件非常简单的事情。一方面它接收来自生产者的消息,另一方面将它们推送到队列中。交易所必须确切地知道如何处理它收到的消息。它应该附加到特定队列吗?它应该附加到许多队列中吗?或者它应该被丢弃。其规则由交换类型定义 。
工作模式
简单模式
- simple
- 应用场景:手机短信、邮件单发
package com.whlll.rabbitmq.simple;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author dell
* @date 2021/9/28 20:16
*/
public class SimpleMode {
private Channel channel;
@Before
public void channelInit() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("47.115.207.246");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
channel = connection.createChannel();
}
//声明组件、交换机和队列、简单模板案例、交换机使用默认交换机,队列需要声明
@Test
public void myQueueDeclare() throws IOException {
channel.queueDeclare("simple",
false,//队列是否持久化
false,//队列是否专属
false,//队列是否自动删除,从第一个消费端监听队列开始
//计算,到最后一个消费端断开连接,队列就会自动删除
null);//map类型 key值固定一批属性
System.out.println("队列创建成功");
}
//发送消息到队列 生产端 永远不会把消息直接发给队列,发给交换机
@Test
public void send() throws IOException {
String msg = "whlll你好啊";
byte[] msgByte = msg.getBytes(StandardCharsets.UTF_8);
//将消息发给(AMQP DEFAULT)交换机 名字""
channel.basicPublish(
"",//发送给的交换机的名字,默认为空
"simple",//设置路由key
null,//发送消息时携带的参数
msgByte//消息体
);
}
@Test
public void consume() throws IOException {
channel.basicConsume("simple", false,
new DeliverCallback() {
/**
* 传递回调对象. 消息就在这个对象里
*
* @param s 当前消费端id
* @param delivery 封装了消息的对象
* @throws IOException
*/
@Override
public void handle(String s, Delivery delivery) throws IOException {
//从消息对象中拿到信息
byte[] body = delivery.getBody();
System.out.println(new String(body));
//如果autoAck false说明消费玩消息,需要手动确认
channel.basicAck(
delivery.getEnvelope().getDeliveryTag(),
false);
}
}, new CancelCallback() {
/**
* 当连接对象channel 主动关闭消费端连接时 cancel 这个方法才会被调用
* @param s 消费端id
* @throws IOException
*/
@Override
public void handle(String s) throws IOException {
}
});
//使用while true 将线程卡死,否则看不到消息消费逻辑
while (true);
}
}
Work模式
- work queues
- 工作队列(又名:任务队列)背后的主要思想是避免立即执行资源密集型任务而不得不等待它完成。相反,我们安排任务稍后完成。我们将一个任务封装 成一条消息并发送到队列中。在后台运行的工作进程将弹出任务并最终执行作业。当您运行许多工人时,任务将在他们之间共享
- 应用场景:抢红包、资源分配
轮询分发
- 轮询分发(均匀分给每一个消费者):默认为轮询 可以不设置为手动应答
package com.whlll.rabbitmq.work;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author dell
* @date 2021/10/5 15:35
*/
public class WorkMode {
private Channel channel;
@Before
public void channelInit() throws IOException, TimeoutException {
ConnectionFactory factory = new ConnectionFactory();
factory.setHost("47.115.207.246");
factory.setPort(5672);
factory.setUsername("admin");
factory.setPassword("admin");
Connection connection = factory.newConnection();
channel = connection.createChannel();
}
@Test
public void myQueueDeclare() throws IOException {
channel.queueDeclare(
"work",
false,
false,
false,
null);
System.out.println("队列申明成功");
}
@Test
public void send() throws IOException {
String msg = "hahah whlll";
byte[] msgByte = msg.getBytes(StandardCharsets.UTF_8);
channel.basicPublish(
"",
"work",
null,
msgByte
);
}
//消费端
@Test
public void consume01() throws IOException {
channel.basicConsume("work", false,
new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
byte[] body = delivery.getBody();
System.out.println("消费者01:" + new String(body));
//如果autoAck false说明消费完消息,需要手动确认
channel.basicAck(delivery.getEnvelope().getDeliveryTag(),
false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
while (true);
}
@Test
public void consume02() throws IOException {
channel.basicConsume("work", false,
new DeliverCallback() {
@Override
public void handle(String s, Delivery delivery) throws IOException {
byte[] body = delivery.getBody();
channel.basicAck(
delivery.getEnvelope().getDeliveryTag(),
false);
}
}, new CancelCallback() {
@Override
public void handle(String s) throws IOException {
}
});
while (true);
}
}
公平分发
- 公平分发(谁新能好优先分给谁处理):配置Qos并谁设置为手动应答
- Qos:每次从队列读取的消息条数(根据具体的设备资源情况而定)
package com.whlll.rabbitmq.work;
import com.rabbitmq.client.*;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;
/**
* @author dell
* @date 2021/10/5 15:35
*/
public class WorkMode {
private Channel channel;
@Before
public void channelInit()