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();
    }
}
相关推荐
num_killer5 小时前
小白的Langchain学习
java·python·学习·langchain
期待のcode6 小时前
Java虚拟机的运行模式
java·开发语言·jvm
程序员老徐6 小时前
Tomcat源码分析三(Tomcat请求源码分析)
java·tomcat
a程序小傲6 小时前
京东Java面试被问:动态规划的状态压缩和优化技巧
java·开发语言·mysql·算法·adb·postgresql·深度优先
仙俊红6 小时前
spring的IoC(控制反转)面试题
java·后端·spring
阿湯哥6 小时前
AgentScope Java 集成 Spring AI Alibaba Workflow 完整指南
java·人工智能·spring
小楼v7 小时前
说说常见的限流算法及如何使用Redisson实现多机限流
java·后端·redisson·限流算法
与遨游于天地7 小时前
NIO的三个组件解决三个问题
java·后端·nio
czlczl200209257 小时前
Guava Cache 原理与实战
java·后端·spring
yangminlei7 小时前
Spring 事务探秘:核心机制与应用场景解析
java·spring boot