rabbitMQ详解

1、概念

  1.1、什么是mq(message queue)

        本质上是一个队列,FIFO先进先出,不过在队列中存放的内容是message

 1.2、mq能做什么

        1、流量消峰

如果订单系统最多能处理一万次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两万次下单操作系统是处理不了的,只能限制订单超过一万后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。
简单来说: 就是在访问量剧增的情况下,但是应用仍然不能停,比如“双十一”下单的人多,但是淘宝这个应用仍然要运行,所以就可以使用消息中间件采用队列的形式减少突然访问的压力

         2、应用解耦

以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内存被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中间用户感受不到物流系统的故障,提升系统的可用性。

        3、异步处理

有些服务间调用是异步的,例如 A 调用 B,B 需要花费很长时间执行,但是 A 需要知道 B 什么时候可以执行完,以前一般有两种方式,A 过一段时间去调用 B 的查询 api 查询。或者 A 提供一个 callback api, B 执行完之后调用 api 通知 A 服务。
这两种方式都不是很优雅,使用消息总线,可以很方便解决这个问题,A 调用 B 服务后,只需要监听 B 处理完成的消息,当 B 处理完成后,会发送一条消息给 MQ,MQ 会将此消息转发给 A 服务。这样 A 服务既不用循环调用 B 的查询 api,也不用提供 callback api。同样 B 服务也不用做这些操作。A 服务还能及时的得到异步处理成功的消息。
 

 1.3、rabbitMQ的特点

        1、性能好时效性微秒级,社区活跃度高,erlang语言编写,并发好,有管理界面,管理方便,适合数据量不是特别大时选择

1.4、工作原理

 

 Broker:接收和分发消息的应用,一个broker就是一个rabbitMQ

channel:轻量级的连接工具

exchange:message到达broker的第一站,根据分发规则,匹配查询表中的routing key,分发到queue中

queue:消息被推送到这里,等待customer取走

2、rabbitMQ安装

        2.1、下载erlang、rabbitMQ,二者版本要和linux版本保持一致(centOS为例)

        erlang:rabbitmq/erlang - Packages · packagecloud

        rabbitMQ:https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.8.8

        下载完成后执行:

                rpm -ivh erlang-22.3-1.el7.x86_64.rpm               安装erlang

                yum install socat -y                安装socat环境

                rpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm                安装rabbitMQ

                erl -v         检查erlang安装是否成功

                systemctl start rabbitmq-server               启动mq

                systemctl status rabbitmq-server                查看mq状态

                systemctl stop rabbitmq-server               停止mq

                systemctl restart rabbitmq-server        重启

2.2、访问rabbitmq后台管理界面

        1、停止rabbitMQ服务

                systemctl stop rabbitmq-server 

        2、开启 web 管理插件

                rabbitmq-plugins enable rabbitmq_management

        3、添加新账号、设置用户角色、设置用户权限

                rabbitmqctl add_user admin 123                新账号

                rabbitmqctl set_user_tags admin administrator                用户角色

                rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"        权限(表示拥有所有资源的配置、读写权限)

        4、查看当前用户和角色

                rabbitmqctl list_users

        5、访问web管理界面

                linux的ip:15672

http://192.168.150.131:15672/http://192.168.150.131:15672/

3、生产者和消费者案例

3.1、单个生产者,单个消费者

生产者

package com.agw.test1;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/27 10:49
 */
public class Producer {

    public static final String QUEUE_NAME = "hello2";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.150.131");
        factory.setUsername("admin");
        factory.setPassword("123");

        Connection connection = factory.newConnection();
        //获取连接channel
        Channel channel = connection.createChannel();

        /*
        生成一个队列
            1、队列名称
            2、队列消息是否持久化
            3、是否只让一个消费者独自消费该队列消息
            4、消费者断开连接之后是否删除该队列
            5、其他参数
         */

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        /*
        发送消息
            1、交换机名称,默认空字符串标识
            2、路由的key
                默认采用队列名称作为路由key,消息会路由到该队列中
            3、其他参数
            4、发送消息的内容
         */
        String message = "hello world";
        channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
        System.out.println("发送了一条消息");
        channel.close();
        connection.close();
    }
}

消费者 

package com.agw.test1;

import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer {
    //队列名称
    public static final String QUEUE_NAME = "hello2";
    public static void main(String[] args) throws IOException, TimeoutException {
        //创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.150.131");
        factory.setUsername("admin");
        factory.setPassword("123");
        
        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();

        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("接收到的消息:" + message);
            System.out.println(delivery.getEnvelope().getDeliveryTag());//消息数量(第几个)
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        /*
        消费者消费消息
            1、哪个队列
            2、消费成功之后,是否要自动应答
            3、队列推送消息给消费者的时候,如何消费消息
            4、消费者取消消费消息的回调接口
         */
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

3.2、单个生产者,多个消费者(轮巡消费,平均分配)

        将以上获取连接封装为工具类

package com.agw.utils;

import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/27 15:51
 */
public class RabbitUtils {
    public static Channel getChannel() throws Exception {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.150.131");
        factory.setUsername("admin");
        factory.setPassword("123");
        Connection connection = factory.newConnection();
        //获取连接channel
        Channel channel = connection.createChannel();
        return channel;
    }
}

生产者

package com.agw.test2;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

/**
 * @author angw
 * @version 1.0
 * @Title:生产者
 * @date 2022/12/27 10:49
 */
public class Producer02 {

    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish("",QUEUE_NAME,null,message.getBytes());
            System.out.println("发送了一条消息" + message);
        }
        
    }
}

消费者

package com.agw.test2;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.*;

import java.io.IOException;
import java.util.concurrent.TimeoutException;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer02 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("接收到的消息:" + message);
        };

        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}
package com.agw.test2;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer03 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("接收到的消息:" + message);
        };

        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

消费者手动应答或者拒绝策略

package com.agw.test2.task02;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer02 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("接收到的消息:" + message);
//            /*
//            手动确认应答
//                参数: 1、那个消息 2、一次应答多个还是当前消息,false表示当前消息,true表示当前和之前的一起确认
//             */
//            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);\
            /*
            拒绝确认应答:
                1、那个消息
                2、拒绝多个消息还是一个
                3、是否重新入队
                    true    重新入队
                    false   丢弃消息,或者进入死信队列
             */
//            channel.basicNack(delivery.getEnvelope().getDeliveryTag(),false,false);
            //第一个参数:哪个消息,第二个参数:是否重新入队
            channel.basicReject(delivery.getEnvelope().getDeliveryTag(),false);
        };

        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        //false表示手动确认应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    }
}

3.2.1、未应答的消息会重新入队,其他消费者会消费

生产者同上

