Loading

rabbitmq入门

一、中间件概念基础

中间件是介于应用系统系统软件之间的一类软件,它使用系统软件所提供的基础服务,衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享、功能共享的目的。

目前,它并没有很严格的定义,但是普遍接受IDC的定义:中间件是一种独立的系统软件服务程序,分布式应用软件借助这种软件在不同的技术之间共享资源,中间件位于客户机服务器的操作系统之上,管理计算资源和网络通信。

从这个意义上可以用一个等式来表示中间件:中间件=平台+通信,这也就限定了只有用于分布式系统中才能叫中间件,同时也把它与支撑软件和实用软件区分开来。

简单说:中间件有个很大的特点,是脱离于具体设计目标,而具备提供普遍独立功能需求的模块。这使得中间件一定是可替换的。如果一个系统设计中,中间件时不可替代的,不是架构、框架设计有问题,那么就是这个中间件,在别处可能是个中间件,在这个系统内是引擎。

1、什么是消息中间件

1.1、单体架构

在实际的项目中,大部分的企业项目开发中,在早起都采用的是单体的架构模式

在企业开发当中,大部分的初期架构都采用的是单体架构的模式进行架构,而这种架构的典型的特点:就是把所有的业务和模块,源代码,静态资源文件等都放在一个工程中,如果其中的一个模块升级或者迭代发生一个很小的变动都会重新编译和重新部署项目。

这种架构存在的问题是:

  • 耦合度太高
  • 不易维护
  • 服务器的成本高
  • 以及升级架构的复杂度也会增大

1.2、分布式架构

和单体架构不同的是,单体架构是一个请求发起 jvm调度线程(确切的是 tomcat线程池)分配线程 Thread来处理请求直到释放,而分布式系统是:一个请求时由多个系统共同来协同完成,jvm和环境都可能是独立。

如果生活中的比喻的话,单体架构就像建设一个小房子很快就能够搞定,如果你要建设一个鸟巢或者大型的建筑,你就必须是各个环节的协同和分布,这样目的也是项目发展到后期的时候要去部署和思考的问题。

分布式架构系统存在的特点和问题如下:

存在问题:

  • 学习成本高,技术栈过多
  • 运维成本和服务器成本增高
  • 人员的成本也会增高
  • 项目的负载度也会上升
  • 面临的错误和容错性也会成倍增加
  • 占用的服务器端口和通讯的选择的成本高
  • 安全性的考虑和因素逼迫可能选择 RMI/MQ相关的服务器端通讯

好处:

  • 服务系统的独立,占用的服务器资源减少和占用的硬件成本减少,可以合理的分配服务资源,不造成服务器资源的浪费
  • 系统的独立维护和部署,耦合度降低,可插拔性
  • 系统的架构和技术栈的选择可以变的灵活(而不是单纯地选择 java)
  • 弹性的部署,不会造成平台因部署造成的瘫痪和停服的状态

1.3、基于消息中间件的分布式系统的架构

从上图中可以看出来,消息中间件的是

  1. 利用可靠的消息传递机制进行系统和系统直接的通讯
  2. 通过提供消息传递和消息的派对机制,它可以在分布式系统环境下扩展进程间的通讯

消息中间件应用的场景

  1. 跨系统数据传递
  2. 高并发的流量削峰
  3. 数据的并发和异步处理
  4. 大数据分析与传递
  5. 分布式事务

比如你有一个数据要进行迁移或者请求并发过多的时候,比如你有10W的并发请求下订单,我们可以在这些订单入库之前,我们可以把订单请求堆积到消息队列中,让它稳健可靠的入库和执行

img

1.4、常见的消息中间件

ActiveMQ、RabbitMQ、Kafka、RocketMQ等

2、消息队列协议

