RabbitMQ

128 阅读25分钟

什么是RabbitMQ

MQ(Message Queue)消息队列

  • 消息队列中间件,是分布式系统中的重要组件
  • 主要解决,异步处理,应用解耦,流量削峰等问题
  • 从而实现高性能,高可用,可伸缩和最终一致性的架构
  • 使用较多的消息队列产品:RabbitMQ,RocketMQ,ActiveMQ,ZeroMQ,Kafka等

异步处理

  • 用户注册后,需要发送验证邮箱和手机验证码;
  • 将注册信息写入数据库,发送验证邮件,发送手机,三个步骤全部完成后,返回给客户端

image.png

应用解耦

  • 场景:订单系统需要通知库存系统
  • 如果库存系统异常,则订单调用库存失败,导致下单失败
    • 原因:订单系统和库存系统耦合度太高

image.png

  • 订单系统:用户下单后,订单系统完成持久化处理,将消息写入消息队列,返回用户,下单成功;
  • 库存系统:订阅下单的消息,获取下单信息,库存系统根据下单信息,再进行库存操作;
  • 假如:下单的时候,库存系统不能正常运行,也不会影响下单,因为下单后,订单系统写入消息队列就不再关心其他的后续操作了,实现了订单系统和库存系统的应用解耦;
  • 所以说,消息队列是典型的:生产者消费者模型
  • 生产者不断的向消息队列中生产消息,消费者不断的从队列中获取消息
  • 因为消息的生产和消费都是异步的,而且只关心消息的发送和接收,没有业务逻辑的入侵,这样就实现了生产者和消费者的解耦

流量削峰

  • 抢购,秒杀等业务,针对高并发的场景
  • 因为流量过大,暴增会导致应用挂掉,为解决这个问题,在前端加入消息队列

image.png

  • 用户的请求,服务器接收后,首先写入消息队列,如果超过队列的长度,就抛弃,甩一个秒杀结束的页面!
  • 说白了,秒杀成功的就是进入队列的用户;

背景知识介绍

AMQP高级消息队列协议

  • 即Advanced Message Queuing Protocol,一个提供统一消息服务的应用层标准高级消息队列协议
  • 协议:数据在传输的过程中必须要遵守的规则
  • 基于此协议的客户端可以与消息中间件传递消息
  • 并不受产品、开发语言等条件的限制

JMS

  • Java Message Server,Java消息服务应用程序接口, 一种规范,和JDBC担任的角色类似
  • 是一个Java平台中关于面向消息中间件的API,用于在两个应用程序之间,或分布式系统中发送消息,进行异步通信

二者的联系

  • JMS是定义了统一接口,统一消息操作;AMQP通过协议统一数据交互格式
  • JMS必须是java语言;AMQP只是协议,与语言无关

