01_RabbitMQ安装及工作模式

一、消息队列MQ

中间件

1.1 什么是消息队列

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

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

1.2 为何用消息队列

支付成功

service层功能:(操作)

1.修改订单状态;update 订单表

2.扣减库存;update 库存表

3.淘金币;update 淘金币表

4.发消息;A a,B b

5.发放优惠卷;insert 用户优惠卷表

业务service 流程
支付 成功:1,2,3,4,5|54321|24531
发货 1
客服聊天 4

从上面的描述中可以看出消息队列是一种应用间的异步协作机制,那什么时候需要使用 MQ 呢?

以常见的订单系统为例,用户点击【下单】按钮之后的业务逻辑可能包括:扣减库存、生成相应单据、发红包、发短信通知。在业务发展初期这些逻辑可能放在一起同步执行 ,随着业务的发展订单量增长,需要提升系统服务的性能,这时可以将一些不需要立即生效的操作拆分出来异步执行,比如发放红包、发短信通知等。这种场景下就可以用 MQ ,在下单的主流程(比如扣减库存、生成相应单据)完成之后发送一条消息到 MQ 让主流程快速完结,而由另外的单独线程拉取MQ的消息(或者由 MQ 推送消息),当发现 MQ 中有发红包或发短信之类的消息时,执行相应的业务逻辑。

以上是用于业务解耦的情况,其它常见场景包括最终一致性、广播、错峰流控等等。

1.3 RabbitMQ

RabbitMQ是一个消息代理。核心思想:接收,保存,转发消息。是目前非常热门的一款消息中间件。

1.4 特点

RabbitMQ 是一个由 Erlang 语言开发的 AMQP 的开源实现。

AMQP :Advanced Message Queue,高级消息队列协议。它是应用层协议的一个开放标准,为面向消息的中间件设计,基于此协议的客户端与消息中间件可传递消息,并不受产品、开发语言等条件的限制。

RabbitMQ 最初起源于金融系统,用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。具体特点包括:

  1. 可靠性(Reliability)

    RabbitMQ 使用一些机制来保证可靠性,如持久化、传输确认、发布确认。

  2. 灵活的路由(Flexible Routing)

    在消息进入队列之前,通过 Exchange 来路由消息的。对于典型的路由功能,RabbitMQ 已经提供了一些内置的 Exchange 来实现。针对更复杂的路由功能,可以将多个 Exchange 绑定在一起,也通过插件机制实现自己的 Exchange 。

  3. 消息集群(Clustering)

    多个 RabbitMQ 服务器可以组成一个集群,形成一个逻辑 Broker 。

  4. 高可用(Highly Available Queues)

    队列可以在集群中的机器上进行镜像,使得在部分节点出问题的情况下队列仍然可用。

  5. 多种协议(Multi-protocol)

    RabbitMQ 支持多种消息队列协议,比如 STOMP、MQTT 等等。

  6. 多语言客户端(Many Clients)

    RabbitMQ 几乎支持所有常用语言,比如 Java、.NET、Ruby 等等。

  7. 管理界面(Management UI)

    RabbitMQ 提供了一个易用的用户界面,使得用户可以监控和管理消息 Broker 的许多方面。

  8. 跟踪机制(Tracing)

    如果消息异常,RabbitMQ 提供了消息跟踪机制,使用者可以找出发生了什么。

  9. 插件机制(Plugin System)

    RabbitMQ 提供了许多插件,来从多方面进行扩展,也可以编写自己的插件。

应用场景

  • 异步处理:把所有消息保存在中间件中,等到需要处理的时候再出处理消息;

  • 流量销峰:短时间内访问量突然增加,使用mq,进行流量销峰,直接拒绝多余的请求;

  • 日志处理:

  • 应用程序解耦:A服务向B服务发送请求,B服务需要修改业务逻辑,A发送的请求全部保存在消息队列中先不处理,等到B服务器修改完成重新部署完成之后,再读取队列中的信息,对A服务器发送的请求进行处理。

1.4 RabbitMQ 中的概念模型

考试:

生产者:学生(提交试卷到FTP)

队列:FTP(保存试卷)

消费者:老师(从FTP上获取试卷,批改)