面试题:为什么消息中间件不直接使用 http协议

  • 因为 http请求报文头和响应报文头是比较复杂的,包含了Cookie,数据的加密解密,窗台吗,响应码等附加的功能,但是对于一个消息而言,我们并不需要这么复杂,也没有这个必要性,它其实就是负责数据传递,存储,分发就行,一定要追求的是高性能。尽量简洁,快速
  • 大部分情况下 http大部分都是短链接,在实际的交互过程中,一个请求到响应都很有可能会中断,中断以后就不会执行持久化,就会造成请求的丢失。这样就不利于消息中间件的业务场景,因为消息中间件可能是一个长期的获取信息的过程,出现问题和故障要对数据或消息执行持久化等,目的是为了保证消息和数据的高可靠和稳健的运行

常见的消息中间件协议有有:OpenWire、AMQP、MQTT、Kafka,OpenMessage协议

2.1、AMQP协议

AMQP:是高级消息队列协议。

特性:

  • 分布式事务支持
  • 消息的持久化支持
  • 高性能和高可靠的消息处理优势

2.2、MQTT协议

MQTT协议(Message Queueing Telemetry Transport)消息队列是 IBM开放的及时通讯协议,物联网系统架构中的重要组成部分

特点:

  • 轻量
  • 结构简单
  • 传输快,不支持事务
  • 没有持久化设计

应用场景:

  • 适用于计算能力有限
  • 低带宽
  • 网络不稳定的场景

2.3、OpenMessage协议

是近几年由阿里、雅虎和滴滴出行、Stremalio等公司共同参与创立的分布式信息中间件、流处理等领域的应用开发标准

特点:

  • 结构简单
  • 解析速度快
  • 支持事务和持久化设计

2.4、Kafka协议

Kafka协议是基于 TCP/IP的二进制协议。消息内部是 通过长度来分割,由一些基本数据类型组成

特点:

  • 结构简单
  • 解析速度快
  • 无事务支持
  • 有持久化设计

3、消息队列持久化

持久化

简单来说就是将数据存入磁盘,而不是存在内存中随服务器重启断开而消失,使数据能够永久保存。

ActiveMQ RabbitMQ Kafka RockerMQ
文件存储 支持 支持 支持 支持
数据库 支持 / / /

4、消息的分发策略

MQ消息队列有如下几个角色

  • 生产者
  • 存储消息
  • 消费者

生产者生成消息以后,MQ进行存储,消费者是如何获取消息的呢?

而消息队列 MQ是一种推送的过程

ActiveMQ RabbitMQ Kafka RockerMQ
发布订阅 支持 支持 支持 支持
轮询分发 支持 支持 支持 /
公平分发 / 支持 支持 /
重发 支持 支持 支持 支持
消息拉取 / 支持 支持 支持

5、消息队列高可用和高可靠

什么是高可用机制

所谓高可用:是指产品在规定的条件和规定的时刻或时间内处于可执行规定功能状态的能力

当业务量增加时,请求也过大,一台消息中间件服务器的会触及硬件(CPU,内存,磁盘)的极限,一台消息服务器你已经无法满足业务的需求,所以消息中间件必须支持集群部署,来达到高可用的目的.

5.1、集群模式Master-slave主从共享数据的部署方式

生产者将消费发送到 Master节点,所有的都连接这个消息队列共享这块数据区域,Master节点负责写入,一旦 Master挂掉,slave节点继续服务。从而形成高可用

5.2、Master-slave主从同步部署方式

这种模式写入消息同样在 Master主节点上,但是主节点会同步数据到 slave节点形成副本,和 zookeeper或者 redis主从机制很雷同。这样可以达到负载均衡的效果,如果消费者有多个这样就可以去不同的节点进行消费,以为消息的拷贝和同步会占用很大的带宽和网络资源。

5.3、 多主集群同步部署模式

和上面的区别不是特别的大,但是它的写入可以往任意节点去写入

5.4、多主集群转发部署模式

  • 如果你插入的数据是 broker-1中国,元数据信息会存储数据的相关描述和记录存放的位置。它会对描述信息也就是元数据信息进行同步

  • 如果消费者在 broker-2中进行消费,发现自己节点没有对应的信息,可以从对应的元数据信息中去查询,然后返回对应的消息信息,

  • 场景:比如买火车票或者黄牛买演唱会门票,比如第一个黄牛有顾客说要买的演唱会门票,但是没有但是他回去联系其他的黄牛询问,如果有就返回。