消费者

package com.agw.task03;

import com.agw.utils.RabbitUtils;
import com.agw.utils.ThreadSleep;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer03 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("C03接收到的消息:" + message + ",时间较长");
            ThreadSleep.sleep(30);
            /*
            手动确认应答
                参数: 1、那个消息 2、一次应答多个还是当前消息,false表示当前消息,true表示当前和之前的一起确认
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };

        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        //false表示手动确认应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    }
}
package com.agw.task03;

import com.agw.utils.RabbitUtils;
import com.agw.utils.ThreadSleep;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer003 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,false,false,false,null);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            System.out.println("C03接收到的消息:" + message + ",时间较短");
            ThreadSleep.sleep(3);
            /*
            手动确认应答
                参数: 1、那个消息 2、一次应答多个还是当前消息,false表示当前消息,true表示当前和之前的一起确认
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
        };
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        //false表示手动确认应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    }
}

时间较长的消费者消费消息,还未应答之前出现异常了,服务关闭了,该消息会重新入队供其他消费者消费

3.3、队列和消息的持久化

保证mq中断重启后,消息和队列依然存在

package com.agw.task04;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.MessageProperties;

import java.util.Scanner;

/**
 * @author angw
 * @version 1.0
 * @Title:队列持久化
 * @date 2022/12/27 10:49
 */
public class Producer04 {

    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        //队列是否持久化
        boolean persistence = true;
        channel.queueDeclare(QUEUE_NAME,persistence,false,false,null);

        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            //第三个参数表示消息持久化
            channel.basicPublish("",QUEUE_NAME, MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
            System.out.println("发送了一条消息" + message);
        }
    }
}

 3.4、预取值

也就是不公平分发,不是轮训分发,根据消费者处理速度进行分配,需要手动设置每个消费者的混充区的大小(也就是最大处理值)

package com.agw.task05;

import com.agw.utils.RabbitUtils;
import com.agw.utils.ThreadSleep;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

/**
 * @author angw
 * @version 1.0
 * @Title:消费者
 * @date 2022/12/27 11:49
 */
public class Customer005 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.queueDeclare(QUEUE_NAME,true,false,false,null);
        //设置预取值,表示通道上未确认消息最大个数
        int prefetchCount = 4;
        channel.basicQos(prefetchCount);

        System.out.println("准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            System.out.println(consumerTag);  //消费者标记
            String message = new String(delivery.getBody());//消息内容
            ThreadSleep.sleep(1);
            /*
            手动确认应答
                参数: 1、那个消息 2、一次应答多个还是当前消息,false表示当前消息,true表示当前和之前的一起确认
             */
            channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
            System.out.println("C03接收到的消息:" + message + ",时间较短");
        };
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        //false表示手动确认应答
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME,autoAck,deliverCallback,cancelCallback);
    }
}

注意:预取值不是越大越好,太大了容易导致硬盘堆积,太小了吞吐量小,应反复实验找到合适的值,一般在100-300之间是最佳的

4、发布确认

4.1、单个发布确认、批量发布确认

让生产者知道消息已经到达队列了,保证安全

发布确认分为单个发布确认和批量发布确认

单个发布确认:发布消息后只有被确认后,后面的消息才能继续发布,发布速度慢

批量发布确认:预先设置好一些批量设置的值,相比单个速度较快,但当发布出现问题时,不宜排查

package com.agw.task06;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.Channel;

import java.util.UUID;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/27 21:10
 */
public class PublishiConfirmsTest {

    public static int COUNT = 230;
    public static void main(String[] args) throws Exception {
//        publishMessage();
        publishMessageBatch();
    }

    //单个消息发布确认
    public static void publishMessage() throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = UUID.randomUUID().toString();

        channel.queueDeclare(queueName,false,false,false,null);
        //开启发布确认,去发布消息更加安全
        channel.confirmSelect();

        long begin = System.currentTimeMillis();
        for (int i = 1; i < COUNT + 1; i++) {
            String message = "消息" + i;
            channel.basicPublish("",queueName,null,message.getBytes());
            //等待broker确认收到消息的方法
            boolean flag = channel.waitForConfirms();
            if (flag){
                System.out.println("收到消息" + i);
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("共耗时:" + (end - begin));

    }

    //批量发布消息确认
    public static void publishMessageBatch() throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);
        //开启发布确认,去发布消息更加安全
        channel.confirmSelect();

        //批量的值
        int batchSize = 50;
        int outstandingMessageCount = 0;

        long begin = System.currentTimeMillis();
        for (int i = 1; i < COUNT + 1; i++) {
            String message = "消息" + i;
            channel.basicPublish("",queueName,null,message.getBytes());
            outstandingMessageCount ++;
            //等待broker确认收到消息的方法
            if (outstandingMessageCount == batchSize){
                boolean falg = channel.waitForConfirms();
                if (falg){
                    System.out.println("收到消息" + i);
                    outstandingMessageCount = 0;
                }

            }
        }

        //没有确认消息的
        if (outstandingMessageCount > 0){
            boolean flag = channel.waitForConfirms();
            if (flag){
                System.out.println("收到消息" );
                outstandingMessageCount = 0;
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("批量发布确认消息,共耗时:" + (end - begin));

    }
}

4.2、异步发布确认

 //异步消息发布确认
    public static void publishMessageAsync() throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = UUID.randomUUID().toString();
        channel.queueDeclare(queueName,false,false,false,null);
        //开启发布确认,去发布消息更加安全
        channel.confirmSelect();

        /*
        一个线程安全的map,key有序,可通过key获取key之前的所有数据
         */
        ConcurrentSkipListMap<Long, String> outstandingComfirms = new ConcurrentSkipListMap<>();
        //确认收到消息的回调
        ConfirmCallback ackCallback = (sequenceNumber,multple) -> {
            if (multple){
                //多个,此时签收小于等于sequenceNmber消息,签收也就是从map中移除
                ConcurrentNavigableMap<Long, String> comfirmedPartMap = outstandingComfirms.headMap(sequenceNumber);
                comfirmedPartMap.clear();
            }else {
                //单个,只签收当前sequenceNumber
                outstandingComfirms.remove(sequenceNumber);
            }
        };

        //未确认收到消息的回调
        ConfirmCallback nackCallback = (sequenceNumber,multple) -> {
            String message = outstandingComfirms.get(sequenceNumber);
            System.out.println("序号为" + sequenceNumber + "需要重新发送");
        };

        //添加监听器,收到消息或者未收到消息的回调
        channel.addConfirmListener(ackCallback,nackCallback);

