消息队列—RabbitMQ

简介

1、什么是 RabbitMQ

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

2、RabbitMQ的模式和类型

2.1、模式

2.2、四种交换机类型

2.2.1、fanout交换机

2.2.2、direct交换机

2.2.3、topic交换机

2.2.4、hander交换机

它是根据头部信息来决定的,在我们发送的消息中是可以携带一些头部信息的(类似于 HTTP),我们可以根据这些头部信息来决定路由到哪一个消息队列中。

2.2.4.1、Configuration
@Configuration
public class RabbitConfiguration {
  @Bean("headerExchange")  //注意这里返回的是HeadersExchange
  public HeadersExchange exchange(){
    return ExchangeBuilder
      .headersExchange("amq.headers")  //RabbitMQ为我们预置了两个,这里用第一个就行
      .build();
  }
​
  @Bean("yydsQueue")
  public Queue queue(){
      return QueueBuilder.nonDurable("yyds").build();
  }
​
  @Bean("binding")
  public Binding binding2(@Qualifier("headerExchange") HeadersExchange exchange,  //这里和上面一样的类型
                         @Qualifier("yydsQueue") Queue queue){
    return BindingBuilder
      .bind(queue)
      .to(exchange)//使用HeadersExchange的to方法,可以进行进一步配置 header
            //.whereAny("a", "b").exist();   这个是只要存在任意一个指定的头部Key就行
      //.whereAll("a", "b").exist();   这个是必须存在所有指定的的头部Key
      .where("test").matches("hello");   //比如我们现在需要消息的头部信息中包含test,并且值为hello才能转发给我们的消息队列
    //.whereAny(Collections.singletonMap("test", "hello")).match();  传入Map也行,批量指定键值对
  }
}

3、下载安装

Releases · rabbitmq/rabbitmq-server · GitHub

3.1、docker中

docker pull rabbitmq:management
# 访问端口 5672,管理后端访问端口 15672
# http://192.168.174.128:15672,guest/guest
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
​
# 查看日志
docker logs -f rabbitmq

3.2、CentOS中

下载 rabbitmq的 CentOS版,还要下载 Erlang语言环境,要注意版本匹配问题,

# 查看系统版本号
lsb_release -a

3.2.1、下载 Erlang

https://www.erlang-solutions.com/downloads/

# 创建对应目录
mkdir -p /usr/rabbitmq
cd /usr/rabbitmq/
​
# 通过 github 下载,很慢,推荐先下载到 Windows中,再上传进去
# wget https://packages.erlang-solutions.com/erlang-solutions-2.0-1.noarch.rpm
# 解压 erlang
rpm -Uvh erlang-solutions-2.0-1.noarch.rpm
# 安装
yum install -y erlang
# 查看版本号,检查是否安装成功
erl -v

3.2.2、安装 RabbitMQ

# 先安装 socat,rabbitmq中会用到
yum install -y socat
​
# 通过 github 下载,很慢,推荐先下载到 Windows中,再上传进去
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.13/rabbitmq-server-3.8.13-1.el8.noarch.rpm
# 解压 rabbitmq
rpm -Uvh rabbitmq-server-3.8.13-1.el8.noarch.rpm
​
# 下载一下
yum install rabbitmq-server -y
​
# 启动服务
systemctl start rabbitmq-server
# 查看服务状态
systemctl status rabbitmq-server
# 停止服务
systemctl stop rabbitmq-server
# 开机启动服务
systemctl enable rabbitmq-server

3.2.3、安装管理后台

IP地址:15672 就可以访问管理后台了,guest只能本机访问

如果无法访问,可能你使用的阿里云防火墙问题,需要把这个端口加入安全组中。

rabbitmq-plugins enable rabbitmq_management

3.3、Ubuntu中

# sudo apt install erlang // 不需要单独下,因为下 RabbitMQ 的时候会自动下 erlang
​
sudo apt install rabbitmq-server
# 安装完成后会自动启动,可以查看下状态
sudo rabbitmqctl status
​
# 开启 RabbitMQ 的管理面板插件
sudo rabbitmq-plugins enable rabbitmq_management
​
# 创建用户,并给他权限
sudo rabbitmqctl add_user admin admin
sudo rabbitmqctl set_user_tags admin administrator

3.4、RabbitMQ 的端口

端口5672 就是 amqp协议来进行连接;

25672 是集群化端口;

15672 是管理面板的端口。

3.5、用户与环境

# 新增用户
rabbitmqctl add_user admin admin
​
# 分配角色
rabbitmqctl set_user_tags admin administrator
​
# 为用户添加资源权限。假如不受与角色的话,就去配置权限
rabbitmqctl.bat set_permissions -p / admin ".*" ".*" ".*"

3.5.1、角色

  • administrator:可以登录控制台、查看所有信息、可以对rabbitmq进行管理

  • monitoring:监控者 登录控制台,查看所有信息

  • policymaker:策略制定者 登录控制台,指定策略

  • managment:普通管理员 登录控制台

3.5.1.1、none:

不能访问management plugin

3.5.1.2:management:查看自己相关节点信息
  • 列出自己可以通过 AMQP 登入的虚拟机

  • 查看自己的虚拟机节点 virtual hosts 的 queues,exchanges 和 bindings信息

  • 查看和关闭自己的 channels和connections

  • 查看有关自己的虚拟机节点virtual hosts的统计信息。包括其他用户在这个节点virtual hosts中的活动信息。

3.5.1.3:Policymaker
  • 包含management所有权限

  • 查看和创建和删除自己的virtual hosts所属的policies和parameters信息。

3.5.1.4:Monitoring
  • 包含management所有权限

  • 罗列出所有的virtual hosts,包括不能登录的virtual hosts。

  • 查看其他用户的connections和channels信息

  • 查看节点级别的数据如clustering和memory使用情况

  • 查看所有的virtual hosts的全局统计信息。

3.5.1.5:Administrator
  • 最高权限

  • 可以创建和删除virtual hosts

  • 可以查看,创建和删除users

  • 查看创建permisssions

  • 关闭所有用户的connections

3.5.2、相关命令

