RabbitMQ使用指南

1 MQ 简 介

消息中间件利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。对于消息中间件,常见角色大致也就有 Producer(生产者)、Consumer(消费者)。

常见的消息中间件产品:

(1). ActiveMQ

ActiveMQ 是 Apache 出品,最流行的,能力强劲的开源消息总线。ActiveMQ 是一个完全支持 JMS1.1 和 J2EE 1.4 规范的 JMS Provider 实现。我们在本次课程中介绍 ActiveMQ 的使用。

(2). RabbitMQ

AMQP 协议的领导实现,支持多种场景。淘宝的 MySQL 集群内部有使用它进行通讯,

OpenStack 开源云平台的通信组件,最先在金融行业得到运用。

(3). ZeroMQ

史上最快的消息队列系统

(4). Kafka

Apache 下的一个子项目 。特点:高吞吐,在一台普通的服务器上既可以达到 10W/s

的吞吐速率;完全的分布式系统。适合处理海量数据

2 MQ 作 用

(1). 解耦 :中间件中的生产者只管发送消息 , 消费者只要从队列当中获取消息进行消费就可以 , 从而来实现业务的解耦 .

(2). 冗余存储 : 有些情况下,处理数据的过程会失败。消息中间件可以把数据进行持久化直 到它们已经被完全处理,通过这一方式规避了数据丢失风险。在把一个消息从消息中间件中删 除之前,需要你的处理系统明确地指出该消息己经被处理完成,从而确保你的数据被安全地保 存直到你使用完毕。

(3). 可恢复性: 当系统一部分组件失效时,不会影响到整个系统 。 消息中间件降低了进程间的 稿合度,所以即使一个处理消息的进程挂掉,加入消息中间件中的消息仍然可以在系统恢复后 进行处理 。

(4). 顺序保证: 在大多数使用场景下,数据处理的顺序很重要,大部分消息中间件支持一定程 度上的顺序性。

(5). 缓冲: 在任何重要的系统中,都会存在需要不同处理时间的元素。消息中间件通过一个缓 冲层来帮助任务最高效率地执行,写入消息中间件的处理会尽可能快速 。

(6). 异步通信: 在很多时候应用不想也不需要立即处理消息 。 消息中间件提供了异步处理机制,允许应用把一些消息放入消息中间件中,但并不立即处理它,在之后需要的时候再慢慢处理 。

3 RabbitMQ 安装及启动
3.1 安装依赖环境

rpm -ivh erlang-20.3.8.6-1.el6.x86_64.rpm

yum -y install epel-release

yum -y install socat

3.2 安装 rabbitMQ

rpm -ivh rabbitmq-server-3.7.7-1.el6.noarch.rpm

3.3 添加用户

默认情况下管理界面只能在 Linux 系统本机可以访问, 如果想其他的主机也能访问,需要配置:

rabbitmq-plugins enable rabbitmq_management

添加访问用户:

rabbitmqctl add_user admin admin

3.4 RabbitMQ 启动/停止

启动 : service rabbitmq-server start

停止: service rabbitmq-server stop

查看状态: service rabbitmq-server status

4 Rabbit MQ 管理界面访问
4.1 Overview 概 要

该栏目主要展示的是 MQ 的概要信息 , 如消息的数量, Connection , Channel, Exchange , Queue , Consumer 的数量.
在这里插入图片描述

4.2 Exchange 交换器

该栏目主要展示的是当前虚拟主机下的交换器,也可以在此添加一个新的交换器, 并且配置对应的交换器的规则属性。
在这里插入图片描述
4.3 Queues 队 列

该栏目展示的是消息队列的信息,里面有各个队列的概要信息, 也可以在此栏目添加队列
Queue
在这里插入图片描述
4.4 Admin 系统管理

该栏目展示的是用户管理的信息, 包含用户列表的展示,添加用户,添加虚拟主机等信息
在这里插入图片描述

5 RabbitMQ 的相关概念
5.1 生产者与消费者

5.1.1 生产者

