Rabbit MQ实战

Rabbit MQ实战

消息队列是一种应用之间的通信方式,消息发送后可以立即返回,由消息系统保证消息的可靠传递。

1. 什么是消息队列

消息(Message)是指应用间传送的数据。消息可以非常简单,比如只包含文本字符串,也可以很复杂,可以嵌入对象。

消息队列是一种应用之间的通信方式,消息发送后可以立即返回,由消息系统保证消息的可靠传递。消息发布者只管把消息发布到MQ中而不管是谁来取,消息使用者只管从消息队列中取消息而不管是谁发布的。这样发布者和使用者都不知道对方的存在。

2. 为什么使用消息队列

  • 接口响应时间过长,可以通过消息队列异步操作提高接口响应。业务解耦。
  • 同时接收大规模请求,可以使用消息队列错峰流控。削峰限流。
  • 还可以用于最终一致性、广播。

3. Rabbit MQ简介

  • 由Enlang语言开发的AMQP的开源实现。
  • AMQP : Advanced Message Queue高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。
  • RabbitMQ最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:

特点

  1. 可靠性 通过持久化、传输确认、发布确认等机制保证可靠性
  2. 灵活的路由 消息进入队列之前,通过Exchange来路由消息。对于典型的路由功能,RabbitMQ 提供了一些内置的Exchange绑定在一起,也通过插件机制实现自己的Exchange
  3. 消息集群 提供高可用服务,避免单点故障
  4. 高可用 集群提供镜像模式
  5. 多协议 支持多种消息队列协议,比如STOMP、MQTT、AMQP等等
  6. 多语言客户端 几乎支持所有常用的语言,比如Java 、.net
  7. 管理界面 简单易用的用户界面,使用户可以监控和管理消息Broker的许多方面
  8. 跟踪机制 消息跟踪机制,可以记录异常信息

4. 消息分发策略对比

Active MQRabbit MQKafkaRocket MQ
发布订阅支持支持支持支持
轮询分发支持支持支持/
公平分发/支持支持/
重发支持支持/支持
消息拉去/支持支持支持

4. 安装Rabbit MQ

4.1 安装卸载

# 添加erlang solutions源
wget https://packages.erlang-solutions.com/erlang-solutions-2.0-1.noarch.rpm
# 也可直接从官网下载
sudo rpm -Uvh erlang-solutions-2.0-1.noarch.rpm
yum install -y erlang

--------------------------------------------------------------------------------
# 直接安装对应版本 
[root@localhost otp_src_22.1]# mv otp_src_22.1.tar.gz /usr/local
[root@localhost otp_src_22.1]# cd /usr/local
[root@localhost local]# tar -zxvf otp_src_22.1.tar.gz 
[root@localhost local]# mv otp_src_22.1 erlang
[root@localhost local]# cd erlang
[root@localhost erlang]# ./configure --prefix=/usr/local/erlang
[root@localhost erlang]# make & make install
#设置环境变量(按a输入,按esc  输入:wq保存,q不保存)
vim /etc/profile
#加入一条:
export PATH=$JAVA_HOME/bin/:$PATH:/usr/local/erlang/otp_src_22.1/bin:$PATH
#让配置生效
source /etc/profile
---------------------------------------------------------------------------
#查看erlang是否安装成功
erl
# 安装rabbit mq 注意版本对应
yum install -y socat
rpm -Uvh rabbitmq-server-3.8.16-1.el7.noarch.rpm 
yum install -y rabbitmq-server 

----------------------------------------------------------------
# 删除
# 查看rabbitmq安装的相关列表
$ yum list | grep rabbitmq
# 卸载rabbitmq已安装的相关内容
$ yum -y remove rabbitmq-server.noarch

# 查看erlang安装的相关列表
$ yum list | grep erlang
# 卸载erlang已安装的相关内容
$ yum -y remove erlang-*
$ yum remove erlang.x86_64(当卸载不干净时执行这个)

# 部分相关文件夹大家看着删除,我就是记录一下
$ rm -rf /usr/lib64/erlang 
$ rm -rf /var/lib/rabbitmq
$ rm -rf /usr/local/erlang
$ rm -rf /usr/local/rabbitmq

4.2 启动rabbitmq

