中间件RabbitMQ | 青训营笔记

109 阅读11分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 8 天

消息队列

分布式中间件

  • ActiveMQ
  • RibbitMQ:支持事务,容错性较高,支持模式多
  • Kafka:基于tcp/ip二进制协议,性能最高,不支持事务
  • RocketMQ

场景

  • 消息中间件监控数据
  • 异步数据传输
  • 任务调度
  • 海量数据同步场景
  • 分布式事务场景
  • 大数据分析

协议

  • AMQP
  • MQTT
  • 持久化设计
  • kafka协议
  • 消息分发设计
  • 高可用设计
  • 可靠性设计
  • 容错设计

负载均衡中间件

  • Nginx
  • LVS负载均衡软件
  • KeepAlive
  • CDN

缓存中间件

  • memcache
  • redis

数据库中间件

  • mycat
  • shardingJdbc

本质

负责数据的传接收、存储和传递,接收数据,接收请求,存储数据,发送数据等功能的技术服务。

核心组成部分

  1. 消息的协议:计算机底层操作系统和app通讯共同遵守的约定

    • rabbitMQ遵守的AMQP协议(建立在tcp/ip协议上)
  2. 消息的持久化机制

    • 将数据存入磁盘,而不是存在内存中随服务器重启断开而消失
    • 持久化方式:文件存储、数据库
  3. 消息的分发策略

    • 发布订阅
    • 轮询分发:所有的服务器不论性能都是一样的
    • 公平分发:能力高多分发,能力低少分发
    • 重发
    • 消息拉取
  4. 消息的高可用、高可靠

    • 高可用:指产品在规定的条件和时间内处于可执行规定功能状态的能力,从是否能集群的部署考虑
    • 高可靠:系统无故障低持续运行,如系统异常不影响线上业务员的正常运行,从系统持久化方面考虑
  5. 消息的容错机制

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

因为http请求头复杂,对于一个消息中间件不需要这么复杂,就是负责数据传递、存储、分发;并且http是短连接(不存储),会造成消息的丢失。

AMQP协议

高级消息队列协议,采用Erlang语言实现

特性:

  1. 分布式事务支持

  2. 消息的持久化支持

  3. 高性能高可靠消息处理优势

  4. Advanced Message Queuing Protocol:高级消息队列

    1. 创建连接
    2. 开启通道
    3. 发送消息
    4. 释放资源

MQTT协议

特点:

  1. 轻量
  2. 结构简单
  3. 传输块,不支持事务
  4. 没有持久化设计

OpenMessage协s议

如RocketMQ使用

特点:

  1. 结构简单
  2. 解析速度块
  3. 支持事务和持久化设计

Kafka协议

基于tcp/ip的二进制协议,消息内容通过长度来分割,由一些基本数据类型组成

特点:

  1. 结构简单
  2. 解析速度块
  3. 无事务支持
  4. 支持持久化设计

RibbitMQ消息队列

安装

Downloading and Installing RabbitMQ — RabbitMQ

Erlang Solutions - Scalable Distributed Technology (erlang-solutions.com)

环境部署:Erlang+rabbitmq

直接地址:

rabbitmq:pan.baidu.com/s/1aJH6ibJH…

提取码:ujje

erlang:pan.baidu.com/s/1sdhwdL6-…

提取码:udqn

1.安装erlang

配置环境变量

image-20230206161026870.png

在Path中配置

%ERLANG_HOME%\bin

cmd验证是否安装成功

image-20230206161358377.png

2.安装rabbitMQ

打开RabbitMQ的sbin目录

D:\Environment\RabbitMQ-3.7.4\rabbitmq_server-3.7.4\sbin>

输入以下命令安装插件

rabbitmq-plugins enable rabbitmq_management

image-20230206162058238.png

输入命令查看是否安装成功

rabbitmqctl status

image-20230206163621398.png

打开浏览器,输入网址访问管理界面的登录页面

http://127.0.0.1:15672 

用户名和密码都是guest

image-20230206165114445.png

问题:

出现 Authentication failed (rejected by the remote node), please check the Erlang cookie

修改cookie改成一样的即可

  1. C:\Windows\System32\config\systemprofile.erlang.cookie
  2. C:\Users{{电脑用户名}}.erlang.cookie

如果浏览器打不开登录页面

  1. 输入rabbitmq-plugins enable rabbitmq_management开启管理界面
  2. 检查erlang环境