        long begin = System.currentTimeMillis();
        for (int i = 1; i < COUNT + 1; i++) {
            String message = "消息" + i;
            outstandingComfirms.put(channel.getNextPublishSeqNo(),message);
            channel.basicPublish("",queueName,null,message.getBytes());

        }
        long end = System.currentTimeMillis();
        System.out.println("共耗时:" + (end - begin));
    }

 异步的效率要更高,更安全

4.3、以上三种发布确认速度对比

单个发布确认:每个消息都要等待确认,效率低,吞吐量有限

批量发布确认:一批一批确认,效率较高,吞吐量合理,但出现问题难以排查,一批中有一个出现错误,整批都会重新发送,会出问题

异步发布确认:最佳的性能,更高效和安全,出错了容易控制,但实现较为复杂

5、交换机(exchanges)

mq中,生产者生产的消息不会直接发送到队列上,会先到达交换机中,由交换机做一系列操作后推送到交换机上

5.1、交换机的类型

直接(direct)、主题(topic)、扇出(fanout)、标题(headers)

5.1.1、扇出(fanout)

package com.agw.task07;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.TimeoutException;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/27 10:49
 */
public class Producer07 {

    //交换机名称
    public static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception {
        //获取连接channel
        Channel channel = RabbitUtils.getChannel();

        //声明交换机,参数一:交换机名称,参数二:交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.FANOUT);
        System.out.println("等待输入消息");
        Scanner scanner = new Scanner(System.in);
        while (scanner.hasNext()){
            String message = scanner.next();
            channel.basicPublish(EXCHANGE_NAME,"",null,message.getBytes());
            System.out.println("发送了一条消息" + message);
        }
    }
}

两个消费者,一个将消息打印在控制台,一个持久化到磁盘

public class Customer07 {
    //交换机名称
    public static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();

        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("消费者1准备消费,把消息打印");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("消费者1接收到的消息:" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}
public class Customer007 {
    //交换机名称
    public static final String EXCHANGE_NAME = "logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();

        //临时队列
        String queueName = channel.queueDeclare().getQueue();
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"");
        System.out.println("消费者2准备消费,把消息写入磁盘");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            File file = new File("D:\\rabbitmq_info.txt");
            FileUtils.writeStringToFile(file,message,"utf-8",true);
            System.out.println("文件成功写入");
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}

5.1.2、直接(direct)

生产者

public class Producer08 {
    //交换机名称
    public static final String EXCHANGE_NAME = "direct_logs";
    public static void main(String[] args) throws Exception {
        //获取连接channel
        Channel channel = RabbitUtils.getChannel();
        //声明交换机,参数一:交换机名称,参数二:交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        HashMap<String, String> bindingkeyMap = new HashMap<>();
        bindingkeyMap.put("info","普通info信息");
        bindingkeyMap.put("warning","警告warning信息");
        bindingkeyMap.put("error","错误日志");
        bindingkeyMap.put("debug","debug信息");
        Set<Map.Entry<String, String>> entries = bindingkeyMap.entrySet();
        for (Map.Entry<String, String> bindingKeyEntry : bindingkeyMap.entrySet()){
            String key = bindingKeyEntry.getKey();
            String value = bindingKeyEntry.getValue();
            channel.basicPublish(EXCHANGE_NAME,key,null,value.getBytes());
            System.out.println("发送了消息:" + value);
        }
    }
}

消费者

public class Customer08 {
    //交换机名称
    public static final String EXCHANGE_NAME = "direct_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        //绑定交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        String queueName = "console";
        channel.queueDeclare(queueName,false,false,false,null);
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"info");
        channel.queueBind(queueName,EXCHANGE_NAME,"warning");
        System.out.println("消费者1准备消费,把消息打印");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("消费者1接受的路由" + delivery.getEnvelope().getRoutingKey() + ",接收到的消息:" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}
public class Customer008 {
    //交换机名称
    public static final String EXCHANGE_NAME = "direct_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = "disk";
        //绑定交换机
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        channel.queueDeclare(queueName,false,false,false,null);
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"error");
        System.out.println("消费者2准备消费,把消息写入磁盘");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            File file = new File("D:\\direct_error.txt");
            FileUtils.writeStringToFile(file,message,"utf-8",true);
            System.out.println("文件成功写入,key为:" + delivery.getEnvelope().getRoutingKey() + ",消息:" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}

5.1.3、主题(topic)

 可以分模块,更加细粒度化的接收消息,使用topic,这里路由key不可以随意写,需满足一定的规则,如:"stock.usd.nyse", "nyse.vmw",单词列表最多不可超过255个字节,其中有两个替换符:

        *(星号)可以代替一个单词

        #(井号)可以替代零个或多个单词

案例:

生产者

public class Producer09 {
    //交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";
    public static void main(String[] args) throws Exception {
        //获取连接channel
        Channel channel = RabbitUtils.getChannel();
        //声明交换机,参数一:交换机名称,参数二:交换机类型
        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);

        Map<String, String> bindingKeyMap = new HashMap<>();
        bindingKeyMap.put("quick.orange.rabbit","被队列 Q1Q2 接收到");
        bindingKeyMap.put("lazy.orange.elephant","被队列 Q1Q2 接收到");
        bindingKeyMap.put("quick.orange.fox","被队列 Q1 接收到");
        bindingKeyMap.put("lazy.brown.fox","被队列 Q2 接收到");
        bindingKeyMap.put("lazy.pink.rabbit","虽然满足两个绑定但只被队列 Q2 接收一次");
        bindingKeyMap.put("quick.brown.fox","不匹配任何绑定不会被任何队列接收到会被丢弃");
        bindingKeyMap.put("quick.orange.male.rabbit","是四个单词不匹配任何绑定会被丢弃");
        bindingKeyMap.put("lazy.orange.male.rabbit","是四个单词但匹配 Q2");

        for (Map.Entry<String, String> bindingKeyEntry : bindingKeyMap.entrySet()){
            String key = bindingKeyEntry.getKey();
            String value = bindingKeyEntry.getValue();
            channel.basicPublish(EXCHANGE_NAME,key,null,value.getBytes());
            System.out.println("发送了消息:" + value);
        }
    }
}

消费者

public class Customer09 {
    //交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = "Q1";
        channel.queueDeclare(queueName,false,false,false,null);
        //绑定交换机
//        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"*.orange.*");

        System.out.println("消费者1准备消费,把消息打印");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("消费者1接受的路由" + delivery.getEnvelope().getRoutingKey() + ",接收到的消息:" + message);
        };
        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}
