RabbitMQ 归纳总结

Rabbit MQ 归纳总结


前言

MQ 是什么,MQ 有哪些模式,MQ是如何和spring 整合的?


提示:以下是本篇文章正文内容,下面案例可供参考

一、消息中间件概述

1.1.MQ 概述

MQ 全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间进行通信。

在这里插入图片描述

1.2.MQ 优势

1.2.1 应用解耦

MQ 相当于一个中介,生成方通过MQ 与 消费方进行交互,它将应用程序进行解耦合
系统的耦合性越高,容错性就越低,可维护性就越差

使用MQ使得应用间解耦,提升容错性和可维护性

1.2.2 任务异步处理

将不需要同步处理的并且耗时长的操作由消息队列通知消息接收方进行异步处理。提高了应用程序的响应时间

在这里插入图片描述在这里插入图片描述

1.2.3 削峰填谷

如订单系统,在下单的时候就会往数据库写数据。但是数据库只能支撑每秒1000左右的并发写入,并发量再高就容易宕机。低峰期的时候并发也就100多个,但是在高峰期时候,并发量会突然激增到5000以上,这个时候数据库肯定卡死了

在这里插入图片描述

1.3. MQ 劣势

1. 系统的可用性降低

系统引入的外部依赖越多,系统的稳定性就越差。一旦MQ 宕机就会对系统的业务造成影响。如何保证MQ 的高可用?

2. 系统复杂度提高

QM 的加入增加了系统的复杂度,以前系统间是同步的远程调用,现在是MQ 进行异步调用,如何保证消息没有被重复消费?怎么处理消息丢失情况?怎么保证消息传递的顺序性?

3. 一致性问题

A 系统处理完任务,通过MQ 给B、C、D 三个系统发消息,如果B系统、C系统处理成功,D系统处理失败。如何保证消息处理的一致性?

二、常见的MQ 产品

目前市场有很多的MQ 产品,例如 RabbitMQ 、RocketMQ、ActiveMQ、kafka、ZeroMQ、MetaMQ 等,也有直接用Redis 充当消息队列的案例,而这些消息队列产品各有侧重,在实际选型时,需要结合自身需求以及MQ产品特征,综合考虑

在这里插入图片描述
在这里插入图片描述
实现MQ 的常见的两种方式:
AMQP、JMS

AMQP:
AMQP,即 Advanced Message Queuing Protocol(高级消息队列协议),是一个网络协议,是应用层协议的一个开放标准,为面向消息的中间件设计。基于此协议的客户端与消息中间件可传递消息,遵循此协议,不收客户端和中间件产品和开发语言限制。2006年,AMQP 规范发布。类比HTTP。

在这里插入图片描述

JMS:
JMS 即 Java 消息服务(JavaMessage Service)应用程序接口,是一个 Java 平台中关于面向消息中间件的APIJMS 是 JavaEE 规范中的一种,类比JDBC很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ 官方没有提供 JMS 的实现包,但是开源社区有

AMQP 与 JMS 区别:

  • JMS是定义了统一的接口,来对消息操作进行统一;AMQP是通过规定协议来统一数据交互的格式

  • JMS限定了必须使用Java语言;AMQP只是协议,不规定实现方式,因此是跨语言的。

  • JMS规定了两种消息模式;而AMQP的消息模式更加丰富

三、RabbitMQ 简介

RabbitMQ官方地址:http://www.rabbitmq.com/

2007年,Rabbit 技术公司基于 AMQP 标准开发的 RabbitMQ 1.0 发布。RabbitMQ 采用 Erlang 语言开发。Erlang 语言专门为开发高并发和分布式系统的一种语言,在电信领域使用广泛。

RabbitMQ 基础架构如下图:
在这里插入图片描述
RabbitMQ 中的相关概念:

  • Broker 接收和分发消息 ,RabbitMQ Server 就是 Message Broker
  • Virtual host : 出于多租户和安全因素考虑设计的,把AMQP的基本组件划分到一个虚拟的分组中,类似于网络中的namespace 概念。当多个不同的用户使用同一个RabbitMQ Server 提供的服务时,可以划分出多个vhost,每个用户在自己的vhost 创建 exchange 和 queue 等
  • Connection: publisher / consumer 和 broker 之间的TCP 连接
  • Channel : 如果每一次访问RabbitMQ 都建立一个Connection,在消息量大的时候建立多个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-subscrib) and fanout (multicast)
  • Queue : 消息最终被送到这里等待Consumer 取走
  • Binding: exchange 和 queue 之间的虚拟连接,binding 中可以包含routing key。Binding 信息被保存到 Exchange 中的查询表中,用于Message 的分发依据。