rabbitmqctl add_user 账号 密码
rabbitmqctl set_user_tags 账号 administrator
rabbitmqctl change_password Username Newpassword 修改密码
rabbitmqctl delete_user Username 删除用户
rabbitmqctl list_users 查看用户清单
rabbitmqctl set_permissions -p / 用户名 ".*" ".*" ".*" 为用户设置administrator角色
rabbitmqctl set_permissions -p / root ".*" ".*" ".*"

3.5.3、用户与环境

guest 用户的默认可以进入的虚拟环境 Can access virtual hosts 是 /,新用户则是未设置。我们可以新建一个虚拟环境给用户去使用。

4、简单使用

如果使用的时候不指定交换机,那么就会使用默认的交换机,默认的交换机使用的是 direct模式,并且路由规则是按照队列名称,即在交换机中写入的消息,指定的路由规则是 “queue1”,那么他就会发给名字叫“queue1”的队列,而不会发给路由规则是这个的队列。

队列做持久化,那么肯定会存盘;如果不做持久化,也会存盘,只不过会随着重启服务器丢失掉.

测试的时候可以通过管理后台的图形化界面去操作,通过交换机去发送消息给队列,然后创建队列,然后绑定交换机和队列,然后在 Exchanges 下,通过 Publish Message 来发送消息,然后 Queues 来模拟接收消息,Ack Mode 选择 NACK,

在图形化界面中创建好交换机、队列,绑定好关系,就不需要在代码中写了,

若消费者去消费一个不存在的队列,那么是会报错的,是不会自己创建的。当然生产者发送消息给不存在的交换机也是一样的。

