RabbitMQ

MQ是什么?

MQ(message queue),从字面意思上看,本质是个队列,FIFO 先入先出,只不过队列中存放的内容是
message 而已,还是一种跨进程的通信机制,用于上下游传递消息。在互联网架构中,MQ 是一种非常常
见的上下游“逻辑解耦+物理解耦”的消息通信服务。使用了 MQ 之后,消息发送上游只需要依赖 MQ,不
用依赖其他服务。

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 服务还能及时的得到异步处理成功的消息。

什么是RabbitMQ

RabbitMQ 的概念

RabbitMQ 是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包

裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑 RabbitMQ 是

一个快递站,一个快递员帮你传递快件。RabbitMQ 与快递站的主要区别在于,它不处理快件而是接收,

存储和转发消息数据。

四大核心概念

  1. 生成者

    ​ 产生数据发送消息的程序是生产者

  2. 交换机

    ​ 交换机是 RabbitMQ 非常重要的一个部件,一方面它接收来自生产者的消息,另一方面它将消息

    推送到队列中。交换机必须确切知道如何处理它接收到的消息,是将这些消息推送到特定队列还是推

    送到多个队列,亦或者是把消息丢弃,这个得有交换机类型决定

  3. 队列

    ​ 队列是 RabbitMQ 内部使用的一种数据结构,尽管消息流经 RabbitMQ 和应用程序,但它们只能存

    储在队列中。队列仅受主机的内存和磁盘限制的约束,本质上是一个大的消息缓冲区。许多生产者可

    以将消息发送到一个队列,许多消费者可以尝试从一个队列接收数据。这就是我们使用队列的方式

  4. 消费者

    ​ 消费与接收具有相似的含义。消费者大多时候是一个等待接收消息的程序。请注意生产者,消费

    者和消息中间件很多时候并不在同一机器上。同一个应用程序既可以是生产者又是可以是消费者。

RabbitMQ的几种模式

交换机和队列间的关系:发布订阅 / 路由 / 主题模式 / RPC

队列和消费者的关系:work-轮询 / work-公平

发布订阅 Publish/Subscribe

广播的效果,交换机下的所有队列都会收到消息

路由 Routing

精确匹配,队列绑定交换机时需要设置RoutingKey,生成者发送数据到交换机时需要设置一个RoutingKey的值,交换机会比对两个RoutingKey,然后将消息发送到对应的队列中

(默认值,队列创建时会绑定默认的交换机,交换机的模式为路由模式,如果没有设置绑定的RoutingKey,则默认的RoutingKey就是队列名)

主题模式 topic

模糊匹配,和RoutingKey类似,不过支持了模糊匹配

交换机和队列绑定时需要设置的RoutingKey可以使用#或*来完成匹配规则的限制

# 匹配一个或多个

* 匹配一个

RPC

Work-轮询

1、轮询模式的分发:一个消费者一条,按均分配;
2、公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;

Work-公平

1、轮询模式的分发:一个消费者一条,按均分配;
2、公平分发:根据消费者的消费能力进行公平分发,处理快的处理的多,处理慢的处理的少;按劳分配;

RabbitMQ工作原理

Broker:接收和分发消息的应用,RabbitMQ Server 就是 Message Broker

Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似

于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出

多个 vhost,每个用户在自己的 vhost 创建 exchange/queue 等

Connection:publisher/consumer 和 broker 之间的 TCP 连接

Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP

Connection 的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程

序支持多线程,通常每个 thread 创建单独的 channel 进行通讯,AMQP method 包含了 channel id 帮助客

户端和 message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的

Connection 极大减少了操作系统建立 TCP connection 的开销

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

消息到 queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout

(multicast)

Queue:消息最终被送到这里等待 consumer 取走

Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key,Binding 信息被保

存到 exchange 中的查询表中,用于 message 的分发依据

RabbitMQ的安装

Linux安装 https://www.rabbitmq.com/install-rpm.html

Erlang和RabbitMQ 版本选型要求 https://www.rabbitmq.com/which-erlang.html

