RabbitMQ是什么?如何使用

1. 什么是MQ

消息队列(Message Queue,简称MQ)

  • 从字面意思上看,本质是个队列,FIFO先入先出,只不过队列中存放的内容是message而已。
    其主要用途:不同进程Process/线程Thread之间通信。

为什么会产生消息队列?有几个原因:

  • 不同进程(process)之间传递消息时,两个进程之间耦合程度过高,改动一个进程,引发必须修改另一个进程,为了隔离这两个进程,在两进程间抽离出一层(一个模块),所有两进程之间传递的消息,都必须通过消息队列来传递,单独修改某一个进程,不会影响另一个;

  • 不同进程(process)之间传递消息时,为了实现标准化,将消息的格式规范化了,并且,某一个进程接受的消息太多,一下子无法处理完,并且也有先后顺序,必须对收到的消息进行排队,因此诞生了事实上的消息队列;

MQ框架非常的多,比较流行的有RabbitMq、kafka,以及阿里开源的RocketMQ。本文主要介绍RabbitMq。

区别

优点 缺点 适用场景
kafka 吞吐量非常大,性能非常好,技术生态完整 功能比较单一 分布式日志搜集、大数据采集,linkin用来处理分布式日志用的
RabbitMQ 消息可靠性强,功能全面 吞吐量较低。消息挤压会影响性能,erlang语言比较小众 企业内部系统调用
RocketMQ 高吞吐、高性能、高可用、高级功能非常全 技术生态不是很完整 几乎全场景,尤其适合金融 ,阿里拿来做金融用的

2. 为什么使用消息队列

主要有三个作用:

  • 解耦。如图所示。本身生产者所需要的数据被消费者们所需要,这要给他们都添加响应的方法,但如果消费者3突然不需要该信息,生产者就要删掉该方法,但如果增加这个中间层进行解耦,就可以让他们只去找这个消息队列去拿内容,而不需要再次去寻找生产者,生产者可以更专注于自己的业务。

  • 异步。如图所示。一个客户端请求发送进来,系统A会调用系统B、C、D三个系统,同步请求的话,响应时间就是系统A、B、C、D的总和,也就是800ms。如果使用MQ,系统A发送数据到MQ,然后就可以返回响应给客户端,不需要再等待系统B、C、D的响应,可以大大地提高性能。对于一些非必要的业务,比如发送短信,发送邮件等等,就可以采用MQ。

  • 削峰。就是当业务量大的时候,让他做一个桥的作用,不让数据一股脑打到数据库上,让数据库宕机。使用MQ,是让sql语句不在直接打到数据库,而是把数据发送到MQ,MQ短时间积压数据是可以接受的,然后由消费者每次拉取2000条进行处理,防止在请求峰值时期大量的请求直接发送到MySQL导致系统崩溃。

3. RabbitMQ介绍

RabbitMQ 是一个开源的消息代理和队列服务器,用于在分布式系统中存储、转发和接收消息。它是基于高级消息队列协议(AMQP)实现的,支持多种客户端和协议,可以用于多种场景,如负载均衡、分布式事务处理、消息通知等。

RabbitMQ 的核心概念

RabbitMQ 的核心概念包括生产者、消费者、队列、交换机和绑定。生产者负责发送消息到交换机,交换机根据路由规则将消息转发到绑定的队列,消费者从队列中获取消息进行处理。RabbitMQ 支持多种类型的交换机,如直接交换机(direct)、扇形交换机(fanout)、主题交换机(topic)和头交换机(headers),它们各自适用于不同的路由策略和模式。

RabbitMQ 的工作原理

RabbitMQ 的工作原理是通过Broker(消息代理服务器)来接收、存储和转发消息。Broker 包含一个或多个虚拟主机,每个虚拟主机可以有自己的队列、交换机和绑定。消息的生产者将消息发送到交换机,交换机根据绑定规则将消息路由到队列,消费者监听队列并处理消息。

RabbitMQ 的使用场景

RabbitMQ 可以用于实现系统间的解耦、异步处理和流量削峰。例如,在电商系统中,订单生成后可以将订单信息发送到消息队列,库存系统和物流系统可以从队列中获取订单信息进行处理,这样即使某个系统暂时不可用,也不会影响整个流程的进行。此外,RabbitMQ 还可以用于实现延迟消息和定时任务,如订单超时未支付自动取消等功能。

4. 使用

RabbitMQ是由ErLang开发的,他需要erlang的环境来进行运行。

所以要先安装erlang,你可以根据rabbitMQ官网来看erlang适应的版本。