Producer: 生产者,就是投递消息的一方。

生产者创建消息,然后发布到 RabbitMQ 中。消息一般可以包含 2 个部分:消息体和标签 (Label)。消息体也可以称之为 payload ,在实际应用中,消 息体一般是一个带有业务逻辑结构 的数据,比如一个 JSON 字符串。当然可以进一步对这个消息体进行序列化操作。消息的标签用来表述这条消息 , 比如 一个交换器的名称和一个路由键 。 生产者把消息交由 RabbitMQ , RabbitMQ 之后会根据标签把消息发送给感兴趣的消费者(Consumer ) 。

5.1.2 消费者

Consumer: 消费者 ,就是接收消息的一方。

消费者连接到 RabbitMQ 服务器,并订阅到队列上 。当消费者消费一 条消息时 , 只是消费消息的消息体(payload )。在消息路由的过程中 ,消息的标签会丢弃 ,存入到队列中的消息只有消息体,消费者也只会消费到消息体 ,也就不知道消息的生产者是谁,当然消费者也不需要知道 。

5.2 队列

Queue: 队列,是 RabbitMQ 的内部对象,用 于存储消息。
在这里插入图片描述

5.3 交换器, 路由键, 绑定

5.3.1 交换器

Exchange: 交换器。在上图中我们暂时可以理解成生产者将消息投递到队列中,实际上 这个在 RabbitMQ 中不会发生。真实情况是,生产者将消息发送到 Exchange (交换器),由交换器将消息路由到一个或者多个队列中。如果路由不到,或 许会返回给生产者, 或许直接丢弃。这里可以将RabbitMQ 中的交换器看作一个简单的实体。

RabbitMQ 使用指南
RabbitMQ 中的 交换器有四种类型, 四种类型分别是 fanout、direct、topic 、headers,不同的类型有着不 同的路由策略。

5.3.2 路由键

RoutingKey : 路由键 。

生产者将消息发给交换器 的时候, 一般会指定 一个 RoutingKey ,用 来指定这个消息的路由规则,而这个 RoutingKey 需要与交换器类型和绑定键 (BindingKey) 联合使用才能最终生效。

在交换器类型和绑定键 (BindingKey) 固定的情况下,生产者可以在发送消息给交换器时, 通过指定 RoutingKey 来决定消息流向哪里。

5.3.3 绑定

Binding: 绑定。RabbitMQ 中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键(BindingKey) ,这样 RabbitMQ 就知道如何正确地将消息路由到队列了。

5.4 交换器类型

(1).fanout : 它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中。

(2).direct: 该类型的交换器路由规则也很简单, 它会把消息路由到那些

BindingKey 和 RoutingKey 完全匹配的队列中。

(3).topic : 前面讲到 direct 类型的交换器路由规则是完全匹配 BindingKey和 RoutingKey ,但是这种严格的匹配方式在很多情况下不能满足实际业务的需求。topic 类型的交换器在匹配规则上进行了扩展,它与 direct 类型的交换器相似,也是将消息路由到 BindingKey 和 RoutingKey 相匹配的队 列中,但这里的匹配规则有些不同,它约定:

RoutingKey 为一个点号"." 分割的字符串 , 如 : com.jhjy.client , com.itheima.exam。

BindingKey 与 RoutingKey 一样也是点号"." 分割的字符串。

BindingKey 中可以存在两种特殊的字符串 “*” 和 “#” , 用于模糊匹配,其中

"#"用于匹配一个单词, "*"用于匹配多个单个(可以是零个)。

(4). headers : 该类型的交换器不依赖于路由键的匹配规则来路由消息,而是根据发送的消息内容中 的 headers 属性进行匹配。

6 生产者发送消息

6.1 队列绑定

6.1.1 创建队列

在 RabbitMQ 的后台管理界面中创建一个队列 , 指定队列名称。
在这里插入图片描述
6.1.2 创建交换器 Exchange

在 RabbitMQ 的后台管理界面中创建一个交换器,指定交换器的名称, 并且指定交换器类型。

