RabbitMQ总结:
1、RabbitMQ的 http访问端口:15672 项目访问端口:5672
2、AMQ协议:
生产者、消费者、虚拟主机、交换机、队列
过程:
生产者与消费者都要绑定同一个虚拟主机账户
生产者生产消息,通过channel 将消息放到绑定的交换机或者quene队列中
消费者通过通道绑定与生产者相同的交换机或者队列,==实现监听==,一旦有消息进入,会从交换机或者队列中取出消息消费
3、RabbitMQ参数:
队列相关:队列持久化,通道独占队列,是否自动删除
消息相关:消息自动确认,消息的消费条数
4、RabbitMQ的五种消息模式
直连模式
工作模式:能者多劳:关闭消息自动确认,消息每次消费条数设置为1
广播模式:所有消费者同时拿到消息消费
路由模式:与消息中的路由信息相同的消费者才能拿到消息
订阅模式:引入通配符,消息的路由信息符合消费者通配符规则的消费者消费消息
5、RabbitMQ的应用场景:
异步处理:登陆发送手机验证码、邮箱验证等
解耦:订单系统、库存系统之间
流量削峰:秒杀=设置消息存储上限,超过消息上限的消息都回丢失,存储的消息即为秒杀成功的用户
6、RabbitMQ集群
普通集群:(主从关系)
主节点提供服务,从节点只是备份作用
主节点宕机之后,集群不可用
主节点宕机时的队列会同步到从节点
如果主节点队列中的消息持久化了,那么主节点恢复之后,会与从节点同步队列,并将持久化的消息放入队列中
镜像集群:
可以将集群中的多个节点设置为镜像
设置为镜像的节点可以提供消息服务
未设置镜像的节点还是备份节点
MQ消息队列
MQ产品:
activeMQ
:信息吞吐量小,老牌消息队列,中小企业使用
Kafka
:只追求性能,对于事物和数据的一致性几乎没有
RocketMQ
:开源版本也是不支持事务,付费版支持
RabbitMQ
:对数据的一致性、稳定性、和可靠性都是最好的
RabbitMQ
安装RabbitMQ
安装:
1、rabbitMQ是基于erLang语言开发的,首先安装erLang依赖包
rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm
2、安装erLang内存管理的依赖
rpm -ivh socat-1.7.3.2-2.el7.x86_64.rpm
3、安装rabbitMQ
rpm -ivh rabbitmq-server-3.7.18-1.el7.noarch.rpm
4、配置rabbitMQ的配置文件
在etc/rabbitmq/下应该有一个rabbitmq.config的配置文件
find / -name rabbitmq.config.example
找到这个文件,将这个文件复制到etc/rabbitmq/下,命名为rabbitmq.config
修改这个文件
:set nu 显示行号
修改第61行,将注释去掉,并且去掉后面的逗号
允许来并用户在网络上访问
5、启动rabbitmq中的插件管理
rabbitmq-plugins enable rabbitmq_management
6、启动rabbitmq
rabbitmq-plugins enable rabbitmq_management
ystemctl start rabbitmq-server
注:如果你的主机名是数字,那么启动会报错,将主机名修改成英文主机名即可
临时修改主机名
hostname newHostname
7、访问web管理页面
如果访问不到需要关闭linux的防火墙
如果提示:User can only log in via localhost
解决方案:
找到这个文件rabbit.app
C:\Program Files\RabbitMQ Server\rabbitmq_server-3.7.13\ebin\rabbit.app
将第39行:{loopback_users, [<<”guest”>>]},
改为:{loopback_users, []},
然后重启服务
原因:rabbitmq从3.3.0开始禁止使用guest/guest权限通过除localhost外的访问
默认端口15672
RabbitMQ管理命令行
#1、服务启动相关
systemctl start|restart|stop|status rabbitmq-server
#2、管理命令行 用来在不使用web管理页面情况下操作RabbitMQ
rabbitmqctl help
#3、插件管理命令行
rabbitmq-plugins enable|list|disable
rabbitmq-plugins enable rabbitmq-management 启动web管理页面插件
rabbitmq-plugins list 插件列表
AMQ协议
1、生产者通过通道将消息放到虚拟主机中Virtual Host,虚拟主机将相当于一个数据库,每一个生产者都要有一个自己的虚拟主机,这样多个应用之间可以做到互不影响。
2、访问虚拟主机,需要将用户与虚拟主机绑定,默认的来宾账户guest可以访问所有的虚拟主机
3、消息可以放到交换机中,根据不同的规则,分配到不同的队列中;有的模式不需要使用交换机
RabbitMQ的应用
1、新建项目
2、导入依赖
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.7.2</version>
</dependency>
第一种模式:直连模式
生产者
public class Provider {
//生产消息
@Test
public void testSendMessage() throws IOException, TimeoutException {
//创建mq的连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost("192.168.168.132");
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/ems");
//设置访问虚拟主机的用户名密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
//获取连接对象
Connection connection = connectionFactory.newConnection();
//通过连接中的通道
Channel channel = connection.createChannel();
//通道绑定对应的消息队列
//参数1:队列名称,如果不存在,自动创建
//参数2:用来定义队列是否要持久化 true持久化,false 不持久化
//参数3:exclusive 是否独占队列 true独占队列 false不独占 允许其他通道绑定队列
//参数4:autoDelete 是否在消费完成后自动删除队列, true 删除 false 不删除
//参数5:额外的附加参数
channel.queueDeclare("hello", false, false, false, null);
//发布消息
//参数1:交换机名称 参数2:队列名称 参数3:传递消息额外设置 参数4:消息的具体内容
channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
channel.close();
connection.close();
}
}
消费者
public class Consumer {
//消费消息
public static void main(String[] args) throws IOException, TimeoutException {
//创建mq的连接工厂
ConnectionFactory connectionFactory = new ConnectionFactory();
//设置连接rabbitmq主机
connectionFactory.setHost("192.168.168.132");
//设置端口号
connectionFactory.setPort(5672);
//设置连接哪个虚拟主机
connectionFactory.setVirtualHost("/ems");
//设置访问虚拟主机的用户名密码
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
//获取连接对象
Connection connection = connectionFactory.newConnection();
//通过连接中的通道
Channel channel = connection.createChannel();
//通道绑定对应的消息队列
//参数1:队列名称,如果不存在,自动创建
//参数2:用来定义队列是否要持久化 true持久化,false 不持久化
//参数3:exclusive 是否独占队列 true独占队列 false不独占 允许其他通道绑定队列
//参数4:autoDelete 是否在消费完成后自动删除队列, true 删除 false 不删除
//参数5:额外的附加参数
channel.queueDeclare("hello", false, false, false, null);
//消费消息
//参数1:消费哪个队列的消息
//参数2:开始消息的自动确认机制
//参数3:消费时的回调接口
channel.basicConsume("hello", true, new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(body);
}
});
//因为consumer是需要一直对队列进行监听的,所以不建议关闭通道和连接
// 只要生产者在队列中放进去一条消息,消费者就会立马进行消费
}
}
因为生产者和消费者都需要获取连接,这样产生了大量的代码冗余,我们可以将代码提炼出来,封装成一个工具类
public class RabbitMQUtils {
//工厂在项目加载时只创建一次
private static ConnectionFactory connectionFactory;
static {
connectionFactory = new ConnectionFactory();
connectionFactory.setHost("192.168.168.132");
connectionFactory.setPort(5672);
connectionFactory.setVirtualHost("/ems");
connectionFactory.setUsername("ems");
connectionFactory.setPassword("123");
}
public static Connection getConnection() {
try {
return connectionFactory.newConnection();
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
return null;
}
public static void closeConnection(Channel channel, Connection connection) {
try {
if (channel != null) {
channel.close();
}
if (connection != null) {
connection.close();
}
} catch (IOException e) {
e.printStackTrace();
} catch (TimeoutException e) {
e.printStackTrace();
}
}
}
RabbitMQ的API参数细节
通道绑定队列,通道可以绑定多个队列,单不一定会往队列里发送消息
channel.queueDeclare("hello", false, false, false, null);
通道发布消息到队列,通过此方法通道将消息发布到对应的队列中
channel.basicPublish("", "hello", null, "hello rabbitmq".getBytes());
队列的持久化
channel.queueDeclare("hello", true, false, false, null);
队列中消息持久化设置为true时,即使重启rabbitmq服务,队列也能保留,但是如果队列中发布的消息没有持久化,则重启之后队列中的消息会小时
发布消息的持久化
channel.basicPublish("", "hello", MessageProperties.PERSISTENT_TEXT_PLAIN, "hello rabbitmq".getBytes());
MessageProperties.PERSISTENT_TEXT_PLAIN 就可以将消息持久化
//通道绑定对应的消息队列
//参数1:队列名称,如果不存在,自动创建
//参数2:用来定义队列是否要持久化 true持久化,false 不持久化
//参数3:exclusive 是否独占队列 true独占队列 false不独占 允许其他通道绑定队列
//参数4:autoDelete 是否在消费完成后自动删除队列, true 删除 false 不删除 消费者会一直监听这个队列,只有当消费者关掉服务的时候,队列才会自动删除
//参数5:额外的附加参数
channel.queueDeclare("hello", false, false, false, null);
第二种模式:工作队列模式
生产者
public class Provider {
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定队列
channel.queueDeclare("work",true,false,false,null);
//发布队列消息
for (int i = 1; i <= 10; i++) {
channel.basicPublish("","work",null,(i+"hello work quene").getBytes());
}
//关闭连接
RabbitMQUtils.closeConnection(channel,connection);
}
}
消费者-1
public class Consumer1 {
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = RabbitMQUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
channel.basicQos(1);//每次消费一个队列中的消息
//绑定队列
channel.queueDeclare("work",true,false,false,null);
//消费队列消息
//参数1:队列名称 参数2:消息自动确认 消费者向rabbitmq自动确认消息消费,不管消费者有没有消费完,队列都将删除确认过消费的消息
channel.basicConsume("work",false,new DefaultConsumer(channel){
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费者-1:"+new String(body));
//手动确认 参数1:手动确认消息标识 参数2:false 每次确认一个
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
消费者-2
public class Consumer2 {
public static void main(String[] args) throws IOException {
//获取连接
Connection connection = RabbitMQUtils.getConnection();
//获取通道
final Channel channel = connection.createChannel();
channel.basicQos(1);
//绑定队列
channel.queueDeclare("work",true,false,false,null);
//消费队列消息
channel.basicConsume("work",false,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));
channel.basicAck(envelope.getDeliveryTag(),false);
}
});
}
}
对于消费者1 和消费者2,队列默认是平均配分消息的
这样就会出现一种情况:
如果消费者1消费比较慢,那么消费者2消费完消息之后,消费者1还在慢慢的消费分配给他的消息
这样就可能造成消费者1的服务崩掉或者消费者2服务的浪费
实现能者多劳:
消息的自动确认机制
//消费消息
channel.basicConsume("work",true,new DefaultConsumer(channel))
当第二个参数为true时,消息的确认机制为 自动确认,消费者自动向rabbitmq确认消息消费,不管有没有消费完,
队列都会将已经确认消费的消息删除调,这样就会产生一个问题:如果消费能力弱的消费者,在消费一半信息的
时候突然宕机,那么剩下一般的消息,就会丢失。这是我们不允许的;
解决上述问题
1、关闭消息自动 确认机制
channel.basicConsume("work",false,new DefaultConsumer(channel))
2、告诉消息队列在消费消息时,不能一次性的把所有消息都分配给消费者,要一个个的消费
channel.basicQos(1);//每次消费一个队列中的消息
第三种模式:广播模式 fanout
例如购物车结算的时候要调用库存系统和订单系统 ,就可以用到订阅模式
生产者
public class Provider {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//将通道声明指定的交换机,没有则创建新的交换机 参数1:交换机名称 参数2:交换机类型
channel.exchangeDeclare("logs", "fanout");
//发布消息
channel.basicPublish("logs", "", null, "fanout : message".getBytes());
//关闭资源
RabbitMQUtils.closeConnection(channel, connection);
}
}
消费者1、2、3
public class Consumer1 {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
Channel channel = connection.createChannel();
//通道绑定交换机
channel.exchangeDeclare("logs","fanout");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定交换机和队列
channel.queueBind(queue,"logs","");
//消费
channel.basicConsume(queue,true,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));
}
});
}
}
第四种模式:路由模式(direct)
生产者
public class Provider {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定交换机,指定交换机类型为路由模式 参数1:交换机名称 参数2:交换机类型:路由模式
channel.exchangeDeclare("logs_direct","direct");
//声明路由名称
String routingKey="info";
//消费 参数2:指定消息发布的路由名称
channel.basicPublish("logs_direct",routingKey,null,("这是direct模型发布的基于Routing key为["+routingKey+"]").getBytes());
//关闭资源
RabbitMQUtils.closeConnection(channel,connection);
}
}
消费者1
public class Consumer1 {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定交换机,指定交换机类型为路由模式 参数1:交换机名称 参数2:交换机类型:路由模式
channel.exchangeDeclare("logs_direct","direct");
//临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机绑定 可以绑定多个
channel.queueBind(queue,"logs_direct","error");
channel.queueBind(queue,"log_direct","info");
channel.queueBind(queue,"log_direct","warning");
channel.basicConsume(queue,true,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));
}
});
}
}
消费者2
public class Consumer2 {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定交换机,指定交换机类型为路由模式 参数1:交换机名称 参数2:交换机类型:路由模式
channel.exchangeDeclare("logs_direct","direct");
//临时队列
String queue = channel.queueDeclare().getQueue();
//绑定队列和交换机绑定
channel.queueBind(queue,"logs_direct","error");
channel.basicConsume(queue,true,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));
}
});
}
}
第五种模式:订阅模式(动态路由)topics
生产者
public class Provider {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定交换机,指定交换机类型为路由模式 参数1:交换机名称 参数2:交换机类型:动态路由模式
channel.exchangeDeclare("topics", "topic");
//声明路由名称
String routingKey = "user.save.result";
//消费 参数2:指定消息发布的路由名称
channel.basicPublish("topics", routingKey,null,("这里是topic动态路由模型,routingkey:["+routingKey+"]").getBytes());
//关闭资源
RabbitMQUtils.closeConnection(channel,connection);
}
}
消费者
public class Consumer1 {
public static void main(String[] args) throws IOException {
//获取连接对象
Connection connection = RabbitMQUtils.getConnection();
//获取通道
Channel channel = connection.createChannel();
//绑定交换机,指定交换机类型为路由模式 参数1:交换机名称 参数2:交换机类型:路由模式
channel.exchangeDeclare("topics","topic");
//临时队列
String queue = channel.queueDeclare().getQueue();
//临时队列和交换机绑定 动态通配符形式绑定
channel.queueBind(queue,"topics","user.*");//*代表一个单词
channel.queueBind(queue,"topics","user.#");//#代表多个单词
channel.basicConsume(queue,true,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));
}
});
}
}
Springboot整合RabbitMQ
Springboot自带整合RabbitMQ
新建项目时,选择message下的springboot RabbitMQ的依赖即可
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!--springboot自带rabbit的测试依赖-->
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit-test</artifactId>
<scope>test</scope>
</dependency>
配置RabbitMQ地址:
spring:
rabbitmq:
host: 192.168.168.132
port: 5672
username: ems
password: 123
virtual-host: /ems
1、直连模式
生产者
//直连模式
@RequestMapping("/hello")
public String hello() {
rabbitTemplate.convertAndSend("hello", "hello,world");
return "success";
}
消费者
@Component
//默认持久化的、非独占的、不是自动删除的队列
//@RabbitListener(queuesToDeclare = @Queue(value = "hello",durable = "true",autoDelete = "true"))
@RabbitListener(queuesToDeclare = @Queue("hello"))
public class HelloCustomer {
@RabbitHandler//标注这个方法是拿到消息之后的回调函数
public void callBack(String message) {
System.out.println("message:" + message);
}
}
2、工作模式
生产者
//工作模式
@RequestMapping("/work")
public String work() {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("work", "work模式:" + i);
}
return "success";
}
消费者
@Component
public class WorkConsumer {
//标注监听,代表开启监听,并且标识这个方法为这个队列的消息回调函数
@RabbitListener(queuesToDeclare = @Queue("work"))
public void consumer1(String message){
System.out.println("消费者1:"+message);
}
@RabbitListener(queuesToDeclare = @Queue("work"))
public void consumer2(String message){
System.out.println("消费者2:"+message);
}
}
3、广播模式
生产者
//广播模式
@RequestMapping("/fanout")
public String fanout() {
rabbitTemplate.convertAndSend("logs", "", "fanout模式:");
return "success";
}
消费者
@Component
public class FanoutConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//不指定名称即创建临时队列
exchange = @Exchange(value = "logs",type = "fanout")//参数1:交换机名称 参数2:交换机类型
)
})
public void consumer1(String message){
System.out.println("消费者1:"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,//不指定名称即创建临时队列
exchange = @Exchange(value = "logs",type = "fanout")//参数1:交换机名称 参数2:交换机类型
)
})
public void consumer2(String message){
System.out.println("消费者2:"+message);
}
}
4、路由模式
生产者
//路由模式
@RequestMapping("/direct")
public String direct() {
//参数1:交换机名称,参数2:路由名称 参数3:消息内容
rabbitTemplate.convertAndSend("direct", "info", "路由为info的direct模式:");
return "success";
}
消费者
@Component
public class DirectConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "direct", type = "direct"),
key = {"error"}
)
})
public void consumer1(String message) {
System.out.println("消费者1:"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "direct", type = "direct"),
key = {"info","error","warning"}
)
})
public void consumer2(String message) {
System.out.println("消费者2:"+message);
}
}
5、订阅模式
生产者
//订阅模式
@RequestMapping("/topic")
public String topic() {
//参数1:交换机名称,参数2:路由名称 参数3:消息内容
rabbitTemplate.convertAndSend("topic", "user.order.result", "路由为info的direct模式:");
return "success";
}
消费者
@Component
public class TopicConsumer {
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "topic",type = "topic"),
key ={"user.#"}
)
})
public void consumer1(String message){
System.out.println("消费者1:"+message);
}
@RabbitListener(bindings = {
@QueueBinding(
value = @Queue,
exchange = @Exchange(value = "topic",type = "topic"),
key ={"user.*"}
)
})
public void consumer2(String message){
System.out.println("消费者2:"+message);
}
}
RabbitMQ的应用场景
1、异步处理
2、应用解耦
3、流量削峰
RabblitMQ的集群
1、普通集群(副本集群)
架构图
核心解决问题:当集群中的master宕机,MQ集群不可用,但可以对Queue中的信息进行备份,不至于丢失
1、主节点上的队列也会同步到从节点上,但是主节点上队列的消息,不会同步到从节点,队列中的消息,建议持久化
2、主节点不宕机的情况下,消费者也可以连接从节点去消费消息,从节点消费的消息也是从主节点中拿过来的,消费之后,主节点中的消息也会同步消失
搭建集群
- 准备一台虚拟机服务器,安装好RabbitMQ安装文件,克隆两台服务器
- 要保证三台服务器的erLang.cookie文件一致,才能搭建集群 文件位置在
/var/lib/rabbitmq/.erlang.cookie
- 启动三台RabbitMQ服务
- 查看集群状态
rabbitmqctl cluster_status
- 加入集群
rabbitmqctl join_cluster rabbit@192.168.168.132
- 启动MQ
rabbitmqctl start_app
2、镜像集群(重点常用)
架构图
镜像队列就是将队列在三个节点之间设置主从关系,消息会在三个节点之间进行自动同步,如果其中一个节点不可用,并不会导致消息丢失,
或者服务不可用的情况,提升MQ集群整体的高可用性
策略说明:
镜像模式是在普通集群的基础上,设置一下策略即可成为镜像集群,多个节点都可以同时提供服务
策略:
1、查看策略
rabbitmqctl list_policies
2、设置策略
rabbitmqctl set_policy ha-all '^hello' '{"ha-mode":"all","ha-sync-mode":"automatic"}'
说明:设置策略名为“ha-all”,匹配所有队列名称以“hello”开头的队列,镜像参数:镜像队列模式为:“all”,镜像同步方式为:“automatic”
设置策略之后
说明:因为设置的策略是“all”,所有的节点都设置镜像,都对外提供服务
3、删除策略
rabbitmqctl clear_policy 策略名称