安装好erlang之后,将erlang配置成环境变量,也就是将erlang的sbin目录保存在环境变量的path中。

然后官网安装windows包

这是安装好的目录。

安装完之后,他会自动执行,这里我出现了一个错误,就是他自动运行之后,并没有运行起来,我怀疑是windows的运行方式和其他的不一样。
那我是如何解决的呢,就是先通过windows的services.msc进入服务,关闭自动运行的rabbitMQ,然后调整成手动运行,之后在执行rabbitMQ的bat命令

运行命令

java 复制代码
rabbitmq-server start

当你看见下面情况时候

证明运行成功了。

然后你可以访问web页面,http://localhost:15672

账号密码默认是:guest/guest

进入到下面页面,就大功告成了。

用户

查看当前拥有用户

java 复制代码
rabbitmqctl list_users

查看权限

java 复制代码
rabbitmqctl list_permissions

添加用户

java 复制代码
rabbitmqctl add_user user_name password

设置用户tag

java 复制代码
rabbitmqctl set_user_tags user_name administrator

设置用户权限

java 复制代码
rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

5. java操作

当然你的服务配置好,并没有结束,而是刚刚开始,我们需要用编程语言去操作他,这里我使用java。
首先引入环境,springboot是拥有mq环境的,所以只需要引入即可。

pom 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

依然是在配置yml中加入RabbitMQ的配置信息

yml 复制代码
spring:
    rabbitmq:
        host: 127.0.0.1
        port: 5672
        username: guest
        password: guest

然后是创建RabbitMQ的配置类,放入到IOC当中。

java 复制代码
@Configuration
public class DirectRabbitConfig {
    @Bean
    public Queue rabbitmqDemoDirectQueue() {
        /**
         * 1、name:    队列名称
         * 2、durable: 是否持久化
         * 3、exclusive: 是否独享、排外的。如果设置为true,定义为排他队列。则只有创建者可以使用此队列。也就是private私有的。
         * 4、autoDelete: 是否自动删除。也就是临时队列。当最后一个消费者断开连接后,会自动删除。
         * */
        return new Queue(RabbitMQConfig.RABBITMQ_DEMO_TOPIC, true, false, false);
    }
    
    @Bean
    public DirectExchange rabbitmqDemoDirectExchange() {
        //Direct交换机
        return new DirectExchange(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, true, false);
    }

    @Bean
    public Binding bindDirect() {
        //链式写法,绑定交换机和队列,并设置匹配键
        return BindingBuilder
                //绑定队列
                .bind(rabbitmqDemoDirectQueue())
                //到交换机
                .to(rabbitmqDemoDirectExchange())
                //并设置匹配键
                .with(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING);
    }
}

然后是你运行操作数据所需要的服务类

java 复制代码
@Service
public class RabbitMQServiceImpl implements RabbitMQService {
    //日期格式化
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Override
    public String sendMsg(String msg) throws Exception {
        try {
            String msgId = UUID.randomUUID().toString().replace("-", "").substring(0, 32);
            String sendTime = sdf.format(new Date());
            Map<String, Object> map = new HashMap<>();
            map.put("msgId", msgId);
            map.put("sendTime", sendTime);
            map.put("msg", msg);
            rabbitTemplate.convertAndSend(RabbitMQConfig.RABBITMQ_DEMO_DIRECT_EXCHANGE, RabbitMQConfig.RABBITMQ_DEMO_DIRECT_ROUTING, map);
            return "ok";
        } catch (Exception e) {
            e.printStackTrace();
            return "error";
        }
    }
}

削峰填谷

在高并发场景下,例如秒杀活动,用户请求量可能瞬间激增,导致服务器无法承受。RabbitMQ 通过消息队列的方式,将瞬时流量缓冲到队列中,然后以稳定的速率处理这些请求,从而实现削峰填谷。

配置文件示例

以下是一个 Spring Boot 项目的 RabbitMQ 配置文件示例:

java 复制代码
spring.application.name=springboot_rabbitmq
spring.rabbitmq.host=192.168.0.102
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=admin
spring.rabbitmq.virtual-host=/
spring.rabbitmq.listener.simple.acknowledge-mode=manual # 设置手动应答
spring.rabbitmq.listener.simple.prefetch=2 # 每次最多可处理信息量

消费者代码示例

消费者代码通过 @RabbitListener 注解监听队列,并手动应答消息:

java 复制代码
@Component
@RabbitListener(queuesToDeclare = @Queue(name = "springboot-limit"))
public class CurrentlimitCustomer {
@RabbitHandler
public void receive(String msg, Channel channel, Message message) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
Thread.sleep(1000 * 10); // 模拟处理时间
System.out.println("=====限流====>");
System.out.println(msg);
System.out.println(channel);
System.out.println(message);
// 手动签收消息
channel.basicAck(deliveryTag, true);
} catch (Exception e) {
// 处理异常
}
}
}

生产者代码示例

生产者代码通过 rabbitTemplate 发送消息到队列:

java 复制代码
@Test
public void test09() throws Exception {
for (int i = 0; i < 10; i++) {
rabbitTemplate.convertAndSend("springboot-limit", "限流测试");
}
Thread.sleep(1000 * 1000); // 模拟延迟
}

削峰填谷的优势

  1. 提高系统稳定性:通过缓冲瞬时流量,避免系统过载。
  2. 提升用户体验:减少请求失败的概率,提高系统响应速度。
  3. 简化系统设计:通过消息队列实现异步处理,降低系统耦合度。

RabbitMQ 的削峰填谷功能在处理高并发场景中具有显著优势,能够有效提高系统的稳定性和可用性。

交换机

Exchange交换机是消息路由的核心组件,主要用于接收生产者发送的消息,并根据路由键(Routing Key)将消息分发到绑定的队列(Queue)。它在消息队列系统(如RabbitMQ)中起到类似"邮局"的作用,负责将消息分拣到正确的队列,从而实现生产者与消费者的解耦。

交换机的核心功能

生产者将消息发送到交换机,而非直接发送到队列。交换机根据绑定规则(Binding Key)和路由键将消息转发到对应的队列。这样,生产者无需关心消息的具体消费队列,消费者也无需了解消息的来源。

交换机的类型

交换机有四种主要类型,每种类型适用于不同的场景:

1. Direct直连交换机

就如同他的名字,直接将内容放入到指定的queue队列。消息通过完全匹配路由键转发到绑定的队列。适用于点对点精确路由场景,例如订单系统根据订单ID分发消息。

java 复制代码
 String queueName = "hello.queue1";
rabbitTemplate.convertAndSend(queueName,"hello,everyone");

或者指定routingKey来发送

java 复制代码
String exchangeName = "hello.direct";
rabbitTemplate.convertAndSend(exchangeName ,"nihao,everyone");

通过下图可以看到,我指定hello.queue1的routingkey为nihao,所以只会发送给hello.queue1。
是可以多个queue绑定相同的routingKey的

2. Fanout广播交换机

广播,顾名思义就是将绑定的queue都发。将消息广播到所有绑定的队列。适用于发布/订阅模式,例如系统日志广播或实时通知。

java 复制代码
String exchangeName = "hello.fanout";
rabbitTemplate.convertAndSend(exchangeName ,null,"hello,everyone");

Consumer

java 复制代码
package com.itheima.consumer.listener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitMQListener {
    @RabbitListener(queues = "hello.queue1")
    public void onMessage(String message) {
        Logger logger = LogManager.getLogger(RabbitMQListener.class);
        logger.info("hello.queue1获取到的信息为{}",message);
    }
    @RabbitListener(queues = "hello.queue2")
    public void onMessage2(String message) {
        Logger logger = LogManager.getLogger(RabbitMQListener.class);
        logger.info("hello.queue2获取到的信息为{}",message);
    }
}

3. topic主题交换机

如果说上面的direct交换机是选择队列去发送,那这个topic交换机就是他的升级版,topic交换机可以将routingKey设置为通配符样式。

发送到类型是 topic 交换机的消息的routing key 不能随意写,必须满足一定的要求,它必须是一个单词列表,并且以点号. 分隔开 。

这些单词可以是任意单词,比如说:"stock.usd.nyse","nyse.vmw","quick.orange.rabbit" 这种类型的。当然这个单词列表最多不能超过 255 个字节。

在这个规则列表中,其中有两个替换符是特别需要注意的:

  1. *(星号) 可以代替一个单词,注意是一个单词,不是一个字母
  2. #(井号) 可以替代零个或多个单词,注意是一个单词,不是一个字母

下面代码就可以发送给这两个queue。

java 复制代码
package com.itheima.publisher;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

@SpringBootTest
public class PublisherTest {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Test
    public void publisherTest(){
        String exchangeName = "hello.topic";
        rabbitTemplate.convertAndSend(exchangeName ,"china.news","hello,everyone");
    }
}

声明队列和交换机