5.5、Master-slave与 Broker-cluster组合的方案

实现多主多从的热备机制来完成消息的高可用以及数据的热备机制,在生产规模达到一定的阶段的时候,这种使用的频率比较高

5.6什么是高可靠机制

所谓高可靠是指:系统可以无故障低持续运行,比如一个系统突然崩溃,报错,异常等等并不影响线上业务的正常运行,出错的几率极低,就称之为高可靠

在高并发的业务场景中,如果不能保证系统的高可靠,那造成的隐患和损失是非常严重的

如何保证中间件消息的可靠性呢,可以从两个方面考虑:

  • 消息的传输:通过协议来保证系统间数据解析的正确性。
  • 消息的存储区可靠:通过持久化来保证消息的可靠性。

二、RabbitMQ安装

2.1、概述

RabbitMQ是一个开源的遵循 AMQP协议实现的基于 Erlang语言编写,支持多种客户端(语言),用于在分布式系统中存储消息,转发消息,具有高可用,高可扩性,易用性等特征。

2.2、下载RabbitMQ

https://www.rabbitmq.com/download.html

安装在ubuntu服务器上

编写sh脚本

#!/bin/sh

sudo apt-get install curl gnupg debian-keyring debian-archive-keyring apt-transport-https -y

## Team RabbitMQ's main signing key
sudo apt-key adv --keyserver "hkps://keys.openpgp.org" --recv-keys "0x0A9AF2115F4687BD29803A206B73A36E6026DFCA"
## Launchpad PPA that provides modern Erlang releases
sudo apt-key adv --keyserver "keyserver.ubuntu.com" --recv-keys "F77F1EDA57EBB1CC"
## PackageCloud RabbitMQ repository
curl -1sLf 'https://packagecloud.io/rabbitmq/rabbitmq-server/gpgkey' | apt-key add -

## Add apt repositories maintained by Team RabbitMQ
sudo tee /etc/apt/sources.list.d/rabbitmq.list <<EOF
## Provides modern Erlang/OTP releases
##
## "bionic" as distribution name should work for any reasonably recent Ubuntu or Debian release.
## See the release to distribution mapping table in RabbitMQ doc guides to learn more.
deb http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu bionic main
deb-src http://ppa.launchpad.net/rabbitmq/rabbitmq-erlang/ubuntu bionic main

## Provides RabbitMQ
##
## "bionic" as distribution name should work for any reasonably recent Ubuntu or Debian release.
## See the release to distribution mapping table in RabbitMQ doc guides to learn more.
deb https://packagecloud.io/rabbitmq/rabbitmq-server/ubuntu/ bionic main
deb-src https://packagecloud.io/rabbitmq/rabbitmq-server/ubuntu/ bionic main
EOF

## Update package indices
sudo apt-get update -y

## Install Erlang packages
sudo apt-get install -y erlang-base \
                        erlang-asn1 erlang-crypto erlang-eldap erlang-ftp erlang-inets \
                        erlang-mnesia erlang-os-mon erlang-parsetools erlang-public-key \
                        erlang-runtime-tools erlang-snmp erlang-ssl \
                        erlang-syntax-tools erlang-tftp erlang-tools erlang-xmerl

## Install rabbitmq-server and its dependencies
sudo apt-get install rabbitmq-server -y --fix-missing

执行脚本后安装成功。

2.3、查看 RabbitMq状态

#Active: active (running) 说明处于运行状态

systemctl status rabbitmq-server

用service指令也可以查看,同systemctl指令

service rabbitmq-server status

2.4、启动、停止、重启

 service rabbitmq-server start  # 启动
 service rabbitmq-server stop  # 停止
 service rabbitmq-server restart # 重启

2.5、启用 web端可视化操作界面,我们还需要配置Management Plugin插件

启用插件

rabbitmq-plugins enable rabbitmq_management

装完后重启

