RabbitMQ入门学习

RabbitMQ

MQ基本概念

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

  • MQ,消息队列,存储消息的中间件
  • 分布式系统通信两种方式:直接远程调用和借助第三方完成间接通信。
  • 发送方称为生产者,接收方称为消费者。

MQ的优势

优势:

  • 应用解耦
  • 异步提速
  • 削峰填谷

劣势:

  • 系统可用性降低
  • 系统复杂度提高
  • 一致性问题

RabbitMQ简介

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

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

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提供了6种工作模式:简单模式、work queues、Publish/Subscribe 发布与订阅模式、Routing路由模式、Topics主题模式、RPC远程调用模式(远程调用,不太算MQ;暂不作介绍)。

JMS

JMS即Java消息服务(JavaMessage Service)应用程序接口,是一个Java平台中关于面向消息中间件的API。

JMS是javaEE规范中的一种,类比JDBC。

很多消息中间件都实现了JMS规范,例如:ActiveMQ。RabbitMQ官方没有提供JMS的实现包,但是开源社区有。

RabbitMQ的安装和配置

1、安装Erlang环境支持

安装之前要安装一些必要的库:

apt-get install build-essential

apt-get install libncurses5-dev

apt-get install libssl-dev

apt-get install m4

apt-get install unixodbc unixodbc-dev

apt-get install freeglut3-dev libwxgtk2.8-dev

apt-get install xsltproc

apt-get install fop

apt-get install tk8.5

安装好后安装Erlang:

# apt-get install erlang

查看Erlang版本:

erl --version

2、安装RabbitMQ

直接安装

apt-get install rabbitmq-server

查看运行状态

service rabbitmq-server status

在这里插入图片描述

3、配置RabbitMQ

查看log文件:

cd /var/log/rabbitmq/

打开log文件:

vim rabbit@begoit-916-01.log

看到在config file(s)后面没有对应的配置文件,所以可以自己创建这个配置文件:

cd /etc/rabbitmq

vim rabbitmq.config

在配置文件中写入如下内容:

[{rabbit, [{loopback_users, []}]}].

这里的意思是开放使用,rabbitmq默认创建的用户guest,密码也是guest,这个用户默认只能是本机访问,localhost或者127.0.0.1,从外部访问需要添加上面的配置。

保存配置后重启服务:

service rabbitmq-server restart

4、安装插件

查看已安装插件:

rabbitmq-plugins list

安装插件:

rabbitmq-plugins enable rabbitmq_management

重启rabbitmq服务:

service rabbitmq-server restart

注意:需要在阿里云服务器配置安全策略中开放15672端口才可以使用web页面

通过http://ip:15672并使用guest,guest访问。

在这里插入图片描述

RabbitMQ角色

1.RabbitMQ的用户角色分类:
none、management、policymaker、monitoring、administrator
2.RabbitMQ各类角色描述:
none
不能访问 management plugin

management
用户可以通过AMQP做的任何事外加:
列出自己可以通过AMQP登入的virtual hosts
查看自己的virtual hosts中的queues, exchanges 和 bindings
查看和关闭自己的channels 和 connections
查看有关自己的virtual hosts的“全局”的统计信息,包含其他用户在这些virtual hosts中的活动。

policymaker
management可以做的任何事外加:
查看、创建和删除自己的virtual hosts所属的policies和parameters

monitoring
management可以做的任何事外加:
列出所有virtual hosts,包括他们不能登录的virtual hosts
查看其他用户的connections和channels
查看节点级别的数据如clustering和memory使用情况
查看真正的关于所有virtual hosts的全局的统计信息

administrator
policymaker和monitoring可以做的任何事外加:
创建和删除virtual hosts
查看、创建和删除users
查看创建和删除permissions
关闭其他用户的connections

3.创建用户并设置角色:
可以创建管理员用户,负责整个MQ的运维,例如:

rabbitmqctl add_user user_admin passwd_admin