# 启动rabbitmq
[root@slave01 opt]# systemctl start rabbitmq-server
[root@slave01 opt]# systemctl status rabbitmq-server.service
● rabbitmq-server.service - RabbitMQ broker
   Loaded: loaded (/usr/lib/systemd/system/rabbitmq-server.service; disabled; vendor preset: disabled)
   Active: active (running) since 二 2021-05-11 21:33:15 CST; 1min 45s ago
 Main PID: 8128 (beam.smp)
   Status: "Initialized"
   CGroup: /system.slice/rabbitmq-server.service
           ├─8128 /usr/lib64/erlang/erts-11.2/bin/beam.smp -W w -MBas ageffcbf -MHas ageffcbf -MBlmbcs 5...
           ├─8143 erl_child_setup 32768
           ├─8167 /usr/lib64/erlang/erts-11.2/bin/epmd -daemon
           ├─8187 inet_gethost 4
           └─8221 inet_gethost 4

5月 11 21:32:41 slave01 rabbitmq-server[8128]: ##########  Licensed under the MPL 2.0. Website: http...com
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Doc guides: https://rabbitmq.com/documentation.html
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Support:    https://rabbitmq.com/contact.html
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Tutorials:  https://rabbitmq.com/getstarted.html
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Monitoring: https://rabbitmq.com/monitoring.html
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Logs: /var/log/rabbitmq/rabbit@slave01.log
5月 11 21:32:41 slave01 rabbitmq-server[8128]: /var/log/rabbitmq/rabbit@slave01_upgrade.log
5月 11 21:32:41 slave01 rabbitmq-server[8128]: Config file(s): (none)
5月 11 21:33:15 slave01 rabbitmq-server[8128]: Starting broker... completed with 0 plugins.
5月 11 21:33:15 slave01 systemd[1]: Started RabbitMQ broker.
Hint: Some lines were ellipsized, use -l to show in full.
[root@slave01 opt]# systemctl enable rabbitmq-server
Created symlink from /etc/systemd/system/multi-user.target.wants/rabbitmq-server.service to /usr/lib/systemd/system/rabbitmq-server.service.

4.3 配置图形化界面

# 配置图形化界面
rabbitmq-plugins enable rabbitmq_management
# 出现问题
{:query, :rabbit@slave01, {:badrpc, :timeout}}
# 需要设置主机
vi /etc/hosts
# 先通过hostname查到自己的主机名
127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 slave01
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.169.129 slave01(对应的主机名称)

# 安装成功
Enabling plugins on node rabbit@slave01:
rabbitmq_management
The following plugins have been configured:
  rabbitmq_management
  rabbitmq_management_agent
  rabbitmq_web_dispatch
Applying plugin configuration to rabbit@slave01...
Plugin configuration unchanged.
# 可以访问 ip:15672  账号:gust 密码:gust 远程访问需要授权
[root@slave01 rabbitmq]# rabbitmqctl add_user admin admin
Adding user "admin" ...
Done. Don't forget to grant the user permissions to some virtual hosts! See 'rabbitmqctl help set_permissions' to learn more.
[root@slave01 rabbitmq]# rabbitmqctl set_user_tags admin administrator
Setting tags for user "admin" to [administrator] ...

# 授予权限
rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

4.4 docker 安装

# docker 安装rabbit mq
[root@master opt]# docker -v
Docker version 20.10.6, build 370c289
# 拉取镜像
[root@master opt]# docker pull rabbitmq:management

[root@master opt]# docker run -di --name myrabbit -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 -p 25672:25672 -p 61613:61613 -p 1883:1883 rabbitmq:management 
a45bac2ef4cefcad3bca364eb1682bdeb6e4eb04bb47ec5bfd9e9f42b68e759f
# 查看
docker ps -a
# 启动
docker start a45bac2ef4ce

5 Rabbit MQ 用户权限

none、management、policymaker、monitoring、administrator

RabbitMQ各类角色描述:

  1. none
    • 不能访问 management plugin
  2. management
    • 用户可以通过AMQP做的任何事外加:
    • 列出自己可以通过AMQP登入的virtual hosts
    • 查看自己的virtual hosts中的queues, exchanges 和 bindings
    • 查看和关闭自己的channels 和 connections
    • 查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。
  3. policymaker
    • management可以做的任何事外加:
    • 查看、创建和删除自己的virtual hosts所属的policies和parameters
  4. monitoring
    • management可以做的任何事外加:
    • 列出所有virtual hosts,包括他们不能登录的virtual hosts
    • 查看其他用户的connections和channels
    • 查看节点级别的数据如clustering和memory使用情况
    • 查看真正的关于所有virtual hosts的全局的统计信息
  5. administrator
    • policymaker和monitoring可以做的任何事外加:
    • 创建和删除virtual hosts
    • 查看、创建和删除users
    • 查看创建和删除permissions
    • 关闭其他用户的connections