消息:试卷

消息模型

所有 MQ 产品从模型抽象上来说都是一样的过程:

消费者(consumer)订阅某个队列。生产者(producer)创建消息,然后发布到队列(queue)中,最后将消息发送到监听的消费者。

  • producing的意思是发送。一个发送消息的程序叫做producer

  • queue,即队列,它由RabbitMQ管理。尽管消息会在你的应用和RabbitMQ之间流过,但他们只被保存在队列中。队列没有边界限制,你想存多少消息就能存多少------它本质上是一个无限制的缓冲区。一个队列可以接收多个producer的消息,也可以被多个consumer读取。

  • consuming的意思类似于接收。一个等待接收消息的程序叫做consumer

消息流

RabbitMQ 基本概念

上面只是最简单抽象的描述,具体到 RabbitMQ 则有更详细的概念需要解释。上面介绍过 RabbitMQ 是 AMQP 协议的一个开源实现,所以其内部实际上也是 AMQP 中的基本概念:

RabbitMQ 内部结构

  1. Message

    消息,消息是不具名的,它由消息头和消息体组成。消息体是不透明的,而消息头则由一系列的可选属性组成,这些属性包括routing-key(路由键)、priority(相对于其他消息的优先权)、delivery-mode(指出该消息可能需要持久性存储)等。

  2. Publisher

    消息的生产者,也是一个向交换器发布消息的客户端应用程序。

  3. Exchange

    交换器,用来接收生产者发送的消息并将这些消息路由给服务器中的队列。

  4. Binding

    绑定,用于消息队列和交换器之间的关联。一个绑定就是基于路由键将交换器和消息队列连接起来的路由规则,所以可以将交换器理解成一个由绑定构成的路由表。

  5. Queue

    消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。

  6. Connection

    网络连接,比如一个TCP连接。

  7. Channel

    信道,多路复用连接中的一条独立的双向数据流通道。信道是建立在真实的TCP连接内地虚拟连接,不管是发布消息、订阅队列还是接收消息,这些动作都是通过信道完成。因为对于操作系统来说建立和销毁 TCP 都是非常昂贵的开销,所以引入了信道的概念,以复用一条 TCP 连接。

  8. Consumer

    消息的消费者,表示一个从消息队列中取得消息的客户端应用程序。

  9. Virtual Host

    虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / 。

  10. Broker

    表示消息队列服务器实体。

RabbitMQ 常用交换器

Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct(直联)、fanout(扇形)、topic(主题)、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:

生产者A/B ---> 队列A/队列B --->消费者A/B/C

direct 交换器

消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为"dog",则只转发 routing key 标记为"dog"的消息,不会转发"dog.puppy",也不会转发"dog.guard"等等。它是完全匹配、单播的模式。

fanout 交换器

每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。

topic 交换器

topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号"#"和符号"

"。#匹配0个或多个单词,

匹配不多不少一个单词。

二、RabbitMQ 安装

2.1 tar安装

2.1.1 安装文件准备

上传文件到对应文件夹下

  • rabbitmq-server-generic-unix-3.6.1.tar.xz

2.1.2 安装Erlang

由于RabbitMQ是基于**Erlang(面向高并发的语言)**语言开发,所以在安装RabbitMQ之前,需要先安装Erlang。

安装编辑工具

复制代码
yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

创建文件夹erlang

复制代码
makdir erlang
makdir rabbitmq

上传安装包并解压

复制代码
tar -zxvf otp_src_22.0.tar.gz

进入解压目录下,配置安装路径

复制代码
cd otp_src_22.0
​
./configure --prefix=/usr/local/erlang

安装

复制代码
make & make install

配置erlang环境变量

复制代码
echo 'export PATH=$PATH:/opt//erlang/bin' >> /etc/profile

刷新环境变量

复制代码
source /etc/profile

检验是否安装成功

复制代码
#输入命令检验是否安装成功,如下输出表示安装成功
erl
​
# 输入halt().命令退出
halt().

2.1.3 安装RabbitMQ

由于是tar.xz格式的所以需要用到xz,没有的话就先安装

复制代码
yum install -y xz

