【消息中间件MQ系列】RabbitMQ安装与使用,并与SpringBoot整合

热门系列:


目录

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.总结


1.序言

RabbitMQ想必做软件开发的大部分人都有所耳闻。今天就和大家分享一下我个人对rabbitMQ的使用实践。但是我觉得会用还不够。还得了解其前世今生,充分了解其特性,才能更好的使用它。对于为什么用消息中间件?有什么好处?请各位看官移步我的另一篇博文《消息队列详解:ActiveMQ、RocketMQ、RabbitMQ、Kafka》。


2.RabbitMQ安装

环境介绍:WIN7操作系统,erl10.6,rabbitmq_server-3.7.7

2.1 安装Erlang

因为RabbitMQ是使用erlang语言开发的,所以需要erlang的运行环境。

2.1.1 Erlang下载

下载地址:https://www.erlang.org/downloads,本文选择OTP 21.0.1 Windows 64-bit Binary File

下载完成后是一个otp_win64_22.2.exe安装程序,自行安装到一个指定硬盘。本文安装到E盘中(安装程序尽量别安装在C盘)

2.1.2 Erlang环境设置

打开控制面板→系统和安全→系统→高级系统设置→环境变量

在系统变量中添加erlang的环境变量,如下图:

在系统变量path中添加%ERLANG_HOME%\bin;

设置完成之后,进入cmd。输入erl -v :

出现如图,就说明erlang环境配置ok了!

 

2.2 RabbitMQ安装

2.2.1 RabbitMQ下载

官网下载地址:https://www.rabbitmq.com/download.html

本文选择解压缩安装rabbitmq-server-windows-3.7.7.zip

下载完压缩包后,本文还是解压到E盘。

2.2.2 RabbitMQ环境设置

还是在环境变量设置中,添加RABBITMQ_SERVER,值为解压后的文件路径。如下图:

设置系统变量path,添加%RABBITMQ_SERVER%\sbin;

2.2.3 安装插件

打开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环境搭建与安装完成!


3.RabbitMQ与Springboot整合实践

3.1 添加rabbitMQ的依赖

因为AMQP对rabbitMQ有很好的封装,所以在需要使用消息中间件的项目或服务中直接使用如下依赖:

<!--整合rabbitMQ-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
    <version>2.1.3.RELEASE</version>
</dependency>

3.2 rabbitMQ的使用

本文作为演示使用,所以操作都是简单的测试操作。我只使用了一个微服务项目做示范,没有分多项目测试。

项目目录结构如下:

项目application.yml文件添加:

spring:
  rabbitmq:
    addresses: 127.0.0.1
    host: guest
    password: guest

3.2.1 普通工作队列模式(不指定交换机)

结构:一个消息生产者,三个消息消费者;三个消息消费者使用同一个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();
    }

}

调用接口,发送消息,打印日志如下:

以上是两次调用的结果,发现消息接收顺序并不一致。所以可以肯定,默认工作队列模式下:一生产者对多消费者时,各个消费者轮流获取,消息顺序不保证一致。

刚才我换掉消费的监听队列名称之后,再发送了一次消息,发现消息还存储在队列之中,在等待消费:

3.2.2 Direct交换机模式

队列配置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即可!

 

3.2.3 Fanout交换机模式

队列配置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时)的效果差不多,也是每个队列都能接收到生产者(同一交换机情况)发出全部的消息。

 

3.2.4 Topic交换机模式

队列配置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是可以指定匹配规则的。如上图测试结果一样:

  • 因为ROUTING_KEY_A是以topic为前缀匹配所有,所以生产者的三个消息因为都是topic开头,所以消费者A都可以接受到
  • 因为ROUTING_KEY_B是指定了routingKey=topic.msg,所以只有生产者的第二条消息有消费者B 和A接收到
  • 因为ROUTING_KEY_C为topic.*.msg,所以只要生产者使用的routingKey前缀为topic同时后缀为msg,消费者C都可以接收到

注:#和*符号表示匹配所有


4.总结

其实,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

 

本博客皆为学习、分享、探讨为本,欢迎各位朋友评论、点赞、收藏、关注,一起加油!

 

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

善良勤劳勇敢而又聪明的老杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值