RabbitMQ 使用指南
6.1.3 绑定队列与交换器

在交换器列表点击对应的交换器 , 进入到绑定界面 , 指定队列名称 queue , 指定 RoutingKey , 通过该 RoutingKey 来绑定该队列与交换器 Exchange 。

RabbitMQ 使用指南
之后,在发送消息时, 指定了 Exchange ,及 RoutingKey, 就可以将该消息路由到该队列 queue 中。
在这里插入图片描述

6.2 发送消息逻辑代码

6.2.1 引入依赖

RabbitMQ 使用指南
6.2.2 发送消息

RabbitMQ 使用指南RabbitMQ 使用指南
6.3 发送消息平台监测

RabbitMQ 使用指南

7 消费者接受消息

7.1 引入依赖

RabbitMQ 使用指南
7.2 接收消息

RabbitMQ 使用指南
RabbitMQ 使用指南
7.3 结果输出

RabbitMQ 使用指南
其中:

consumerTag : 消息消费者的标签

properties : 消息内容的头信息数据

envelope : 消息体的数据包,其中包含消息发送时指定的exchange, routingKey等信息.

8 RabbitMQ 死信队列

RabbitMQ 消息的消费有两种确认模式: 自动确认和手动确认
自动确认:Broker(RabbitMQ 服务器)在将消息发送给消费者后即将消息从队列中删除,无论消费者是否消费成功。如果消费者消费时业务代码出现异常或者还未消费完毕时系统宕机,就会导致消息丢失。

手动确认:消费者消费完毕后手动地向 Broker 发送确认通知,Broker 收到确认通知后再从队列中删除对应的消息。

由于自动确认方式存在的缺陷,对于一些重要的消息,实际中一般采用手动确认的方式来保证消息业务的可靠性。

那么在手动确认方式下,消费者业务中具体如何保证消息的可靠性呢? 下面我们介绍一种方式,即采用 重试 + 手动确认 + 死信队列 的方式来保证消费信息不丢失。

8.1 死信队列

死信(Dead Letter),指无法被消费者正确地进行业务处理的消息,消费者消费时业务程序抛出了异常,其主要原因有两个。一、消息本身是有问题的(主要原因),如付款消息中传递的银行卡号不存在。二、由于网络波动等原因,消费者依赖的第三方服务调用异常,如调用第三方接口失败,数据库由于无法获取连接访问失败等情况。对于这类消息,一般将其放入 RabbitMQ 的死信队列中,使用专门的消费者对死信进行处理,或者进行人工补偿。

8.2 死信队列的消息来源

1、消息被否定确认使用 channel.basicNack 或 channel.basicReject ,并且此时requeue 属性被设置为false。
2、消息在队列中的时间超过了设置的TTL(time to live)时间。
3、消息数量超过了队列的容量限制。

当一个队列中的消息满足上述三种情况任一个时,该消息就会从原队列移至死信队列,若该队列没有绑定死信队列则消息被丢弃。

Tips:死信队列和普通的业务队列完全一样,只不过是业务上创建用来存储处理失败的消息的队列。所以其工作方式也和业务队列相同,死信仍然需要交换机的转发到达死信队列。

8.3 死信队列的配置

1、为业务队列绑定死信交换机(即用来转发该队列中中死信的交换机)。
2、将死信队列与死信交换机绑定。

8.4 代码示例
@Configuration
public class RabbitMQConfig {

    public static final String BUSINESS_EXCHANGE_NAME = "business-exchange";
    public static final String DEAD_LETTER_EXCHANGE_NAME = "dead-letter-exchange";
    public static final String BUSINESS_QUEUE_NAME = "business-queue";
    public static final String DEAD_LETTER_QUEUE_NAME = "dead-letter-queue";
    public static final String ROUTING_KEY = "routing-key";