第一次解压,解压为tar

复制代码
/bin/xz -d rabbitmq-server-3.7.15.tar.xz

第二次解压

复制代码
tar -xvf  rabbitmq-server-3.7.15.tar

改名

复制代码
mv rabbitmq-server-3.7.15.tar  rabbitmq

配置环境变量

复制代码
echo 'export PATH=$PATH:/opt/rabbitmq/rabbitmq/sbin' >> /etc/profile

刷新环境变量

复制代码
source /etc/profile

2.2 rpm安装

tar包:压缩文件,windows中的zip包

rpm包:安装文件,windows中的安装包,.exe文件,自动处理软件之间的依赖关系

2.2.1 安装文件准备

2.2.2 安装Erlang

由于RabbitMQ是基于**Erlang(面向高并发的语言)**语言开发,所以在安装RabbitMQ之前,需要先安装Erlang。

执行rpm -ivh xxx.rpm安装

复制代码
yum -y install epel-release
​
yum -y install socat
​
rpm -ivh erlang-23.2.1-1.el7.x86_64.rpm
​
rpm -ivh rabbitmq-server-3.8.14-1.el7.noarch.rpm

https://pkgs.org/download/libcrypto.so.1.1(OPENSSL_1_1_0)(64bit)

2.3 Rabbitmq命令

启动

启动:

-detached在后台启动Rabbit

复制代码
rabbitmq-server -detached

停止:

复制代码
rabbitmqctl stop

状态:

复制代码
rabbitmqctl status

开启web插件

rabbitmq启动之后默认有很多插件可以使用∶

复制代码
 rabbitmq-plugins list
复制代码
rabbitmq-plugins enable rabbitmq_management

访问:http://127.0.0.1:15672/

默认账号密码:guest guest(这个账号只允许本机访问)

rabbitmq默认只能使用localhost访问,windows无法直接访问

添加管理员账号

角色:

administrator:管理员

managment:普通管理员

复制代码
# 添加用户
# rabbitmqctl add_user Username Password
rabbitmqctl add_user rabbitadmin 123456
​
# 分配用户标签
# rabbitmqctl set_user_tags User Tag #[administrator]:管理员标签
rabbitmqctl set_user_tags rabbitadmin administrator

控制台

创建交换机

默认

创建队列
绑定

交换机和队列做绑定

三、RabbitMQ工作模式

3.1 引入依赖关系

复制代码
        <!-- https://mvnrepository.com/artifact/com.rabbitmq/amqp-client -->
        <dependency>
            <groupId>com.rabbitmq</groupId>
            <artifactId>amqp-client</artifactId>
            <version>5.11.0</version>
        </dependency>

3.2 队列模式(消费者)

队列模式不需要生命交换机,使用交换机,交换机使用默认交换机(直连交换机,key=队列名字)

1. 简单队列

一个生产者对应一个消费者

获取连接

复制代码
package test.mq;
​
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
​
import java.io.IOException;
import java.util.concurrent.TimeoutException;
​
public class ConnectionUtil {
​
    public static Connection getConnection() {
        ConnectionFactory factory = new ConnectionFactory();
        factory.setHost("192.168.190.130");
        factory.setPort(5672);
        factory.setVirtualHost("/");
        factory.setUsername("sn");
        factory.setPassword("sn");
​
        Connection connection = null;
        try {
            connection = factory.newConnection();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        return connection;
    }
}
​

生产者

queueDeclare(name,durable,exclusive,autoDelete);

  • name: 队列的名称;

  • durable: 是否持久化;

  • exclusive: 是否独享、排外的;