public class Customer009 {
    //交换机名称
    public static final String EXCHANGE_NAME = "topic_logs";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        String queueName = "Q2";
        channel.queueDeclare(queueName,false,false,false,null);
        //绑定交换机
//        channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
        //绑定交换机和队列
        channel.queueBind(queueName,EXCHANGE_NAME,"*.*.rabbit");
        channel.queueBind(queueName,EXCHANGE_NAME,"lazy.#");
        System.out.println("消费者2准备消费,把消息写入磁盘");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            File file = new File("D:\\topic_logs.txt");
            FileUtils.writeStringToFile(file,message,"utf-8",true);
            System.out.println("文件成功写入,key为:" + delivery.getEnvelope().getRoutingKey() + ",消息:" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(queueName,true,deliverCallback,cancelCallback);
    }
}

6、死信队列

消息由于某些原因,无法被消费会进入死信队列

 死信的来源

        1、消息 TTL 过期(消息存活时间过期)

        2、队列达到最大长度(队列满了,无法再添加数据到 mq 中)

        3、消息被拒绝(basic.reject 或 basic.nack)并且 requeue=false

6.1、死信演示

6.1.1、消息TTL过期

生产者

public class Producer10 {
    public static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);

        String message = "info";
        //消息的TTL过期时间,如果在这个时间内这个消息没有被消费会进入死信队列
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().expiration("5000").build();
        channel.basicPublish(NORMAL_EXCHANGE_NAME,"zhangsan", properties,message.getBytes());
        System.out.println("发送了一条消息" + message);
    }
}

消费者1

public class Customer10 {
    //交换机名称
    public static final String NORAML_EXCHANGE_NAME = "normal_exchange";
    public static final String NORAML_QUEUE_NAME = "normal-queue";
    public static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    public static final String DEAD_QUEUE_NAME = "dead-queue";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        System.out.println("消费者1准备消费,把消息打印");

        //声明一个死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        //一个死信交换机
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //交换机和队列的绑定关系
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,"lisi");

        //正常队列和私信交换机的绑定关系
        HashMap<String, Object> deadLetterParams = new HashMap<>();
        deadLetterParams.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);
        deadLetterParams.put("x-dead-letter-routing-key","lisi");

        //声明一个正常队列
        channel.queueDeclare(NORAML_QUEUE_NAME,false,false,false,deadLetterParams);
        //一个正常的交换机
        channel.exchangeDeclare(NORAML_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //交换机和队列的绑定关系
        channel.queueBind(NORAML_QUEUE_NAME,NORAML_EXCHANGE_NAME,"zhangsan");

        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("消费者1接收到的消息:" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(NORAML_QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

消费者2(死信消费者)

public class Customer010 {
    public static final String DEAD_QUEUE_NAME = "dead-queue";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        System.out.println("死信消费者准备消费");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("消费者收到死信" + message);
        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(DEAD_QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

队列参数

 6.1.2、队列达到最大长度

生产者

public class Producer11 {
    public static final String NORMAL_EXCHANGE_NAME = "normal_exchange";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();
        channel.exchangeDeclare(NORMAL_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            channel.basicPublish(NORMAL_EXCHANGE_NAME,"zhangsan", null,message.getBytes());
        }
        System.out.println("发送了10条消息");
    }
}

消费者

        //正常队列和私信交换机的绑定关系
        HashMap<String, Object> deadLetterParams = new HashMap<>();
        deadLetterParams.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);
        deadLetterParams.put("x-dead-letter-routing-key","lisi");
        //队列最大6条消息
        deadLetterParams.put("x-max-length",6);

6.1.3、消息被拒绝

消费者

public class Customer3 {
    //交换机名称
    public static final String NORAML_EXCHANGE_NAME = "normal_exchange";
    public static final String NORAML_QUEUE_NAME = "normal-queue";
    public static final String DEAD_EXCHANGE_NAME = "dead_exchange";
    public static final String DEAD_QUEUE_NAME = "dead-queue";
    public static void main(String[] args) throws Exception {
        Channel channel = RabbitUtils.getChannel();
        System.out.println("消费者1准备消费,把消息打印");

        //声明一个死信队列
        channel.queueDeclare(DEAD_QUEUE_NAME,false,false,false,null);
        //一个死信交换机
        channel.exchangeDeclare(DEAD_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //交换机和队列的绑定关系
        channel.queueBind(DEAD_QUEUE_NAME,DEAD_EXCHANGE_NAME,"lisi");

        //正常队列和私信交换机的绑定关系
        HashMap<String, Object> deadLetterParams = new HashMap<>();
        deadLetterParams.put("x-dead-letter-exchange",DEAD_EXCHANGE_NAME);
        deadLetterParams.put("x-dead-letter-routing-key","lisi");

        //声明一个正常队列
        channel.queueDeclare(NORAML_QUEUE_NAME,false,false,false,deadLetterParams);
        //一个正常的交换机
        channel.exchangeDeclare(NORAML_EXCHANGE_NAME, BuiltinExchangeType.DIRECT);
        //交换机和队列的绑定关系
        channel.queueBind(NORAML_QUEUE_NAME,NORAML_EXCHANGE_NAME,"zhangsan");

        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            if (message.equals("info5")){
                System.out.println("消息" + message + "被拒绝签收了");
                channel.basicReject(delivery.getEnvelope().getDeliveryTag(),false);
            }else {
                System.out.println("消费者1接收到的消息:" + message);
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(),false);
            }

        };

        //4、消费者取消消费消息的回调接口
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };
        channel.basicConsume(NORAML_QUEUE_NAME,false,deliverCallback,cancelCallback);
    }
}

7、延迟队列

延迟队列内部是有序的,用来存储需要在指定时间执行某些操作的元素的队列。

一般用于大批量订单取消支付、高并发对数据库操作的时机

7.1、整合springboot项目演示

1、创建springboot项目,添加一下依赖,springboot项目需要是2.5.x或者2.6.x版本

<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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.47</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!--swagger-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--RabbitMQ 测试依赖-->
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、连接rabbitmq

spring.rabbitmq.host=192.168.150.131
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123

3、编写配置文件

package com.agw.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author angw
 * @version 1.0
 * @Title:声明交换机、队列、路由key之间的关系
 * @date 2022/12/29 16:20
 */
@Configuration
public class TtlQueueConfig {
    public static final String X_EXCHANGE = "X";
    public static final String QUEUE_A = "QA";
    public static final String QUEUE_B = "QB";
    public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
    public static final String DEAD_LETTER_QUEUE = "QD";
    // 声明 xExchange
    @Bean("xExchange")
    public DirectExchange xExchange(){
        return new DirectExchange(X_EXCHANGE);
    }
    // 声明 yExchange
    @Bean("yExchange")
    public DirectExchange yExchange(){
        return new DirectExchange(Y_DEAD_LETTER_EXCHANGE);
    }
    //声明队列 A ttl 为 10s 并绑定到对应的死信交换机
    @Bean("queueA")
    public Queue queueA(){
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //声明队列的 TTL
        args.put("x-message-ttl", 10000);
        return QueueBuilder.durable(QUEUE_A).withArguments(args).build();
    }
    // 声明队列 A 绑定 X 交换机
    @Bean
    public Binding queueaBindingX(@Qualifier("queueA") Queue queueA,
                                  @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queueA).to(xExchange).with("XA");
    }
    //声明队列 B ttl 为 40s 并绑定到对应的死信交换机
    @Bean("queueB")
    public Queue queueB(){
        Map<String, Object> args = new HashMap<>(3);
        //声明当前队列绑定的死信交换机
        args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
        //声明当前队列的死信路由 key
        args.put("x-dead-letter-routing-key", "YD");
        //声明队列的 TTL
        args.put("x-message-ttl", 40000);
        return QueueBuilder.durable(QUEUE_B).withArguments(args).build();
    }
    //声明队列 B 绑定 X 交换机
    @Bean
    public Binding queuebBindingX(@Qualifier("queueB") Queue queue1B,
                                  @Qualifier("xExchange") DirectExchange xExchange){
        return BindingBuilder.bind(queue1B).to(xExchange).with("XB");
    }
    //声明死信队列 QD
    @Bean("queueD")
    public Queue queueD(){
        return new Queue(DEAD_LETTER_QUEUE);
    }
    //声明死信队列 QD 绑定关系
    @Bean
    public Binding deadLetterBindingQAD(@Qualifier("queueD") Queue queueD,
                                        @Qualifier("yExchange") DirectExchange yExchange){
        return BindingBuilder.bind(queueD).to(yExchange).with("YD");
    }
}