RabbitMQ 提供了6 种模式:简单模式、work 模式,Publish/ Subscrib 发布与订阅模式,Routing 路由模式,Topics 主题模式,RPC 远程调用模式。

官网对应模式介绍:https://www.rabbitmq.com/getstarted.html

在这里插入图片描述

2.2.安装与配置RabbitMQ

这里使用docker 安装:

  1. 查找 rabbitmq:management 镜像
docker search rabbitmq:management
  1. 拉取 rabbitmq 镜像
# management 不可省略(否则打不开 管理端)
docker pull rabbitmq:management
  1. 创建容器 rabbitmq 容器
docker run -id --name=rabbitmq 
-p 5671:5671 \
-p 5672:5672 \
-p 15672:15672 \
rabbitmq:management

创建成功! 打开浏览器输入 http://ip:15672 即可访问 ,默认用户名:guest,密码:guest

2.3. Rabbit MQ 的几种模式

2.3.1简单模式 simple

一个生产者,一个消费者,不需要设置交换机(使用默认的交换机)

// 将获取连接的代码封装
package com.geng.utils;

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

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

public class ConnectionUtils {
    
    
    public static Connection getConnection() throws IOException, TimeoutException {
    
    
        //1. 创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //1.1设置主机地址:默认为localhost
        connectionFactory.setHost("192.168.43.6");
        //1.2 设置连接端口,默认为 5672
        connectionFactory.setPort(5672);
        //1.3 设置虚拟主机名称,默认为 /
        connectionFactory.setVirtualHost("/gjt");
        //1.4 设置连接用户名,默认为 guest
        connectionFactory.setUsername("guest");
        //1.5 设置连接密码,默认为 guest
        connectionFactory.setPassword("guest");
        //2. 创建连接
        return connectionFactory.newConnection();
    }
}

package com.geng.rabbitmq.simple;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

import java.io.IOException;
import java.util.concurrent.TimeoutException;
//生产者
public class Produce {
    
    

    public static String QUEUE_KEY = "simple-queue" ;

    public static void main(String[] args) throws IOException, TimeoutException {
    
    

        Connection connection = ConnectionUtils.getConnection();
       //3. 创建频道
        Channel channel = connection.createChannel();
       //4. 声明队列
        /**
         * 参数1: 队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接
         * 参数4:是否在不使用的时候自动删除队列
         * 其他参数
         */

        channel.queueDeclare(QUEUE_KEY,true,false,false,null);

       //5. 要发送的消息
        /**
         * 参数1:交换机的名称,如果没有则用默认的 default exchange
         * 参数2:路由key ,简单模式可以传递队列名称
         * 参数3:消息其他属性
         * 参数4:消息内容
         */
        String message = "good night,耿俊廷";
        /**
         *
         */
        channel.basicPublish("", QUEUE_KEY,null,message.getBytes());
       //6. 关闭资源
        channel.close();
        connection.close();
    }
}

package com.geng.rabbitmq.simple;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class ConsumerTest {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    

        //2. 获得连接
        Connection connection = ConnectionUtils.getConnection();
        //3. 创建通道
        Channel channel = connection.createChannel();
        //4. 声明队列
        channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);

        //5.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));

            }
        };
        channel.basicConsume(Produce.QUEUE_KEY,true,consumer);

    }
}

2.3.2 工作队列模式 workQueue

一个生产者,多个消费者(竞争关系),不需要设置交换机(使用默认交换机)

package com.geng.rabbitmq.workQueue;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;

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

public class Produce {
    
    

    public static String QUEUE_KEY = "simple-queue" ;

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //2. 获得连接
        Connection connection = ConnectionUtils.getConnection();

       //3. 创建频道
        Channel channel = connection.createChannel();
       //4. 声明队列
        /**
         * 参数1: 队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接
         * 参数4:是否在不使用的时候自动删除队列
         * 其他参数
         */

        channel.queueDeclare(QUEUE_KEY,true,false,false,null);