  • autoDelete: 是否自动删除;

复制代码
/**
 * 〈简单队列——消息生产者〉
 */
public class Producer {
    private final static String QUEUE_NAME = "my-que";
​
    public static void main(String[] args) throws Exception {
        sendMessage();
    }
​
    public static void sendMessage() throws Exception {
        //1、获取连接
        Connection connection = ConnectionUtil.getConnection();
        //2、声明信道
        Channel channel = connection.createChannel();
        //3、声明(创建)队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //4、定义消息内容
        String message = "hello rabbitmq ";
        //5、发布消息
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println("发送消息:" + message );
        //6、关闭通道
        channel.close();
        //7、关闭连接
        connection.close();
    }
}
​

管理工具中查看消息

消费者

复制代码
/**
 * 〈消息消费者〉
 */
public class Customer {
​
    private final static String QUEUE_NAME = "my-que";
​
    public static void main(String[] args) throws Exception {
        getMessage();
​
    }
​
    public static void getMessage() throws Exception {
        //1、获取连接
        Connection connection = ConnectionUtil.getConnection();
        //2、声明通道
        Channel channel = connection.createChannel();
        //3、声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //4、定义队列的消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope,
                                       AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msgString = new String(body, "utf-8");
                System.out.println("接收的消息:" + msgString);
            }
        };
        //5、监听队列
     /*
         true:表示自动确认,只要消息从队列中获取,无论消费者获取到消息后是否成功消费,都会认为消息已经成功消费
         false:表示手动确认,消费者获取消息后,服务器会将该消息标记为不可用状态,等待消费者的反馈,
                如果消费者一直没有反馈,那么该消息将一直处于不可用状态,并且服务器会认为该消费者已经挂掉,不会再给其
                发送消息,直到该消费者反馈。
      */
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
}
​

2.Work模式

一个生产者,多个消费者,每个消费者获取到的消息唯一,默认轮询获取。

2.1 轮询分发

不关心速度,效率低,平均,123123123

生产者

复制代码
/**
 * 〈轮询分发——生产者〉
 */
public class Send {
    private static final String QUEUE_NAME = "my-que";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        for (int i = 0; i < 50; i++) {
            String msg = "hello " + i;
            System.out.println("[mq] send:" + msg);
            channel.basicPublish("", QUEUE_NAME, null, msg.getBytes());
            Thread.sleep(i * 20);
        }
        channel.close();
        connection.close();
    }
}

消费者

创建两个消费者

  • 消费者1:每接收一条消息后休眠1秒

  • 消费者2:每接收一条消息后休眠2秒

复制代码
/**
 * 〈消费者1〉
 */
public class Receive1 {
    private static final String QUEUE_NAME = "my-que";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel、
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] Receive1 msg:" + msg);
                try {
                    // 注:消费者2修改为2秒,其他一样
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = true;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }
​
}

结果

work模式默认轮询分发,将消息队列中的消息,依次发送给所有消费者。一个消息只能被一个消费者获取。

2.2 公平分发

消费者关闭自动应答,开启手动回执,消费者完成业务接口方法后可以告知消息队列处理完成,消息队列从队列中取一条消息发送给消费者。

效率高的消费者消费消息多。

复制代码
/*
 * 〈消费者1〉
 */
public class Receive1 {
    private static final String QUEUE_NAME = "my-que";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel、
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        
        channel.basicQos(0,1,false);
        //定义一个消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] Receive1 msg:" + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                    //手动回执
                    // 应答成功,basicAck(消息标识符,是否批量应答)
                    // channel.basicAck(envelope.getDeliveryTag(), true);
                    // 应答失败,basicReject(消息标识符,是否重新发送)
                    channel.basicReject(envelope.getDeliveryTag(), false);
                }
            }
        };
        boolean autoAck = fasle;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }
​
}

3.3 交换器模式(交换机)

在RabbitMQ中,交换器主要有四种类型:direct、fanout、topic、headers

3.发布/订阅模式(Fanout)

未支付的订单--付款:消息(用户编号,商品编号,付款金额)

  • queue-a:修改订单状态 1s

  • queue-b:发消息 1s

  • queue-c:发红包 1s

一个生产者发送的消息会被多个消费者获取(一样的)。发送到Fanout Exchange的消息都会被转发到与该Exchange绑定(binding)的所有的Queue上。这种模式不需要任何Routekey,需要提前将Exchange与Queue进行绑定,一个Exchange可以绑定多个Queue,一个Queue可以和多个Exchange绑定。如果接收到消息的Exchange没有与任何Queue绑定,则消息会丢失。

生产者

复制代码
/**
 * 〈订阅模式——生产者〉
 */