package com.agw.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class MsgTtlQueueConfig {
 public static final String Y_DEAD_LETTER_EXCHANGE = "Y";
 public static final String QUEUE_C = "QC";
 //声明队列 C 死信交换机
 @Bean("queueC")
 public Queue queueB(){
 Map<String, Object> args = new HashMap<>(3);
 //声明当前队列绑定的死信交换机
 args.put("x-dead-letter-exchange", Y_DEAD_LETTER_EXCHANGE);
 //声明当前队列的死信路由 key
 args.put("x-dead-letter-routing-key", "YD");
 //没有声明 TTL 属性
 return QueueBuilder.durable(QUEUE_C).withArguments(args).build();
 }
 //声明队列 B 绑定 X 交换机
 @Bean
 public Binding queuecBindingX(@Qualifier("queueC") Queue queueC,
                               @Qualifier("xExchange") DirectExchange xExchange){
 return BindingBuilder.bind(queueC).to(xExchange).with("XC");
 }
}

4、添加swagger配置类,配合接口测试使用的,接口测试网址http://127.0.0.1:8080/swagger-ui.html

package com.agw.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/29 16:30
 */
@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket webApiConfig(){
        return new Docket(DocumentationType.SWAGGER_2)
                .groupName("webApi")
                .apiInfo(webApiInfo())
                .select()
                .build();
    }
    private ApiInfo webApiInfo(){
        return new ApiInfoBuilder()
                .title("rabbitmq 接口文档")
                .description("本文档描述了 rabbitmq 微服务接口定义")
                .version("1.0")
                .contact(new Contact("enjoy6288", "http://atguigu.com",
                        "1551388580@qq.com"))
                .build();
    }
}

5、生产者

package com.agw.producer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/29 16:51
 */
@RestController
@RequestMapping("ttl")
@Slf4j
public class SendMsgController {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @GetMapping("/sendMsg/{message}")
    public void sendMsg(@PathVariable String message){
      log.info("当前时间:{},发送了两条消息给TTL队列:{}",new Date(), message);
      rabbitTemplate.convertAndSend("X","XA","消息来自ttl为10s的队列 :" + message);
      rabbitTemplate.convertAndSend("X","XB","消息来自ttl为40s的队列 :" + message);
    }

    @GetMapping("/sendExpirationMsg/{message}/{ttlTime}")
    public void sendExpirationMsg(@PathVariable String message, @PathVariable String ttlTime){
        log.info("当前时间:{},发送了一条时长:{}的消息给QC队列:{}",new Date(),ttlTime, message);

        //设置消息ttl的过期时间
        MessagePostProcessor messagePostProcessor = correlationData -> {
          correlationData.getMessageProperties().setExpiration(ttlTime);
          return correlationData;
        };
        rabbitTemplate.convertAndSend("X","XC","消息来自QC队列 :" + message,messagePostProcessor);
    }
}

消费者

package com.agw.customer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/29 17:16
 */
@Component
@Slf4j
public class DeadLetterCustomer {

    @RabbitListener(queues = "QD")
    public void receiveD(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间{},收到死信队列的消息:{}",new Date(),msg);
    }
}

测试后发现:给队列设置过期时间,消息到达过期时间后可以及时放入死信消息,而给消息设置过期时间后,如果第二个延迟时间短,第一个延迟时间长,第二个并不能及时的放入到死信队列,mq会首先检查第一个是否过期,过期就丢入,再检查下一个

7.2、消息延迟问题解决

7.2.1、延时队列插件安装

1、安装延时队列插件Community Plugins — RabbitMQ,下载rabbitmq_delayed_message_exchange 插件

2、放入/usr/lib/rabbitmq/lib/rabbitmq_server-3.8.8/plugins目录下

3、执行rabbitmq-plugins enable rabbitmq_delayed_message_exchange安装

安装完成后刷新mq,多出如下交换机即为安装成功

7.2.2、代码演示延迟消息推送

1、自定义交换机,延迟推送消息

package com.agw.config;

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.CustomExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/29 20:49
 */
@Configuration
public class DelayedQueueConfig {

    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
    @Bean
    public Queue delayedQueue() {
        return new Queue(DELAYED_QUEUE_NAME);
    }
    /*
    自定义交换机 我们在这里定义的是一个延迟交换机
        不需要死信队列和死信交换机,支持消息延迟投递,消息生产出来后,没有达到投递时间,不会投递给队列,
        会先存储到一个分布式系统表中,到达投递时间才会投递

     */
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        /*
        自定义交换机的类型
            1、交换机名称
            2、新交换机类型
            3、是否持久化
            4、是否自动删除
            5、参数(设置交换机类型)
         */
        args.put("x-delayed-type", "direct");
        return new CustomExchange(DELAYED_EXCHANGE_NAME, "x-delayed-message", false, false, args);
    }

    //绑定交换机和队列之间的关系
    @Bean
    public Binding bindingDelayedQueue(@Qualifier("delayedQueue") Queue queue,
                                       @Qualifier("delayedExchange") CustomExchange delayedExchange) {

        return BindingBuilder.bind(queue).to(delayedExchange).with(DELAYED_ROUTING_KEY).noargs();
    }
}