如果每次都在rabbit的控制台去创建队列交换机啥的,未免太麻烦了,spring-amqp为我们封装了代码创建的方法。

java 复制代码
package com.itheima.consumer.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    @Bean
    public Queue helloQueue1(){
        return new Queue("hello.queue1");
    }

    @Bean
    public Queue helloQueue2(){
        return new Queue("hello.queue2");
    }

    @Bean
    public FanoutExchange helloFanout(){
        return new FanoutExchange("hello.fanout");
    }
    @Bean
    public Binding helloBinding1(){
        return BindingBuilder.bind(helloQueue1()).to(helloFanout());
    }
    @Bean
    public Binding helloBinding2(){
        return BindingBuilder.bind(helloQueue2()).to(helloFanout());
    }

    @Bean
    public DirectExchange helloDirect(){
        return new DirectExchange("hello.direct");
    }
}

通过这个操作,就可以实现让服务去创建这些内容。

注解方式

amqp为我们提供了更方便的方式,注解方式

通过下列的操作,可以直接在监听类上完成queue和exchange的创建和绑定。

queue就是监听的队列,exchange就是队列要绑定的交换机,key就是routingKey。

java 复制代码
package com.itheima.consumer.listener;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class RabbitMQListener {
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hello.queue1",durable = "true"),
            exchange = @Exchange(name = "hello.direct",type = ExchangeTypes.DIRECT),
            key = {"nihao","zaijian"}
    ))
    public void onMessage(String message) {
        Logger logger = LogManager.getLogger(RabbitMQListener.class);
        logger.info("hello.queue1获取到的信息为{}",message);
    }
    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hello.queue1",durable = "true"),
            exchange = @Exchange(name = "hello.direct",type = ExchangeTypes.DIRECT),
            key = {"china","emilia"}
    ))
    public void onMessage2(String message) {
        Logger logger = LogManager.getLogger(RabbitMQListener.class);
        logger.info("hello.queue2获取到的信息为{}",message);
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "hello.queue3",durable = "true"),
            exchange = @Exchange(name = "hello.topic",type = ExchangeTypes.TOPIC),
            key = {"china.#","#.emilia"}
    ))
    public void onMessage3(String message) {
        Logger logger = LogManager.getLogger(RabbitMQListener.class);
        logger.info("hello.queue3获取到的信息为{}",message);
    }
}

消息转换器

rabbitMQ自带的消息转换器是臃肿的。

我通过下面的代码向交换机发送内容。

java 复制代码
package com.itheima.publisher;

import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;

@SpringBootTest
public class PublisherTest {
    @Resource
    private RabbitTemplate rabbitTemplate;
    @Test
    public void publisherTest(){
        HashMap<String, Object> map = new HashMap<>();
        map.put("id","1");
        map.put("name","cmc");
        map.put("sex","男");
        String exchangeName = "hello.topic";
        rabbitTemplate.convertAndSend(exchangeName ,"i love u.emilia",map);
    }
}

这个是交换机里面的结果,可以看见第二个结果是我使用了jackson的消息转换,将内容转换为了json,明显大小变小了很多,所以更加推荐使用json消息转换。

使用下面的代码,将amqp的消息转换实现注册为bean,amqp就会自动转换消息接受类型了。

java 复制代码
package com.itheima.consumer.config;

import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class RabbitConfig {
    @Bean
    public MessageConverter messageConverter(){
        return new Jackson2JsonMessageConverter();
    }
}
相关推荐
在坚持一下我可没意见1 小时前
Spring Boot 实战(一):拦截器 + 统一数据返回 + 统一异常处理,一站式搞定接口通用逻辑
java·服务器·spring boot·后端·spring·java-ee·tomcat
QQ_4376643141 小时前
分布式RPC网络框架
网络·c++·分布式·rpc
廋到被风吹走1 小时前
【JDK版本】JDK1.8相比JDK1.7 语言特性之函数式编程
java·开发语言·python
fire-flyer1 小时前
Reactor Context 详解
java·开发语言
3***89191 小时前
TypeScript 与后端开发Node.js
java
老兵发新帖1 小时前
Spring Boot 的配置文件加载优先级和合并机制分析
java·spring boot·后端
明洞日记1 小时前
【JavaWeb手册004】Spring Boot的核心理念
java·spring boot·后端
计算机毕设指导61 小时前
基于微信小程序的健康指导平台【源码文末联系】
java·spring boot·mysql·微信小程序·小程序·tomcat·maven
⑩-1 小时前
Redis GEO
java·redis