public class Send {
    private static final String EXCHANGE_NAME = "my-fanout-ex";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME,"fanout");//分发
        //发送消息
        String msg = "hello exchange";
        System.out.println("[mq] send:" + msg);
        channel.basicPublish(EXCHANGE_NAME, "", null, msg.getBytes());
        channel.close();
        connection.close();
    }
​
}

消费者

两个消费者绑定不同的队列,绑定相同的交换机

复制代码
/**
 * 〈消费者1〉
 */
public class Receive1 {
    private static final String QUEUE_NAME = "my-que-a";
    private static final String EXCHANGE_NAME = "my-fanout-ex";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //绑定到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        //定义一个消费这
        Consumer consumer = new DefaultConsumer(channel)
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] Receive1 msg:" + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }
​
}
复制代码
/**
 * 〈消费者2〉
 */
public class Receive1 {
    private static final String QUEUE_NAME = "my-que-b";
    private static final String EXCHANGE_NAME = "my-fanout-ex";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //绑定到交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, "");
        //定义一个消费这
        Consumer consumer = new DefaultConsumer(channel)
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println("[1] Receive1 msg:" + msg);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    System.out.println("[1] done");
                }
            }
        };
        boolean autoAck = false;
        channel.basicConsume(QUEUE_NAME, autoAck, consumer);
    }
​
}

两个消费者获得了同一条消息。一个消息从交换机同时发送给了两个队列中,监听这两个队列的消费者消费了这个消息;如果没有队列绑定交换机,则消息将丢失。因为交换机没有存储能力,消息只能存储在队列中。

4.路由模式(Direct)

任何发送到Direct Exchange的消息都会被转发到RouteKey指定的Queue,这种模式下不需要将Exchange进行任何绑定(binding)操作,消息传递时需要一个RouteKey,可以简单的理解为要发送到的队列名字。如果vhost中不存在该队列名,消息会丢失。

也就是让消费者有选择性的接收消息

生产者

复制代码
/**
 * 〈路由模式-消息发送者〉
 */
public class Send {
​
    public static final String EXCHANGE_NAME = "my-direct-ex";
    public static final String ROUTING_KEY = "rt-a";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, "direct");
        String msg = "route message ->" + ROUTING_KEY;
        System.out.println("对 " + ROUTING_KEY + " 发送消息:" + msg);
        channel.basicPublish(EXCHANGE_NAME, ROUTING_KEY, null, msg.getBytes());
        //关闭连接
        channel.close();
        connection.close();
    }
​
}

消费者

两个消费者,绑定相同的交换机,不同的队列,不一样的路由

复制代码
/**
 * 〈接收消息1〉
 */
public class Receive1 {
​
    public static final String QUEUE_NAME = "my-que-a";
    public static final String EXCHANGE_NAME = "my-direct-ex";
    public static final String ROUTING_KEY_A = "rt-a";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //设置预读取数
        channel.basicQos(1);
        //绑定交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY_A);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println(envelope.getRoutingKey() + " [1] Receive1 msg:" + msg);
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
​
}
复制代码
/**
 * 〈接收消息2〉
 */
public class Receive2 {
​
    public static final String QUEUE_NAME = "my-que-b";
    public static final String EXCHANGE_NAME = "my-direct-ex";
    public static final String ROUTING_KEY_A = "rt-a";
    public static final String ROUTING_KEY_B = "rt-b";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //设置预读取数
        channel.basicQos(1);
        //绑定交换机和路由器,可以绑定多个路由
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY_A);
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY_B);
        //定义消息消费者
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println(envelope.getRoutingKey() + " [2] Receive1 msg:" + msg);
            }
        };
        //接收消息
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
​
}

路由模式,是以路由规则为导向,引导消息存入符合规则的队列中。再由队列的消费者进行消费的。

5.主题模式(Topic)

上面的路由模式是根据路由key进行完整的匹配(完全相等才发送消息),这里的通配符模式通俗的来讲就是模糊匹配。在进行绑定时要提供一个该队列对应的主题。' # '表示0个或若干个关键字 ,' * '表示一个关键字。如果Exchange没有发现能够与RouteKey匹配的Queue,消息会丢失。

一个关键字是一个单词:root