赋予其administrator角色:

rabbitmqctl set_user_tags user_admin administrator

可以创建RabbitMQ监控用户,负责整个MQ的监控,例如:
#rabbitmqctl add_user user_monitoring passwd_monitor
赋予其monitoring角色:
#rabbitmqctl set_user_tags user_monitoring monitoring

可以创建某个项目的专用用户,只能访问项目自己的virtual hosts

rabbitmqctl add_user user_proj passwd_proj

赋予其monitoring角色:
#rabbitmqctl set_user_tags user_proj management

创建和赋角色完成后查看并确认:

rabbitmqctl list_users

4.RabbitMQ权限控制
默认virtual host:“/”
默认用户:guest
guest具有"/"上的全部权限,仅能有localhost访问RabbitMQ包括Plugin,建议删除或更改密码。可通过将配置文件中loopback_users置孔来取消其本地访问的限制:
[{rabbit, [{loopback_users, []}]}]

用户仅能对其所能访问的virtual hosts中的资源进行操作。这里的资源指的是virtual hosts中的exchanges、queues等,操作包括对资源进行配置、写、读。配置权限可创建、删除、资源并修改资源的行为,写权限可向资源发送消息,读权限从资源获取消息。比如:
exchange和queue的declare与delete分别需要exchange和queue上的配置权限
exchange的bind与unbind需要exchange的读写权限
queue的bind与unbind需要queue写权限exchange的读权限
发消息(publish)需exchange的写权限
获取或清除(get、consume、purge)消息需queue的读权限
对何种资源具有配置、写、读的权限通过正则表达式来匹配,具体命令如下:
set_permissions [-p ]
其中, 的位置分别用正则表达式来匹配特定的资源,如’^(amq.gen.*|amq.default)KaTeX parse error: Expected group after '^' at position 29: …的和默认的exchange,'^̲'不匹配任何资源

需要注意的是RabbitMQ会缓存每个connection或channel的权限验证结果、因此权限发生变化后需要重连才能生效。

为用户赋权:
$sudo rabbitmqctl set_permissions -p /vhost1 user_admin ‘.’ '.’ ‘.*’
该命令使用户user_admin具有/vhost1这个virtual host中所有资源的配置、写、读权限以便管理其中的资源
查看权限:
#rabbitmqctl list_user_permissions user_admin
Listing permissions for user “user_admin” …

#rabbitmqctl list_permissions -p /vhost1
Listing permissions in vhost “/vhost1” …

当连接超时时可能是防火墙禁止访问了,所以要开放该端口:

查询以开放端口:

firewall-cmd --list-ports

开放 某个指定端口:

firewall-cmd --zone=public --add-port=5672/tcp --permanent

重新加载:

firewall-cmd --reload

即可。

或者直接关闭防火墙:

systemctl stop firewalld(只执行这个,重启后不行,还必须执行systemclt disable firewalld)

RabbitMQ五种模式

1、简单模式

当生产者发送消息到交换机,交换机根据消息属性发送到队列,消费者监听绑定队列实现消息的接收和消费逻辑编写.简单模式下,强调的一个队列queue只被一个消费者监听消费.

应用场景:

手机短信,邮件单发