Erlang语言

  • Erlang(['ə:læŋ])是一种通用的面向并发的编程语言,它由瑞典电信设备制造商爱立信所辖的CSLab开发,目的是创造一种可以应对大规模并发活动的编程语言和运行环境
  • 最初是由爱立信专门为通信应用设计的,比如控制交换机或者变换协议等,因此非常适合构建分布式,实时软并行计算系统
  • Erlang运行时环境是一个虚拟机,有点像Java的虚拟机,这样代码一经编译,同样可以随处运行

为什么选择RabbitMQ

  • 我们开篇说消息队列产品那么多,为什么偏偏选择RabbitMQ呢?
  • 先看命名:兔子行动非常迅速而且繁殖起来也非常疯狂,所以就把Rabbit用作这个分布式软件的命名(就是这么简单)
  • Erlang开发,AMQP的最佳搭档,安装部署简单,上手门槛低
  • 企业级消息队列,经过大量实践考验的高可靠,大量成功的应用案例,例如阿里、网易等一线大厂都有使用
  • 有强大的WEB管理页面
  • 强大的社区支持,为技术进步提供动力
  • 支持消息持久化、支持消息确认机制、灵活的任务分发机制等,支持功能非常丰富
  • 集群扩展很容易,并且可以通过增加节点实现成倍的性能提升
  • 总结:如果你希望使用一个可靠性高、功能强大、易于管理的消息队列系统那么就选择RabbitMQ,如果你想用一个性能高,但偶尔丢点数据不是很在乎可以使用kafka或者zeroMQ
  • kafka和zeroMQ的性能爆表,绝对可以压RabbitMQ一头!

RabbitMQ各组件功能

image.png

  • Broker:消息队列服务器实体
  • Virtual Host:虚拟主机
    • 标识一批交换机、消息队列和相关对象,形成的整体
    • 虚拟主机是共享相同的身份认证和加密环境的独立服务器域
    • 每个vhost本质上就是一个mini版的RabbitMQ服务器,拥有自己的队列、交换器、绑定和权限机制
    • vhost是AMQP概念的基础,RabbitMQ默认的vhost是 /,必须在链接时指定
  • Exchange:交换器(路由)
    • 用来接收生产者发送的消息并将这些消息路由给服务器中的队列
  • Queue:消息队列
    • 用来保存消息直到发送给消费者。
    • 它是消息的容器,也是消息的终点。
    • 一个消息可投入一个或多个队列。
    • 消息一直在队列里面,等待消费者连接到这个队列将其取走。
  • Banding:绑定,用于消息队列和交换机之间的关联。
  • Channel:通道(信道)
    • 多路复用连接中的一条独立的双向数据流通道。
    • 信道是建立在真实的TCP连接内的 虚拟链接
    • AMQP命令都是通过信道发出去的,不管是发布消息、订阅队列还是接收消息,都是通过信道完成的
    • 因为对于操作系统来说,建立和销毁TCP连接都是非常昂贵的开销,所以引入了信道的概念,用来复用TCP连接。
  • Connection:网络连接,比如一个TCP连接。
  • Publisher:消息的生产者,也是一个向交换器发布消息的客户端应用程序。
  • Consumer:消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。
  • Message:消息
    • 消息是不具名的,它是由消息头和消息体组成。
    • 消息体是不透明的,而消息头则是由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(优先级)、delivery-mode(消息可能需要持久性存储[消息的路由模式])等。

怎么用RabbitMQ

  • 想要安装RabbitMQ,必须先安装erlang语言环境,类似安装tomcat,必须先安装JDK
  • 查看匹配的版本:www.rabbitmq.com/which-erlan…

image.png

RabbitMQ安装启动

erlang下载:dl.bintray.com/rabbitmq-er…
socat下载:repo.iotti.biz/CentOS/7/x8…
RabbitMQ下载:www.rabbitmq.com/install-rpm…

安装

[root@localhost opt]# rpm -ivh erlang-21.3.8.16-1.el7.x86_64.rpm
[root@localhost opt]# rpm -ivh socat-1.7.3.2-5.el7.lux.x86_64.rpm
[root@localhost opt]# rpm -ivh rabbitmq-server-3.8.6-1.el7.noarch.rpm

启动后台管理插件

[root@localhost opt]# rabbitmq-plugins enable rabbitmq_management

启动RabbitMQ

[root@localhost opt]# systemctl start rabbitmq-server.service
[root@localhost opt]# systemctl status rabbitmq-server.service
[root@localhost opt]# systemctl restart rabbitmq-server.service
[root@localhost opt]# systemctl stop rabbitmq-server.service

查看进程

[root@localhost opt]# ps -ef | grep rabbitmq

测试

    1. 关闭防火墙: systemctl stop firewalld
    1. 浏览器输入:http://ip:15672
    1. 默认帐号密码:guest,guest用户默认不允许远程连接
      1. 创建账号
    [root@localhost opt]# rabbitmqctl add_user laosun 123456
    
      1. 设置用户角色
    [root@localhost opt]# rabbitmqctl set_user_tags laosun administrator
    
      1. 设置用户权限
    [root@localhost opt]# rabbitmqctl set_permissions -p "/" laosun ".*"".*" ".*"
    
      1. 查看当前用户和角色
    [root@localhost opt]# rabbitmqctl list_users
    
      1. 查看当前用户和角色
    [root@localhost opt]# rabbitmqctl change_password laosun 123123
    

image.png

管理界面介绍

  • overview:概览
  • connections:查看链接情况
  • channels:信道(通道)情况
  • Exchanges:交换机(路由)情况,默认4类7个
  • Queues:消息队列情况
  • Admin:管理员列表
  • 端口:
    • 5672:RabbitMQ提供给编程语言客户端链接的端口
    • 15672:RabbitMQ管理界面的端口
    • 25672:RabbitMQ集群的端口

RabbitMQ快速入门

依赖

<dependencies>
    <dependency>
        <groupId>com.rabbitmq</groupId>
        <artifactId>amqp-client</artifactId>
        <version>5.7.3</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.25</version>
        <scope>compile</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
        <version>3.9</version>
    </dependency>
</dependencies>

日志依赖log4j(可选项)

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %m%n

log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=rebbitmq.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %m%n

log4j.rootLogger=debug, stdout,file

创建连接

  • 先创建好虚拟主机

image.png

package util;

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

/**
* @BelongsProject: lagou-rabbitmq
* @Author: GuoAn.Sun
* @Description: 专门与RabbitMQ获得连接
*/
public class ConnectionUtil {
    public static Connection getConnection() throws Exception{
        //1.创建连接工厂
        ConnectionFactory factory = new ConnectionFactory();
        //2.在工厂对象中设置MQ的连接信息(ip,port,vhost,username,password)
        factory.setHost("192.168.204.141");
        factory.setPort(5672);
        factory.setVirtualHost("/lagou");
        factory.setUsername("laosun");
        factory.setPassword("123123");
        //3.通过工厂获得与MQ的连接
        Connection connection = factory.newConnection();
        return connection;
    }

    public static void main(String[] args) throws Exception{
        Connection connection = getConnection();
        System.out.println("connection = " + connection);
        connection.close();;
    }
}

RabbitMQ模式

  • RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此我们只学习前5种
  • 在线手册:www.rabbitmq.com/getstarted.…

image.png

  • 5种消息模型,大体分为两类:
    • 1和2属于点对点
    • 3、4、5属于发布订阅模式(一对多)
  • 点对点模式:P2P(point to point)模式包含三个角色:
    • 消息队列(queue),发送者(sender),接收者(receiver)
    • 每个消息发送到一个特定的队列中,接收者从中获得消息
    • 队列中保留这些消息,直到他们被消费或超时
    • 特点:
      1. 每个消息只有一个消费者,一旦消费,消息就不在队列中了
      2. 发送者和接收者之间没有依赖性,发送者发送完成,不管接收者是否运行,都不会影响消息发送到队列中(我给你发微信,不管你看不看手机,反正我发完了)
      3. 接收者成功接收消息之后需向对象应答成功(确认)
    • 如果希望发送的每个消息都会被成功处理,那需要P2P
  • 发布订阅模式:publish(Pub)/subscribe(Sub)
    • pub/sub模式包含三个角色:交换机(exchange),发布者(publisher),订阅者(subcriber)
    • 多个发布者将消息发送交换机,系统将这些消息传递给多个订阅者
    • 特点:
      1. 每个消息可以有多个订阅者
      2. 发布者和订阅者之间在时间上有依赖,对于某个交换机的订阅者,必须创建一个订阅后,才能消费发布者的消息
      3. 为了消费消息,订阅者必须保持运行状态;类似于,看电视直播。
    • 如果希望发送的消息被多个消费者处理,可采用本模式

简单模式

工作队列模式

发布订阅模式

路由模式

通配符模式

持久化

  • 消息的可靠性是RabbitMQ的一大特色,那么RabbitMQ是如何避免消息丢失?
    • 消费者的ACK确认机制,可以防止消费者丢失消息
    • 万一在消费者消费之前,RabbitMQ服务器宕机了,那消息也会丢失
  • 想要将消息持久化,那么 路由和队列都要持久化 才可以

Spring整合RabbitMQ

  • 五种消息模型,在企业中应用最广泛的就是最后一种:定向匹配topic
  • Spring AMQP 是基于 Spring 框架的AMQP消息解决方案,提供模板化的发送和接收消息的抽象层,提供基于消息驱动的 POJO的消息监听等,简化了我们对于RabbitMQ相关程序的开发。

生产端工程

  • 依赖
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.0.1.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
    <scope>compile</scope>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.9</version>
</dependency>
  • spring-rabbitmq-producer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

    <!-- 1.配置连接 -->
    <rabbit:connection-factory
            id="connectionFactory"
            host="192.168.204.141"
            port="5672"
            username="laosun"
            password="123123"
            virtual-host="/lagou"
    />
    <!-- 2.配置队列 -->
    <rabbit:queue name="test_spring_queue_1"/>
    <!-- 3.配置rabbitAdmin:主要用于在Java代码中对理队和队列进行管理,用于创建、绑定、删除队列与交换机,发送消息等 -->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!-- 4.配置topic类型exchange;队列绑定到交换机 -->
    <rabbit:topic-exchange name="spring_topic_exchange">
        <rabbit:bindings>
            <rabbit:binding queue="test_spring_queue_1" pattern="msg.#"/>
        </rabbit:bindings>
    </rabbit:topic-exchange>
    <!-- 5. 配置消息对象json转换类 -->
    <bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
    <!-- 6. 配置RabbitTemplate(消息生产者) -->
    <rabbit:template id="rabbitTemplate"
                     connection-factory="connectionFactory"
                     exchange="spring_topic_exchange"
                     message-converter="jsonMessageConverter"
    />

</beans>
  • 发消息
public class Sender {
    public static void main(String[] args) {
        // 1.创建spring容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/spring-rabbitmq-producer.xml");
        // 2.从容器中获取对象
        RabbitTemplate template = context.getBean(RabbitTemplate.class);
        // 3.发送消息
        Map<String, String> map = new HashMap();
        map.put("name", "大佬孙");
        map.put("email", "19998539@qq.com");
        template.convertAndSend("msg.user", map);
        context.close();
    }
}

消费端工程

  • 依赖与生产者一致
  • spring-rabbitmq-consumer.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/rabbit
       http://www.springframework.org/schema/rabbit/spring-rabbit.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 1. 配置连接 -->
    <rabbit:connection-factory
            id="connectionFactory"
            host="192.168.204.141"
            port="5672"
            username="laosun"
            password="123123"
            virtual-host="/lagou"
    />

    <!-- 2. 配置队列 -->
    <rabbit:queue name="test_spring_queue_1"/>
    <!-- 3.配置rabbitAdmin -->
    <rabbit:admin connection-factory="connectionFactory"/>
    <!-- 4.springIOC注解扫描包-->
    <context:component-scan base-package="listener"/>
    <!-- 5.配置监听 -->
    <rabbit:listener-container connection-factory="connectionFactory">
        <rabbit:listener ref="consumerListener" queue-names="test_spring_queue_1" />
    </rabbit:listener-container>
</beans>
  • 消费者
    • MessageListener接口用于spring容器接收到消息后处理消息
    • 如果需要使用自己定义的类型 来实现 处理消息时,必须实现该接口,并重写onMessage()方法
    • 当spring容器接收消息后,会自动交由onMessage进行处理
@Component
public class ConsumerListener implements MessageListener {

    // jackson提供序列化和反序列中使用最多的类,用来转换json的
    private static final ObjectMapper MAPPER = new ObjectMapper();
    public void onMessage(Message message) {
        try {
            // 将message对象转换成json
            JsonNode jsonNode = MAPPER.readTree(message.getBody());
            String name = jsonNode.get("name").asText();
            String email = jsonNode.get("email").asText();
            System.out.println("从队列中获取:【"+name+"的邮箱是:"+email+"】");
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
  • 启动项目
public class TestRunner {
    public static void main(String[] args) throws Exception {
        // 获得容器
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/spring-rabbitmq-consumer.xml");
        // 让程序一直运行,别终止
        System.in.read();
    }
}

消息成功确认机制

在实际场景下,有的生产者发送的消息是必须保证成功发送到消息队列中,那么如何保证成功投递呢?

  • 事务机制
  • 发布确认机制

事务机制

  • AMQP协议提供的一种保证消息成功投递的方式,通过信道开启 transactional 模式
  • 并利用信道 的三个方法来实现以事务方式 发送消息,若发送失败,通过异常处理回滚事务,确保消息成功投递
    • channel.txSelect(): 开启事务
    • channel.txCommit() :提交事务
    • channel.txRollback() :回滚事务
  • Spring已经对上面三个方法进行了封装,所以我们只能使用原始的代码演示

Confirm发布确认机制

  • RabbitMQ为了保证消息的成功投递,采用通过AMQP协议层面为我们提供事务机制的方案,但是采用事务会大大降低消息的吞吐量
  • 本机SSD硬盘测试结果10w条消息未开启事务,大约8s发送完毕;而开启了事务后,需要将近310s,差了30多倍。
  • 那么有没有更加高效的解决方式呢?答案就是采用Confirm模式。
  • 事务效率为什么会这么低呢?试想一下:10条消息,前9条成功,如果第10条失败,那么9条消息要全部撤销回滚。太太太浪费
  • 而confirm模式则采用补发第10条的措施来完成10条消息的送达

消费端限流

  • 在沙漠中行走,3天不喝水,突然喝水,如果使劲喝,容易猝死,要一口一口慢慢喝
  • 我们 Rabbitmq 服务器积压了成千上万条未处理的消息,然后随便打开一个消费者客户端,就会出现这样的情况: 巨量的消息瞬间全部喷涌推送过来,但是单个客户端无法同时处理这么多数据,就会被压垮崩溃
  • 所以,当数据量特别大的时候,我们对生产端限流肯定是不科学的,因为有时候并发量就是特别大,有时候并发量又特别少,这是用户的行为,我们是无法约束的
  • 所以我们应该对消费端限流,用于保持消费端的稳定
  • 例如:汽车企业不停的生产汽车,4S店有好多库存车卖不出去,但是也不会降价处理,就是要保证市值的稳定,如果生产多少台,就卖多少台,不管价格的话,市场就乱了,所以我们要用不变的价格来稳住消费者购车,才能平稳发展
  • RabbitMQ 提供了一种 Qos (Quality of Service,服务质量)服务质量保证功能
    • 即在非自动确认消息的前提下,如果一定数目的消息未被确认前,不再进行消费新的消息

过期时间TTL

  • Time To Live:生存时间、还能活多久,单位毫秒
  • 在这个周期内,消息可以被消费者正常消费,超过这个时间,则自动删除(其实是被称为deadmessage并投入到死信队列,无法消费该消息)
  • RabbitMQ可以对消息和队列设置TTL
    • 通过队列设置,队列中所有消息都有相同的过期时间
    • 对消息单独设置,每条消息的TTL可以不同(更颗粒化)

死信队列

  • DLX(Dead Letter Exchanges)死信交换机/死信邮箱,当消息在队列中由于某些原因没有被及时消费而变成死信(dead message)后,这些消息就会被分发到DLX交换机中,而绑定DLX交换机的队列,称之为:“死信队列”
  • 消息没有被及时消费的原因:
    • 消息被拒绝(basic.reject/ basic.nack)并且不再重新投递 requeue=false
    • 消息超时未消费
    • 达到最大队列长度

image.png

延迟队列

  • 延迟队列:TTL + 死信队列的合体
  • 死信队列只是一种特殊的队列,里面的消息仍然可以消费
  • 在电商开发部分中,都会涉及到延时关闭订单,此时延迟队列正好可以解决这个问题

RabbitMQ集群

rabbitmq有3种模式,但集群模式是2种。详细如下:

  • 单一模式:即单机情况不做集群,就单独运行一个rabbitmq而已。之前我们一直在用
  • 普通模式:默认模式,以两个节点(A、B)为例来进行说明
    • 当消息进入A节点的Queue后,consumer从B节点消费时,RabbitMQ会在A和B之间创建临时通道进行消息传输,把A中的消息实体取出并经过通过交给B发送给consumer
    • 当A故障后,B就无法取到A节点中未消费的消息实体
      • 如果做了消息持久化,那么得等A节点恢复,然后才可被消费
      • 如果没有持久化的话,就会产生消息丢失的现象
  • 镜像模式:非常经典的 mirror 镜像模式,保证 100% 数据不丢失。
    • 高可靠性解决方案,主要就是实现数据的同步,一般来讲是 2 - 3 个节点实现数据同步
    • 对于 100% 数据可靠性解决方案,一般是采用 3 个节点。
    • 在实际工作中也是用得最多的,并且实现非常的简单,一般互联网大厂都会构建这种镜像集群模式
  • 还有主备模式,远程模式,多活模式等,本次课程不作为重点,可自行查阅资料了解

集群搭建

前置条件:准备两台linux,并安装好rabbitmq

  • 集群步骤如下:
  1. 修改 /etc/hosts 映射文件 1号服务器:
127.0.0.1 A localhost localhost.localdomain localhost4
localhost4.localdomain4
::1 A localhost localhost.localdomain localhost6
localhost6.localdomain6

192.168.204.141 A
192.168.204.142 B

2号服务器:

127.0.0.1 B localhost localhost.localdomain localhost4
localhost4.localdomain4
::1 B localhost localhost.localdomain localhost6
localhost6.localdomain6

192.168.204.141 A
192.168.204.142 B
  1. 相互通信,cookie必须保持一致,同步 rabbitmq的cookie 文件:跨服务器拷贝 .erlang.cookie(隐藏文件,使用 ls -all 显示)
[root@A opt]# scp /var/lib/rabbitmq/.erlang.cookie 192.168.204.142:/var/lib/rabbitmq

修改cookie文件,要重启服务器,reboot

  1. 停止防火墙,启动rabbitmq服务
[root@A ~]# systemctl stop firewalld
[root@A ~]# systemctl start rabbitmq-server
  1. 加入集群节点
[root@B ~]# rabbitmqctl stop_app
[root@B ~]# rabbitmqctl join_cluster rabbit@A
[root@B ~]# rabbitmqctl start_app
  1. 查看节点状态
[root@B ~]# rabbitmqctl cluster_status
  1. 查看管理端
    • 搭建集群结构之后,之前创建的交换机、队列、用户都属于单一结构,在新的集群环境中是不能用的
    • 所以在新的集群中重新手动添加用户即可(任意节点添加,所有节点共享)
[root@A ~]# rabbitmqctl add_user laosun 123123
[root@A ~]# rabbitmqctl set_user_tags laosun administrator
[root@A ~]# rabbitmqctl set_permissions -p "/" laosun ".*" ".*" ".*"
- 注意:当节点脱离集群还原成单一结构后,交换机,队列和用户等数据 都会重新回来

image.png

  • 此时,集群搭建完毕,但是默认采用的模式“普通模式”,可靠性不高

镜像模式

  • 将所有队列设置为镜像队列,即队列会被复制到各个节点,各个节点状态一致
    • 语法:set_policy {name} {pattern} {definition}
      • name:策略名,可自定义
      • pattern:队列的匹配模式(正则表达式)
        • "^" 可以使用正则表达式,比如"^queue_" 表示对队列名称以“queue_”开头的所有队列进行镜像,而"^"表示匹配所有的队列
      • definition:镜像定义,包括三个部分ha-mode, ha-params, ha-sync-mode
        • ha-mode:(High Available,高可用)模式,指明镜像队列的模式,有效值为all/exactly/nodes,当前策略模式为 all,即复制到所有节点,包含新增节点
        all:表示在集群中所有的节点上进行镜像
        exactly:表示在指定个数的节点上进行镜像,节点的个数由ha-params指定
        nodes:表示在指定的节点上进行镜像,节点名称通过ha-params指定
        
        • ha-params:ha-mode模式需要用到的参数
        • ha-sync-mode:进行队列中消息的同步方式,有效值为automatic和manual
[root@A ~]# rabbitmqctl set_policy xall "^" '{"ha-mode":"all"}'
  • 通过管理端设置镜像策略

image.png

HAProxy实现镜像队列的负载均衡

  • 虽然我们在程序中访问A服务器,可以实现消息的同步,虽然在同步,但都是A服务器在接收消息,A太累
  • 是否可以想Nginx一样,做负载均衡,A和B轮流接收消息,再镜像同步

HAProxy简介

  • HA(High Available,高可用),Proxy(代理)
  • HAProxy是一款提供高可用性,负载均衡,并且基于TCP和HTTP应用的代理软件
  • HAProxy完全免费
  • HAProxy可以支持数以万计的并发连接
  • HAProxy可以简单又安全的整合进架构中,同时还保护web服务器不被暴露到网络上

image.png

HAProxy与Nginx

OSI:(Open System Interconnection:开放式系统互联 是把网络通信的工作分为7层,分别是物理层,数据链路层,网络层,传输层,会话层,表示层和应用层)

  • Nginx的优点:
    • 工作在OSI第7层,可以针对http应用做一些分流的策略
    • Nginx对网络的依赖非常小,理论上能ping通就就能进行负载功能,屹立至今的绝对优势
    • Nginx安装和配置比较简单,测试起来比较方便;
    • Nginx不仅仅是一款优秀的负载均衡器/反向代理软件,它同时也是功能强大的Web应用服务器
  • HAProxy的优点:
    • 工作在网络4层和7层,支持TCP与Http协议,
    • 它仅仅就只是一款负载均衡软件;单纯从效率上来讲HAProxy更会比Nginx有更出色的负载均衡速度,在并发处理上也是优于Nginx的
    • 支持8种负载均衡策略 ,支持心跳检测
  • 性能上HA胜,功能性和便利性上Nginx胜
  • 对于Http协议,Haproxy处理效率比Nginx高。所以,没有特殊要求的时候或者一般场景,建议使用Haproxy来做Http协议负载
  • 但如果是Web应用,那么建议使用Nginx!
  • 总之,大家可以结合各自使用场景的特点来进行合理地选择

安装和配置

HAProxy下载:www.haproxy.org/download/1.…

  • 解压
[root@localhost opt]# tar -zxvf haproxy-1.8.12.tar.gz
  • make时需要使用 TARGET 指定内核及版本
[root@localhost opt]# uname -r
3.10.0-514.6.2.el7.x86_64

根据内核版本选择编译参数:

image.png

  • 进入目录,编译和安装
[root@localhost opt]# cd haproxy-1.8.12
[root@localhost haproxy-1.8.12]# make TARGET=linux2628
PREFIX=/usr/local/haproxy
[root@localhost haproxy-1.8.12]# make install PREFIX=/usr/local/haproxy
  • 安装成功后,查看版本
[root@localhost haproxy-1.8.12]# /usr/local/haproxy/sbin/haproxy -v
  • 配置启动文件,复制haproxy文件到/usr/sbin下 ,复制haproxy脚本,到/etc/init.d下
[root@localhost haproxy-1.8.12]# cp /usr/local/haproxy/sbin/haproxy
/usr/sbin/
[root@localhost haproxy-1.8.12]# cp ./examples/haproxy.init
/etc/init.d/haproxy
[root@localhost haproxy-1.8.12]# chmod 755 /etc/init.d/haproxy
  • 创建系统账号
[root@localhost haproxy-1.8.12]# useradd -r haproxy
  • haproxy.cfg 配置文件需要自行创建
[root@localhost haproxy-1.8.12]# mkdir /etc/haproxy
[root@localhost haproxy-1.8.12]# vim /etc/haproxy/haproxy.cfg
  • 添加配置信息到haproxy.cfg
#全局配置
global
    #设置日志
    log 127.0.0.1 local0 info
    #当前工作目录
    chroot /usr/local/haproxy
    #用户与用户组
    user haproxy
    group haproxy
    #运行进程ID
    uid 99
    gid 99
    #守护进程启动
    daemon
    #最大连接数
    maxconn 4096

#默认配置
defaults
    #应用全局的日志配置
    log global
    #默认的模式mode {tcp|http|health},TCP是4层,HTTP是7层,health只返回OK
    mode tcp
    #日志类别tcplog
    option tcplog
    #不记录健康检查日志信息
    option dontlognull
    #3次失败则认为服务不可用
    retries 3
    #每个进程可用的最大连接数
    maxconn 2000
    #连接超时
    timeout connect 5s
    #客户端超时30秒,ha就会发起重新连接
    timeout client 30s
    #服务端超时15秒,ha就会发起重新连接
    timeout server 15s

#绑定配置
listen rabbitmq_cluster
        bind 192.168.204.143:5672
        #配置TCP模式
        mode tcp
        #简单的轮询
        balance roundrobin
        #RabbitMQ集群节点配置,每隔5秒对mq集群做检查,2次正确证明服务可用,3次失败证
        明服务不可用
        server A 192.168.204.141:5672 check inter 5000 rise 2 fall 3
        server B 192.168.204.142:5672 check inter 5000 rise 2 fall 3

#haproxy监控页面地址
listen monitor
        bind 192.168.204.143:8100
        mode http
        option httplog
        stats enable
        # 监控页面地址 http://192.168.204.143:8100/monitor
        stats uri /monitor
        stats refresh 5s
  • 启动HAProxy
[root@localhost haproxy]# service haproxy start

image.png

  • 项目发消息,只需要将服务器地址修改为143即可,其余不变
  • 所有的请求都会交给HAProxy,其负载均衡给每个rabbitmq服务器

KeepAlived搭建高可用的HAProxy集群

现在的最后一个问题暴露出来了,如果HAProxy服务器宕机,rabbitmq服务器就不可用了。所以我们需要对HAProxy也要做高可用的集群

概述

  • Keepalived是Linux下一个轻量级别的高可用热备解决方案
  • Keepalived的作用是检测服务器的状态,它根据TCP/IP参考模型的第三、第四层、第五层交换机制检测每个服务节点的状态,如果有一台web服务器宕机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,同时使用其他服务器代替该服务器的工作,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。
  • keepalived基于vrrp(Virtual Router Redundancy Protocol,虚拟路由冗余协议)协议,vrrp它是一种主备(主机和备用机)模式的协议,通过VRRP可以在网络发生故障时透明的进行设备切换而不影响主机之间的数据通信
  • 两台主机之间生成一个虚拟的ip,我们称漂移ip,漂移ip由主服务器承担,一但主服务器宕机,备份服务器就会抢夺漂移ip,继续工作,有效的解决了群集中的单点故障
  • 说白了,将多台路由器设备虚拟成一个设备,对外提供统一ip(VIP)

image.png

安装KeepAlived

  • 修改hosts文件的地址映射
ip用途主机名
192.168.204.143KeepAlived HAProxyC
192.168.204.144KeepAlived HAProxyD
  • 安装 keepalived
[root@C ~]# yum install -y keepalived
  • 修改配置文件(内容大改,不如删掉,重新创建)
[root@C ~]# rm -rf /etc/keepalived/keepalived.conf
[root@C ~]# vim /etc/keepalived/keepalived.conf
! Configuration File for keepalived

global_defs {
    router_id C ## 非常重要,标识本机的hostname
}
vrrp_script chk_haproxy{
    script "/etc/keepalived/haproxy_check.sh" ## 执行的脚本位置
    interval 2 ## 检测时间间隔
    weight -20 ## 如果条件成立则权重减20
}

vrrp_instance VI_1 {
    state MASTER ## 非常重要,标识主机,备用机143改为 BACKUP
    interface ens33 ## 非常重要,网卡名(ifconfig查看)
    virtual_router_id 66 ## 非常重要,自定义,虚拟路由ID号(主备节点要相同)
    priority 100 ## 优先级(0-254),一般主机的大于备机
    advert_int 1 ## 主备信息发送间隔,两个节点必须一致,默认1秒
    authentication { ## 认证匹配,设置认证类型和密码,MASTER和BACKUP必须使
        用相同的密码才能正常通信
        auth_type PASS
        auth_pass 1111
    }
    track_script {
        chk_haproxy ## 检查haproxy健康状况的脚本
    }
    virtual_ipaddress { ## 简称“VIP”
        192.168.204.66/24 ## 非常重要,虚拟ip,可以指定多个,以后连接mq就用这个虚拟ip
    }
}

virtual_server 192.168.204.66 5672 { ## 虚拟ip的详细配置
    delay_loop 6 # 健康检查间隔,单位为秒
    lb_algo rr # lvs调度算法rr|wrr|lc|wlc|lblc|sh|dh
    lb_kind NAT # 负载均衡转发规则。一般包括DR,NAT,TUN 3种
    protocol TCP # 转发协议,有TCP和UDP两种,一般用TCP
    real_server 192.168.204.143 5672 { ## 本机的真实ip
        weight 1 # 默认为1,0为失效
    }
}
  • 创建执行脚本 /etc/keepalived/haproxy_check.sh
#!/bin/bash
COUNT=`ps -C haproxy --no-header |wc -l`
if [ $COUNT -eq 0 ];then
    /usr/local/haproxy/sbin/haproxy -f /etc/haproxy/haproxy.cfg
    sleep 2
    if [ `ps -C haproxy --no-header |wc -l` -eq 0 ];then
        killall keepalived
    fi
fi

Keepalived 组之间的心跳检查并不能察觉到 HAproxy 负载是否正常,所以需要使用此脚本。在 Keepalived 主机上,开启此脚本检测 HAproxy 是否正常工作,如正常工作,记录日志。如进程不存在,则尝试重启 HAproxy ,2秒后检测,如果还没有,则关掉主 Keepalived ,此时备Keepalived 检测到主 Keepalived 挂掉,接管VIP,继续服务

  • 授权,否则不能执行
[root@C etc]# chmod +x /etc/keepalived/haproxy_check.sh
  • 启动keepalived(两台都启动)
[root@C etc]# systemctl stop firewalld
[root@C etc]# service keepalived start | stop | status | restart
  • 查看状态
[root@C etc]# ps -ef | grep haproxy
[root@C etc]# ps -ef | grep keepalived
  • 查看ip情况 ip addr 或 ip a
[root@C etc]# ip a

image.png

  • 此时,安装完毕,按照上面的步骤就可以安装第二台了(服务器hostname和ip注意要修改)
  • 常见的网络错误:子网掩码、网关等信息要一致

image.png

测试ip漂移的规则

  • 查看虚拟ip ip addr 或 ip a
  • 目前,C节点是主机,所以虚拟ip在C节点

image.png

  • 停止C的keepalived,虚拟ip漂移到D节点

image.png

  • 重新启动C节点keepalived,虚拟ip依旧在D节点,并不会由于C的回归而回归

image.png

  • 停止D的keepalived,虚拟ip再漂移回C节点

image.png

  • 测试vip+端口是否提供服务(在141,A服务器上测试)
[root@A ~]# curl 192.168.204.66:5672
AMQP ## 正常提供AMQP服务,表示通过vip访问mq服务正常