安装Erlang环境

  • 安装GCC GCC-C++ Openssl等模块

    yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel
    
  • 安装ncurses

    yum -y install ncurses-devel
    
  • 下载Erlang rpm 安装包 https://packagecloud.io/rabbitmq/erlang

    (个人用的版本是 erlang-23.3.4.11-1.el7.x86_64.rpm)

    点击进去获取下载链接

    wget --content-disposition https://packagecloud.io/rabbitmq/erlang/packages/el/7/erlang-23.3.4.11-1.el7.x86_64.rpm/download.rpm
    

  • 下载过后可以得到一个rpm文件

  • 解压

    #进入下载目录
    cd /usr/local/rabbitmq/
    
    # 解压
    rpm -Uvh erlang-23.3.4.11-1.el7.x86_64.rpm
    
    # 安装  该步骤可以省略
    yum install -y erlang
    
    # 查看版本
    erl -v
    

安装RabbitMQ

  • ​ 下载 https://github.com/rabbitmq/rabbitmq-server/releases/tag/v3.9.21

  • 解压/安装

    # 解压
    rpm -Uvh rabbitmq-server-3.9.21-1.el8.noarch.rpm
    # 安装
    yum install -y rabbitmq-server
    
  • 运行

    # 启动rabbitmq
    systemctl start rabbitmq-server
    
    # 查看rabbitmq状态
    systemctl status rabbitmq-server
    

  • 安装RabbitMQWeb管理界面插件

    # 安装RabbitMQWeb管理界面插件
    rabbitmq-plugins enable rabbitmq_management
    
  • 添加远程用户

    用户角色简述:
    administrator:可以登录控制台、查看所有信息、并对rabbitmq进行管理
    monToring:监控者;登录控制台,查看所有信息
    policymaker:策略制定者;登录控制台指定策略
    managment:普通管理员;登录控制

    # 添加用户
    # rabbitmqctl add_user 用户名 密码
    rabbitmqctl add_user test test
    
    # 设置用户角色,分配操作权限
    #rabbitmqctl set_user_tags 用户名 角色
    rabbitmqctl set_user_tags test administrator
    
    # 为用户添加资源权限(授予访问虚拟机根节点的所有权限)
    rabbitmqctl set_permissions -p / test ".*" ".*" ".*"
    

    (可以通过管理界面进行添加)

  • 常用命令

    # 设置rabbitmq服务开机自启动
    systemctl enable rabbitmq-server
    
    # 关闭rabbitmq服务
    systemctl stop rabbitmq-server
    
    # 重启rabbitmq服务
    systemctl restart rabbitmq-server
    
    # 修改密码
    rabbitmqctl change_ password 用户名 新密码
    
    # 删除用户
    rabbitmqctl delete_user 用户名
    
    # 查看用户清单
    rabbitmqctl list_users
    

RabbitMQ的使用

SpringBoot

依赖

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

yml

server:
  port: 8082

spring:
  application:
    name: consumers
  rabbitmq:
    host: 139.155.20.171
    port: 5672
    username: admin
    password: admin
    virtual-host: /boot

声明队列、交换机并进行绑定

@Configuration
public class RabbitMQConfig {

    /**
     * 声明虚拟机
     * DirectExchange/FanoutExchange/TopicExchange
     * (路由/广播/主题)
     * @return  
     * @param1  交换机名
     * @param2  durable 队列是否持久化
     * @param3  autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除队列。
     */
    public DirectExchange directExchange() {
        return new DirectExchange("order.direct.exchange", true, false);
    }
    
    /**
     * 声明队列
     *
     * @return
     * @param1  queue 队列的名称
     * @param2	durable 队列是否持久化
     * @param3 	exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
     * @param4 	autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除队列。
     */@Bean
    public Queue directQueueSms() {
        return new Queue("order.sms.direct", true, false, false);
    }

    /**
     * 虚拟机绑定队列
     *
     * @return 
     * bing( 队列名 ) 
     * to( 交换机名 )  
     * with( routingKey )  
     */
    @Bean
    public Binding directBinding1() {
        return BindingBuilder.bind(directQueueSms()).to(directExchange()).with("sms");
    }
}

生产者

@SpringBootTest
class Product2Test {
    
    @Autowired
    RabbitTemplate rabbitTemplate;
    