实现:

    //消费者
    @SneakyThrows
    @GetMapping("/hello-world")
    @ResponseBody
    public String producerHello() {
        //1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();

        //2、设置参数, 也可以在配置文件中设置
        connectionFactory.setHost("[ip地址]"); //默认为localhost
        connectionFactory.setPort(5672);
        connectionFactory.setVirtualHost("/test"); //设置虚拟机,默认为"/“
        connectionFactory.setUsername("youxin"); //默认为guest
        connectionFactory.setPassword("1234"); //默认为guest

        //3、创建连接connection
        Connection connection = connectionFactory.newConnection();
        //4、创建频道 channel;
        Channel channel = connection.createChannel();
        //5、创建队列queue
        /*
        * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments)
        *  1、queue:队列名称
        *  2、durable:是否持久化,如果为true,当mq重启后消息还在
        *  3、exclusive:
        *       * 是否独占,只能有一个消费者监听这个队列
        *       * 当connection关闭时,是否删除队列
        *  4、autoDelete: 没有消费者使用时是否自动删除队列
        *  5、arguments: 其他属性(构造参数)
        *
        * */
        //如果没有"hello-world"这个队列则会自动创建该队列
        channel.queueDeclare("hello-world", true, false, false, null);
        //6、发送消息

        /*
        basicPublish(String exchange, String routingKey, BasicProperties props, byte[] body)
        参数:
            1、exchange : 交换机名称,简单模式下会使用默认的""
            2、routingKey : 路由名称
            3、props : 其他配置信息,路由头等
            4、body : 消息体(队列中只能够传输字节类型)
         */
        channel.basicPublish("", "hello-world", null, "hello rabbitmq!".getBytes());

        //7、关闭连接
        channel.close();
        connection.close();
        return "send rabbitmq queue!";
    }

	//生产者
	@ResponseBody
    @GetMapping("/received")
    @SneakyThrows
    public String receivedMessageQueue() {
        //1、创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();

        //2、设置参数, 也可以在配置文件中设置
            connectionFactory.setHost("[ip地址]"); //默认为localhost
            connectionFactory.setPort(5672);
            connectionFactory.setVirtualHost("/test"); //设置虚拟机,默认为"/“
            connectionFactory.setUsername("youxin"); //默认为guest
            connectionFactory.setPassword("1234"); //默认为guest

        //3、创建连接connection
        Connection connection = connectionFactory.newConnection();
        //4、创建频道 channel;
        Channel channel = connection.createChannel();
        //5、创建队列queue
        /*
        * queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete,
                                 Map<String, Object> arguments)
        *  1、queue:队列名称
        *  2、durable:是否持久化,如果为true,当mq重启后消息还在
        *  3、exclusive:
        *       * 是否独占,只能有一个消费者监听这个队列
        *       * 当connection关闭时,是否删除队列
        *  4、autoDelete: 没有消费者使用时是否自动删除队列
        *  5、arguments: 其他属性(构造参数)
        *
        * */
        //如果没有"hello-world"这个队列则会自动创建该队列
        channel.queueDeclare("hello-world", true, false, false, null);

        //6、接收消息

        /*
        basicConsume(String queue, boolean autoAck, Consumer callback)
        参数:
        queue: 队列名称
        autoAck : 自动接收消息
        callback : 回调函数
         */

        Consumer consumer = new DefaultConsumer(channel) {
            //就相当于一个对调方法
            /*
            handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body)
            参数:
            consumerTag : 消费者标识
            envelope : 获取信息,交换机,路由key...
            properties : 配置信息
            body : 消息体

             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumerTag==>" + consumerTag);
                System.out.println("getExchange==>" + envelope.getExchange());
                System.out.println("getRoutingKey==>" + envelope.getRoutingKey());
                System.out.println("getDeliveryTag==>" + envelope.getDeliveryTag());
                System.out.println("properties==>" + properties.toString());
                System.out.println("body==>" + new String(body));
                System.out.println("=================================================");
            }
        };
        channel.basicConsume("hello-world", true, consumer);
        //因为是监听模式,所以可以不用关闭channel和connection
        //channel.close();
        //connection.close();
        return "received message!";
    }

2、工作模式

一个生产者,多个消费者,每个消费者获取到的消息唯一(消费者彼此竞争成为接收者)

生产者:发送消息到交换机
交换机:根据消息属性将消息发送给队列
消费者:多个消费者,同时绑定监听一个队列,之间形成了争抢消息的效果

应用场景:

抢红包、资源分配系统

基本实现:

	//生产者
	//work模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("/work-producer")
    public String workProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //如果没有"hello-world"这个队列则会自动创建该队列
        channel.queueDeclare("work", true, false, false, null);
        for (int i = 0; i < 100; i++) {
            channel.basicPublish("", "work", null, ("hello rabbitmq!--->" + i).getBytes());
        }
        //7、关闭连接
        channel.close();
        connection.close();
        return "send rabbitmq queue!";
    }

	//消费者
	//work模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("/work-consumer1")
    public String workConsumer1() {

        //创建频道
        Channel channel = connection.createChannel();
        //如果没有"hello-world"这个队列则会自动创建该队列
        channel.queueDeclare("work", true, false, false, null);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer1-received-body==>" + new String(body) + "channelNumber==>" + channel.getChannelNumber()) ;
                //进行手动应答
                /*
                 * 第一个参数:自动应答
                 * 第二个参数:false表示收到消息了
                 */
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("work", false, consumer);
        return "consumer1 received!";
    }

    //work模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("/work-consumer2")
    public String workConsumer2() {
        //创建频道
        Channel channel = connection.createChannel();
        //如果没有"hello-world"这个队列则会自动创建该队列
        channel.queueDeclare("work", true, false, false, null);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer2-received-body==>" + new String(body) + "channelNumber==>" + channel.getChannelNumber());
                //进行手动应答
                /*
                 * 第一个参数:自动应答
                 * 第二个参数:false表示收到消息了
                 */
                channel.basicAck(envelope.getDeliveryTag(),false);
            }
        };
        channel.basicConsume("work", false, consumer);
        return "consumer2 received!";
    }