所以当消费者调用 channel.basicConsume(queueName, true... 这个方法去监听队列的时候,应该提前做一个队列是否存在的判断,不存在就另作处理。

4.0、AMQP协议

Advanced Message Queuing Protocol(高级消息队列协议)。是应用层协议的一个开发标准,为面向消息的中间件设计。

Producer 把消息给交换机,然后交换机把消息给队列,队列会做根据规则做分发。

4.0.1、概念

  • Server:又称 Broker,接受客户端的连接,实现 AMQP实体服务。 安装 rabbitmq-server就是一个 Broker,即一个节点。

  • Connection:连接,应用程序与Broker的网络连接 TCP/IP,有三次握手和四次挥手

  • Channel:网络信道,几乎所有的操作都在 Channel中进行,Channel是进行消息读写的通道,客户端可以建立对各 Channel,每个 Channel 代表一个会话任务。

  • Message:消息:服务与应用程序之间传送的数据,由Properties和body组成,Properties可是对消息进行修饰,比如消息的优先级,延迟等高级特性,Body则就是消息体的内容。

  • Virtual Host:虚拟地址,用于进行逻辑隔离,最上层的消息路由,一个虚拟主机理由可以有若干个Exhange和Queueu,同一个虚拟主机里面不能有相同名字的Exchange,Virture Host就相当于磁盘,也可理解为文件夹,做隔离的。

  • Exchange:交换机,接受消息,根据路由键发送消息到绑定的队列。交换机不具备消息存储的能力。

  • Bind

  • 于路由条件,让对应消费者收到消息,而不是给每一个消费者。

  • Queue:队列,也称为 Message Queue,消息队列,保存消息并将它们转发给消费者。

4.0.2、流转过程

4.0.2.1、AMQP生产者流转过程

4.0.2.2、AMQP消费者流转过程

如果 Basic.ACK 是正常应答,那么服务器就会把消息从队列中移除,这样就是个正常的流程,

如果是 false,不应答,那么服务器就会把消息就会不断重试,直至消费者消费掉。

无限制的重试会导致服务器的资源消耗完毕,就会出现故障,消息队列就会被挂起,就会屏蔽接收生产者的消息

4.0.2.3、RabbitMQ 基于 channel 去处理而非 connection

原来的连接是一个短连接,那么就会经过三次握手、四次挥手等过程,又开又关,耗时又长,性能开销又大,

RabbitMQ中把连接做成了长连接,里面会有很多信道channel,就是说只需要连接一次即可,通信都是通过 channel 进行的,并发量很高,性能很高。

4.0.3、支持的模式

RabbitMQ Tutorials | RabbitMQ

简单模式 Simple、工作模式 Work、发布订阅模式 Publish/Subscribe、路由模式 Routing、主题模式 Topics、参数模式 RPC。

4.1、使用 RabbitMQ管理界面

网址是:http://192.168.174.128:15672,账号密码都是 guest。

4.1.1、Virtual Hosts

4.1.1.1、新建虚拟环境

Virtual Hosts 就是虚拟环境,环境之间是隔离的。可以点击用户的名字去设置使用的环境。

4.1.2、exchanges

4.1.2.1、模拟生产者发布消息给交换机

指定路由规则,然后发布消息。

4.1.3、queues

4.1.3.1、队列绑定交换机和路由规则

Routing key 就是路由规则,交换机通过收到对应路由规则的消息会自动发送给对应的队列。

4.1.3.2、队列查看并消费消息
  • Nack message requeue true:拒绝消息,取出查看后再放回去,也就是查看后不会将消息从消息队列取出,一次可以拒绝多个消息,在 Messages 中输入想要查看的数量即可。

  • Ack message requeue false:确认应答,确认后消息会从消息队列中移除,一次可以确认多个消息。

  • Reject message requeue true/false:也是拒绝此消息,但是可以指定是否重新排队,即是否取出消息。

4.2、Simple简单模式

<!-- 普通的 maven项目引入的依赖 -->
<dependency>
    <groupId>com.rabbitmq</groupId>
    <artifactId>amqp-client</artifactId>
    <version>5.14.2</version>
</dependency>

4.2.1、生产者Producer

生产者做的是从生产者到消息队列的工作。

public class Producer {
  public static void main(String[] args) {
    // 1、创建连接工程
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("192.168.174.128");
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    connectionFactory.setVirtualHost("/");
    Connection connection = null;
    Channel channel = null;
    try {
      // 2、创建连接 Connection
      connection = connectionFactory.newConnection("生产者");
      // 3、通过连接获取通道 Channel
      channel = connection.createChannel();
      // 4、通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
      String queueName = "queue1";
      /** 声明队列,如果此队列不存在,会自动创建
       * 队列的名称
       * 是否做持久化durable,即 rabbitmq-server 重启后此队列就销毁了
       * 是否是独占队列,即拥有排他性
       * 是否自动删除,随着最后一个消费者消费完毕消息后是否把队列自动删除
       * 携带的附加参数
       */
      channel.queueDeclare(queueName, false, false, false, null);
      
      // 最好是声明了队列之后就让队列绑定到交换机。否则会自动绑定默认的交换机
      // 队列名称、交换机名称、交换机发送给队列的路由规则
      // channel.queueBind("yyds", "amq.direct", "my-yyds");
      // 5、准备消息内容
      String message = "Hello 树先生!";
      // 6、发送消息给队列queue
      /**
       * 根据路由规则发布消息
       * 这里是 "",没有指定交换机,队列会有一个默认的交换机
       */
      channel.basicPublish("", queueName, null, message.getBytes());
      System.out.println("消息发送成功!");
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 7、关闭连接
        if (channel != null && channel.isOpen()) {
            try {
                channel.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        // 8、关闭通道
        if (connection != null && connection.isOpen()) {
            try {
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
  }
}
4.2.1.1、queueDeclare方法

声明队列。

  • queue:队列的名称

  • durable:是否持久化。

  • exclusive:是否排他,如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。排他队列是基于 Connection 可见,同一个 Connection 的不同 Channel 是可以同时访问同一个连接创建的排他队列。并且,如果一个 Connection 已经声明了一个排他队列,其他的 Connection 是不允许建立同名的排他队列的,即使该队列是持久化的,一旦 Connection 关闭或者客户端退出,该排他队列都会自动被删除。

  • autoDelete:是否自动删除。

  • arguments:设置队列的其他一些参数,这里我们暂时不需要什么其他参数。

4.2.1.2、queueBind方法

根据路由规则,队列绑定交换机。

  • queue:需要绑定的队列名称。

  • exchange:需要绑定的交换机名称。

  • routingKey:交换机把消息发送给队列的路由规则。

4.2.1.3、basicPublish方法

发送消息给交换机,并指定此消息的路由规则。

  • exchange:对应的 Exchange名称,默认的则为 ""。

  • routingKey:这里我们填写绑定时指定的 routingKey,使用默认交换机的话,routingKey 就是队列名称。

  • props:其他的配置。

  • body:消息本体。

4.2.2、消费者Consumer

消费者做的是从消息队列消费队列中的消息的工作。是异步于 main线程的。

public class Consumer {
  public static void main(String[] args) {
    // 1、创建连接工程
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("192.168.174.128");
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    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() {
          @Override
          public void handle(String s, Delivery delivery) throws IOException {
              System.out.println("收到消息:" + new String(delivery.getBody(), "UTF-8"));
          }
      }, new CancelCallback() {
          @Override
          public void handle(String s) throws IOException {
              System.out.println("消息接收失败!");
          }
      });
      // 还要其他几种接收方法
      // channel.basicNack(delivery.getEnvelope().getDeliveryTag(), false, false);
      // channel.basicReject(delivery.getEnvelope().getDeliveryTag(), false);
      
      // 这个会先执行,因为接收消息是异步多线程进行的
      System.out.println("开始接收消息...");
      // 阻塞住程序
      System.in.read();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        // 5、关闭连接
        // 6、关闭通道
        // 。。。
    }
  }
}
4.2.2.1、basicConsume

异步多线程持续接收消息。

  • queue:消息队列名称。

  • autoAck:是否自动确认应答,消费者从消息队列取出数据后,需要跟服务器进行确认应答,当服务器收到确认后,会自动将消息删除。

  • deliver:消息接收后的函数回调,我们可以在回调中对消息进行处理,处理完成后,需要给服务器确认应答。

  • cancel:当消费者取消订阅时进行的函数回调,这里暂时用不到。

4.3、Work 工作模式

work又分为两种模式:轮询模式(平均分配)、公平模式(能者多劳)。

4.3.1、Work模式—轮询模式

轮询模式的应答模式可以使用自动应答。即 basicConsume 的第二个参数是 true。当然也可以使用手动应答。

4.3.1.1、Producer

使用默认交换机。

public class Producer {
    public static void main(String[] args) {
        // 1、创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.174.128");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2、创建连接 Connection
            connection = connectionFactory.newConnection("生产者");
            // 3、通过连接获取通道 Channel
            channel = connection.createChannel();
            for (int i = 1; i <= 20; i++) {
                // 4、消息的内容
                String msg = "Hello :" + i;
                // 5: 发送消息给中间件
                // 交换机名称、队列名称/routingkey、属性参数配置、发送消息的内容
                channel.basicPublish("", "queue1", null, msg.getBytes());
            }
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 6、关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 7、关闭连接
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
4.3.1.2、Work1
public class Work1 {
    public static void main(String[] args) {
        // 1: 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.174.128");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2、创建连接 Connection
            connection = connectionFactory.newConnection("消费者");
            // 3、通过连接获取通道 Channel
            channel = connection.createChannel();
            // 4、通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
            // 队列已存在就不需要创建了
            // 5、定义接受消息的回调
            channel.basicConsume("queue1", true, new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    try {
                        System.out.println("Work1-收到消息:" + new String(delivery.getBody(), "UTF-8"));
                        // 模拟消费者的性能
                        TimeUnit.SECONDS.sleep(1);
                    } catch (Exception ex) {
                        ex.printStackTrace();
                    }
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收消息出现异常...");
                }
            });
            System.out.println("Work1-开始接受消息...");
            System.in.read();
        } catch (Exception ex) {
            ex.printStackTrace();
            System.out.println("接收消息出现异常...");
        } finally {
            // 6: 关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
            // 7、关闭连接
            if (connection != null && connection.isOpen()) {
                try {
                    connection.close();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }
    }
}
4.3.1.3、Work2

跟 Work1 一模一样,可以改变睡眠的时间,就相当于模拟两个消费者去消费。

但是最后是平均分配。

4.3.2、Work模式—公平模式

公平模式的应答模式必须是手动应答,即 basicConsume 的第二个参数是 false,然后添加手动应答代码。因为如果是自动应答,那么调用 basicConsume 的时候就会应答了,还没有去睡眠,那么就无法体现性能的差异了。

basicQos(1) 就是一个指标,如果没有设置 Qos,那么就是 null,那么就会采用轮询模式;如果定义出来了,那么就会使用公平模式,规定消费者每次可以从队列里面拿几条消息。

basicQos 已经过时了,意义相当于消费者一次可以取的消息数量 Prefetch count,

// 配置类中
@Resource
private CachingConnectionFactory connectionFactory;
​
@Bean(name = "listenerContainer")
public SimpleRabbitListenerContainerFactory listenerContainer(){
    SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
    factory.setConnectionFactory(connectionFactory);
    factory.setPrefetchCount(1);   //将PrefetchCount设定为1表示一次只能取一个
    return factory;
}
@Component
public class TestListener {
    @RabbitListener(queues = "yyds",  containerFactory = "listenerContainer")
    public void receiver(String data){
        System.out.println("一号消息队列监听器 "+data);
    }
​
    @RabbitListener(queues = "yyds", containerFactory = "listenerContainer")
    public void receiver2(String data){
        System.out.println("二号消息队列监听器 "+data);
    }
}
4.3.2.1、Producer

与轮询模式的一样,仍然使用默认交换机。

4.3.2.2、Work1

相比轮询模式,修改了一些代码。

public class Work1 {
  public static void main(String[] args) {
    // 1: 创建连接工厂
    ConnectionFactory connectionFactory = new ConnectionFactory();
    connectionFactory.setHost("192.168.174.128");
    connectionFactory.setPort(5672);
    connectionFactory.setUsername("guest");
    connectionFactory.setPassword("guest");
    connectionFactory.setVirtualHost("/");
    Connection connection = null;
    Channel channel = null;
    try {
        // 2、创建连接 Connection
        connection = connectionFactory.newConnection("消费者");
        // 3、通过连接获取通道 Channel
        channel = connection.createChannel();
        // 4、通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
        // 队列已存在就不需要创建了
        // 5、定义接受消息的回调
        Channel finalChannel = channel;
        // 定义指标
        finalChannel.basicQos(1);
        finalChannel.basicConsume("queue1", false, new DeliverCallback() {
            @Override
            public void handle(String s, Delivery delivery) throws IOException {
                try {
                    System.out.println("Work1-收到消息:" + new String(delivery.getBody(), "UTF-8"));
                    TimeUnit.SECONDS.sleep(1);
                    finalChannel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        }, new CancelCallback() {
            @Override
            public void handle(String s) throws IOException {
                System.out.println("接收消息出现异常...");
            }
        });
        System.out.println("Work1-开始接受消息...");
        System.in.read();
      } catch (Exception ex) {
          ex.printStackTrace();
          System.out.println("接收消息出现异常...");
      } finally {
          // 6: 关闭通道
          // 7、关闭连接
      }
  }
}
4.3.2.3、Work2

跟 Work1 一模一样,可以改变睡眠的时间,就相当于模拟两个消费者去消费。

最后是能者多劳,谁的性能好就多消费。

4.4、Publish/Subscribe 发布订阅模式—fanout交换机

fanout模式的交换机,只要是绑定了交换机的队列,每一个队列都会收到交换机发来的消息。这里的交换机、队列、绑定关系,都已经在图形化管理界面中创建好了。

4.4.1、Producer

public class Producer {
    public static void main(String[] args) {
        // 1、创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.174.128");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2、创建连接 Connection
            connection = connectionFactory.newConnection("生产者");
            // 3、通过连接获取通道 Channel
            channel = connection.createChannel();
            // 4、通过通道创建交换机、声明队列、绑定关系、路由key、发送消息、接收消息
            // 队列已存在就不需要创建了
//            String queueName = "queue1";
//            channel.queueDeclare(queueName, false, false, false, null);
            // 5、准备消息内容
            String message = "Hello fanout!";
            // 6、发送消息给队列queue
            String exchangeName = "fanout-exchange";
            String type = "fanout";  // 这个 Fanout交换机已在图形化界面中创建
            String routingKey = "";
            channel.basicPublish(exchangeName, routingKey, null, message.getBytes());
            System.out.println("消息发送成功!");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 7、关闭连接
            // 8、关闭通道
        }
    }
}

4.4.2、Consumer

public class Consumer {
    public static void main(String[] args) {
        // 启动三个线程去执行
        new Thread(runnable, "queue1").start();
        new Thread(runnable, "queue2").start();
        new Thread(runnable, "queue3").start();
    }
    private static Runnable runnable = () -> {
        // 获取队列的名称
        final String queueName = Thread.currentThread().getName();
        // 1、创建连接工程
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.174.128");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("guest");
        connectionFactory.setPassword("guest");
        connectionFactory.setVirtualHost("/");
        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() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
                    System.out.println("收到消息:" + new String(delivery.getBody(), "UTF-8"));
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("消息接收失败!");
                }
            });
            // 这个会先执行,因为接收消息是异步的
            System.out.println("开始接收消息...");
            // 阻塞住程序
            System.in.read();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5、关闭连接
            // 6、关闭通道
        }
    };
}

4.5、Routing 路由模式—direct交换机

direct模式的交换机,就是在发布订阅模式上多了个指定 Routing Key。direct模式是默认的交换机模式。

交换机和队列的关系在图形化界面中定义好。

4.5.1、Producer

在 fanout交换机的基础上,给 routingKey 指定值就好了。

4.6、Topics 主题模式—topic交换机

topic模式交换机,在路由模式基础上,做了一个模糊匹配,#代表0到多级,*代表一级,每一级用 . 隔开。

4.6.0、默认的 topic交换机 amq.rabbitmq.trace

这是用于帮助我们记录和追踪生产者和消费者使用消息队列的交换机,它是一个内部的交换机。

如果想使用此交换机,操作如下:

// 在控制台将虚拟主机/test的追踪功能开启
sudo rabbitmqctl trace_on -p /test

创建一个新的队列 trace:

将队列与交换机进行绑定(这里是在交换机中进行的,同一个队列绑定两个路由规则):

随便给一个交换机发送消息,可以看到在正常发送消息,并且消费者已经处理之后,trace 队列中新增了两条消息,里面有很多记录消息。

4.6.1、Producer

在 fanout交换机的基础上,给 routingKey 指定值就好了。比如:com.chw.rabbitmq、rabbitmq、com.rabbitmq 等等,测试路由是否生效。

4.7、RPC 参数模式

5、SpringBoot 整合 RabbitMQ

5.0、前置知识

5.0.1、引入依赖

<!-- SpringBoot的 RabbitMQ starter依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

5.0.2、修改配置文件

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

5.0.3、交换机、队列、绑定关系配置类模板

@Configuration
public class RabbitConfiguration {
  @Bean("directExchange")  //定义交换机Bean,可以很多个
  public Exchange exchange(){
      return ExchangeBuilder.directExchange("amq.direct").build();
  }
​
  @Bean("yydsQueue")     //定义消息队列
  public Queue queue(){
      return QueueBuilder.nonDurable("yyds").build();
  }
​
  @Bean("binding")
  public Binding binding(@Qualifier("directExchange") Exchange exchange,
                         @Qualifier("yydsQueue") Queue queue){
      return BindingBuilder
              .bind(queue)   //绑定队列
              .to(exchange)  //到交换机
              .with("my-yyds")   //使用自定义的routingKey
              .noargs();
  }
}

5.0.4、rabbitTemplate的方法

rabbitTemplate是用在生产者方的,进行发送消息。
​
convertAndSend  // 简单地发送消息
​
convertSendAndReceive  // 发送消息,会等待消费者消费然后得到响应结果。
// 要修改消费者Listener 的返回类型,如果是 void,那么生产者发送后会得到 null。

5.0.5、发送和接收对象,用 json格式传输

// 先在配置类中加入一个转换器
@Configuration
public class RabbitConfiguration {
    ...
​
    @Bean("jacksonConverter")   //直接创建一个用于JSON转换的Bean
    public Jackson2JsonMessageConverter converter(){
        return new Jackson2JsonMessageConverter();
    }
}
​
​
​
// 发送的时候
@Test
void publisher() {
    template.convertAndSend("amq.direct", "yyds", new User());
}
​
​
​
// 接收的时候
@Component
public class TestListener {
    //指定messageConverter为我们刚刚创建的Bean名称
  @RabbitListener(queues = "yyds", messageConverter = "jacksonConverter")
  public void receiver(User user){  //直接接收User类型
      System.out.println(user);
  }
}

5.1、fanout交换机

5.1.1、config配置类

@Configuration
public class RabbitMQConfig {
    @Value("${myParams.rabbitmq.fanoutExchangeName}")
    private String exchangeName;
    
    // 1、声明注册 fanout模式的交换机
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange(exchangeName, true, false);
    }
    
    // 2、声明队列,sms_fanout_queue、email_fanout_queue、wechat_fanout_queue
    @Bean
    public Queue smsQueue() {
        return new Queue("sms_fanout_queue", true);
    }
    @Bean
    public Queue emailQueue() {
        return new Queue("email_fanout_queue", true);
    }
    @Bean
    public Queue wechatQueue() {
        return new Queue("wechat_fanout_queue", true);
    }
    
    // 3、交换机绑定队列
    @Bean
    public Binding smsBinding() {
        return BindingBuilder.bind(smsQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue()).to(fanoutExchange());
    }
    @Bean
    public Binding wechatBinding() {
        return BindingBuilder.bind(wechatQueue()).to(fanoutExchange());
    }
}

5.1.2、service—生产者

@Service
public class OrderService {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    @Value("${myParams.rabbitmq.fanoutExchangeName}")
    private String exchangeName;
    private String routeKey = "";
    
    public void makeOrder(Long userId, Long productId, int num) {
        // 业务处理
        String message = UUID.randomUUID().toString();
        System.out.println("用户 " + userId + " 的订单编号是:" + orderId);
        // 发送订单信息给 RabbitMQ的交换机
        // 交换机名称、路由key/队列名称、消息内容
        rabbitTemplate.convertAndSend(exchangeName, routeKey, message);
    }
}

5.1.3、consumer—消费者

@Component
public class EmailListener {
    @RabbitListener(queues = {"email_fanout_queue"})
    public void receiveMsg(String msg) {
        System.out.println("EmailConsumer 收到消息:" + msg);
    }
}

5.1.4、test

先启动 启动类,然后再调用 test模块的方法去发送消息。

@SpringBootTest
class OrderServiceTest {
    @Autowired
    private OrderService orderService;
    @Test
    void makeOrderTest() {
        orderService.makeOrder(1L, 1L, 10);
    }
}

5.2、direct交换机

5.2.1、config

@Configuration
public class RabbitMQConfig {
    @Value("${myParams.rabbitmq.directExchangeName}")
    private String exchangeName;
    // 1、声明注册 direct模式的交换机
    @Bean
    public DirectExchange directExchange() {
        return new DirectExchange(exchangeName, true, false);
    }
    // 2、声明队列,sms_direct_queue、email_direct_queue、wechat_direct_queue
    @Bean
    public Queue smsQueue() {
        return new Queue("sms_direct_queue", true);
    }
    @Bean
    public Queue emailQueue() {
        return new Queue("email_direct_queue", true);
    }
    @Bean
    public Queue wechatQueue() {
        return new Queue("wechat_direct_queue", true);
    }
    // 3、交换机绑定队列
    @Bean
    public Binding smsBinding() {
        return BindingBuilder.bind(smsQueue()).to(directExchange()).with("sms");
    }
    @Bean
    public Binding emailBinding() {
        return BindingBuilder.bind(emailQueue()).to(directExchange()).with("email");
    }
    @Bean
    public Binding wechatBinding() {
        return BindingBuilder.bind(wechatQueue()).to(directExchange()).with("wechat");
    }
}

5.2.2、service—生产者

和 fanout的一样,只是给 routeKey 加了值,然后修改交换机的名称,

5.2.3、consumer—消费者

和 fanout的一样,只是要修改监听的队列的名称,

5.2.4、test

和 fanout的一样,进行发送消息即可。

5.3、topic交换机

这次使用 SpringBoot 的另一种方式去做绑定。

但是更推荐使用 config的代码方式。

5.3.1、service—生产者

和 fanout的一样,只是给 routeKey 加了路由值,然后修改交换机的名称,

5.3.2、consumer—消费者

@Service
@RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "email_topic_queue", durable = "true", autoDelete = "false"),
        exchange = @Exchange(value = "topic_order_exchange", type = ExchangeTypes.TOPIC),
        key = "#.email.#"
))
public class EmailConsumer {
    @RabbitHandler
    public void receiveMsg(String msg) {
        System.out.println("EmailConsumer 收到消息:" + msg);
    }
}