2、生产者发送延迟消息

//发送延迟消息
    public static final String DELAYED_EXCHANGE_NAME = "delayed.exchange";
    public static final String DELAYED_ROUTING_KEY = "delayed.routingkey";
    @GetMapping("sendDelayMsg/{message}/{delayTime}")
    public void sendMsg(@PathVariable String message,@PathVariable Integer delayTime) {
        log.info("当前时间:{},发送了一条时长:{}的消息给delayed.queue队列:{}",new Date(),delayTime, message);

        //设置消息ttl的过期时间
        MessagePostProcessor messagePostProcessor = correlationData -> {
            correlationData.getMessageProperties().setDelay(delayTime);
            return correlationData;
        };
        rabbitTemplate.convertAndSend(DELAYED_EXCHANGE_NAME, DELAYED_ROUTING_KEY,"消息来自delayed.queue队列 :" + message,messagePostProcessor);
    }

3、消费者实时接收

//接收延迟队列的消息
    public static final String DELAYED_QUEUE_NAME = "delayed.queue";
    @RabbitListener(queues = DELAYED_QUEUE_NAME)
    public void receiveDelay(Message message){
        String msg = new String(message.getBody());
        log.info("当前时间{},收到延迟队列的消息:{}",new Date(),msg);
    }

8、发布确认高级

整合springboot以异步确认为例

1、创建springboot项目,添加配置文件

spring.rabbitmq.host=192.168.150.131
spring.rabbitmq.port=5672
spring.rabbitmq.username=admin
spring.rabbitmq.password=123
spring.rabbitmq.publisher-confirm-type=correlated

2、配置类

package com.agw.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ConfirmConfig {
 public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
 public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
 //声明业务 Exchange
 @Bean("confirmExchange")
 public DirectExchange confirmExchange(){
 return new DirectExchange(CONFIRM_EXCHANGE_NAME);
 }
 // 声明确认队列
 @Bean("confirmQueue")
 public Queue confirmQueue(){
 return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
 }
 // 声明确认队列绑定关系
 @Bean
 public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                             @Qualifier("confirmExchange") DirectExchange exchange){
 return BindingBuilder.bind(queue).to(exchange).with("key1");
 }
}

3、生产者

package com.agw.producer;

import com.agw.config.MyCallBack;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.Date;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/29 16:51
 */
@RestController
@RequestMapping("/confirm")
@Slf4j
public class Producer {

    @Resource
    private RabbitTemplate rabbitTemplate;

    @Resource
    private MyCallBack myCallBack;

    //设置他的回调对象
    @PostConstruct
    public void init(){
        //回调哪个接口
        rabbitTemplate.setConfirmCallback(myCallBack);
    }

    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
    @GetMapping("/sendMessage/{message}")
    public void sendMessage(@PathVariable String message) {

        //相关性数据
        CorrelationData correlationData1 = new CorrelationData("1");
        String routeKey1 = "key1";
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME, routeKey1, routeKey1+message,correlationData1);
        log.info("当前时间:{},发送了消息:{}", new Date(), message);

        CorrelationData correlationData2 = new CorrelationData("2");
        String routeKey2 = "key2";
        rabbitTemplate.convertAndSend(CONFIRM_EXCHANGE_NAME, routeKey2, routeKey2 + message,correlationData2);
        log.info("当前时间:{},发送了消息:{}", new Date(), message);
        
    }
}

消费者

package com.agw.customer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
@Slf4j
public class ConfirmConsumer {
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";

    @RabbitListener(queues = CONFIRM_QUEUE_NAME)
    public void receiveMsg(Message message) {
        String msg = new String(message.getBody());
        log.info("接受到队列{}的消息:{}",CONFIRM_QUEUE_NAME, msg);
    }
}

回调接口

package com.agw.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/30 10:57
 */
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback {

    /**
     *
     * @param correlationData  消息相关数据
     * @param ack   交换机是否收到消息
     * @param cause     未收到的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null? correlationData.getId() : "";
        if (ack){
            log.info("交换机收到消息,标记为:{}" + id);
        }else {
            log.info("交换机未收到消息,原因:{}",cause);
        }
    }
}

注意:这里如果发送的消息路由key如果不存在,消息到达交换机后会丢失

8.2、回退消息

当某个消息交换机接收后,但是消息的路由key不存在,消息被退回实现

生产者

//设置他的回调对象
    @PostConstruct
    public void init(){
        //回调哪个接口
        rabbitTemplate.setConfirmCallback(myCallBack);

        /*
        设置交换机如果无法将消息进行路由时,将消息返回给生产者,true表示退回,false表示丢弃
         */
        rabbitTemplate.setMandatory(true);
        //设置回退消息交给谁处理
        rabbitTemplate.setReturnsCallback(myCallBack);
    }

回调函数

package com.agw.config;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ReturnedMessage;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/30 10:57
 */
@Component
@Slf4j
public class MyCallBack implements RabbitTemplate.ConfirmCallback, RabbitTemplate.ReturnsCallback {

    /**
     *     消息发布成功之后的响应
     * @param correlationData  消息相关数据
     * @param ack   交换机是否收到消息
     * @param cause     未收到的原因
     */
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        String id = correlationData != null? correlationData.getId() : "";
        if (ack){
            log.info("交换机收到消息,标记为:{}" + id);
        }else {
            log.info("交换机未收到消息,原因:{}",cause);
        }
    }

    //回退的方法,消息无法路由调用该方法
    @Override
    public void returnedMessage(ReturnedMessage returnedMessage) {
        log.info("消息:{},被交换机{}退回,退回原因:{},路由key:{}",new String(returnedMessage.getMessage().getBody()),
                returnedMessage.getExchange(),returnedMessage.getReplyText(),returnedMessage.getRoutingKey());

    }
}

8.3、备份交换机解决路由key不存在问题

一个正常交换机、一台备份交换机,三个队列 

配置类

package com.agw.config;

import org.springframework.amqp.core.*;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author angw
 * @version 1.0
 * @Title:
 * @date 2022/12/30 15:53
 */
@Configuration
public class ConfirmConfig {
    public static final String CONFIRM_EXCHANGE_NAME = "confirm.exchange";
    public static final String CONFIRM_QUEUE_NAME = "confirm.queue";
    public static final String BACKUP_EXCHANGE_NAME = "backup.exchange";
    public static final String BACKUP_QUEUE_NAME = "backup.queue";
    public static final String WARNING_QUEUE_NAME = "warning.queue";