实现结果:

在这里插入图片描述

3、发布\订阅者模式

一个生产者发送的消息会被多个消费者获取。

交换机为fanout

生产端:发送消息到交换机
交换机:由于是发布订阅模式,会将这个消息发送同步到后端所有与其绑定的队列
消息端:简单模式 1个队列绑定一个消费者 争抢模式 1个队列绑定多个消费者

应用场景
邮件的群发,广告的群发

基本实现:

	// 发布/订阅者模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("/publish-producer")
    public String publishProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //申明交换机
        /*
         * 第一个参数:交换机的名字
         * 第二个参数:交换机的类型
         *      如果使用的是发布订阅模式:只能写fanout
         */
        channel.exchangeDeclare("publish-exchange","fanout");

        //发送消息到交换机
        for (int i = 0; i < 100; i++) {
            channel.basicPublish("publish-exchange", "", null, ("hello rabbitmq!--->" + i).getBytes());
        }
        channel.close();
        connection.close();
        return "send rabbitmq exchange!";
    }

	//消费者
	//发布订阅模式:生产者没有将消息发送到队列而是发送到交换机,每个消费者都有自己的队列,每个队列都要绑定到交换机,消费者获取到生产者发送的信息是完整的
    @SneakyThrows
    @ResponseBody
    @GetMapping("/publish-consumer1")
    public String publishConsumer1() {
        Channel channel = connection.createChannel();
        //申明队列
        channel.queueDeclare("publish1", true, false, false, null);
        //申明交换机
        channel.exchangeDeclare("publish-exchange", "fanout");
        /*将队列绑定到交换机
         * 第一个参数:队列的名字
         * 第二个参数:交换机的名字
         * 第三个参数:路由的key
         */
        channel.queueBind("publish1", "publish-exchange", "");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer1--receivedMessage===>" + new String(body));
            }
        };
        channel.basicConsume("publish1", true, consumer);
        return "publish consumer1 received!";
    }

    @SneakyThrows
    @ResponseBody
    @GetMapping("/publish-consumer2")
    public String publishConsumer2() {
        Channel channel = connection.createChannel();
        //申明队列
        channel.queueDeclare("publish2", true, false, false, null);
        //申明交换机
        channel.exchangeDeclare("publish-exchange", "fanout");
        /*将队列绑定到交换机
         * 第一个参数:队列的名字
         * 第二个参数:交换机的名字
         * 第三个参数:路由的key
         */
        channel.queueBind("publish2", "publish-exchange", "");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer2--receivedMessage===>" + new String(body));
            }
        };
        channel.basicConsume("publish2", true, consumer);
        return "publish consumer2 received!";
    }