复制代码
/**
 * 〈主题模式-消息发送者〉
 */
public class Send {
​
    public static final String EXCHANGE_NAME = "my-topic-ex";
    public static final String ROUTING_KEY = "rt-key";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明交换机
        channel.exchangeDeclare(EXCHANGE_NAME, "topic");
        String routingKey = ROUTING_KEY + ".publish";
        // String routingKey = ROUTING_KEY + ".add";
        // String routingKey = ROUTING_KEY + ".update";
        String msg = "route message ->" + routingKey;
        channel.basicPublish(EXCHANGE_NAME, routingKey, null, msg.getBytes());
        System.out.println("对 " + routingKey + " 发送消息:" + msg);
        //关闭连接
        channel.close();
        connection.close();
    }
​
}

消费者

复制代码
/**
 * 〈接收消息1〉
 */
public class Receive1 {
​
    public static final String QUEUE_NAME = "my-que-a";
    public static final String EXCHANGE_NAME = "my-topic-ex";
    public static final String ROUTING_KEY = "rt-key";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //设置预读取数
        channel.basicQos(1);
        //绑定交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY + ".add");
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY + ".update");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println(envelope.getRoutingKey() + " [1] Receive1 msg:" + msg);
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
​
}
复制代码
/**
 * 〈接收消息1〉
 */
public class Receive1 {
​
    public static final String QUEUE_NAME = "my-que-b";
    public static final String EXCHANGE_NAME = "my-topic-ex";
    public static final String ROUTING_KEY = "rt-key";
​
    public static void main(String args[]) throws Exception {
        //获取连接
        Connection connection = ConnectionUtil.getConnection();
        //获取channel
        Channel channel = connection.createChannel();
        //声明队列
        channel.queueDeclare(QUEUE_NAME, false, false, false, null);
        //设置预读取数
        channel.basicQos(1);
        //绑定交换机
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY + ".add");
        channel.queueBind(QUEUE_NAME, EXCHANGE_NAME, ROUTING_KEY + ".update");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                String msg = new String(body, "utf-8");
                System.out.println(envelope.getRoutingKey() + " [1] Receive1 msg:" + msg);
            }
        };
        channel.basicConsume(QUEUE_NAME, true, consumer);
    }
​
}

3.4 总结

  • 队列模式:关注是一个队列有几个消费者 ,发布者向队列发送消息(使用服务器默认得交换机,direct,key=队列名)

    • 简单队列模式:一个生产者一个队列一个消费者

    • Work模式:一个生产者一个队列多个消费者

      • 轮询分发:123123123消息评分分配

      • 公平分发:效率高得多分

  • 交换机模式:关注是接收消息得交换机类型 ,发布者向交换机发送消息

    • 发布订阅模式:fanout扇形交换机,没有routekey,所有和交换机绑定得队列都接收消息

    • 路由模式:direct直连交换机,有routekey(不能使用通配符),根据routekey对应队列接收消息

    • 主题模式:topic主题交换机,有routekey(使用通配符),根据routekey匹配对应队列接收消息

相关推荐
运维&陈同学2 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
时差9532 小时前
Flink Standalone集群模式安装部署
大数据·分布式·flink·部署
菠萝咕噜肉i2 小时前
超详细:Redis分布式锁
数据库·redis·分布式·缓存·分布式锁
只因在人海中多看了你一眼5 小时前
分布式缓存 + 数据存储 + 消息队列知识体系
分布式·缓存
zhixingheyi_tian8 小时前
Spark 之 Aggregate
大数据·分布式·spark
求积分不加C9 小时前
-bash: ./kafka-topics.sh: No such file or directory--解决方案
分布式·kafka
nathan05299 小时前
javaer快速上手kafka
分布式·kafka
谭震鸿13 小时前
Zookeeper集群搭建Centos环境下
分布式·zookeeper·centos
天冬忘忧18 小时前
Kafka 工作流程解析:从 Broker 工作原理、节点的服役、退役、副本的生成到数据存储与读写优化
大数据·分布式·kafka
IT枫斗者1 天前
如何解决Java EasyExcel 导出报内存溢出
java·服务器·开发语言·网络·分布式·物联网