    // 声明确认队列
    @Bean("confirmQueue")
    public Queue confirmQueue() {
        return QueueBuilder.durable(CONFIRM_QUEUE_NAME).build();
    }

    //声明确认队列与交换机的绑定关系
    @Bean
    public Binding queueBinding(@Qualifier("confirmQueue") Queue queue,
                                @Qualifier("confirmExchange") DirectExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with("key1");
    }

    //声明备份 Exchange
    @Bean("backupExchange")
    public FanoutExchange backupExchange() {
        return new FanoutExchange(BACKUP_EXCHANGE_NAME);
    }

    //声明确认 Exchange 交换机的备份交换机
    @Bean("confirmExchange")
    public DirectExchange confirmExchange() {
        ExchangeBuilder exchangeBuilder =
                ExchangeBuilder.directExchange(CONFIRM_EXCHANGE_NAME)
                        .durable(true)
                        //设置该交换机的备份交换机
                        .withArgument("alternate-exchange", BACKUP_EXCHANGE_NAME);
        return (DirectExchange) exchangeBuilder.build();
    }

    // 声明警告队列
    @Bean("warningQueue")
    public Queue warningQueue() {
        return QueueBuilder.durable(WARNING_QUEUE_NAME).build();
    }

    // 声明报警队列绑定关系
    @Bean
    public Binding warningBinding(@Qualifier("warningQueue") Queue queue,
                                  @Qualifier("backupExchange") FanoutExchange
                                          backupExchange) {
        return BindingBuilder.bind(queue).to(backupExchange);
    }

    // 声明备份队列
    @Bean("backQueue")
    public Queue backQueue() {
        return QueueBuilder.durable(BACKUP_QUEUE_NAME).build();
    }

    // 声明备份队列绑定关系
    @Bean
    public Binding backupBinding(@Qualifier("backQueue") Queue queue,
                                 @Qualifier("backupExchange") FanoutExchange backupExchange) {
        return BindingBuilder.bind(queue).to(backupExchange);
    }
}

报警消费者

package com.agw.customer;

import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

/**
 * @author angw
 * @version 1.0
 * @Title:报警消费者
 * @date 2022/12/30 15:56
 */
@Slf4j
@Component
public class WarningCustomer {
    public static final String WARNING_QUEUE_NAME = "warning.queue";

    @RabbitListener(queues = WARNING_QUEUE_NAME)
    public void receiveWarningMsg(Message message){
        String msg = new String(message.getBody());
        log.error("报警了,发现了不可路由的消息:{}",msg);
    }
}

9、其他知识

9.1.1、幂等性

对于同一个操作发起的一次或者多次请求,结果是一致的,比如支付商品,第一次支付成功,但返回结果时候出现了异常,钱已经扣过了,用户再次点击会进行二次扣款,这就不是幂等性

9.1.2、消息重复消费

消费者在消费MQ消息时,MQ已经把消息发送给了消费者,消费者在给MQ返回ack的时候网络中断,MQ未收到确认消息,这条消息会发送给其他消费者,重复消费

解决:给消息设置一个全局id,根据全局id判断是否已经消费过了,可以使用数据库,但不推荐,推荐使用redis进行缓存,利用setnx命令,天然具有幂等性,效率高

 9.2、优先级队列

普通队列是遵循消息先进先出的规则

应用场景:某些消息需要优先推出

1、生产者设置消息优先级

package com.agw.task11;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.AMQP;
import com.rabbitmq.client.Channel;

import java.util.Scanner;

/**
 * @author angw
 * @version 1.0
 * @Title:优先级消费
 * @date 2022/12/27 10:49
 */
public class Producer11 {

    public static final String QUEUE_NAME = "hello";

    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();

        //设置消息的优先级
        AMQP.BasicProperties properties = new AMQP.BasicProperties().builder().priority(5).build();
        for (int i = 1; i < 11; i++) {
            String message = "info" + i;
            if (i == 5){
                channel.basicPublish("", QUEUE_NAME, properties, message.getBytes());
            }else {
                channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
            }
        }
        System.out.println("发送完成");
    }
}

2、消费者设置队列最大优先级(最大255,但官方推荐1-10)

package com.agw.task11;

import com.agw.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;

import java.util.HashMap;

/**
 * @author angw
 * @version 1.0
 * @Title:优先级消费
 * @date 2022/12/27 11:49
 */
public class Customer11 {
    //队列名称
    public static final String QUEUE_NAME = "hello";
    public static void main(String[] args) throws Exception {
        //创建连接工厂
        Channel channel = RabbitUtils.getChannel();

        HashMap<String, Object> params = new HashMap<>();
        //设置优先级队列最大优先级
        params.put("x-max-priority",10);
        channel.queueDeclare(QUEUE_NAME,false,false,false,params);
        System.out.println("开始消费消息");
        DeliverCallback deliverCallback = (consumerTag,delivery) ->{
            String message = new String(delivery.getBody());//消息内容
            System.out.println("接收到的消息:" + message);

        };
        CancelCallback cancelCallback = (consumerTag) -> {
            System.out.println(consumerTag + "消费者取消消费消息");
        };

        channel.basicConsume(QUEUE_NAME,true,deliverCallback,cancelCallback);
    }
}

9.3、惰性队列

使用场景:默认情况下,队列中的消息大多存储在内存中,当内存中消息较多无法接受新消息时,会置换到磁盘中,但这个效率较慢,惰性队列是直接将消息存储在磁盘中,不用关心消费者是否消费,占用的空间也更小

使用x-queue-mode设置

Map<String, Object> args = new HashMap<String, Object>();
args.put("x-queue-mode", "lazy");
channel.queueDeclare("myqueue", false, false, false, args)

10、rabbitmq集群搭

搭建三台mq集群

1、修改三台主机名称为node1、node2、node3

vim /etc/hostname      

hostname         查看主机名称

修改玩主机名称后重启服务器:reboot

2、配置各个节点的 hosts 文件,让各个节点都能互相识别对方,给三个节点都加上:

192.168.150.132 node1
192.168.150.133 node2
192.168.150.134 node3

vim /etc/hosts   修改配置文件     

3、启动三台mq服务

  rabbitmq-server -detached

4、将节点2连接到节点1

rabbitmqctl stop_app

rabbitmqctl reset

rabbitmqctl join_cluster rabbit@node1

rabbitmqctl start_app(只启动应用服务)

5、将节点3连接到节点2或节点1

rabbitmqctl stop_app

rabbitmqctl reset

rabbitmqctl join_cluster rabbit@node2

rabbitmqctl start_app

6、查看集群状态

rabbitmqctl cluster_status

8、需要重新设置用户

创建账号 rabbitmqctl add_user admin 123