实现结果:

在这里插入图片描述

4、路由模式

将消息携带routing key和队列绑定的routing key,如果匹配上了,就把消息发送给队列。

基本实现:

	//生产者
	//路由模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("route-producer")
    public String routeProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //如果是路由模式,第二个参数只能为direct
        channel.exchangeDeclare("route-exchange", "direct");
        //发送消息
        for (int i = 0; i < 100; i++) {
            if (i % 2 == 0) {
                //如果是偶数,将消息发送到路由键为route1队列中
                channel.basicPublish("route-exchange", "route1", null, ("hello rabbitmq!--->" + i).getBytes());
            }else {
                //如果是奇数,将消息发送到路由键为route2队列中
                channel.basicPublish("route-exchange", "route2", null, ("hello rabbitmq!--->" + i).getBytes());
            }
        }
        channel.close();
        connection.close();
        return "send rabbitmq route!";
    }

	//消费者
	//路由模式:
    @SneakyThrows
    @ResponseBody
    @GetMapping("/route-consumer1")
    public String routeConsumer1() {
        Channel channel = connection.createChannel();
        //队列申明
        channel.queueDeclare("route-queue1", true, false, false, null);
        //交换机申明
        channel.exchangeDeclare("route-exchange", "direct");

        //绑定队列到交换机,接收偶数队列中的消息
        channel.queueBind("route-queue1", "route-exchange", "route1");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer1--receivedMessage===>" + new String(body));
            }
        };
        channel.basicConsume("route-queue1", true, consumer);
        return "route consumer1 received!";
    }

    @SneakyThrows
    @ResponseBody
    @GetMapping("/route-consumer2")
    public String routeConsumer2() {
        Channel channel = connection.createChannel();
        //队列申明
        channel.queueDeclare("route-queue2", true, false, false, null);
        //交换机申明
        channel.exchangeDeclare("route-exchange", "direct");

        //绑定队列到交换机,接收偶数队列中的消息
        channel.queueBind("route-queue2", "route-exchange", "route2");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("consumer2--receivedMessage===>" + new String(body));
            }
        };
        channel.basicConsume("route-queue2", true, consumer);
        return "route consumer2 received!";
    }

实现结果:

在这里插入图片描述

5、topic模式

非常类似于路由模式的结构,区别在于后端队列绑定交换机使用的路由key.

topic模式相当于对路由模式的升级,topic模式主要就是在匹配的规则上可以实现模糊匹配

*:只能匹配一个单词 如:user.* user.username
#:可以匹配一个到多个单词 如:user.# user.username.xbb

在这里插入图片描述

应用场景
实现多级传递的路由筛选工作.

基本实现:

	//生产者
	//topic模式
    @SneakyThrows
    @ResponseBody
    @GetMapping("topic-producer")
    public String topicProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