5.3.3、test

和 fanout的一样,进行发送消息即可。

6、其他功能

@Bean
public Queue queue(){
    return QueueBuilder.nonDurable("yyds")
      .deadLetterExchange("dlx.direct").deadLetterRoutingKey("dl-yyds")
      .ttl(5000).maxLength(3)   //将最大长度设定为3
      .build();
}

6.1、过期时间TTL

6.1.1、简介

过期时间TTL 表示可以对消息设置预期的时间,在这个时间内都可以被消费者接收获取;过了之后消息将自动被删除。RabbitMQ可以对 消息和队列 设置 TTL。目前有两种方法可以设置:

  • 第一种方法是通过队列属性设置,队列中所有消息都有相同的过期时间。

  • 第二种方法是对消息进行单独设置,每条消息 TTL可以不同。 如果上述两种方法同时使用,则消息的过期时间以两者之间 TTL较小的那个数值为准。

消息在队列的生存时间一旦超过设置的 TTL值,就称为 dead message,会被投递到死信队列, 消费者将无法再收到该消息。

推荐给队列设置 TTL,因为给队列设置 TTL,过期了可以转移到死信队列;而给消息设置 TTL,那么消息就会直接移除,不会进入死信队列。

6.1.2、给队列设置过期时间

6.1.2.1、config
@Configuration
public class TtlRabbitMQConfig {
    private String exchangeName = "ttl_direct_exchange";
    // 1、声明注册 direct模式的交换机
    @Bean
    public DirectExchange ttlExchange() {
        return new DirectExchange(exchangeName, true, false);
    }
    // 2、声明队列
    @Bean
    public Queue ttlQueue() {
        // 设置过期时间
        Map<String, Object> args = new HashMap<>();
        // 单位是 ms
        args.put("x-message-ttl", 5000);
​
        return new Queue("ttl_direct_queue", true, false, false, args);
    }
​
    // 3、交换机绑定队列
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
    }
}
6.1.2.2、service—生产者
public void makeTtlOrder(Long userId, Long productId, int num) {
    // 业务处理
    String orderId = UUID.randomUUID().toString();
    System.out.println("用户 " + userId + " 的订单编号是:" + orderId);
    // 发送订单信息给 RabbitMQ的交换机
    // 交换机名称、路由key/队列名称、消息内容
    rabbitTemplate.convertAndSend("ttl_direct_exchange", "ttl", orderId);
}

