此处环境为windows下测试案例使用,故整体是在windows下搭建的环境
下载路径https://www.erlang.org/downloads,双击下载文件
选择安装路径
为Erlang设置环境变量
在环境变量path中添加D:\software\javamq\erl10.0.1\bin
(路径为安装路径的bin)
打开cmd指令,输入
erl -version
erl
此时,表示erlang安装成功
双击可执行文件,安装路径F:\server\RabbitMQ Server\
安装好后,可以设置环境变量,也可不设置,此处就不设置了,进入安装目录F:\server\RabbitMQ Server\rabbitmq_server-3.7.15\sbin,然后使用cmd指令安装插件
rabbitmq-plugins enable rabbitmq_management
继续输入指令
rabbitmq-server.bat
打开浏览器,输入http://localhost:15672/,输入账号/密码guest/guest(账号密码可以自己在配置文件设置)
使用指令
rabbitmqctl status
说明安装是成功的,并且说明现在RabbitMQ Server已经启动了,运行正常。
RabbitMQ是AMQP的实现
首先说下传统的JMS与AMQP的对比
|
JMS |
AMQP |
定义 |
Java api |
网络线性协议(Wire-protocol) |
跨语言 |
否 |
是 |
跨平台 |
否 |
是 |
Model |
提供两种消息模型 (1)point-to-point (2)Pub/Sub |
提供了五种消息模型: (1)direct exchange (2)fanout exchange (3)topic change (4)headers exchange (5)system exchange 本质来讲,后四种和JMS的pub/sub模型没有太大差别,仅是在路由机制上做了更详细的划分 |
支持消息类型 |
多种消息类型: TextMessage MapMessage BytesMessage StreamMessage ObjectMessage Message (只有消息头和属性) |
byte[] 当实际应用时,有复杂的消息,可以将消息序列化后发送。 |
综合评价 |
JMS 定义了JAVA API层面的标准;在java体系中,多个client均可以通过JMS进行交互,不需要应用修改代码,但是其对跨平台的支持较差; |
AMQP定义了wire-level层的协议标准;天然具有跨平台、跨语言特性。 |
RabbitMQ简介:
RabbitMQ是一个由erlang开发的AMQP(Advanved Message Queue Protocol)的开源实现。
核心概念
Message
消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。
Publisher
消息的生产者,也是一个向交换器发布消息的客户端应用程序。
Exchange
交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。
Exchange有4种类型:direct(默认),fanout, topic, 和headers,不同类型的Exchange转发消息的策略有所区别
Queue
消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。
Binding
绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。Exchange 和Queue的绑定可以是多对多的关系。
Connection
网络连接,比如一个TCP连接。
Channel
信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内的虚拟连接,AMQP 命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。Consumer
消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
Virtual Host
虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。
Broker
表示消息队列服务器实体
常用的交换机有以下三种,因为消费者是从队列获取信息的,队列是绑定交换机的(一般),所以对应的消息推送/接收模式也会有以下几种:
DirectExchange
直连型交换机,根据消息携带的路由键将消息投递给对应队列。
大致流程,有一个队列绑定到一个直连交换机上,同时赋予一个路由键routingkey。然后当一个消息携带着路由值为X,这个消息通过生产者发送给交换机时,交换机就会根据这个路由值X去寻找绑定值也是X的队列。
消息中的路由键(routingkey)如果和Binding中的bindingkey一致,交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“dog”,则只转发routingkey标记为“dog”的消息,不会转发“dog.puppy”,也不会转发“dog.guard”等等。它是完全匹配、单播的模式
FanoutExchange
扇型交换机,这个交换机没有路由键概念,就算你绑了路由键也是无视的。这个交换机在接收到消息后,会直接转发到绑定到它上面的所有队列。每个发到fanout类型交换器的消息都会分到所有绑定的队列上去。fanout交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout类型转发消息是最快的。
TopicExchange
主题交换机,这个交换机其实跟直连交换机流程差不多,但是它的特点就是在它的路由键和绑定键之间是有规则的。
简单地介绍下规则:
*(星号)用来表示一个单词(必须出现的)
#(井号)用来表示任意数量(零个或多个)单词
通配的绑定键是跟队列进行绑定的,举个小例子
队列Q1绑定键为*.TT.*队列Q2绑定键为TT.#
如果一条消息携带的路由键为A.TT.B,那么队列Q1将会收到;
如果一条消息携带的路由键为TT.AA.BB,那么队列Q2将会收到;
当*(星号)和#(井号)这两个特殊字符都未在绑定键中出现的时候,此时主题交换机就拥有的直连交换机的行为。所以主题交换机也就实现了扇形交换机的功能,和直连交换机的功能。
注:前提先启动rabbitMq
1.首先分别创建生产者、消费者项目,此处可以将两个项目作为一个子项目放入到一个父项目中
添加父项目依赖
com.csrcb
springboot_rabbitmq
pom
1.0-SNAPSHOT
springboot_rabbitmq_provider
springboot_rabbitmq_consumer
org.springframework.boot
spring-boot-starter-parent
2.2.5.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-amqp
net.logstash.logback
logstash-logback-encoder
4.11
org.projectlombok
lombok
分别在两个子项目中添加父依赖
springboot_rabbitmq
com.csrcb
1.0-SNAPSHOT
4.0.0
springboot_rabbitmq_provider
以及消费者的依赖
springboot_rabbitmq
com.csrcb
1.0-SNAPSHOT
4.0.0
springboot_rabbitmq_consumer
分别为两个项目编写application.properties配置文件
server.port=8090
spring.application.name=SpringBoot-RabbitMQ-Provider
spring.profiles.active=dev
#对于rabbitMQ的支持
spring.rabbitmq.host=127.0.0.1
#注:默认服务端口号为5672,15672位界面浏览端口号
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
spring.rabbitmq.virtual-host=/
#配置发布消息确认回调
spring.rabbitmq.publisher-confirms=true
2.在生产者项目中编写配置类RabbitConfig.java
/**
Broker:它提供一种传输服务,它的角色就是维护一条从生产者到消费者的路线,保证数据能按照指定的方式进行传输,
Exchange:消息交换机,它指定消息按什么规则,路由到哪个队列。
Queue:消息的载体,每个消息都会被投到一个或多个队列。
Binding:绑定,它的作用就是把exchange和queue按照路由规则绑定起来.
Routing Key:路由关键字,exchange根据这个关键字进行消息投递。
vhost:虚拟主机,一个broker里可以有多个vhost,用作不同用户的权限分离。
Producer:消息生产者,就是投递消息的程序.
Consumer:消息消费者,就是接受消息的程序.
Channel:消息通道,在客户端的每个连接里,可建立多个channel.
*/
@Configuration
public class RabbitMqConfig {
@Value("${spring.rabbitmq.host}")
private String host;
@Value("${spring.rabbitmq.port}")
private int port;
@Value("${spring.rabbitmq.username}")
private String username;
@Value("${spring.rabbitmq.password}")
private String password;
@Value("${spring.rabbitmq.virtual-host}")
private String virtualHost;
public static final String EXCHANGE_A = "my-mq-direct_exchange";
public static final String EXCHANGE_B = "my-mq-exchange_B";
public static final String EXCHANGE_C = "my-mq-exchange_C";
public static final String QUEUE_A = "QUEUE_A";
public static final String QUEUE_A_FAIL = "QUEUE_A_FAIL";
public static final String QUEUE_B = "QUEUE_B";
public static final String ROUTINGKEY_A = "spring-boot-routingKey_A";
public static final String ROUTINGKEY_A_FAIL = "spring-boot-routingKey_A_FAIL";
public static final String ROUTINGKEY_B = "spring-boot-routingKey_B";
//建立一个连接容器,类型数据库的连接池
@Bean
public ConnectionFactory connectionFactory() {
CachingConnectionFactory connectionFactory = new CachingConnectionFactory(host,port);
connectionFactory.setUsername(username);
connectionFactory.setPassword(password);
connectionFactory.setVirtualHost(virtualHost);
connectionFactory.setPublisherConfirms(true);//确认机制
// connectionFactory.setPublisherReturns(true);
//发布确认,template要求CachingConnectionFactory的publisherConfirms属性设置为true
return connectionFactory;
}
//RabbitMQ的使用入口
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//必须是prototype类型
public RabbitTemplate rabbitTemplate() {
RabbitTemplate template = new RabbitTemplate(this.connectionFactory());
template.setMessageConverter(this.jsonMessageConverter());
template.setMandatory(true);
return template;
}
//把交换机,队列,通过路由关键字进行绑定,写在RabbitConfig类当中
/**
* 针对消费者配置
* 1. 设置交换机类型
* 2. 将队列绑定到交换机
FanoutExchange: 将消息分发到所有的绑定队列,无routingkey的概念
HeadersExchange :通过添加属性key-value匹配
DirectExchange:按照routingkey分发到指定队列
TopicExchange:多关键字匹配
*/
@Bean
public DirectExchange testDirectExchange() {
return new DirectExchange(EXCHANGE_A);
}
/**
* 获取队列A
* durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
* exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
* autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
* return new Queue("TestDirectQueue",true,true,false);
* 一般设置一下队列的持久化就好,其余两个就是默认false
* @return
*/
@Bean
public Queue queueA() {
return new Queue(QUEUE_A, true); //队列持久
}
@Bean
public Queue queueAFail(){
return new Queue(QUEUE_A_FAIL, true);//队列持久
}
//将队列和交换机绑定, 并设置用于匹配键:spring-boot-routingKey_A
@Bean
public Binding binding() {
return BindingBuilder.bind(queueA()).to(testDirectExchange()).with(ROUTINGKEY_A);
}
@Bean
public Binding bindingAFail() {
return BindingBuilder.bind(queueAFail()).to(testDirectExchange()).with(RabbitMqConfig.ROUTINGKEY_A_FAIL);
}
@Bean
public MessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
//一个交换机可以绑定多个消息队列,也就是消息通过一个交换机,可以分发到不同的队列当中去。
// /**
// * 获取队列B
// * @return
// */
// @Bean
// public Queue queueB() {
// return new Queue(QUEUE_B, true); //队列持久
// }
// @Bean
// public Binding bindingB(){
// return BindingBuilder.bind(queueB()).to(defaultExchange()).with(ROUTINGKEY_B);
// }
// @Bean
// public CharacterEncodingFilter characterEncodingFilter() {
// CharacterEncodingFilter filter = new CharacterEncodingFilter();
// filter.setEncoding("UTF-8");
// filter.setForceEncoding(true);
// return filter;
// }
}
3.在服务层定义消息发布的方法以及具体消息内容方法的实现
public interface QueueMessageService extends RabbitTemplate.ConfirmCallback {
/**
* 发送消息到rabbitmq消息队列
* @param message 消息内容
* @param exchange 交换配置
* @param queueRoutingKey routingKey的队列
* @throws Exception
*/
void send(Object message, String exchange, String queueRoutingKey) throws Exception;
}
实现类
@Service
@Slf4j
public class QueueMessageServiceImpl implements QueueMessageService {
//由于配置类RabbitMqConfig配置类中的rabbitTemplate的scope属性设置为ConfigurableBeanFactory.SCOPE_PROTOTYPE
private RabbitTemplate rabbitTemplate;
@Autowired
public QueueMessageServiceImpl(RabbitTemplate rabbitTemplate){
this.rabbitTemplate = rabbitTemplate;
//设置回调为当前类对象
rabbitTemplate.setConfirmCallback(this);
}
@Override
public void send(Object message, String exchange, String queueRoutingKey) throws Exception {
//构建回调id为uuid
String callBackId = UUID.randomUUID().toString();
CorrelationData correlationId = new CorrelationData(callBackId);
log.info("开始发送消息内容:{}",message.toString());
//发送消息到消息队列
rabbitTemplate.convertAndSend(exchange, queueRoutingKey, message, correlationId);
log.info("发送定制的回调id:{}",callBackId);
}
/**
* 消息回调确认方法
* @param correlationData 请求数据对象
* @param ack 是否发送成功
* @param s
*/
@Override
public void confirm(CorrelationData correlationData, boolean ack, String s) {
log.info(" 回调id:" + correlationData.getId());
if (ack) {
log.info("消息发送成功");
} else {
log.info("消息发送失败:" + s);
}
}
}
4.编写控制路由(消息发布的触发条件),向消息对立发布消息
@RestController
@RequestMapping(value = "/provider")
@Slf4j
public class ProvoderSend {
@Autowired
private QueueMessageService queueMessageService;
@PostMapping("/send")
public String sendMessage() {
try {
Map messageMap = new HashMap<>();
messageMap.put("messageId",UUID.randomUUID().toString());
messageMap.put("messageData","测试信息");
messageMap.put("createTime", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
queueMessageService.send(messageMap, RabbitMqConfig.EXCHANGE_A, RabbitMqConfig.ROUTINGKEY_A);
return "success";
} catch (Exception e) {
log.error(""+e);
return "error";
}
}
}
5.启动项目,可以使用idea自带的类似postman工具发送请求(也可使用单元测试测试),触发生产消息队列条件
结合日志看到已经成功发送
再到rabbitmq提供的管理端浏览器页面(端口15672)可以看到已经有一个消息待消费(由于此时尚未启动消费者服务,故此处没有看到有消费者)
1.创建消费者项目模块,编写配置文件,可以直接将生产者的配置文件复制粘贴(但服务端口号要不一致)
2.创建消费者的配置类
@Configuration
public class RabbitMqConfig {
public static final String EXCHANGE_A = "my-mq-direct_exchange";
public static final String EXCHANGE_B = "my-mq-exchange_B";
public static final String EXCHANGE_C = "my-mq-exchange_C";
public static final String QUEUE_A = "QUEUE_A";
public static final String QUEUE_A_FAIL = "QUEUE_A_FAIL";
public static final String QUEUE_B = "QUEUE_B";
public static final String ROUTINGKEY_A = "spring-boot-routingKey_A";
public static final String ROUTINGKEY_A_FAIL = "spring-boot-routingKey_A_FAIL";
public static final String ROUTINGKEY_B = "spring-boot-routingKey_B";
//此处可以配置rabbitmq的相关配置,不过配置了就类似于消息队列的生产者
}
3.定义消费者消费服务,监听消息队列的队列(注此处@RabbitListener(queues = RabbitMqConfig.QUEUE_A)注解用于类上会报错,用与方法上)
@Component
@Slf4j
public class ConsumerService {
@RabbitListener(queues = RabbitMqConfig.QUEUE_A)
@RabbitHandler
public void consumeMessage(Message message){
log.info("收到的消息:{}",message);
}
}
4.定义消费者启动类,启动项目
可以看到启动后,消费了消息
再到后端管理页面,可以看到,已有一个消费者,且数据被消费