//        channel.queueDeclare("topic-queue", true, false, false, null);
        //申明交换机
        channel.exchangeDeclare("topic-exchange", "topic");
        for (int i = 0; i < 100; i++) {
            channel.basicPublish("topic-exchange", "hello.world.world", null, ("topic模式" + i).getBytes());
        }
        channel.close();
        connection.close();
        return "send rabbitmq topic!";
    }

	//消费者
	@SneakyThrows
    @ResponseBody
    @GetMapping("/topic-consumer1")
    public String topicConsumer1() {
        Channel channel = connection.createChannel();
        //申明队列
        channel.queueDeclare("topic-queue", true, false, false, null);
        //申明交换机
        channel.exchangeDeclare("topic-exchange", "topic");
        //绑定
        channel.queueBind("topic-queue", "topic-exchange", "hello.#");
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("topicConsumer1--hello--ReceivedMessage==>" + new String(body));
            }
        };

        channel.basicConsume("topic-queue", true, consumer);
        return "topic consumer1 received!";
    }

    @SneakyThrows
    @ResponseBody
    @GetMapping("/topic-consumer2")
    public String topicConsumer2() {
        Channel channel = connection.createChannel();
        //申明队列
        channel.queueDeclare("topic-queue", true, false, false, null);
        //申明交换机
        channel.exchangeDeclare("topic-exchange", "topic");
        //绑定
        channel.queueBind("topic-queue", "topic-exchange", "*.world");
        Consumer consumer = new DefaultConsumer(channel) {

            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("topicConsumer2--world--ReceivedMessage==>" + new String(body));
            }
        };

        channel.basicConsume("topic-queue", true, consumer);
        return "topic consumer2 received!";
    }

实现结果:

在这里插入图片描述

三种交换机:

direct(路由) —>对应路由模式 —>100%routing key匹配上了,交换机才会把数据发送给队列
fanout(发布订阅) —>对应发布订阅模式 —>只要队列发布了交换机,就会把数据发送给队列,不会去管routing key是
否匹配
topic(主题)—>对应主题模式 —>可以定义多级路径,可以进行模糊匹配。

RabbitMQ中的高级属性

1、confirm机制

放到队列中的消息,怎么保证一定成功的放入队列了呢?
confirm机制:只要消息放入队列成功,那么队列就一定会给反馈

基本实现:

	//生产者
	//confirm机制
    @SneakyThrows
    @ResponseBody
    @GetMapping("/confirm-producer")
    public String confirmProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //开启confirm消息机制
        channel.confirmSelect();
        //对消息进行监听
        channel.addConfirmListener(new ConfirmListener() {
            @Override
            public void handleAck(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息发送成功!");
            }

            @Override
            public void handleNack(long deliveryTag, boolean multiple) throws IOException {
                System.out.println("消息发送失败!");
            }
        });

        channel.queueDeclare("hello-world", true, false, false, null);
        channel.basicPublish("", "hello-world", null, "hello, world!".getBytes());
        //想要监听到发送的消息,不能够将channel和connection关闭
//        channel.close();
//        connection.close();
        return "send confirm rabbitmq message!";
    }

结果:

在这里插入图片描述

2、return机制

生产者发送消息的时候,如果交换机不存在,或者路由的key不存在,这时候就需要监听这种到达不了的消息。
注意:当前的队列中必须要有消费者存在

基本实现:

	//生产者
	//return机制
    @SneakyThrows
    @ResponseBody
    @GetMapping("return-producer")
    public String returnProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.addReturnListener(new ReturnListener() {

            /***
             * @author youxin
             * @date 2022-06-01 16:59
             * @param replyCode 队列相应给浏览器的状态码
             * @param replyText 状态码对于的文本信息
             * @param exchange 交换机的名字
             * @param routingKey 路由的key
             * @param properties 消息的属性
             * @param body 消息体的内容
             * @return void
             * @throws IOException
             * @since
             */
            @Override
            public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("监听到不可达的消息");
                System.out.println("状态码" + replyCode);
                System.out.println("文本信息" + replyText);
                System.out.println("交换机名字" + exchange);
                System.out.println("body内容" + new String(body));
            }
        });
        //这里的第三个参数如果设置为true,表示要监听不可达的消息进行处理
        //如果设置为false,那么队列会直接删除这个消息
        channel.basicPublish("topic-exchange", "route-key", true, null, "return机制".getBytes());
        return "return rabbitmq message!";
    }

结果:

在这里插入图片描述

3、消费端的限流问题

假设消费者挂了,消息全部堆积到队列里面,然后当消费者重新启动时,队列里的消息就全部发送过来,但是客户端没办法同时去处理那么多的消息
这种场景下就需要对消费者进行限流。