授权账号和密码

新增用户

rabbitmqctl add_user admin admin

设置用户分配操作权限

rabbitmqctl set_user_tags admin administrator

用户级别:

  1. administrator可以登录控制台、查看所有信息,对mq进行管理
  2. monitoring 监控者 登录控制台,查看所有信息
  3. policymaker 策略制定者 登录控制台 指定策略
  4. managment 普通管理员 登录控制台

image-20230206165919312.png

为用户添加资源权限

rabbitmqctl.bat set_permissions -p / admin ".*" ".*" ".*"
rabbitmqctl change_password Username Newpassword
rabbitmqctl delete_user
rabbitmqctl list_users #查看用户清单

入门案例

应用场景:高内聚、低耦合;派单、抢票,流量监控,日志监控等

image-20230206220559692.png

创建一个maven工程项目

导入rabbitmq的maven依赖

java原生依赖

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

Provider

package com.liu.rabbitmq.simple;
​
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
​
public class Producer {
​
    public static void main(String[] args) {
        //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
        //1.创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(15672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
​
        Connection connection = null;
        Channel channel = null;
        try {
            // 2.创建连接connection
            connection = connectionFactory.newConnection("生产者");
            // 3.通过连接获取通道channel
            channel = connection.createChannel();
            //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
            String queryName = "queue1";
            //声明队列 参数:队列的名称 是否要持久化 排他性是否是独占队列  是否自动删除队列  携带附加参数
            channel.queueDeclare(queryName,false,false,false,null);
            // 5.准备消息内容
            String message = "Hello liuxiang!";
            // 6.发送消息给队列queue 第一个参数代表默认的交换机,交换机不能没有
            channel.basicPublish("",queryName,null,message.getBytes());
​
            System.out.println("消息发送成功");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (channel !=null && channel.isOpen()){
                try {
                    //8.关闭通道
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7.关闭连接
            if (connection !=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
​
​
    }
​
}
​

Consumer

package com.liu.rabbitmq.simple;
​
import com.rabbitmq.client.*;
​
import java.io.IOException;
​
public class Consumer {
    public static void main(String[] args) {
        //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
        //1.创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
​
        Connection connection = null;
        Channel channel = null;
        try {
            // 2.创建连接connection
            connection = connectionFactory.newConnection("生产者");
            // 3.通过连接获取通道channel
            channel = connection.createChannel();
            //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
            channel.basicConsume("queue1", true, new DeliverCallback() {
                public void handle(String consumerTag, Delivery message) throws IOException {
                    System.out.println("收到消息是"+ new String(message.getBody(),"UTF-8"));
                }
            },new CancelCallback(){
                public void handle(String consmerTag) throws IOException{
                    System.out.println("接收失败");
                }
            });
            System.out.println("开始接收消息");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (channel !=null && channel.isOpen()){
                try {
                    //8.关闭通道
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7.关闭连接
            if (connection !=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
​

结果:

image-20230206214206592.png

问题:如果报错IO异常

在rabbitMQ管理界面设置允许即可!

image-20230206214249311.png

让Admin变成可接入状态

image-20230206214420569.png

RabbitMQ模式

默认模式是direct模式

headers模式:携带参数

简单模式

通过交换机推送消息到队列,消费者监听队列;绑定的路由key就是队列的名字

image-20230206221717504.png

image-20230206221623111.png

发布订阅模式(fanout)

image-20230206222407555.png

image-20230206222828138.png

声明队列绑定交换机

image-20230206223126403.png

发送消息

image-20230206223246471.png

队列接收消息

image-20230206223353470.png

代码模式理解

Provider

public static void main(String[] args) {
        //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
        //1.创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
​
        Connection connection = null;
        Channel channel = null;
        try {
            // 2.创建连接connection
            connection = connectionFactory.newConnection("生产者");
            // 3.通过连接获取通道channel
            channel = connection.createChannel();
            //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
            String queryName = "queue1";
            //声明队列 参数:队列的名称 是否要持久化 排他性是否是独占队列  是否自动删除队列  携带附加参数
            channel.queueDeclare(queryName,false,false,false,null);
            // 5.准备消息内容
            String message = "Hello liuxiang!";
            // 6.准备交换机
            String exchangeName = "fanout-exchange";
            //7.定义路由key
            String routeKey = "";
            //8.交换机的类型
            String type = "fanout";
​
            channel.basicPublish(exchangeName, routeKey,null,message.getBytes());
​
            System.out.println("消息发送成功");
        } catch (Exception ex) {
            ex.printStackTrace();
        } finally {
            if (channel !=null && channel.isOpen()){
                try {
                    //8.关闭通道
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7.关闭连接
            if (connection !=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }

Consumer

 private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
            //1.创建连接工程
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");
​
            //获取队列的名称
            final String queueName = Thread.currentThread().getName();
            Connection connection = null;
            Channel channel = null;
            try {
                // 2.创建连接connection
                connection = connectionFactory.newConnection("生产者");
                // 3.通过连接获取通道channel
                channel = connection.createChannel();
                //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
                channel.basicConsume(queueName, true, new DeliverCallback() {
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName+":收到消息是"+ new String(message.getBody(),"UTF-8"));
                    }
                },new CancelCallback(){
                    public void handle(String consmerTag) throws IOException{
                        System.out.println("接收失败");
                    }
                });
                System.out.println("开始接收消息");
                System.in.read();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (channel !=null && channel.isOpen()){
                    try {
                        //8.关闭通道
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                // 7.关闭连接
                if (connection !=null && connection.isOpen()){
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    } ;
​
    public static void main(String[] args) {
        new Thread(runnable,"queue1").start();
        new Thread(runnable,"queue2").start();
        new Thread(runnable,"queue3").start();
    }

Routing模式(direct)

在发布订阅模式上增加了路由key,相当于指定发送消息

image-20230206223830212.png

image-20230206224259860.png

主题模式(topic)

进行模糊路由

image-20230206224809756.png

交换机 #:可以是0级

image-20230206225621126.png

代码模式:只需要替换上面代码三步

// 6.准备交换机
String exchangeName = "topic-exchange";
//7.定义路由key
String routeKey = "com.order.text.xxx";
//8.交换机的类型
String type = "topic";

完整代码

Provider

  public static void main(String[] args) {
        //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
        //1.创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
​
        Connection connection = null;
        Channel channel = null;
        try {
            // 2.创建连接connection
            connection = connectionFactory.newConnection("生产者");
            // 3.通过连接获取通道channel
            channel = connection.createChannel();
            // 4.准备消息内容
            String message = "Hello liuxiang!";
            // 5.准备交换机
            String exchangeName = "direct-message-exchange";
            //6.定义路由key
            String routeKey = "course";
            //7.交换机的类型
            String type = "direct";
            //8 声明交换机 持久化就是交换机不会随着服务器重启而丢失 true代表不丢失
            channel.exchangeDeclare(exchangeName,type,true);
            //声明队列
            channel.queueDeclare("queue5",true,false,false,null);
            channel.queueDeclare("queue6",true,false,false,null);
            channel.queueDeclare("queue7",true,false,false,null);
            //绑定队列和交换机关系
            channel.queueBind("queue5",exchangeName,"order");
            channel.queueBind("queue6",exchangeName,"order");
            channel.queueBind("queue7",exchangeName,"course");
​
            channel.basicPublish(exchangeName, routeKey,null,message.getBytes());
​
            System.out.println("消息发送成功");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常");
        } finally {
            if (channel !=null && channel.isOpen()){
                try {
                    //8.关闭通道
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7.关闭连接
            if (connection !=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
​
​
    }

Consumer

private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
            //1.创建连接工程
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");
​
            //获取队列的名称
            final String queueName = Thread.currentThread().getName();
            Connection connection = null;
            Channel channel = null;
            try {
                // 2.创建连接connection
                connection = connectionFactory.newConnection("生产者");
                // 3.通过连接获取通道channel
                channel = connection.createChannel();
                //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
                channel.basicConsume(queueName, true, new DeliverCallback() {
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println(queueName+":收到消息是"+ new String(message.getBody(),"UTF-8"));
                    }
                },new CancelCallback(){
                    public void handle(String consmerTag) throws IOException{
                        System.out.println("接收失败");
                    }
                });
                System.out.println(queueName+"开始接收消息");
                System.in.read();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (channel !=null && channel.isOpen()){
                    try {
                        //8.关闭通道
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                // 7.关闭连接
                if (connection !=null && connection.isOpen()){
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    } ;
​
    public static void main(String[] args) {
        new Thread(runnable,"queue5").start();
        new Thread(runnable,"queue6").start();
        new Thread(runnable,"queue7").start();
    }

工作队列模式(work)

轮询分发(Round-Robin)

一个消费者一条,按均分配

Producer

 public static void main(String[] args) {
        //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
        //1.创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("127.0.0.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
​
        Connection connection = null;
        Channel channel = null;
        try {
            // 2.创建连接connection
            connection = connectionFactory.newConnection("生产者");
            // 3.通过连接获取通道channel
            channel = connection.createChannel();
            // 4.准备消息内容
            for (int i = 1; i <=20 ; i++) {
                String message = "刘想:"+ "\t" + i;
                channel.basicPublish("", "queue1",null,message.getBytes());
                Thread.sleep(1000);
            }
​
            System.out.println("消息发送成功");
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("发送消息出现异常");
        } finally {
            if (channel !=null && channel.isOpen()){
                try {
                    //8.关闭通道
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7.关闭连接
            if (connection !=null && connection.isOpen()){
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
​
​
    }

Work1

 private static Runnable runnable = new Runnable() {
        @Override
        public void run() {
            //所有的中间件都是基于tcp、ip协议之上的新协议,rabbitmq遵循amqp
            //1.创建连接工程
            ConnectionFactory connectionFactory = new ConnectionFactory();
            connectionFactory.setHost("127.0.0.1");
            connectionFactory.setPort(5672);
            connectionFactory.setUsername("admin");
            connectionFactory.setPassword("admin");
            connectionFactory.setVirtualHost("/");
​
            Connection connection = null;
            Channel channel = null;
            try {
                // 2.创建连接connection
                connection = connectionFactory.newConnection("生产者");
                // 3.通过连接获取通道channel
                channel = connection.createChannel();
//                channel.basicQos(1);
                //4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
                channel.basicConsume("queue1", true, new DeliverCallback() {
                    @Override
                    public void handle(String consumerTag, Delivery message) throws IOException {
                        System.out.println("Work1"+":收到消息是"+ new String(message.getBody(),"UTF-8"));
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                },new CancelCallback(){
                    @Override
                    public void handle(String consmerTag) throws IOException{
                        System.out.println("接收失败");
                    }
                });
                System.out.println("Work1"+"开始接收消息");
                System.in.read();
            } catch (Exception ex) {
                ex.printStackTrace();
            } finally {
                if (channel !=null && channel.isOpen()){
                    try {
                        //8.关闭通道
                        channel.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
                // 7.关闭连接
                if (connection !=null && connection.isOpen()){
                    try {
                        connection.close();
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }
        }
    } ;
​
    public static void main(String[] args) {
        new Thread(runnable,"queue1").start();
    }

Work2与Work1代码一致

结果:都是公平的!

image-20230207180139128.png

公平分发

根据消费者的消费能力进行公平分发,处理快的处理多,能者多劳

修改这一部分代码:改为手动应答即可!

//4.通过创建交换机,声明队列,绑定关系,路由key,发送消息,接收消息
finalChannel.basicConsume("queue1", false, new DeliverCallback() {
    @Override
    public void handle(String consumerTag, Delivery message) throws IOException {
        try {
            System.out.println("Work1"+":收到消息是"+ new String(message.getBody(),"UTF-8"));
            Thread.sleep(1000);
            //改为手动应答
         finalChannel.basicAck(message.getEnvelope().getDeliveryTag(),false);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

SpringBoot整合RabbitMQ

Provider

1.导入依赖

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

2.rabbitMQ的配置文件

在本机上不用配置,再真实服务器上需要配置

#服务端口
server:
  port: 8080
#配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    host: 127.0.0.1
    virtual-host: /
    port: 5672

3.服务层

@Service
public class OrderService {
​
    @Autowired(required = false)
    private RabbitTemplate rabbitTemplate;
​
    public void makeOrder(String userid,String productid,int num){
        //1.根据商品id查询库存是否充足
        //2.保存订单
        String orderId = UUID.randomUUID().toString();
        System.out.println("订单生成成功:"+orderId);
        //3.通过消息队列完成消息的分发
        //参数1:交换机 2:路由key 3:消息内容
        String exchangeName = "fanout_order_exchange";
        String routingKey = "";
        rabbitTemplate.convertAndSend(exchangeName,routingKey,orderId);
    }
}

4.rabbitMQ配置类

@Configuration
public class RabbitMqConfiguration {
​
    //声明注册fanout模式的交换机
    @Bean
    public FanoutExchange fanoutExchange(){
        return new FanoutExchange("fanout_order_exchange",true,false);
    }
    //声明队列
    @Bean
    public Queue smsQueue(){
        return new Queue("sms.fanout.queue",true);
    }
    @Bean
    public Queue messageQueue(){
        return new Queue("message.fanout.queue",true);
    }
    @Bean
    public Queue emailQueue(){
        return new Queue("email.fanout.queue",true);
    }
    //完成绑定关系
    @Bean
    public Binding msmBinding(){
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding messageBinding(){
        return BindingBuilder.bind(messageQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding emailBinding(){
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }
}

5.结果

image-20230209164238485.png

Consumer

1.配置文件

#服务端口
server:
  port: 8081
#配置rabbitmq服务
spring:
  rabbitmq:
    username: admin
    password: admin
    host: 127.0.0.1
    virtual-host: /
    port: 5672

2.service

@Service
@RabbitListener(queues = {"sms.fanout.queue"})
public class FanoutSmsConsumer {
​
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("sms fanout==>接收了订单信息:"+message);
    }
}
@Service
@RabbitListener(queues = {"message.fanout.queue"})
public class FanoutMessageConsumer {
​
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("message fanout==>接收了订单信息:"+message);
    }
}
@Service
@RabbitListener(queues = {"email.fanout.queue"})
public class FanoutEmailConsumer {
​
    @RabbitHandler
    public void receiveMessage(String message){
        System.out.println("email fanout==>接收了订单信息:"+message);
    }
}

注解方式实现

@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "sms.topic.queue",durable = "true",autoDelete = "false"), exchange = @Exchange(value = "topic_order_exchange",type=ExchangeTypes.TOPIC),key = "#.sms.#"
))

RabbitMQ高级

过期时间TTL

表示对消息设置预期的时间,在这个时间内都可以被消费者接收获取,过了之后消息自动删除。

队列参数设置

在配置类中声明

//声明队列
    @Bean
    public Queue directTtlQueue(){
        Map<String,Object> map = new HashMap<>();
        map.put("x-message-ttl",5000);//参数 设置5s过期
        return new Queue("ttl.direct.queue",true,false,map);
    }

消息设置

在service中声明

MessagePostProcessor messagePostProcessor = new MessagePostProcessor(){
    @Override
    public Message postProcessMessage{Message message} throws AmqpException{
        message.getMessageProperties().setExpiration("5000");
        message.getMessageProperties().setContentEncoding("UTF-8");
        return message;
    }
}

两者同时设置以最短的设置为准!

死信队列(DLX)

当一个消息在一个队列中变成死信队列后,它能被重新发送到另一个交换机中,这个交换机就是DLX,绑定DLX的队列就是死信队列。

配置文件

//声明队列
    @Bean
    public Queue directTtlQueue(){
        Map<String,Object> map = new HashMap<>();
        map.put("x-message-ttl",5000);//参数 设置5s过期
        map.put("x-dead-letter-exchange","dead_direct_exchange");
        map.put("x-dead-letter-routing-key","dead");//如果fanout模式不用设置
        return new Queue("ttl.direct.queue",true,false,map);
    }

image-20230211174457072.png

内存磁盘的监控

命令方式:默认是2GB/0.4,随着重启会丢失

低于2GB就会阻塞!

rabbitmqctl set_vm_memory_high_watermark absolute 2GB
rabbitmqctl set_vm_memory_high_watermark relative 0.6 #0.4-0.7之间

磁盘预警

rabbitmqctl set_disk_free_limit memory_limit <fraction> #1.0-2.0之间

内存换页

rabbitmqctl set_vm_memory_high_watermark_paging_ratio 0.5

思考:分布式事务问题,如何保证数据的一致性?

通过消息中间件来解决:可靠生产+可靠消费 (ack机制)

消息确认机制配置:

rabbitmq:
   publisher-confirm-type: correlated

解决可靠消费中消息重试的集中方案:

  1. 控制重发次数
  2. try+catch+手动ack
  3. try+catch+手动ack+死信队列处理+人工干预

配置

listener:
  simple:
    acknowledge-mode: manual #达到次数后,不丢失消息 手动ack
    retry:
      enabled: true #开启重试
      manx-attempts: 3 #最大重试次数
      initial-interval: 2000ms #重试间隔时间