目录
一、rabbitmq的相关概念
RabbitMQ是流行的开源消息队列系统,用erlang语言开发。RabbitMQ是 AMQP(高级消息队列协议)的标准实现。
概念说明:
Broker:简单来说就是消息队列服务器实体。
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息队列载体,每个消息都会被投入到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来。
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以开设多个vhost,用作不同用户的权限分离。
producer:消息生产者,就是投递消息的程序。
consumer:消息消费者,就是接受消息的程序。
channel:消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务。
原文链接https://blog.csdn.net/qq_26597927/article/details/95353748https://blog.csdn.net/qq_26597927/article/details/95353748
二、相关步骤(整合springbbot)
1、配置rabbitmq的文件
2、配置configuration文件,配置连接
3、创建exchange
4、创建队列
5、绑定队列和exchange
6、创建生产者
7、创建消费者
8、完成消费
三、具体实现和对应的原理
以下rabbitmq简称为mq
1、配置rabbitmq的文件
首先我们得知道mq的端口是5672,不是15672,15672的是web的可视化的mq。就是它
接下来就是配置文件的设置
一般是这样的
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
host 为地址,port为端口。username为用户名,password为密码(guest为其默认的初始账号和密码),virtual-host 为虚拟地址(类似为不同的数据库,其中的exchange和queue互不干扰)
如何新增virtual-host
这样你本地mq中就会有这个lib_sys,如果你不去新增直接去连接会连接失败的。
2、配置configuration文件,配置连接
接下来就是配置configuration文件。
@Configuration
public class RabbitmqConfig {
private final RabbitProperties rabbitProperties;
public RabbitmqConfig(RabbitProperties rabbitProperties) {
this.rabbitProperties = rabbitProperties;
}
@Bean
public ConnectionFactory getConnectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
//用户连接的地址
rabbitConnectionFactory.setHost(rabbitProperties.getHost());
//端口号
rabbitConnectionFactory.setPort(rabbitProperties.getPort());
//虚拟主机
rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());
//用户名
rabbitConnectionFactory.setUsername(rabbitProperties.getUsername());
//密码
rabbitConnectionFactory.setPassword(rabbitProperties.getPassword());
//自动连接恢复
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
//设置连接恢复间隔时间默认为500
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
return new CachingConnectionFactory(rabbitConnectionFactory);
}
}
首先创建一个连接工厂,这个工厂要取得我们之前配置的yml文件的配置信息。
如果你的不是本地的,而是对应买的阿里云的mq的话,就是下面的
public ConnectionFactory getConnectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
rabbitConnectionFactory.setHost(rabbitProperties.getHost());
rabbitConnectionFactory.setPort(rabbitProperties.getPort());
rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());
AliyunCredentialsProvider credentialsProvider = new AliyunCredentialsProvider(
rabbitProperties.getUsername(), rabbitProperties.getPassword(), INSTANCE_ID);
rabbitConnectionFactory.setCredentialsProvider(credentialsProvider);
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
return new CachingConnectionFactory(rabbitConnectionFactory);
}
其中 INSTANCE_ID 为你买了mq后阿里云给你的。
3、创建exchange
exchange不存储东西,只是一个交换机,对信息进行交换,一般分为四种,分别是:
DirectExchange :根据对应的路由进行找对应的queue
FanoutExchange:没有路由,找任意的queue
TopicExchange:根据路由进行模糊查找queue
HeaderExchange (基本不用)
借鉴图片分别对应上面写的exchange种类
其中exchange有一下的性质,可以直接去源码进行查看。
// durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效 // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
如果durable是true那么他会存储到本地磁盘一般是 在c盘的 文件里面,但是里面存的文件你也看不了,只要存在磁盘,如果遇到服务器重启或者断电,其内容如果还没被消费,就会一直在。(除非你设置了ttl,成为dlx死信,但是死信在一定程度上也可以找到。。。。)
%APPDATA% usually expands to C:\Users\%USERNAME%\AppData\Roaming or similar.
下面以DirectExchange为例
/**
* 构建exchange,并对其进行命名
*/
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder
.directExchange(QueueEnum.REGISTER_QUEUE.getExchangeName())
.durable(true)
.build();
}
其中direrctExchange为ExchangeVBuilder的构造方法,这里就是设置对应的路由,也可以理解设置需要连接的queue的名字。
其余属性不设置默认就采用false。
4、创建队列
创建好了exchange就可以设置对应的queue了。
当然queue也有对应的属性
Name:队列的名称
Durable:是否持久化(重启rabbitmq之后,队列是否还存在)
Exclusive:是否只被一个客户端连接使用,且当连接关闭后,删除队列
AutoDelete :是否自动删除(当最后一个消费者退订后即被删除)
Arguments:队列的其他属性参数,有如下可选项:
(1)x-message-ttl:消息的过期时间,单位:毫秒;
(2)x-expires:队列过期时间,队列在多长时间未被访问将被删除,单位:毫秒;
(3)x-max-length:队列最大长度,超过该最大值,则将从队列头部开始删除消息;
(4)x-max-length-bytes:队列消息内容占用最大空间,受限于内存大小,超过该阈值则从队列头部开始删除消息;
(5)x-overflow:设置队列溢出行为。这决定了当达到队列的最大长度时消息会发生什么。有效值是drop-head、reject-publish或reject-publish-dlx。仲裁队列类型仅支持drop-head;
(6)x-dead-letter-exchange:死信交换器名称,过期或被删除(因队列长度超长或因空间超出阈值)的消息可指定发送到该交换器中;
(7)x-dead-letter-routing-key:死信消息路由键,在消息发送到死信交换器时会使用该路由键,如果不设置,则使用消息的原来的路由键值
(8)x-single-active-consumer:表示队列是否是单一活动消费者,true时,注册的消费组内只有一个消费者消费消息,其他被忽略,false时消息循环分发给所有消费者(默认false)
(9)x-max-priority:队列要支持的最大优先级数;如果未设置,队列将不支持消息优先级;
(10)x-queue-mode(Lazy mode):将队列设置为延迟模式,在磁盘上保留尽可能多的消息,以减少RAM的使用;如果未设置,队列将保留内存缓存以尽可能快地传递消息;
(11)x-queue-master-locator:在集群模式下设置镜像队列的主节点信息。
直接上代码
/**
* 队列
*/
@Bean
public Queue queue() {
return QueueBuilder
.durable(QueueEnum.REGISTER_QUEUE.getQueueName())
.build();
}
其中durable()为其queueBuilder的构造方法,里面的参数是queue的名字,需要和exchange对应,不然找不到。(当然队列有死信队列和延迟队列,这里先不考虑)
5、绑定队列和exchange
接下来就是把exchange和queue绑定起来
/**
* 交换机绑定队列
* 队列绑定交换机,并存储起来
*/
@Bean
Binding binding() {
return BindingBuilder
.bind(queue())
.to(directExchange())
.with(QueueEnum.REGISTER_QUEUE.getPath());
}
当然以上的2,3,4,5都是同一个configuration文件里面的,完整的代码见代码1。
6、创建生产者
主要是把信息上传到mq中,可以写一个对应的方法,每次需要使用mq的时候直接调用就好了。
package com.library.until;
import com.library.enums.QueueEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @author zwh
* 作用就是发送消息
*/
@Component
@Slf4j
public class RabbitUtils {
private static AmqpTemplate amqpTemplate;
@Autowired
public void setAmqpTemplate(AmqpTemplate amqpTemplate) {
RabbitUtils.amqpTemplate = amqpTemplate;
}
/**
* 发送普通消息
* @param msg 消息内容
*/
public static void sendNormalMsg(String msg, QueueEnum queueEnum){
amqpTemplate.convertAndSend(queueEnum.getExchangeName(),queueEnum.getPath(),msg);
log.info("send msg :{}",msg);
}
/**
* 发送延迟消息
* @param msg 消息内容
* @param delayTime 延迟时间,单位毫秒
*/
public static void sendDelayMsg(String msg, Long delayTime,QueueEnum queueEnum){
amqpTemplate.convertAndSend(queueEnum.getExchangeName(),queueEnum.getPath(),msg,message -> {
message.getMessageProperties().setExpiration(delayTime.toString());
return message;
});
log.info("send delay msg :{}",msg);
}
}
这里采用的是String,也可以上传其他类型的,比如集合,json等等,这边需要自己改。
然后再业务逻辑中调用方法就好了。
我这里测试的是在注册的时候调用方法,存储信息,然后告诉发信息给用户,告诉用户成功注册。
这样生产者就好了。
7、创建消费者
创建消费者,一般都是实时监听我们的mq,然后获得信息后在做接下来的业务处理。
package com.library.rabbitmqListener;
import com.library.entity.Admin;
import com.library.service.AdminService;
import lombok.RequiredArgsConstructor;
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @author zwh
* 及时监听,并及时消费,作为一个消费者
*/
@Component
@RabbitListener(queues = "register_queue") //对应你的队列的名称
@RequiredArgsConstructor
public class registerListener {
private final AdminService adminService;
@RabbitHandler
public void handle(String msg){
System.out.println("我接收到了mq的消息了");
System.out.println(msg);
Long id = Long.valueOf(msg);
System.out.println("新建的管理员是:" + " " +adminService.getById(id).getName() );
System.out.println("恭喜您成功注册成为一名图书馆管理系统的管理员!");
}
}
这里把打简化为后面得到信息的业务处理。
要注意
@RabbitListener(queues = "register_queue")
@RabbitHandler
这里不能忘了,不然不会执行成功的。
8、完成消费
由于这里的消费是实时的,我先注释掉消费任务,让大家看到我们的mq是否成功的存到信息。
运行后,如果没有消费者,那么这个就不会被消费。
接下来注释去掉,运行。
看到还没完全运行好程序,这个就开始消费了。
然后再看mq
已经被消费过了,所以里面就没有了。
四、延迟队列和死信队列
见下文分析
附录1
package com.library.config;
/**
* @author zhuwenhai
* @date 2021/10/27 15:46
*/
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.library.enums.QueueEnum;
import lombok.RequiredArgsConstructor;
import net.bytebuddy.dynamic.loading.ClassInjector;
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.amqp.RabbitProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
@Configuration
public class RabbitmqConfig {
private final RabbitProperties rabbitProperties;
public RabbitmqConfig(RabbitProperties rabbitProperties) {
this.rabbitProperties = rabbitProperties;
}
@Bean
public ConnectionFactory getConnectionFactory() {
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory =
new com.rabbitmq.client.ConnectionFactory();
//用户连接的地址
rabbitConnectionFactory.setHost(rabbitProperties.getHost());
//端口号
rabbitConnectionFactory.setPort(rabbitProperties.getPort());
//虚拟主机
rabbitConnectionFactory.setVirtualHost(rabbitProperties.getVirtualHost());
//用户名
rabbitConnectionFactory.setUsername(rabbitProperties.getUsername());
//密码
rabbitConnectionFactory.setPassword(rabbitProperties.getPassword());
//自动连接恢复
rabbitConnectionFactory.setAutomaticRecoveryEnabled(true);
//设置连接恢复间隔时间默认为500
rabbitConnectionFactory.setNetworkRecoveryInterval(5000);
return new CachingConnectionFactory(rabbitConnectionFactory);
}
/**
* 构建exchange,并对其进行命名
*/
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder
.directExchange(QueueEnum.REGISTER_QUEUE.getExchangeName())
.durable(true)
.build();
}
/**
* 队列
*/
@Bean
public Queue queue() {
return QueueBuilder
.durable(QueueEnum.REGISTER_QUEUE.getQueueName())
.build();
}
/**
* 交换机绑定队列
* 队列绑定交换机,并存储起来
*/
@Bean
Binding binding() {
return BindingBuilder
.bind(queue())
.to(directExchange())
.with(QueueEnum.REGISTER_QUEUE.getPath());
}
}
附录2
package com.library.enums;
import lombok.Getter;
/**
* @author zwh
*/
@Getter
public enum QueueEnum {
/**
* 队列枚举
*/
//注册队列
REGISTER_QUEUE("register_exchange","register_queue","register_queue")
;
private String exchangeName;
private String queueName;
private String path;
QueueEnum(String exchangeName, String queueName, String path) {
this.exchangeName = exchangeName;
this.queueName = queueName;
this.path = path;
}
}