       //5. 要发送的消息
        /**
         * 参数1:交换机的名称,如果没有则用默认的 default exchange
         * 参数2:路由key ,简单模式可以传递队列名称
         * 参数3:消息其他属性
         * 参数4:消息内容
         */
        String message = "good night,耿俊廷";
        /**
         *
         */
        for(int i = 0; i < 10; i++){
    
    
            channel.basicPublish("", QUEUE_KEY,null,(message+i).getBytes());
        }
       //6. 关闭资源
        channel.close();
        connection.close();
    }
}

package com.geng.rabbitmq.workQueue;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class ConsumerTest {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //或得连接
        Connection connection = ConnectionUtils.getConnection();
        //3. 创建通道
        Channel channel = connection.createChannel();
        //4. 声明队列
        channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);

        //5.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));

            }
        };
        channel.basicConsume(Produce.QUEUE_KEY,true,consumer);

    }
}

package com.geng.rabbitmq.workQueue;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class ConsumerTest2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //或得连接
        Connection connection = ConnectionUtils.getConnection();
        //3. 创建通道
        Channel channel = connection.createChannel();
        //4. 声明队列
        channel.queueDeclare(Produce.QUEUE_KEY,true,false,false,null);

        //5.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));

            }
        };
        channel.basicConsume(Produce.QUEUE_KEY,true,consumer);

    }
}

2.3.3 发布订阅模式 Publish/subscribe

需要设置类型为fanout 的交换机,并且交换机和队列进行绑定,当消息发送到交换机后,交换机会将消息发送到绑定的队列

package com.geng.rabbitmq.ps;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Produce {
    
    
    public static String EXCHANGE = "ps-exchange";
    public static String QUEUE_KEY1 = "ps-queue1";
    public static String QUEUE_KEY2 = "ps-queue2" ;

    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //2. 获得连接
        Connection connection = ConnectionUtils.getConnection();

       //3. 创建频道
        Channel channel = connection.createChannel();
       //4. 声明队列 和 交换机 exchange
        /**
         * 参数1: 队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接
         * 参数4:是否在不使用的时候自动删除队列
         * 其他参数
         */

        channel.queueDeclare(QUEUE_KEY1,true,false,false,null);
        channel.queueDeclare(QUEUE_KEY2,true,false,false,null);
        channel.exchangeDeclare(EXCHANGE, BuiltinExchangeType.FANOUT);

        //队列绑定交换机
        channel.queueBind(QUEUE_KEY1, EXCHANGE, "");
        channel.queueBind(QUEUE_KEY2, EXCHANGE, "");

       //5. 要发送的消息
        /**
         * 参数1:交换机的名称,如果没有则用默认的 default exchange
         * 参数2:路由key ,简单模式可以传递队列名称
         * 参数3:消息其他属性
         * 参数4:消息内容
         */
        String message = "good night,耿俊廷";
        /**
         *
         */
        for(int i = 0; i < 10; i++){
    
    
            channel.basicPublish(EXCHANGE, "",null,(message+i).getBytes());
        }
       //6. 关闭资源
        channel.close();
        connection.close();
    }
}

package com.geng.rabbitmq.ps;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class ConsumerTest {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //或得连接
        Connection connection = ConnectionUtils.getConnection();
        //3. 创建通道
        Channel channel = connection.createChannel();
        //4. 声明队列
        channel.queueDeclare(Produce.QUEUE_KEY1,true,false,false,null);

        channel.queueBind(Produce.QUEUE_KEY1,Produce.EXCHANGE,"");
        //5.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));

            }
        };
        channel.basicConsume(Produce.QUEUE_KEY1,true,consumer);

    }
}

package com.geng.rabbitmq.ps;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class ConsumerTest2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //或得连接
        Connection connection = ConnectionUtils.getConnection();
        //3. 创建通道
        Channel channel = connection.createChannel();
        //4. 声明队列
        channel.queueDeclare(Produce.QUEUE_KEY2,true,false,false,null);
        //队列绑定交换机
        channel.queueBind(Produce.QUEUE_KEY2,Produce.EXCHANGE,"");

        //5.消费消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                System.out.println("路由key为:" + envelope.getRoutingKey()); //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id为:" + envelope.getDeliveryTag());
                //收到的消息
                System.out.println("接收到的消息为:" + new String(body, "utf-8"));

            }
        };
        channel.basicConsume(Produce.QUEUE_KEY2,true,consumer);

    }
}

