01_RabbitMQ安装及工作模式

news2024/11/14 1:22:04

一、消息队列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匹配对应队列接收消息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2156256.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

5.工欲善其事,必先利其器!收集金融数据你必须先做这个!

在正式从网络上获取数据并存储到我们的数据库之前&#xff0c;我们还需要做一些准备工作。其中最重要的无疑是把Python环境配置好。 你可以不好好学习Python&#xff0c;毕竟我后边会一步步教大家&#xff0c;也会提供现成的Python脚本。但是你必须得在你的电脑上把Python安装…

YOLOv10改进,YOLOv10替换主干网络为PP-HGNetV2(百度飞桨视觉团队自研,独家手把手教程,助力涨点)

摘要 PP-HGNetV2(High Performance GPU Network V2) 是百度飞桨视觉团队自研的 PP-HGNet 的下一代版本,其在 PP-HGNet 的基础上,做了进一步优化和改进,最终在 NVIDIA GPU 设备上,将 “Accuracy-Latency Balance” 做到了极致,精度大幅超过了其他同样推理速度的模型。其在…

如何用ChatGPT制作一款手机游戏应用

有没有想过自己做一款手机游戏&#xff0c;并生成apk手机应用呢&#xff1f;有了人工智能&#xff0c;这一切就成为可能。今天&#xff0c;我们就使用ChatGPT来创建一个简单的井字棋游戏&#xff08;Tic-Tac-Toe&#xff09;&#xff0c;其实这个过程非常轻松且高效。 通过Cha…

828 华为云征文|华为 Flexus 云服务器搭建萤火商城 2.0

在今天这个意义非凡的日子&#xff0c;我怀揣着满心的期待与憧憬&#xff0c;毅然踏上了利用华为 Flexus 云服务器搭建轻量级、高性能、前后端分离的电商系统萤火商城 2.0 的征程。这一旅程&#xff0c;注定充满了挑战与惊喜&#xff0c;犹如在浩瀚的数字海洋中探索未知的宝藏。…

java-----方法

目录 什么是方法? 方法的作用&#xff1f; 方法的格式: 方法的重载: 方法的内存原理 方法的值传递: 什么是方法? :程序中最小的执行单元(要么全执行,要么全不执行) public class methoddeom3 {public static void main(String[] args) {System.out.println("hello…

旧系统迁移新框架:FastAPI 的 WSGIMiddleware 让过程变得简单

在现代 Web 开发中&#xff0c;我们经常需要将新的技术与现有的系统整合。FastAPI&#xff0c;作为一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;提供了与 WSGI 应用集成的能力&#xff0c;这使得 Django、Flask 等传统 Python Web 框架可以与 FastAPI…

五种数据库特性对比(Redis/Mysql/SQLite/ES/MongoDB)

做后端开发的程序员基本都要学会数据库的相关知识。 1、关系型数据 今天就着这段时间了解大模型的事需要牵扯到是我们接触最多的、也是入门后端必学的关系型数据库。在关系型数据库中&#xff0c;数据以表的形式进行组织和存储&#xff0c;每个表就像一个 Excel 表格&#xf…

Python学习——【4.4】数据容器(序列)的切片

文章目录 【4.4】数据容器&#xff08;序列&#xff09;的切片一、了解什么是序列二、掌握序列的切片操作 【4.4】数据容器&#xff08;序列&#xff09;的切片 一、了解什么是序列 序列是指&#xff1a;内容连续、有序&#xff0c;可使用下标索引的一类数据容器。 列表、元组…

CSS 的继承性、层叠性与权重问题解析

目录 非 VIP 用户可前往公众号进行免费阅读 继承性 层叠性 CSS的权重问题 如果权重一样,以后出现的为准 以权重大的为准 没有选中,权重为0,就近原则 权重只和css顺序有关 非 VIP 用户可前往公众号进行免费阅读 CSS 的继承性、层叠性与权重问题解析本文主要介绍了 C…

EnvironmentError: [Errno 28] No space left on device - 完美解决方法

&#x1f6a8;EnvironmentError: [Errno 28] No space left on device - 完美解决方法&#x1f4a1; &#x1f6a8;EnvironmentError: [Errno 28] No space left on device - 完美解决方法&#x1f4a1;摘要引言正文1. 错误解析&#xff1a;为什么会出现“No space left on dev…