6.1.3、给消息设置过期时间

6.1.3.1、config
@Configuration
public class TtlRabbitMQConfig {
    private String exchangeName = "ttl_direct_exchange";
    // 1、声明注册 direct模式的交换机
    @Bean
    public DirectExchange ttlExchange() {
        return new DirectExchange(exchangeName, true, false);
    }
    // 2、声明队列
    @Bean
    public Queue ttlMessageQueue() {
        return new Queue("ttl_message_direct_queue", true);
    }
    // 3、交换机绑定队列
    @Bean
    public Binding ttlMessageBinding() {
        return BindingBuilder.bind(ttlMessageQueue()).to(ttlExchange()).with("ttlmessage");
    }
}
6.1.3.2、service—生产者
public void makeTtlMessageOrder(Long userId, Long productId, int num) {
    // 业务处理
    String orderId = UUID.randomUUID().toString();
    System.out.println("用户 " + userId + " 的订单编号是:" + orderId);
    // 给消息设置过期时间 TTL
    MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
        @Override
        public Message postProcessMessage(Message message) throws AmqpException {
            message.getMessageProperties().setExpiration("5000");
            message.getMessageProperties().setContentEncoding("UTF-8");
            return message;
        }
    };
    // 发送订单信息给 RabbitMQ的交换机
    // 交换机名称、路由key/队列名称、消息内容
    rabbitTemplate.convertAndSend("ttl_direct_exchange", "ttlmessage", orderId, messagePostProcessor);
}