    // 声明业务交换机
    @Bean
    public DirectExchange businessExchange(){
        return new DirectExchange(BUSINESS_EXCHANGE_NAME);
    }
    // 声明死信交换机
    @Bean
    public DirectExchange deadLetterExchange(){
        return new DirectExchange(DEAD_LETTER_EXCHANGE_NAME);
    }
    // 声明业务队列
    @Bean
    public Queue businessQueue(){
        Map<String, Object> args = new HashMap<>(2);
        // 设置业务队列的死信交换机
        args.put("x-dead-letter-exchange", DEAD_LETTER_EXCHANGE_NAME);
        return QueueBuilder.durable(BUSINESS_QUEUE_NAME).withArguments(args).build();
    }
    // 声明死信队列
    @Bean
    public Queue deadLetterQueue(){
        return new Queue(DEAD_LETTER_QUEUE_NAME);
    }
    // 将业务队列绑定到业务交换机
    @Bean
    public Binding bindBusinessQueue(){
        return BindingBuilder.bind(businessQueue()).to(businessExchange()).with(ROUTING_KEY);
    }
    // 将死信队列绑定到死信交换机
    @Bean
    public Binding bindDeadLetterQueue(){
        return BindingBuilder.bind(deadLetterQueue()).to(deadLetterExchange()).with(ROUTING_KEY);
    }
}

在上面的配置示例中,我们声明了一个业务队列、一个业务交换机、一个死信队列、一个死信交换机。其中声明业务队列时的 x-dead-letter-exchange 参数指定队列的死信交换机,当信息被判定为死信时就会被Broker自动转发给配置的死信交换机。

生产者代码
我们定义一个Controller接口来测试消息发送,消息体由接口参数传入。

@RestController
@RequestMapping("/rabbitmq")
public class RabbitController {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @PostMapping("/send")
    public void send(@RequestParam String msg){
        rabbitTemplate.convertAndSend(RabbitMQConfig.BUSINESS_EXCHANGE_NAME, RabbitMQConfig.ROUTING_KEY, msg);
    }
}

使用postman调用生产者接口

http://localhost:5006/rabbitmq/send?msg=normal meaage

通过 RabbitMQ 控制台查看可以看到我们声明的交换机个队列成功创建,消息被发送到了业务队列中。
在这里插入图片描述
在这里插入图片描述
消费者手动确认+重试

消费者配置

# rabbitmq服务器连接端口 (默认为5672)
spring.rabbitmq.host=192.168.44.104
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123456
# 开启消费者手动确认
spring.rabbitmq.listener.simple.acknowledge-mode=manual
@Service
@Slf4j
@RabbitListener(queues = "business-queue")
public class RabbitConsumer {
    /**
     * 指定消费的队列
     */
    @RabbitHandler
    public void consume(String msg, Message message, Channel channel){
        boolean success = false;
        int retryCount = 3;
        while (!success && retryCount-- > 0){
            try {
                // 处理消息
                log.info("收到消息: {}, deliveryTag = {}", msg, message.getMessageProperties().getDeliveryTag());
                if(message.equals("dead-letter")){
                    throw new RuntimeException("收到死信");
                }
                // 正常处理完毕,手动确认
                success = true;
                channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            }catch (Exception e){
                log.error("程序异常:{}", e.getMessage());
            }
        }
        // 达到最大重试次数后仍然消费失败
        if(!success){
            // 手动删除,移至死信队列
            try {
                channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

启动消费者,消息被正常消费后手动确认,消息从队列中删除
在这里插入图片描述
在这里插入图片描述
我们下面发送一个私信

http://localhost:5006/rabbitmq/send?msg=dead-letter

在这里插入图片描述
从上图我们可以看到消费的代码重试了3次后将消息否定确认,Broker将消息判断为死信,发送至死信交换机,最终转发到死信队列。
在这里插入图片描述
中看到死信确实被转发到了死信队列。根据实际的业务情况,我们可以创建专门的死信消费者对死信进行处理,或者进行人工补偿。

Tips: 代码示例中没有涉及数据库事务,若消费程序使用了声明式的事务@Transactional,在捕获异常后要手动回滚事务。如下图:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值