	@Test
    public void makeOrder(String userId, String productId, Integer num) {
  
        String orderId = UUID.randomUUID().toString();
        // 2.下单完成后通知用户  短信、邮件、SMS短信
        String exchange = "order.direct.exchange";
        String routingKey = "sms";
        String data = new String("下单成功,你的订单号为:" + orderId);
        rabbitTemplate.convertAndSend(exchange, routingKey, data);
    }

}

消费者

@Component
@RabbitListener(queues = {"order.sms.direct"})  //监听的队列名
public class DirectSmsConsumer {

    /**
     * @param  参数类型自定义,主要看传过来的参数是什么
     */
    @RabbitHandler //处理
    public void handlerQueue(String message) {
        System.out.println("order.sms.direct:" + message);
    }
}

Spring

依赖

<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-amqp</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.amqp</groupId>
    <artifactId>spring-rabbit</artifactId>
    <version>2.2.5.RELEASE</version>
</dependency>

Java

依赖

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

生产者

public class Producer {

    public static void main(String[] args) {
//        1.获取连接工厂ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
//        2.设置连接属性 host:port ...
        connectionFactory.setHost("139.155.20.171");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
//        3.获取连接
            connection = connectionFactory.newConnection("生产者1");
//        4.获取通道
            channel = connection.createChannel();
//        5.设置队列
            /*
             *  如果队列不存在,则会创建
             *  Rabbitmq不允许创建两个相同的队列名称,否则会报错。
             *
             *  @params1: queue 队列的名称
             *  @params2: durable 队列是否持久化
             *  @params3: exclusive 是否排他,即是否私有的,如果为true,会对当前队列加锁,其他的通道不能访问,并且连接自动关闭
             *  @params4: autoDelete 是否自动删除,当最后一个消费者断开连接之后是否自动删除队列。
             *  @params5: arguments 可以设置队列附加参数,设置队列的有效期,消息的最大长度,队列的消息生命周期等等。
             * */
            channel.queueDeclare("queue1", false, false, false, null);

//        6.发送数据
            /*
             * @params1: 交换机exchange
             * @params2: 队列/路由   queueName/routingKey
             * @params3: 属性配置
             * @params4: 需要发送的数据
             * */
            byte[] data = new String("真不咋地111!").getBytes();
            channel.basicPublish("","queue1",null,data);

        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
//        7.关闭连接
            try {
                channel.close();
                connection.close();
                connectionFactory.clone();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }

    }
}

消费者

public class Consumer {
    public static void main(String[] args) {
//        1.获取连接工厂ConnectionFactory
        ConnectionFactory connectionFactory = new ConnectionFactory();
//        2.设置连接属性 host:port username password virtualHost ...
        connectionFactory.setHost("139.155.20.171");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        connectionFactory.setVirtualHost("/");
        Connection connection = null;
        Channel channel = null;
        try {
//        3.获取连接connection
            connection = connectionFactory.newConnection();
//        4.获取通道channel
            channel = connection.createChannel();
//        5.设置Consumer 订阅消息 --------
            channel.basicConsume("queue1",true,  new DeliverCallback() {
                @Override
                public void handle(String s, Delivery delivery) throws IOException {
//        6.获取数据  --------
                    String data = new String(delivery.getBody(), "utf-8");
                    System.out.println(data);
                }
            }, new CancelCallback() {
                @Override
                public void handle(String s) throws IOException {
                    System.out.println("接收失败");
                }
            });

            System.out.println("开始接收消息");
            System.in.read();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
//        7.关闭连接
            try {
                channel.close();
                connection.close();
                connectionFactory.clone();
            } catch (IOException e) {
                e.printStackTrace();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }

    }
}

RabbitMQ高级

过期时间

消息确认机制

消息丢失 https://www.bilibili.com/read/cv15843435/

第一种:生产者弄丢了数据。生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,

​ 因为网络问题啥的,都有可能。

第二种:RabbitMQ 弄丢了数据。MQ还没有持久化自己挂了
第三种:消费端弄丢了数据。刚消费到,还没处理,结果进程挂了,比如重启了。

生产者:确认消息发送到交换机

MQ:确认消息已发送到队列并持久化

消费者:手动ACK

死信队列

持久化机制

内存监控

集群

posted @   晓洋^  阅读(578)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示