6.2、死信队列

6.2.1、简介

DLX,全称为 Dead-Letter-Exchange , 死信交换机,也可称为死信邮箱。当消息在一个队列中变成死信(dead message)之后,它能被重新发送到另一个交换机中,这个交换机就是 DLX ,绑定 DLX 的队列就称之为死信队列。

DLX 也是一个正常的交换机,和一般的交换机没有区别,它能在任何的队列上被指定,实际上就是设置某一个队列的属性。当这个队列中存在死信时,Rabbitmq 就会自动地将这个消息重新发布到设置的 DLX 上去,进而被路由到另一个队列,即死信队列。

要想使用死信队列,只需要在定义队列的时候设置队列参数 x-dead-letter-exchange 指定交换机即可。

消息变成死信,可能是由于以下的原因:

  • 消息被拒绝

  • 消息 TTL 过期

  • 队列达到最大长度。x-max-length 死信交换机和死信队列就是正常的交换机的队列,只不过是普通的队列指定了死信规则。

死信的一般过程就是:

  1. 创建一个交换机和队列,把他们作为死信交换机和死信队列,进行绑定,指定路由规则,

  2. 创建一个正常的交换机和队列,进行绑定,指定路由规则;然后给该队列指定死信交换机,以及指定消息死了后给死信交换机的路由规则,

  3. 然后发送一个指定了路由规则的消息给交换机,然后交换机根据此消息的路由规则给到队列;

  4. 当队列中的这个消息满足死信条件后,就会指定死信消息的路由规则后发给死信交换机,死信交换机根据路由规则给到死信队列。