service rabbitmq-server restart  

2.6、查看rabbitmq用户

rabbitmqctl list_users

2.7、添加管理用户

增加普通用户

rabbitmqctl add_user admin yourpassword 

给普通用户分配管理员角色

rabbitmqctl set_user_tags admin administrator

2.8、访问web控制台

打开浏览器

http://服务器IP:15672/ 来访问你的rabbitmq监控页面。使用刚刚添加的新用户(admin)登录。

image-20210506014624754

三、RabbitMQ-java教程

img

3.1、核心概念

  • Server: 又被称为Broker,接受客户端的连接,实现AMQP服务。就是我们自己安装的rabbitmq-server
  • Connection: 连接,应用程序与Broker的网络连接(使用的是TCP/IP连接)
  • Channel:网络信道,几乎所有的操作都在Channel中进行,Channel是进行消息读写的通道,每个通道
  • Channel代表一个会话任务。
  • Message:消息,服务于应用程序之间传递的数据,由Properties和body组成,Properties可以对消息进行修饰,比如消息的优先级,延迟等高级特征,Body则是消息体的内容。
  • Virtual Host 虚拟地址,用于逻辑层隔离,最上层的消息路由,一个虚拟机理由可以有若干的Exchange和
  • Queueu,同一个虚拟机里面不能有相同名字的Exchange。
  • Exchange: 交换机,接收消息,根据路由键发送消息到绑定的队列(不具备储存消息的能力)
  • Bindings: Exchange和Queue之间的虚拟连接,Binding中可以保护多个routing key.
  • Routing key: 是一个路由规则,虚拟机可以用它来确定如何路由一个特定消息。
  • Queue:队列,也是MessageQueue队列,保存消息转发给消费者

3.2、RabbitMQ的操作流程

第一:获取Conection
第二:获取Channel
第三:定义Exchange,Queue
第四:使用一个RoutingKey将Queue Binding到一个Exchange上
第五:通过指定一个Exchange和一个RoutingKey来将消息发送到对应的Queue上,
第六:Consumer在接收时也是获取connection,接着获取channel,然后指定一个Queue,到Queue上取消息,它对Exchange,RoutingKey及如何Binding都不关心,到对应的Queue上去取消息就行了。

注意:一个PublisherClient发送消息,哪些ConsumerClient可以收到消息,在于Exchange,RoutingKey,Queue的关系上

RabbitMQ的运行流程

3.3、RabbitMQ支持的消息模型

rabbit官方教程源代码:

https://github.com/rabbitmq/rabbitmq-tutorials/tree/master/java

3.4、Simple 简单模式

导入依赖

java原生依赖

<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.10.0</version>
</dependency>

官方教程:https://www.rabbitmq.com/tutorials/tutorial-one-java.html

在上图的模型中,有以下概念:

  1. 生产者,也就是要发送消息的程序
  2. 消费者:消息的接受者,会一直等待消息到来。
  3. 消息队列:图中红色部分。类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息。

生产者

//简单模式
public class Producer {

    private final static String QUEUE_NAME = "hello";

    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        try (Connection connection = factory.newConnection();
             Channel channel = connection.createChannel()) {
            channel.queueDeclare(QUEUE_NAME, false, false, false, null);
            String message = "Hello Wod!";
            channel.basicPublish("", QUEUE_NAME, null, message.getBytes(StandardCharsets.UTF_8));
            System.out.println(" [x] Sent '" + message + "'");
        }
    }
}

消费者

public class Recv {
    private final static String QUEUE_NAME = "hello";
    public static void main(String[] argv) throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("localhost");
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        System.out.println(" [*] Waiting for messages. To exit press CTRL+C");
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(" [x] Received '" + message + "'");
        };
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

3.5、RabbitMQ入门案例 - Work模式

  • 一个生产者、多个消费者
  • 多个消费者,共同监听一个队列
  • 一个消息,只能被一个消费者获取

img

3.6、RabbitMQ入门案例 - fanout 模式