6 简单的生产者案例

package com.zhj.rabbitmq.simple;

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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeoutException;

/**
 * @author zhj
 */
public class Producer {
    public static void main(String[] args) {
        // 基于 amqp 协议

        // 1. 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.169.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2. 创建连接对象
            connection = connectionFactory.newConnection("生产者");
            // 3. 获取通道
            channel = connection.createChannel();
            // 4. 通过通道创建交换机,声明队列,绑定关系,路由key,发送消息和接收消息
            String queueName01 = "queue01";
            /**
             * @params1 队列名称
             * @params2 是否需要持久化
             * @params3 排他性,是否独占
             * @params4 是否自动删除
             * @params5 附带参数
             */
            channel.queueDeclare(queueName01, false, false, false, null);
            // 5. 准备消息内容
            String message = "hello rabbit mq";
            // 6. 发送消息给队列queue
            channel.basicPublish("", queueName01, null, message.getBytes(StandardCharsets.UTF_8));
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭连接
            if (channel != null && channel.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 8. 关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
// ------------------------------------------------------------------------

package com.zhj.rabbitmq.simple;

import com.rabbitmq.client.*;

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

/**
 * @author zhj
 */
public class Consumer {
    public static void main(String[] args) {
        // 基于 amqp 协议

        // 1. 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        connectionFactory.setHost("192.168.169.131");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
            // 2. 创建连接对象
            connection = connectionFactory.newConnection("生产者");
            // 3. 获取通道
            channel = connection.createChannel();
            // 4. 通过通道创建交换机,声明队列,绑定关系,路由key,发送消息和接收消息
            String queueName01 = "queue01";
            channel.basicConsume(queueName01, 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("接收消息失败!");
                }
            });
            // 5. 阻断
            System.out.println("开始接收消息");
            System.in.read();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            // 7. 关闭连接
            if (channel != null && channel.isOpen()) {
                try {
                    connection.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 8. 关闭通道
            if (channel != null && channel.isOpen()) {
                try {
                    channel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                } catch (TimeoutException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

7. AMQP 协议

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

工作过程

  • 发布者(Publisher)发布消息(Message),经由交换机(Exchange)。
  • 交换机根据路由规则将收到的消息分发给与该交换机绑定的队列(Queue)。
  • 最后 AMQP 代理会将消息投递给订阅了此队列的消费者,或者消费者按照需求自行获取。

深入理解

  1. 发布者、交换机、队列、消费者都可以有多个。同时因为 AMQP 是一个网络协议,所以这个过程中的发布者,消费者,消息代理 可以分别存在于不同的设备上。
  2. 发布者发布消息时可以给消息指定各种消息属性(Message Meta-data)。有些属性有可能会被消息代理(Brokers)使用,然而其他的属性则是完全不透明的,它们只能被接收消息的应用所使用。
  3. 从安全角度考虑,网络是不可靠的,又或是消费者在处理消息的过程中意外挂掉,这样没有处理成功的消息就会丢失。基于此原因,AMQP 模块包含了一个消息确认(Message Acknowledgements)机制:当一个消息从队列中投递给消费者后,不会立即从队列中删除,直到它收到来自消费者的确认回执(Acknowledgement)后,才完全从队列中删除。
  4. 在某些情况下,例如当一个消息无法被成功路由时(无法从交换机分发到队列),消息或许会被返回给发布者并被丢弃。或者,如果消息代理执行了延期操作,消息会被放入一个所谓的死信队列中。此时,消息发布者可以选择某些参数来处理这些特殊情况。

8. 核心内容

在这里插入图片描述

8.1核心概念


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

8.2 整体架构


  • 在这里插入图片描述

8.3 运行流程


在这里插入图片描述

8.4 支持的消息模式


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

  • 简单模式Simple

  • 工作模式Work 公平模式(根据服务器性能读取 【basicQos 一次读多少条】)

  • 发布订阅模式 fanout

    发送给所有订阅的队列 没有路由key 是一种广播机制

  • 路由模式 routing (direct) 有routing - key

  • 主题模式 Topic

    模糊匹配 符号“#”匹配路由键的一个或多个词或没有,符号“*”匹配路由键的一个词

  • 参数模式

9. 使用场景

  • 解耦
  • 异步
  • 削峰
  • 分布式事务
  • 索引、缓存、静态化处理的数据同步
  • 流量监控
  • 日志监控(ELK)
  • 下单、订单分发、抢票

10. SpringBoot 整合 Rabbit MQ

导入依赖

		<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>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit-test</artifactId>
            <scope>test</scope>
        </dependency>

配置

server:
  port: 8080

# 配置rabbit mq
spring:
  rabbitmq:
    username: admin
    password: admin
    virtual-host: /
    host: 192.168.169.131
    port: 5672

config

/**
 * @author zhj
 */
@Configuration
public class RabbitMqConfiguration {
    // 1. 声明fanout模式交换机
    @Bean
    public FanoutExchange fanoutExchange() {
        return new FanoutExchange("fanout_order_exchange",true,false);
    }
    // 2. 声明队列 sms email wechat
    @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());
    }
}
// 消息发送
String exchangeName = "fanout_order_exchange";
String routingKey = "";
rabbitTemplate.convertAndSend(exchangeName,routingKey,"发送信息");
@Service
@RabbitListener(queues = "email.fanout.queue")
public class FanoutEmailService {
    @RabbitHandler
    public void receiveMessage(String message) {
        System.out.println("邮件接收到订单信息:" + message);
    }
}

11. 过期时间TTL

给队列设置过期时间,超过时间会转移消息(过期队列可以到死信队列),默认会直接删除

消息过期时间和过期队列同时存在,以时间短的为主

// 过期队列
@Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    @Bean
    public Queue ttlDirectQueue() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000);
        return new Queue("ttl.direct.queue",true,false,false,args);
    }
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
    }
// 消息过期时间
// 消息发送
        String exchangeName = "ttl_direct_exchange";
        String routingKey = "ttl-message";
        MessagePostProcessor messagePostProcessor = new MessagePostProcessor() {
            @Override
            public Message postProcessMessage(Message message) throws AmqpException {
                message.getMessageProperties().setExpiration("8000");
                message.getMessageProperties().setContentEncoding("UTF-8");
                return message;
            }
        };
        rabbitTemplate.convertAndSend(exchangeName,routingKey,order.getId(),messagePostProcessor);

12. 死信队列

消息拒绝,消息过期等会转移如死信队列

// 消息过期移入死信队列
@Configuration
public class TTLRabbitMqConfiguration {
    @Bean
    public DirectExchange ttlDirectExchange() {
        return new DirectExchange("ttl_direct_exchange",true,false);
    }
    @Bean
    public Queue ttlDirectQueue() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-message-ttl", 5000);
        // 长度限制
        args.put("x-max-length", 5);
        args.put("x-dead-letter-exchange","dead_direct_exchange");
        args.put("x-dead-letter-routing-key","dead");
        return new Queue("ttl.direct.queue",true,false,false,args);
    }
    @Bean
    public Queue ttlMessageDirectQueue() {
        return new Queue("ttl.message.direct.queue",true);
    }
    @Bean
    public Binding ttlBinding() {
        return BindingBuilder.bind(ttlDirectQueue()).to(ttlDirectExchange()).with("ttl");
    }

    @Bean
    public Binding ttlMessageBinding() {
        return BindingBuilder.bind(ttlMessageDirectQueue()).to(ttlDirectExchange()).with("ttl-message");
    }
}

// 死信队列
@Configuration
public class DeadRabbitMqConfiguration {
    @Bean
    public DirectExchange deadDirectExchange() {
        return new DirectExchange("dead_direct_exchange",true,false);
    }
    @Bean
    public Queue deadDirectQueue() {
        return new Queue("dead.direct.queue",true);
    }
    @Bean
    public Binding deadBinding() {
        return BindingBuilder.bind(deadDirectQueue()).to(deadDirectExchange()).with("dead");
    }
}

13. 集群搭建

首先需要架设几台服务器做集群使用,初始环境配置各个节点的主机名,与id映射 注意开防防火墙

# vim /etc/hostname 修改主机名
# vim /etc/hosts

127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 slave01
::1         localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.169.131 master
192.168.169.132 slave01
192.168.169.133 slave03
192.168.169.134 slave04
192.168.169.135 slave05
192.168.169.136 slave06

13.1 设置elang cookie

将 rabbit-slave01 上的 .erlang.cookie 文件拷贝到其他两台主机上。该 cookie 文件相当于密钥令牌,集群中的 RabbitMQ 节点需要通过交换密钥令牌以获得相互认证,因此处于同一集群的所有节点需要具有相同的密钥令牌,否则在搭建过程中会出现 Authentication Fail 错误。

RabbitMQ 服务启动时,erlang VM 会自动创建该 cookie 文件,默认的存储路径为 /var/lib/rabbitmq/.erlang.cookie 或 $HOME/.erlang.cookie,该文件是一个隐藏文件,需要使用 ls -al 命令查看。(拷贝.cookie时,各节点都必须停止MQ服务):

停止所有服务,构建Erlang的集群环境

systemctl stop rabbitmq-server
scp /var/lib/rabbitmq/.erlang.cookie root@slave02:/var/lib/rabbitmq/
scp /var/lib/rabbitmq/.erlang.cookie root@slave03:/var/lib/rabbitmq/

由于你可能在三台主机上使用不同的账户进行操作,为避免后面出现权限不足的问题,这里建议将 cookie 文件原来的 400 权限改为 600,命令如下:

 chmod 600 /var/lib/rabbitmq/.erlang.cookie

注:cookie 中的内容就是一行随机字符串,可以使用 cat 命令查看。

13.2 启动服务

在三台主机上均执行以下命令,启动 RabbitMQ 服务:

systemctl start rabbitmq-server

开通 EPMD 端口

epmd进程使用的端口。用于RabbitMQ节点和CLI工具的端点发现服务。

开启防火墙 4369 端口

firewall-cmd --zone=public --add-port=4369/tcp --permanent

重启

systemctl restart firewalld.service

13.3 集群搭建

RabbitMQ 集群的搭建需要选择其中任意一个节点为基准,将其它节点逐步加入。这里我们以 rabbit-slave01 为基准节点,将 rabbit-slave02 和 rabbit-slave03 加入集群。在 rabbit-slave02 和rabbit-slave03 上执行以下命令:

# 1.停止服务
rabbitmqctl stop_app
# 2.重置状态
rabbitmqctl reset
# 3.节点加入, 在一个node加入cluster之前,必须先停止该node的rabbitmq应用,即先执行stop_app
# rabbit-slave02加入slave01, rabbit-salave03加入slave02
rabbitmqctl join_cluster rabbit@rabbit-node1
# 4.启动服务
rabbitmqctl start_app

join_cluster 命令有一个可选的参数 --ram ,该参数代表新加入的节点是内存节点,默认是磁盘节点。如果是内存节点,则所有的队列、交换器、绑定关系、用户、访问权限和 vhost 的元数据都将存储在内存中,如果是磁盘节点,则存储在磁盘中。内存节点可以有更高的性能,但其重启后所有配置信息都会丢失,因此RabbitMQ 要求在集群中至少有一个磁盘节点,其他节点可以是内存节点。当内存节点离开集群时,它可以将变更通知到至少一个磁盘节点;然后在其重启时,再连接到磁盘节点上获取元数据信息。除非是将 RabbitMQ 用于 RPC 这种需要超低延迟的场景,否则在大多数情况下,RabbitMQ 的性能都是够用的,可以采用默认的磁盘节点的形式。这里为了演示, rabbit-slave03 我就设置为内存节点。

另外,如果节点以磁盘节点的形式加入,则需要先使用 reset 命令进行重置,然后才能加入现有群集,重置节点会删除该节点上存在的所有的历史资源和数据。采用内存节点的形式加入时可以略过 reset 这一步,因为内存上的数据本身就不是持久化的。

13.4 查看集群状态

在 rabbit-slave03 和 3 上执行以上命令后,集群就已经搭建成功,此时可以在任意节点上使用 rabbitmqctl cluster_status 命令查看集群状态,输出如下:

[root@slave01 rabbitmq]# rabbitmqctl cluster_status
可以看到 nodes 下显示了全部节点的信息,其中 rabbit-slave02 和 rabbit-slave03 上的节点都是 disc 类型,即磁盘节点;而 rabbit-slave03 上的节点为 ram,即内存节点。此时代表集群已经搭建成功,默认的 cluster_name 名字为 rabbit@slave01,如果你想进行修改,可以使用以下命令:

rabbitmqctl set_cluster_name my_rabbitmq_cluster

在这里插入图片描述

13.5 配置镜像队列

开启镜像队列
这里我们为所有队列开启镜像配置,其语法如下:

rabbitmqctl set_policy ha-all “^” ‘{“ha-mode”:“all”}’


复制系数
在上面我们指定了 ha-mode 的值为 all ,代表消息会被同步到所有节点的相同队列中。这里我们之所以这样配置,因为我们本身只有三个节点,因此复制操作的性能开销比较小。如果你的集群有很多节点,那么此时复制的性能开销就比较大,此时需要选择合适的复制系数。通常可以遵循过半写原则,即对于一个节点数为 n 的集群,只需要同步到 n/2+1 个节点上即可。此时需要同时修改镜像策略为 exactly,并指定复制系数 ha-params,示例命令如下:

rabbitmqctl set_policy ha-two “^” ‘{“ha-mode”:“exactly”,“ha-params”:2,“ha-sync-mode”:“automatic”}’
除此之外,RabbitMQ 还支持使用正则表达式来过滤需要进行镜像操作的队列,示例如下:

rabbitmqctl set_policy ha-all “^ha.” ‘{“ha-mode”:“all”}’
此时只会对 ha 开头的队列进行镜像。更多镜像队列的配置说明,可以参考官方文档:Highly Available (Mirrored) Queues


查看镜像状态
配置完成后,可以通过 Web UI 界面查看任意队列的镜像状态,情况如下:

在这里插入图片描述

13.6 集群的关闭与重启

没有一个直接的命令可以关闭整个集群,需要逐一进行关闭。但是需要保证在重启时,最后关闭的节点最先被启动。如果第一个启动的不是最后关闭的节点,那么这个节点会等待最后关闭的那个节点启动,默认进行 10 次连接尝试,超时时间为 30 秒,如果依然没有等到,则该节点启动失败。

这带来的一个问题是,假设在一个三节点的集群当中,关闭的顺序为 node1,node2,node3,如果 node1 因为故障暂时没法恢复,此时 node2 和 node3 就无法启动。想要解决这个问题,可以先将 node1 节点进行剔除,命令如下:

rabbitmqctl forget_cluster_node rabbit@slave01 --offline

此时需要加上 -offline 参数,它允许节点在自身没有启动的情况下将其他节点剔除。

重置当前节点
# 1.停止服务
rabbitmqctl stop_app
# 2.重置集群状态
rabbitmqctl reset
# 3.重启服务
rabbitmqctl start_app

重新加入集群
# 1.停止服务
rabbitmqctl stop_app
# 2.重置状态
rabbitmqctl reset
# 3.节点加入
rabbitmqctl join_cluster rabbit@rabbit-node1
# 4.重启服务
rabbitmqctl start_app

# 完成后重新检查 RabbitMQ 集群状态
rabbitmqctl cluster_status
# 除了在当前节点重置集群外,还可在集群其他正常节点将节点踢出集群
rabbitmqctl forget_cluster_node rabbit@rabbit-node3

- 变更节点类型
  我们可以将节点的类型从RAM更改为Disk,反之亦然。假设我们想要反转rabbit@rabbit-node2和rabbit@rabbit-node1的类型,将前者从RAM节点转换为磁盘节点,而后者从磁盘节点转换为RAM节点。为此,我们可以使用change_cluster_node_type命令。必须首先停止节点。

- 停止服务