6.2.2、实践1

6.2.2.1、config
@Configuration
public class RabbitConfiguration {
    @Bean("directDlExchange")
    public Exchange dlExchange(){
        return ExchangeBuilder.directExchange("dlx.direct").build();
    }
​
    @Bean("yydsDlQueue")   //创建一个新的死信队列
    public Queue dlQueue(){
        return QueueBuilder
                .nonDurable("dl-yyds")
                .build();
    }
​
    @Bean("dlBinding")   // 将交换机和死信队列进行绑定
    public Binding dlBinding(@Qualifier("directDlExchange") Exchange exchange,
                           @Qualifier("yydsDlQueue") Queue queue){
        return BindingBuilder
                .bind(queue)
                .to(exchange)
                .with("dl-yyds")
                .noargs();
    }
​
​
​
    //给该队列指定死信,当队列从别的交换机接收到的消息满足死信条件时,会发给死信交换机
    @Bean("yydsQueue")
    public Queue queue(){
        return QueueBuilder
                .nonDurable("yyds")
                .deadLetterExchange("dlx.direct")   //指定死信交换机
                .deadLetterRoutingKey("dl-yyds")   //指定死信RoutingKey
                .build();
    }
​
    // 普通的交换机
    @Bean("directExchange")
    public Exchange exchange(){
        return ExchangeBuilder.directExchange("amq.direct").build();
    }
​
    @Bean("binding")
    public Binding binding(@Qualifier("directExchange") Exchange exchange,
                           @Qualifier("yydsQueue") Queue queue){
        //将我们刚刚定义的交换机和队列进行绑定
        return BindingBuilder
                .bind(queue)   //绑定队列
                .to(exchange)  //到交换机
                .with("my-yyds")   //使用自定义的routingKey
                .noargs();
    }
}

6.2.3、实践2

队列一旦被创建,想给他修改配置的话,不能通过修改代码再次创建的方式来实现。

6.2.3.1、config

先创建一个死信队列,然后给 ttl队列绑定死信队列。

@Configuration
public class DeadRabbitMQConfig {
    private String exchangeName = "dead_direct_exchange";
    // 1、声明注册 direct模式的交换机
    @Bean
    public DirectExchange deadExchange() {
        return new DirectExchange(exchangeName, true, false);
    }
    // 2、声明队列
    @Bean
    public Queue deadQueue() {
        return new Queue("dead_direct_queue", true);
    }
    // 3、交换机绑定队列
    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(deadQueue()).to(deadExchange()).with("dead");
    }
}
@Configuration
public class TtlRabbitMQConfig {
  private String exchangeName = "ttl_direct_exchange";
  // 1、声明注册 direct模式的交换机
  @Bean
  public DirectExchange ttlExchange() {
      return new DirectExchange(exchangeName, true, false);
  }
  // 2、声明队列
  @Bean
  public Queue ttlQueue() {
      // 设置过期时间
      Map<String, Object> args = new HashMap<>();
      // 单位是 ms
      args.put("x-message-ttl", 5000);
      args.put("x-dead-letter-exchange", "dead_direct_exchange");
      // 因为死信队列有 key,所以让他接盘的时候也要指定 key
      args.put("x-dead-letter-routing-key", "dead");
      return new Queue("ttl_direct_queue", true, false, false, args);
  }
  // 3、交换机绑定队列
  @Bean
  public Binding ttlBinding() {
      return BindingBuilder.bind(ttlQueue()).to(ttlExchange()).with("ttl");
  }
}

6.3、内存磁盘的监控、内存换页

参考帮助文档:Configuration | RabbitMQ

6.3.1、简介

持久化就是从 rabbitmq服务端的内存memory,写入到 rabbitmq服务端的磁盘 disk space。

当内存使用超过配置的阈值或者磁盘空间剩余空间配置的阈值时,RabbitMQ会暂时阻塞客户端的连接,并且停止接收从客户端发来的消息,以此避免服务器的崩溃,客户端与服务端的心态检测机制也会失效。

当磁盘的剩余空间低于确定的阈值时,RabbitMQ同样会阻塞生产者,这样可以避免因非持久化的消息持续换页而耗尽磁盘空间导致服务器崩溃。

默认情况下,磁盘预警为 50MB的时候会进行预警。表示当前磁盘空间剩下 50MB的时候会阻塞生产者并且停止内存消息换页到磁盘的过程。

这个阈值可以减小,但是不能完全的消除因磁盘耗尽而导致崩溃的可能性。比如在两次磁盘空间的检查空隙内,第一次检查是:60MB ,第二检查可能就是1MB,就会出现警告。

6.3.2、修改内存

fraction/value 为内存阈值。默认情况是:0.4/2GB,代表的含义是:当RabbitMQ的内存超过40%时,就会产生警告并且阻塞所有生产者的连接。通过此命令修改阈值在Broker重启以后将会失效,通过修改配置文件方式设置的阈值则不会随着重启而消失,但修改了配置文件一样要重启broker才会生效。

6.3.2.1、命令的方式

第一条是配置百分比,第二条是配置固定大小,二者选其一。

rabbitmqctl set_vm_memory_high_watermark <fraction>
rabbitmqctl set_vm_memory_high_watermark absolute 50MB
6.3.2.2、配置文件方式 rabbitmq.conf

/etc/rabbitmq/rabbitmq.conf

# vm_memory_high_watermark.relative = 0.4
# 使用relative相对值进行设置fraction,建议取值在04~0.7之间,不建议超过0.7.
vm_memory_high_watermark.relative = 0.6
​
# 使用absolute的绝对值的方式,但是是 KB,MB,GB 对应的命令如下
vm_memory_high_watermark.absolute = 2GB