2.3.4.路由模式 Routing

需要设置类型为 direct 的交换机,交换机和队列进行绑定,当消息到达交换机后,交换机会根据路由规则(routing key),将消息发送到绑定的对应的队列

package com.geng.rabbitmq.routing;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

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

public class Produce {
    
    
    public static String EXCHANGE_ROUTE = "route-exchange";
    public static String QUEUE_INSERT = "queue-insert";
    public static String QUEUE_UPDATE = "queue-update";
    public static void main(String[] args) throws IOException, TimeoutException {
    
    

        //获得连接
        Connection connection = ConnectionUtils.getConnection();
        //获得通道
        Channel channel = connection.createChannel();
        /**
         * 声明交换机
         * 参数1 :交换机名称
         * 参数2 :交换机类型 fanout ,direct, topic ,headers
         */
        channel.exchangeDeclare(EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
        /**
         * 声明队列
         * 参数1: 队列名称
         * 参数2: 是否定义持久化队列
         * 参数3: 是否独占本次连接
         * 参数4: 是否在不使用的时候独占本次连接
         * 参数5: 队列其他参数
         */
        channel.queueDeclare(QUEUE_INSERT,true,false,false,null);
        channel.queueDeclare(QUEUE_UPDATE,true,false,false,null);
        //队列绑定交换机(exchange)
        channel.queueBind(QUEUE_UPDATE,EXCHANGE_ROUTE,"update");
        channel.queueBind(QUEUE_INSERT,EXCHANGE_ROUTE,"insert");

        // 发送消息,插入
        String message = "新增。路由模式 routing key 为 insert";
        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish(EXCHANGE_ROUTE,"insert",null,message.getBytes());

        //发送消息,修改
        String message2 = "修改。 路由模式 routing key 为 update";
        channel.basicPublish(EXCHANGE_ROUTE,"update",null,message2.getBytes());
        //关闭资源
        channel.close();
        connection.close();




    }
}

package com.geng.rabbitmq.routing;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Consumer1 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(Produce.EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare(Produce.QUEUE_INSERT,true,false,false,null);
        //队列绑定交换机
        channel.queueBind(Produce.QUEUE_INSERT,Produce.EXCHANGE_ROUTE,"insert");
        //创建消息消费者回调对象
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                // 路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        /**
         * 参数1: 监听消息队列名称
         * 参数2: 是否接收到消息后自动向mq 回复消息接收到了,mq 接收到回复会删除消息
         */
        channel.basicConsume(Produce.QUEUE_INSERT,true,consumer);
    }
}

package com.geng.rabbitmq.routing;

import com.geng.utils.ConnectionUtils;
import com.rabbitmq.client.*;

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

public class Consumer2 {
    
    
    public static void main(String[] args) throws IOException, TimeoutException {
    
    
        //获取连接
        Connection connection = ConnectionUtils.getConnection();
        //创建通道
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(Produce.EXCHANGE_ROUTE, BuiltinExchangeType.DIRECT);
        //声明队列
        channel.queueDeclare(Produce.QUEUE_UPDATE,true,false,false,null);
        //队列绑定交换机
        channel.queueBind(Produce.QUEUE_UPDATE,Produce.EXCHANGE_ROUTE,"update");
        //创建消息消费者回调对象
        DefaultConsumer consumer = new DefaultConsumer(channel){
    
    
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
    
    
                // 路由key
                System.out.println("路由key为:" + envelope.getRoutingKey());
                //交换机
                System.out.println("交换机为:" + envelope.getExchange());
                //消息id
                System.out.println("消息id:" + envelope.getDeliveryTag());
                //消息内容
                System.out.println("消费者1-接收到的消息为:" + new String(body, "utf-8"));
            }
        };

        //监听消息
        /**
         * 参数1: 交换机名称
         * 参数2: 是否接收到消息后自动向mq 回复消息接收到了,mq 接收到回复会删除消息
         */
        channel.basicConsume(Produce.QUEUE_UPDATE,true,consumer);
    }
}

2.3.5 通配符模式 topic

需要设置类型为topic的交换机,交换机和队列进行绑定,并且指定通配符方式的routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

2.4 Spring 整合 RabbitMQ

猜你喜欢

转载自blog.csdn.net/CXgeng/article/details/123580935