1.序言
2.RabbitMQ安装
2.1 安装Erlang
2.1.1 Erlang下载
2.1.2 Erlang环境设置
2.2 RabbitMQ安装
2.2.1 RabbitMQ下载
2.2.2 RabbitMQ环境设置
2.2.3 安装插件
3.RabbitMQ与Springboot整合实践
3.1 添加rabbitMQ的依赖
3.2 rabbitMQ的使用
3.2.1 普通工作队列模式(不指定交换机)
3.2.2 Direct交换机模式
3.2.3 Fanout交换机模式
3.2.4 Topic交换机模式
4.总结
RabbitMQ想必做软件开发的大部分人都有所耳闻。今天就和大家分享一下我个人对rabbitMQ的使用实践。但是我觉得会用还不够。还得了解其前世今生,充分了解其特性,才能更好的使用它。对于为什么用消息中间件?有什么好处?请各位看官移步我的另一篇博文《消息队列详解:ActiveMQ、RocketMQ、RabbitMQ、Kafka》。
环境介绍:WIN7操作系统,erl10.6,rabbitmq_server-3.7.7
因为RabbitMQ是使用erlang语言开发的,所以需要erlang的运行环境。
下载地址:https://www.erlang.org/downloads,本文选择OTP 21.0.1 Windows 64-bit Binary File
下载完成后是一个otp_win64_22.2.exe安装程序,自行安装到一个指定硬盘。本文安装到E盘中(安装程序尽量别安装在C盘)
打开控制面板→系统和安全→系统→高级系统设置→环境变量
在系统变量中添加erlang的环境变量,如下图:
在系统变量path中添加%ERLANG_HOME%\bin;
设置完成之后,进入cmd。输入erl -v :
出现如图,就说明erlang环境配置ok了!
官网下载地址:https://www.rabbitmq.com/download.html
本文选择解压缩安装rabbitmq-server-windows-3.7.7.zip
下载完压缩包后,本文还是解压到E盘。
还是在环境变量设置中,添加RABBITMQ_SERVER,值为解压后的文件路径。如下图:
设置系统变量path,添加%RABBITMQ_SERVER%\sbin;
打开cmd命令框,切换至rabbitmq_server-3.7.7\sbin目录下,输入rabbitmqctl status;
安装插件,命令:rabbitmq-plugins.bat enable rabbitmq_management,出现:
如此表明RabbitMQ已经安装成功了。在sbin目录中打开cmd,输入命令:rabbitmq-server.bat
RabbitMQ启动成功。打开http://localhost:15672/,即可进入rabbitMQ可视化页面。通过guest/guest默认账号密码进入。
至此,rabbitMQ环境搭建与安装完成!
因为AMQP对rabbitMQ有很好的封装,所以在需要使用消息中间件的项目或服务中直接使用如下依赖:
org.springframework.boot
spring-boot-starter-amqp
2.1.3.RELEASE
本文作为演示使用,所以操作都是简单的测试操作。我只使用了一个微服务项目做示范,没有分多项目测试。
项目目录结构如下:
项目application.yml文件添加:
spring:
rabbitmq:
addresses: 127.0.0.1
host: guest
password: guest
结构:一个消息生产者,三个消息消费者;三个消息消费者使用同一个queue。
队列配置rabbitConfig代码如下:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @date 2020/1/20 11:03
* @description
*/
@Configuration
public class rabbitConfig {
//队列名称
private static final String UNIT_QUEUE = "unitQueue";
@Bean
public Queue getQueueA(){
return new Queue(UNIT_QUEUE);
}
}
消息生产者Producer代码如下:
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:14
* @description
*/
@Component
public class Producer {
private static final String UNIT_QUEUE = "unitQueue";
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMessage(){
//发送10条消息
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend(UNIT_QUEUE,"message is comming. message "+i);
}
}
}
消费者A,B,C的代码分别如下:
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:45
* @description
*/
@Component
@RabbitListener(queues = "unitQueue")
public class ConsumerA {
@RabbitHandler
private void receivedMessage(String msg){
System.out.println("ConsumerA received message is :"+msg);
}
}
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:45
* @description
*/
@Component
@RabbitListener(queues = "unitQueue")
public class ConsumerB {
@RabbitHandler
private void receivedMessage(String msg){
System.out.println("ConsumerB received message is :"+msg);
}
}
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:45
* @description
*/
@Component
@RabbitListener(queues = "unitQueue")
public class ConsumerC {
@RabbitHandler
private void receivedMessage(String msg){
System.out.println("ConsumerC received message is :"+msg);
}
}
启动项目,因为我没有使用测试依赖包,无法直接使用单元测试,所以使用接口调用消息发送:
import com.giveu.newwebeurekaclient11001.util.rabbitMQ.Producer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @date 2019/11/11 14:41
* @description 生产者
*/
@RestController
@Slf4j
public class ProductController {
@Autowired
private Producer rabbitMqProducer;
@PostMapping("/sendMsg")
public void sendMsg(){
rabbitMqProducer.sendMessage();
}
}
调用接口,发送消息,打印日志如下:
以上是两次调用的结果,发现消息接收顺序并不一致。所以可以肯定,默认工作队列模式下:一生产者对多消费者时,各个消费者轮流获取,消息顺序不保证一致。
刚才我换掉消费的监听队列名称之后,再发送了一次消息,发现消息还存储在队列之中,在等待消费:
队列配置rabbitConfig代码如下:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @date 2020/1/20 11:03
* @description
*/
@Configuration
public class rabbitConfig {
//队列名称
private static final String QUEUE_A = "queueA";
private static final String QUEUE_B = "queueB";
private static final String QUEUE_C = "queueC";
//交换机名称
private static final String DIRECT_EXCHANGE = "directExchange";
//路由routingKey名称
private static final String GENERAL_ROUT_KEY = "testKey";
@Bean
public Queue getQueueA(){
return new Queue(QUEUE_A);
}
@Bean
public Queue getQueueB(){
return new Queue(QUEUE_B);
}
@Bean
public Queue getQueueC(){
return new Queue(QUEUE_C);
}
/**
* @date 2020/1/20 15:05
* @description 定义个direct交换器
*/
@Bean
DirectExchange directExchange(){
return new DirectExchange(DIRECT_EXCHANGE);
}
@Bean
public Binding bingWithQueueA(){
return BindingBuilder.bind(getQueueA()).to(directExchange()).with(GENERAL_ROUT_KEY);
}
@Bean
public Binding bingWithQueueB(){
return BindingBuilder.bind(getQueueB()).to(directExchange()).with(GENERAL_ROUT_KEY);
}
@Bean
public Binding bingWithQueueC(){
return BindingBuilder.bind(getQueueC()).to(directExchange()).with(GENERAL_ROUT_KEY);
}
}
消息生产者Producer代码如下:
import org.apache.commons.lang.time.FastDateFormat;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* @date 2020/1/20 11:14
* @description
*/
@Component
public class Producer {
private static final String EXCHANGE_NAME = "directExchange";
private static final String GENERAL_ROUT_KEY = "testKey";
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMessage(){
for (int i = 0; i < 10; i++) {
// 注意 第一个参数是我们交换机的名称 ,第二个参数是routerKey,第三个是你要发送的消息
rabbitTemplate.convertAndSend(EXCHANGE_NAME,GENERAL_ROUT_KEY,"this is a test message."+FastDateFormat.getInstance().format(new Date()));
}
}
}
消费者A,B,C的代码分别根据上面的代码做了一处改动,将@RabbitListener的queues名称改为上述配置对应的名称,如:
import org.springframework.amqp.rabbit.annotation.RabbitHandler;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:45
* @description
*/
@Component
@RabbitListener(queues = "queueA")
public class ConsumerA {
@RabbitHandler
private void receivedMessage(String msg){
System.out.println("ConsumerA received message is :"+msg);
}
}
消费者B和C也是如上,将queues改为queueB,queueC即可,所以不再贴重复代码!
咱们继续调用上述发送消息接口,日志打印如下:
可见每个绑定到direct交换机的队列,经多次测试,发现:每个队列都会获取全部的消息,且有序。
但因为交换机名称指定的是同一个,所以如果需要不同队列消费不同消息,那么指定不同routingKey即可!
队列配置rabbitConfig代码如下:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @date 2020/1/20 11:03
* @description
*/
@Configuration
public class rabbitConfig {
//队列名称
private static final String QUEUE_A = "queueA";
private static final String QUEUE_B = "queueB";
private static final String QUEUE_C = "queueC";
//交换机名称
private static final String FANOUT_EXCHANGE = "fanoutExchange";
@Bean
public Queue getQueueA(){
return new Queue(QUEUE_A);
}
@Bean
public Queue getQueueB(){
return new Queue(QUEUE_B);
}
@Bean
public Queue getQueueC(){
return new Queue(QUEUE_C);
}
/**
* @date 2020/1/20 11:08
* @description 定义个fanout交换器
*/
@Bean
FanoutExchange fanoutExchange(){
return new FanoutExchange(FANOUT_EXCHANGE);
}
@Bean
public Binding bingWithQueueA(){
return BindingBuilder.bind(getQueueA()).to(fanoutExchange());
}
@Bean
public Binding bingWithQueueB(){
return BindingBuilder.bind(getQueueB()).to(fanoutExchange());
}
@Bean
public Binding bingWithQueueC(){
return BindingBuilder.bind(getQueueC()).to(fanoutExchange());
}
}
消息生产者Producer代码如下:
import org.apache.commons.lang.time.FastDateFormat;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:14
* @description
*/
@Component
public class Producer {
private static final String FANOUT_EXCHANGE = "fanoutExchange";
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMessage(){
for (int i = 0; i < 10; i++) {
// 注意 第一个参数是我们交换机的名称 ,第二个参数是routerKey 我们不用管空着就可以,第三个是你要发送的消息
rabbitTemplate.convertAndSend(FANOUT_EXCHANGE,"","this is a test message. message"+i);
}
}
}
消费者代码和3.2.2中一致。打印日志如下:
可见与direct交换机模式(使用同一routingKey时)的效果差不多,也是每个队列都能接收到生产者(同一交换机情况)发出全部的消息。
队列配置rabbitConfig代码如下:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @date 2020/1/20 11:03
* @description
*/
@Configuration
public class rabbitConfig {
//队列名称
private static final String QUEUE_A = "queueA";
private static final String QUEUE_B = "queueB";
private static final String QUEUE_C = "queueC";
//交换机名称
private static final String TOPIC_EXCHANGE = "topicExchange";
//路由routingKey名称
private static final String ROUTING_KEY_A = "topic.#";
private static final String ROUTING_KEY_B = "topic.msg";
private static final String ROUTING_KEY_C = "topic.*.test";
@Bean
public Queue getQueueA(){
return new Queue(QUEUE_A);
}
@Bean
public Queue getQueueB(){
return new Queue(QUEUE_B);
}
@Bean
public Queue getQueueC(){
return new Queue(QUEUE_C);
}
/**
* @date 2020/1/20 15:03
* @description 定义个topic交换器
*/
@Bean
TopicExchange topicExchange(){
return new TopicExchange(TOPIC_EXCHANGE);
}
@Bean
public Binding bingWithQueueA(){
return BindingBuilder.bind(getQueueA()).to(topicExchange()).with(ROUTING_KEY_A);
}
@Bean
public Binding bingWithQueueB(){
return BindingBuilder.bind(getQueueB()).to(topicExchange()).with(ROUTING_KEY_B);
}
@Bean
public Binding bingWithQueueC(){
return BindingBuilder.bind(getQueueC()).to(topicExchange()).with(ROUTING_KEY_C);
}
}
消息生产者Producer代码如下:
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* @date 2020/1/20 11:14
* @description
*/
@Component
public class Producer {
private static final String TOPIC_EXCHANGE = "topicExchange";
@Autowired
private AmqpTemplate rabbitTemplate;
public void sendMessage(){
rabbitTemplate.convertAndSend(TOPIC_EXCHANGE,"topic.log","this is topic.log message.");
rabbitTemplate.convertAndSend(TOPIC_EXCHANGE,"topic.msg","this is topic.msg message.");
rabbitTemplate.convertAndSend(TOPIC_EXCHANGE,"topic.z.test","this is topic.z.test message.");
}
}
消费者代码和3.2.2中一致。打印日志如下:
topic交换机会指定一个routingKey,而这个key是可以指定匹配规则的。如上图测试结果一样:
注:#和*符号表示匹配所有
其实,RabbitMQ可以不使用配置类(如上面的rabbitConfig),因为可以使用注解的方式,灵活指定队列和交换机信息。如下面这种配置形式:
@RabbitListener(containerFactory = "rabbitListenerContainerFactory", bindings = @QueueBinding(
value = @Queue(value = "${mq.config.queue}", durable = "true"),
exchange = @Exchange(value = "${mq.config.exchange}", type = ExchangeTypes.TOPIC),
key = "${mq.config.key}"), admin = "rabbitAdmin")
具体的注解API可自行了解,但看名称也基本知道是指定什么配置啦。
另外,对于rabbitMQ的部分参数和类型说明,可以参考以下链接:
https://www.cnblogs.com/frankltf/p/10373524.html
参考链接:
https://blog.csdn.net/zhm3023/article/details/82217222
https://www.jianshu.com/p/0d400d30936b