发布订阅模式的具体实现

  1. web操作查看视频
  2. 类型:fanout
  3. 特点:Fanout - 发布与订阅模式,是一种广播机制,它是没有路由 key的模式

img

3.7、RabbitMQ入门案例 - Direct 模式

生产者和消费者,具有相同的交换机名称(Exchange)、交换机类型和相同的密匙(routingKey),那么消费者即可成功获取到消息。 (PS:相对比只要交换机名称即可接收到消息的广播模式(fanout),direct模式在其基础上,多加了一层密码限制(routingKey)。)

  • RabbitMQ消息模型的核心思想(core idea): 生产者会把消息发送给RabbitMQ的交换中心(Exchange)
  • Exchange的一侧是生产者,另一侧则是一个或多个队列,由Exchange决定一条消息的生命周期–发送给某些队列,或者直接丢弃掉。

img

3.8、RabbitMQ主题模式(Topic)

img

主体模式其实就是在路由模式的基础上,支持了对key的通配符匹配星号以及井号),以满足更加复杂的消息分发场景。

\# : 匹配一个或者多个
\*:匹配一个
  • lazy.#可以匹配到key=lazy.a或者key=lazy.a.b。

  • *.orange只能匹配到a.orange,无法匹配a.b.orange

关键代码

public class Send {
    private static final String EXCHANGE_NAME = "topic";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 声明exchange
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        // 推送消息到交换机时携带key
        String msg1 = "error.log";
        String key1 = "error.log";
        channel.basicPublish(EXCHANGE_NAME, key1, null, msg1.getBytes());
        String msg2 = "success.log";
        String key2 = "success.log";
        channel.basicPublish(EXCHANGE_NAME, key2, null, msg2.getBytes());
        String msg3 = "a.b.log";
        String key3 = "a.b.log";
        channel.basicPublish(EXCHANGE_NAME, key3, null, msg3.getBytes());
        channel.close();
        connection.close();
    }
}
public class Rec1 {
    private static final String EXCHANGE_NAME = "topic";
    private static final String QUEUE_NAME = "topic_queue1";

    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 交换机绑定,绑定交换机时携带key
        String key = "#.log";
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, key);
        // 接收到消息后的回调函数
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(LocalDateTime.now().toString() + " [x] Received '" + message + "'");
        };
        // 监听队列,每当队列中接收到新消息后会触发回调函数
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}
public class Rec2 {
    private static final String EXCHANGE_NAME = "topic";
    private static final String QUEUE_NAME = "topic_queue2";
    public static void main(String[] args) throws IOException, TimeoutException {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        // 队列声明
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        // 交换机绑定,绑定交换机时携带key
        String key = "*.log";
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, key);
        // 接收到消息后的回调函数
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            String message = new String(delivery.getBody(), "UTF-8");
            System.out.println(LocalDateTime.now().toString() + " [x] Received '" + message + "'");
        };
        // 监听队列,每当队列中接收到新消息后会触发回调函数
        channel.basicConsume(QUEUE_NAME, true, deliverCallback, consumerTag -> { });
    }
}

运行结果

img

img

四、Springboot案例

https://www.iocoder.cn/Spring-Boot/RabbitMQ/?github

1、引入依赖pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>lab-04-rabbitmq-demo</artifactId>
    <dependencies>
        <!-- 实现对 RabbitMQ 的自动化配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <!-- 方便等会写单元测试 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

resources 目录下,创建 application.yaml 配置文件。配置如下:

spring:
  # RabbitMQ 配置项,对应 RabbitProperties 配置类
  rabbitmq:
    host: 127.0.0.1 # RabbitMQ 服务的地址
    port: 5672 # RabbitMQ 服务的端口
    username: guest # RabbitMQ 服务的账号
    password: guest # RabbitMQ 服务的密码

2、Demo01Message

创建 Demo01Message 消息类,提供给当前示例使用。代码如下:

package cn.iocoder.springboot.lab04.rabbitmqdemo.message;
import java.io.Serializable;
public class Demo01Message implements Serializable {
    public static final String QUEUE = "QUEUE_DEMO_01";
    public static final String EXCHANGE = "EXCHANGE_DEMO_01";
    public static final String ROUTING_KEY = "ROUTING_KEY_01";
    /**
     * 编号
     */
    private Integer id;
    public Demo01Message setId(Integer id) {
        this.id = id;
        return this;
    }
    public Integer getId() {
        return id;
    }
    @Override
    public String toString() {
        return "Demo01Message{" +
                "id=" + id +
                '}';
    }
}

3、RabbitConfig

创建 RabbitConfig 配置类,添加 Direct Exchange 示例相关的 Exchange、Queue、Binding 的配置。代码如下:

// RabbitConfig.java

import org.springframework.amqp.core.Queue;

@Configuration
public class RabbitConfig {

    /**
     * Direct Exchange 示例的配置类
     */
    public static class DirectExchangeDemoConfiguration {

        // 创建 Queue
        @Bean
        public Queue demo01Queue() {
            return new Queue(Demo01Message.QUEUE, // Queue 名字
                    true, // durable: 是否持久化
                    false, // exclusive: 是否排它
                    false); // autoDelete: 是否自动删除
        }

        // 创建 Direct Exchange
        @Bean
        public DirectExchange demo01Exchange() {
            return new DirectExchange(Demo01Message.EXCHANGE,
                    true,  // durable: 是否持久化
                    false);  // exclusive: 是否排它
        }

        // 创建 Binding
        // Exchange:Demo01Message.EXCHANGE
        // Routing key:Demo01Message.ROUTING_KEY
        // Queue:Demo01Message.QUEUE
        @Bean
        public Binding demo01Binding() {
            return BindingBuilder.bind(demo01Queue()).to(demo01Exchange()).with(Demo01Message.ROUTING_KEY);
        }
    }
}

4、Demo01Producer

创建 Demo01Producer 类,它会使用 Spring-AMQP 封装提供的 RabbitTemplate ,实现发送消息。代码如下

@Component
public class Demo01Producer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void syncSend(Integer id) {
        // 创建 Demo01Message 消息
        Demo01Message message = new Demo01Message();
        message.setId(id);
        // 同步发送消息
        rabbitTemplate.convertAndSend(Demo01Message.EXCHANGE, Demo01Message.ROUTING_KEY, message);
    }

    public void syncSendDefault(Integer id) {
        // 创建 Demo01Message 消息
        Demo01Message message = new Demo01Message();
        message.setId(id);
        // 同步发送消息
        rabbitTemplate.convertAndSend(Demo01Message.QUEUE, message);
    }

    @Async
    public ListenableFuture<Void> asyncSend(Integer id) {
        try {
            // 发送消息
            this.syncSend(id);
            // 返回成功的 Future
            return AsyncResult.forValue(null);
        } catch (Throwable ex) {
            // 返回异常的 Future
            return AsyncResult.forExecutionException(ex);
        }
    }

}
  • RabbitTemplate 是 AmqpTemplate 接口的实现类,所以此时使用 AmqpTemplate 亦可。不过又因为 RabbitTemplate 还实现了其它接口,所以操作会更为丰富。因此,这里我们选择了注入 RabbitTemplate 属性。
  • #syncSend(Integer id) 方法,调用 RabbitTemplate 的同步发送消息方法。方法定义如下:

5、Demo01Consumer

创建 Demo01Consumer 类,消费消息。代码如下:

// Demo01Consumer.java
@Component
@RabbitListener(queues = Demo01Message.QUEUE)
public class Demo01Consumer {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @RabbitHandler
    public void onMessage(Demo01Message message) {
        logger.info("[onMessage][线程编号:{} 消息内容:{}]", Thread.currentThread().getId(), message);
    }
}
  • 在类上,添加了 @RabbitListener 注解,声明了消费的队列是 "QUEUE_DEMO_01"
  • 在方法上,添加了 @RabbitHandler 注解,申明了处理消息的方法。同时,方法入参为消息的类型。这里,我们设置了「3.1.4 Demo01Message」
  • 如果我们想要获得消费消息的更多信息,例如说,RoutingKey、创建时间等等信息,则可以考虑使用艿艿注释掉的那段代码,通过方法入参为 org.springframework.amqp.core.Message 类型。不过绝大多数情况下,我们并不需要这么做。