6.3.3、内存换页

在某个 Broker节点及内存阻塞生产者之前,它会尝试将队列中的消息换页到磁盘,以释放内存空间,持久化和非持久化的消息都会写入磁盘中,其中持久化的消息本身就在磁盘中有一个副本,所以在转移的过程中持久化的消息会先从内存中清除掉。

默认情况下,内存到达的阈值是 50%时就会换页处理。也就是说,在默认情况下该内存的阈值是 0.4的情况下,当内存超过 0.4*0.5=0.2时,就会进行换页动作。

比如有 1000MB内存,当内存的达到 400MB,已经达到了极限,但是因为配置的换页内存是 0.5,这个时候会在达到极限 400mb之前,会把内存中的 200MB进行转移到磁盘中。从而达到稳健的运行。

可以通过设置 vm_memory_high_watermark_paging_ratio 来进行调整

vm_memory_high_watermark.relative = 0.4
vm_memory_high_watermark_paging_ratio = 0.7(设置小于1的值)

为什么设置小于1,因为你如果你设置为 1的阈值。内存都已经达到了极限了。你在去换页意义不是很大了。

7、集群

7.1、单机搭建集群实践1 - Linux下

可以在控制台中看见集群节点。

7.1.1、搭建步骤

// 一下所有命令都在服务器2 中进行
// 先关闭 RabbitMQ
sudo rabbitmqctl stop_app
​
// 然后在 hosts文件中配置服务器1 的映射
sudo vim /etc/hosts
// 加入这行代码 IP地址 ubuntu-server
192.168.0.9  ubuntu-serverz
​
// 因为我们要确保 /var/lib/rabbitmq/.erlang.cookie 一致,所以要修改。
// 但是因为没有权限修改,所以要先获得权限
sudo chmod 777 /var/lib/rabbitmq/.erlang.cookie
​
// 获得主机中的 cookie文件内容,然后让服务器2 复制一份
sudo cat /var/lib/rabbitmq/.erlang.cookie
​
// 编辑这个 cookie文件,吧刚刚从服务器1 中复制到的 cookie内容放进去。然后再把权限改回去
sudo vim /var/lib/rabbitmq/.erlang.cookie
sudo chmod 400 /var/lib/rabbitmq/.erlang.cookie
​
// 重启然后再关闭 RabbitMQ。应该是为了让 cookie文件生效吧
sudo systemctl restart rabbitmq-server.service
sudo rabbitmqctl stop_app
​
// 进行 join,搭建集群
sudo rabbitmqctl join_cluster rabbit@ubuntu-server
// 启动 RabbitMQ
sudo rabbitmqctl start_app

7.1.2、添加数据备份政策

如果不添加政策,那么服务器2 只能看到服务器1 中的数据信息,而不会备份,一旦服务器1 挂了,之前能在服务器2 中看到的队列等数据就都无法访问了。在服务器2 的控制台中操作。

7.2、单机多实例搭建集群实践2 - Linux下

主从间信息复制共享。主节点挂了,那么队列都会停掉,但是信息会完整保留在从节点,然后当主节点再次开启,就会恢复正常。

当项目连接的是集群的时候,就不能使用以前连接单个 rabbitmq 的配置方式了,需要使用 addresses: IP地址:端口号。

如果采用多机部署方式,需读取其中一个节点的 cookie, 并复制到其他节点(节点之间通过cookie 确定相互是否可通信)。cookie存放在 /var/lib/rabbitmq/.erlang.cookie。

例如:主机名分别为rabbit-1、rabbit-2

  1. 逐个启动各节点

  2. 配置各节点的hosts文件( vim /etc/hosts) ​ ip1:rabbit-1

​ ip2:rabbit-2

其它步骤雷同单机部署方式

7.2.1、确认服务器正常后关闭

通过 ps aux|grep rabbitmq 或 systemctl status rabbitmq-server 命令,判断服务是否可成功启动,然后再关闭掉

7.2.2、依次启动两节点

sudo RABBITMQ_NODE_PORT=5672 RABBITMQ_NODENAME=rabbit-1 rabbitmq-server start &
​
sudo RABBITMQ_NODE_PORT=5673 RABBITMQ_SERVER_START_ARGS="-rabbitmq_management listener [{port,15673}]" RABBITMQ_NODENAME=rabbit-2 rabbitmq-server start &

7.2.3、验证两个节点是否启动

ps aux|grep rabbitmq

7.2.4、让 rabbit-1 作为主节点

#停止应用
sudo rabbitmqctl -n rabbit-1 stop_app
​
#目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-1 reset
​
#启动应用
sudo rabbitmqctl -n rabbit-1 start_app

7.2.5、让 rabbit-2 作为从节点

# 停止应用
sudo rabbitmqctl -n rabbit-2 stop_app
​
# 目的是清除节点上的历史数据(如果不清除,无法将节点加入到集群)
sudo rabbitmqctl -n rabbit-2 reset
​
# 将rabbit2节点加入到rabbit1(主节点)集群当中
#【Server-node服务器的主机名】需要更改成自己的,即最前面 [root@xxx rabbitmq]中,xxx这一串
sudo rabbitmqctl -n rabbit-2 join_cluster rabbit-1@Server-node(需要更改)
​
# 启动应用
sudo rabbitmqctl -n rabbit-2 start_app

7.2.6、验证集群状态

sudo rabbitmqctl cluster_status -n rabbit-1

7.2.7、给15672 node-1 和15673的node-2 设置用户名和密码

注意在访问的时候:web结面的管理需要给15672 node-1 和15673的node-2 设置用户名和密码。

rabbitmqctl -n rabbit-1 add_user admin admin
rabbitmqctl -n rabbit-1 set_user_tags admin administrator
rabbitmqctl -n rabbit-1 set_permissions -p / admin ".*" ".*" ".*"
​
​
​
rabbitmqctl -n rabbit-2 add_user admin admin
rabbitmqctl -n rabbit-2 set_user_tags admin administrator
rabbitmqctl -n rabbit-2 set_permissions -p / admin ".*" ".*" ".*"

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值