html实现TAB选项卡切换

<!DOCTYPE html> <html> <head> <title>选项卡示例</title> <style> .tabs { overflow: hidden; /* 防止选项卡溢出容器 */ border: 1px solid #ccc; background-color: #f1f1f1; } .tab-links { margin: 0; padding: 0; l…

c++----io流

提示&#xff1a;以下 是本篇文章正文内容&#xff0c;下面案例可供参考 1.标准io流 (1)数据的循环输入 对于内置类型&#xff1a;cin和cout直接使用&#xff0c;c已经重载了 (2)对于自定义类型&#xff1a; 需要我们自己对类型进行重载 2.文件io流 ifstream ifile(只输入…

JavaEE: 深入探索TCP网络编程的奇妙世界(四)

文章目录 TCP核心机制TCP核心机制四: 滑动窗口为啥要使用滑动窗口?滑动窗口介绍滑动窗口出现丢包咋办? TCP核心机制五: 流量控制 TCP核心机制 上一篇文章 JavaEE: 深入探索TCP网络编程的奇妙世界(三) 书接上文~ TCP核心机制四: 滑动窗口 为啥要使用滑动窗口? 之前我们讨…

鸿蒙next 带你玩转鸿蒙拍照和相册获取图片

前言导读 各位网友和同学&#xff0c;相信大家在开发app的过程中都有遇到上传图片到服务器的需求&#xff0c;我们一般是有两种方式&#xff0c;拍照获取照片或者调用相册获取照片&#xff0c;今天我们就分享一个小案例讲一下这两种情况的实现。废话不多说我们正式开始 效果图…

Linux 环境永久更换国内pip镜像源地址

1、PYPI国内源路径 &#xff08;清华镜像地址&#xff09; https://pypi.tuna.tsinghua.edu.cn/simple/ &#xff08;中科大镜像地址&#xff09;或者 https://pypi.mirrors.ustc.edu.cn/simple/ &#xff08;阿里云镜像地址&#xff09;或者 https://mirrors.aliyun.com/py…

240922-局域网内通过SSH与SFTP访问RHEL服务器

要通过SFTP&#xff08;安全文件传输协议&#xff09;在局域网内访问一台RHEL服务器&#xff0c;您需要确保以下步骤都已经正确完成&#xff1a; A. 在RHEL服务器上配置SFTP服务 RHEL默认通过sshd服务提供SFTP功能&#xff0c;SFTP使用SSH协议进行文件传输&#xff0c;因此需要…

JAVA自助高效安全无人台球茶室棋牌室系统小程序源码

​探索“自助高效安全无人台球茶室棋牌室系统”的奇妙之旅 &#x1f3b1;&#x1f375;&#x1f3b2; &#x1f50d; 初见惊艳&#xff1a;未来娱乐新体验 &#x1f50d; 走进这家无人值守的台球茶室棋牌室&#xff0c;第一感觉就像是穿越到了未来&#xff01;没有繁琐的前台登…

tornado

Tornado通过使用非阻塞网络1/0&#xff0c;可以扩展到数以万计的开放链接&#xff0c;非常适合 长时间轮询&#xff0c;WebSockets和其他需要与每个用户建立长期连接的应用程序。 特点 注重性能优越&#xff0c;速度快解决高并发异步非阻塞websockets 长连接内嵌了HTTP服务器…

jdk11特性介绍

JDK 11&#xff08;也称为Java 11&#xff09;是Java平台的一个重要版本&#xff0c;它引入了许多新特性和改进&#xff0c;旨在提高开发者的生产力和Java平台的性能。以下是一些JDK 11的主要特性&#xff1a; 局部变量类型推断&#xff08;Local-Variable Syntax for Lambda P…

VMWare虚拟机安装CentOS-7-x86_64-DVD-1611操作系统

VMWare虚拟机安装CentOS7操作系统 1、虚拟机准备打开VMware单机创建新的虚拟机典型与自定义安装选择虚拟机硬件兼容性选择安装程序光盘映射文件(iso)选择客户机操作系统命名虚拟机处理器配置内存配置网络连接类型I/O类型选择磁盘类型选择磁盘指定磁盘的最大磁盘大小磁盘名称我们…