设置用户角色 rabbitmqctl set_user_tags admin administrator

设置用户权限 rabbitmqctl set_permissions -p "/" admin ".*" ".*" ".*"

到这里就完成了!

注意:这里某个队列属于节点1,一旦节点1宕机,会导致其他节点不能使用这个队列,还可能会导致消息丢失

9、解除集群

rabbitmqctl stop_app       停止

rabbitmqctl reset        重置(mq账号会消失)

rabbitmqctl start_app        启动

rabbitmqctl cluster_status        查看集群状态

rabbitmqctl forget_cluster_node        查看所有集群状态

rabbit@node2(node1 机器上执行)(如果没有解除掉,强制解除)

10.2.1、镜像队列

解决某节点挂掉,队列不可用消息丢失问题

三台节点中,给任意一节点添加policy

 ha-mode表示某节点宕机后,镜像队列自动选择一个节点

ha-params表示几个镜像队列,2表示2-1个镜像队列

这样设置后,如果队列存在的节点宕机,镜像队列就会起作用,让然能获取到消息

10.3. Haproxy+Keepalive 高可用负载均衡

 1、下载haproxy(node1和node2两个节点上)

        yum -y install haproxy

2、修改 node1 和 node2 的 haproxy.cfg

        vim /etc/haproxy/haproxy.cfg

#---------------------------------------------------------------------
# Example configuration for a possible web application.  See the
# full configuration options online.
#
#   http://haproxy.1wt.eu/download/1.4/doc/configuration.txt
#
#---------------------------------------------------------------------

#---------------------------------------------------------------------
# Global settings
#---------------------------------------------------------------------
global
    # to have these messages end up in /var/log/haproxy.log you will
    # need to:
    #
    # 1) configure syslog to accept network log events.  This is done
    #    by adding the '-r' option to the SYSLOGD_OPTIONS in
    #    /etc/sysconfig/syslog
    #
    # 2) configure local2 events to go to the /var/log/haproxy.log
    #   file. A line like the following can be added to
    #   /etc/sysconfig/syslog
    #
    #    local2.*                       /var/log/haproxy.log
    #
    log         127.0.0.1 local2

    chroot      /var/lib/haproxy
    pidfile     /var/run/haproxy.pid
    maxconn     4000
    user        haproxy
    group       haproxy
    daemon

    # turn on stats unix socket
    stats socket /var/lib/haproxy/stats

#---------------------------------------------------------------------
# common defaults that all the 'listen' and 'backend' sections will
# use if not designated in their block
#---------------------------------------------------------------------
defaults
    mode                    http
    log                     global
    option                  httplog
    option                  dontlognull
    option                  redispatch
    retries                 3
    timeout connect         5s
    timeout client          120s
    timeout server          120s
    maxconn                 3000

#---------------------------------------------------------------------
# round robin balancing between the various backends
#---------------------------------------------------------------------
listen my_rabbitmq_cluster
    bind 0.0.0.0:6868
    mode	http
    balance     roundrobin
    server  rabbitmq_node1 192.168.150.135:5672 check inter 5000 rise 2 fall 3 weight 1
    server  rabbitmq_node2 192.168.150.133:5672 check inter 5000 rise 2 fall 3 weight 1
    server  rabbitmq_node3 192.168.150.134:5672 check inter 5000 rise 2 fall 3 weight 1
listen monitor
     bind 0.0.0.0:8888
     mode http
     option httplog
     stats enable 
     stats uri /stats
     stats refresh 5s

 3、两台节点启动haproxy

        haproxy -f /etc/haproxy/haproxy.cfg

        ps -ef | grep haproxy

4、访问地址

        http://10.211.55.71:8888/stats

5、下载 keepalived

        yum -y install keepalived

6、节点 node1 配置文件

        vim /etc/keepalived/keepalived.conf 

! Configuration File for keepalived

global_defs {
     router_id nodeA #路由ID, 主备的ID不能相同 
}

vrrp_script check_haproxy {
      script "/etc/keepalived/haproxy_chk.sh"
      interval 5   #检测脚本执行的间隔
}

vrrp_instance VI_1 {
    state MASTER	#是否是主服务器,master为主,backup为从
    interface ens33
    virtual_router_id 51	#主、备机的virtual_router_id必须相同
    priority 100	#主、备机不同的优先级,,主机值比较大,备级较少
    advert_int 1
    authentication {
        auth_type PASS
        auth_pass 1111
    }
    virtual_ipaddress {
        192.168.150.50	#虚拟ip地址
    }
}

7、节点 node2 配置文件

        需要修改 global_defs 的 router_id,如:nodeB

        其次要修改 vrrp_instance_VI 中 state 为"BACKUP";

        最后要将 priority 设置为小于 100 的值

8、添加 haproxy_chk.sh,监控脚本监控keepalived的状态,如果挂掉,另一个立刻上位

        vim /etc/keepalived/haproxy_chk.sh

#!/bin/bash
if [ $(ps -C haproxy --no-header | wc -l) -eq 0 ];then
haproxy -f /etc/haproxy/haproxy.cfg
fi
sleep 2
if [ $(ps -C haproxy --no-header | wc -l) -eq 0 ];then
service keepalived stop
fi

9、修改权限

        chmod 777 /etc/keepalived/haproxy_chk.sh

10、启动 keepalive 命令(node1 和 node2 启动)

        systemctl start keepalived

11、观察 Keepalived 的日志,可查看虚拟ip在那台主机生效

        tail -f /var/log/messages -n 200

12、观察最新添加的 Vip(虚拟ip)

        ip add show

13、使用 vip 地址来访问 rabbitmq 集群

14、node1 模拟 keepalived 关闭状态

        systemctl stop keepalived

10.4、联邦交换机

为了解决某种业务访问其他地区的交换机,网络延迟问题,两个交换机组成一个联邦交换机,本地的交换机收到消息后转发给其他地区的交换机

1、每台节点单独运行

2、每台机器上开启 federation 相关插件

        rabbitmq-plugins enable rabbitmq_federation

        rabbitmq-plugins enable rabbitmq_federation_management

3、在下游配置上游

4、添加policy

         

5、 出现running就成功了

 不是running的解决方式如下,用户改为如下权限:

 

10.5、联邦队列

建立联邦队列,当上游队列消息较多,来不及消费是,会自动给下游队列消费,这两个队列名称相同

原理图:

 

10.6、 shovel

相当于一个铲子,将消息从一方铲到另一方,可以是不同的集群不同的交换机和队列

 

1、开启插件(需要的机器都开启)

        rabbitmq-plugins enable rabbitmq_shovel

        rabbitmq-plugins enable rabbitmq_shovel_management

2、添加shovel源和目的地

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿伟来了an

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值