  rabbitmqctl stop_app

- 变更类型 ram disc

  rabbitmqctl change_cluster_node_type disc

- 重启服务

  rabbitmqctl start_app

清除 RabbitMQ 节点配置

- 如果遇到不能正常退出直接kill进程

  systemctl stop rabbitmq-server

- 查看进程

  ps aux|grep rabbitmq

- 清楚节点rabbitmq配置

  rm -rf /var/lib/rabbitmq/mnesia

14. 分布式事务

分布式事务可以保证数据的最终一致性

14.1 出现条件

在分布式服务调用过程中,虽然双方都加了事务控制,但是只有其中一个服务出现异常,就会造成事务的单方面回滚。一个服务回滚一个服务不回滚

引入Rabbit MQ解决分布式事务

在这里插入图片描述

可靠生产和可靠消费

在这里插入图片描述

增加冗余数据 保证可靠生产在这里插入图片描述

设置消息队列确认机制

在这里插入图片描述

增加配置

在这里插入图片描述

可靠消费

在这里插入图片描述

解决消息重试的方案

  • 控制重发次数

    不要加try catch
    在这里插入图片描述

  • try+catch + 手动ack
    在这里插入图片描述在这里插入图片描述

  • try+catch + 手动ack + 死信队列 +人工干预

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值