基本实现:

	//生产者
	//高级属性-限流问题
    @SneakyThrows
    @ResponseBody
    @GetMapping("limit-producer")
    public String limitProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("limit-test", true, false, false, null);
        for (int i = 0; i < 100; i++) {
            channel.basicPublish("", "limit-test", null, (i + "限流").getBytes());
        }
        channel.close();
        connection.close();
        return "send limit rabbitmq message!";
    }

	//消费者
	//限流消费者
    @SneakyThrows
    @ResponseBody
    @GetMapping("/limit-consumer1")
    public String limitConsumer1() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("limit-test", true, false, false, null);
        /*
         * 开启限流
         * 第一个参数:消息本身的大小,如果设置为0,那么表示对消息大小不限制
         * 第二个参数:一次性推送消息的最大数量,前提消息必须手动应答完成
         * 第三个参数:true:将设置应用到通道  false:只是当前消费者的策略
         */
        channel.basicQos(0, 5, false);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者1收到消息==>" + new String(body));
                try {
                    Thread.sleep(1000);
                }catch (Exception e) {
                    e.printStackTrace();
                }
                //进行手动应答
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //使用限流必须手动应答
        channel.basicConsume("limit-test", false, consumer);
        return "consumer1 received message!";
    }

    @SneakyThrows
    @ResponseBody
    @GetMapping("/limit-consumer2")
    public String limitConsumer2() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("limit-test", true, false, false, null);
        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者2收到消息==>" + new String(body));
                //进行手动应答
                channel.basicAck(envelope.getDeliveryTag(), false);
            }
        };
        //使用限流必须手动应答
        channel.basicConsume("limit-test", false, consumer);
        return "consumer2 received message!";
    }

实现结果:消费者1每隔一秒接收一条消息,并且总共只接收5条消息,剩下的消息全部交由消费者2接收。

在这里插入图片描述

4、ttl队列

给队列中的消息添加时间限制,如果超过时间限制这个消息就会被删除。

基本实现:

	//生产者
	//ttl消息队列:给队列中的消息添加时间限制,如果超过时间限制这个消息就会被删除。
    @SneakyThrows
    @ResponseBody
    @GetMapping("/ttl-producer")
    public String ttlProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        Map<String, Object> argus = new HashMap<>();
        //设置5秒过期时间
        argus.put("x-message-ttl", 5000);
        //这里因为设置了过期时间,所以不能够将持久化置为true
        channel.queueDeclare("ttl-queue", false, false, false, argus);
        for (int i = 0; i < 100; i++) {
            channel.basicPublish("", "ttl-queue", null, ("ttl-queue==>" + i).getBytes());
        }
        channel.close();
        connection.close();
        return "send ttl-message!";
    }

注意:在生产者端设置了ttl过期时间,则在消费者端同样需要设置ttl过期时间

结果:

在生产者发出消息的5秒后再通过消费者监听,消息过期,消费者无法获取消息。

5、死信队列

什么是死信队列

当消息在队列中变成死信之后、可以定义它重新push 到另外一个交换机上、这个交换机 也有自己对应的队列 这个队列就称为死信队列
满足死信的条件:

发送到队列中的消息被拒绝了
消息的ttl时间过期
队列达到了最大长度 再往里面放信息
当这个队列中如果有这个死信的时候、rabbitmq就会将这个消息自动发送到我们提前定义好的死信队列中去
基本实现:

	//生产者
	//死信队列
    @SneakyThrows
    @ResponseBody
    @GetMapping("/dlx-producer")
    public String dlcProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.basicPublish("ttl-dlx-exchange", "dlx.hello", false,null, "死信队列消息".getBytes());
        return "send dlx message!";
    }

	//消费者
	//死信队列消费者
    @SneakyThrows
    @ResponseBody
    @GetMapping("/dlx-consumer")
    public String dlxConsumer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        //绑定交换机
        channel.exchangeDeclare("ttl-dlx-exchange", "topic");
        //申明队列
        Map<String, Object> argus = new HashMap<>();
        argus.put("x-message-ttl", 5000);
        argus.put("x-dead-letter-exchange", "dlx-exchange");
        channel.queueDeclare("ttl-dlx-queue", false, false, false, argus);
        channel.queueBind("ttl-dlx-queue", "ttl-dlx-exchange", "dlx.*");

        //绑定死信队列
        channel.exchangeDeclare("dlx-exchange", "topic");
        //死信队列里面的arguments不设置属性
        channel.queueDeclare("dlx-queue", false, false, false, null);
        channel.queueBind("dlx-queue", "dlx-exchange", "dlx.*");

        Consumer consumer = new DefaultConsumer(channel) {
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者收到消息==>" + new String(body));
                System.out.println("获取消息的交换机名==>" + envelope.getExchange());
            }
        };

        channel.basicConsume("dlx-queue", true, consumer);
        return "dlx consumer received message!";
    }

结果:

在这里插入图片描述

6、延迟队列

延迟队列:即消息进入队列后不会被立即消费,而是到达指定时间后,才会被消费。

需求:下单后30分钟未支付,取消订单,回滚库存。

实现方式:

1、定时任务

2、延迟队列

在这里插入图片描述

在RabbitMQ中并没有提供延迟队列功能。

所以使用ttl+死信队列组合实现延迟队列的效果。

在这里插入图片描述

具体实现就是上面的死信队列的实现过程。

7、消费者端的手动签收和重回消息队列

消费者除了手动签收应答还可以拒绝接受消息,让消息重回队列,即消费者可以收到消息,但是拒绝签收,所以设置requeue为true后将把消息重回消息队列中。

基本实现:

	//生产者
	//消息手动签收和消息重回队列
    @SneakyThrows
    @ResponseBody
    @GetMapping("/refuse-producer")
    public String refuseProducer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("refuse-queue", false, false, false, null);
        channel.basicPublish("", "refuse-queue", null, "拒绝消息".getBytes());
        channel.close();
        connection.close();
        return "send refuse message!";
    }

	//消费者
	//拒绝接收消息和消息重入队列
    @SneakyThrows
    @ResponseBody
    @GetMapping("/refuse-consumer")
    public String refuseConsumer() {
        Connection connection = ConnectionUtils.getConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare("refuse-queue", false, false, false, null);
        //消费者申明
        Consumer consumer = new DefaultConsumer(channel){
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("消费者收到消息==>" + new String(body));
                //拒绝消息
                /*
                 * 第一个参数:自动应答
                 * 第二个参数:true拒绝所有消息,false则拒绝提供的消息
                 * 第三个参数:表示决绝签收之后这个消息是否要重回队列?
                 */
                channel.basicNack(envelope.getDeliveryTag(), false, true);
            }
        };
        //绑定消费者
        /*
         * 第一个参数:队列名字
         * 第二个参数:是否自动应答
         * 第三个参数:消费者
         */
        channel.basicConsume("refuse-queue", false, consumer);

        return "refuse consumer received message!";
    }

结果:消费者接收到消息后拒绝签收消息,又将消息返回到消息队列中,此时消息队列中的消息依旧存在并没有被消费。

在这里插入图片描述

8、保证消息的投递是成功的

1.消息的延迟投递来解决传递的可靠性
在这里插入图片描述
2.日志消息表实现可靠消息的传输
在这里插入图片描述

9、消息追踪

开启trace插件:

rabbitmq-plugins list 查看所有插件

rabbitmq-plugins enable rabbitmq_tracing 打开插件

这时在web管理界面的admin中可以看到trace选项,创建日志记录后,就可以对每条消息进行跟踪了

在这里插入图片描述

rabbitmq-plugins disable rabbitmq_tracing 关闭插件

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值