6、简单测试

创建 Demo01ProducerTest 测试类,编写三个单元测试方法,调用 Demo01Producer 三个发送消息的方式。代码如下:

@SpringBootTest
public class Demo01ProducerTest {
    private Logger logger = LoggerFactory.getLogger(getClass());
    @Autowired
    private Demo01Producer producer;
    @Test
    public void testSyncSend() throws InterruptedException {
        int id = (int) (System.currentTimeMillis() / 1000);
        producer.syncSend(id);
        logger.info("[testSyncSend][发送编号:[{}] 发送成功]", id);
        // 阻塞等待,保证消费
        new CountDownLatch(1).await();
    }

    @Test
    public void tesSyncSendDefault() throws InterruptedException {
        int id = (int) (System.currentTimeMillis() / 1000);
        producer.syncSendDefault(id);
        logger.info("[tesSyncSendDefault][发送编号:[{}] 发送成功]", id);
        // 阻塞等待,保证消费
        new CountDownLatch(1).await();
    }

    @Test
    public void testAsyncSend() throws InterruptedException {
        int id = (int) (System.currentTimeMillis() / 1000);
        producer.asyncSend(id).addCallback(new ListenableFutureCallback<Void>() {
            @Override
            public void onFailure(Throwable e) {
                logger.info("[testASyncSend][发送编号:[{}] 发送异常]]", id, e);
            }
            @Override
            public void onSuccess(Void aVoid) {
                logger.info("[testASyncSend][发送编号:[{}] 发送成功,发送成功]", id);
            }
        });
        logger.info("[testASyncSend][发送编号:[{}] 调用完成]", id);
        // 阻塞等待,保证消费
        new CountDownLatch(1).await();
    }
}

五、RabbitMQ高级-过期时间TTL

1、概述

过期时间 TTL表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;

过了之后消息将自动被删除。RabbitMQ可以对消息和队列设置 TTL,目前有两种方法可以设置。

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间
  • 第二种方法是对消息进行单独设置,每条消息 TTL可以不同

如果上述两种方法同时使用,则消息的过期时间以两者 TTL较小的那个数值为准。消息在队列的生存时间一旦超过设置的 TTL值,就称为 dead message被投递到死信队列,消费者将无法再收到该消息

2、设置队列TTL

@Configuration
public class TTLRabbitMQConfiguration{
    //1.声明注册direct模式的交换机
    @Bean
    public DirectExchange ttldirectExchange(){
        return new DirectExchange("ttl_direct_exchange",true,false);}
    //2.队列的过期时间
    @Bean
    public Queue directttlQueue(){
        //设置过期时间
        Map<String,Object> args = new HashMap<>();
        args.put("x-message-ttl",5000);//这里一定是int类型
        return new Queue("ttl.direct.queue",true,false,false,args);}
    @Bean
    public Binding ttlBingding(){
        return BindingBuilder.bind(directttlQueue()).to(ttldirectExchange()).with("ttl");
    }
}

3、设置生产者

@Component
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    public void makeOrder(){
        //1.根据商品id查询库存是否足够
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println(("订单生产成功:" + orderId));
        //3.通过MQ来完成消息的分发
        //参数1:交换机 参数2:路由key/queue队列名称 参数3:消息内容
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttlmessage";
        //给消息设置过期时间
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
            public Message postProcessMessage(Message message){
                //这里就是字符串
                message.getMessageProperties().setExpiration("5000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            };
        };
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId,messagePostProcessor);
    }
}

4、测试

@SpringBootTest
public class NewTest {
    @Autowired
    private OrderService orderService;
    @Test
    public void testOrder(){
        orderService.makeOrder();
    }
}
posted @ 2021-05-10 02:15  xine  阅读(288)  评论(0编辑  收藏  举报