Rabbitmq中间件入门

1.1MQ概述

MQ全称Message Queue(消息队列),是在消息的传输过程中保存消息的容器。多用于分布式系统之间的通信。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。由于RabbitMq综合能力强劲,所以本文主要介绍Rabbitmq。

        应用之间的远程调用是系统直接调用系统

        加入MQ之后用用之间的调用是把调用的信息发送给中间件

1.2MQ的优势

(1)应用解耦

MQ相当于一个中介,生产方通过MQ与消费方交互,它将应用程序进行解耦。

系统的耦合度越高,容错就越低,可维护性就越低。

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

(2)任务异步处理

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

 一个下单操作耗时:20+300+300+300 = 920ms,用户点击下单按钮后,需要等待920ms才能得到下单的响应,太慢!

 用户点击下单完成按钮后只需要等待25ms就能得到下单的响应,提升用户体验和系统的吞吐量。

(3)削峰填谷

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

 消息被MQ保存起来个,然后系统就可以按照自己的笑给能力来消费,比如每秒1000个消息,这样慢慢写入数据库,这样就不会卡死数据库了。

 但是使用MQ之后,限制消费消息的negligible为100,但是这样一来,高峰期产生的数据就会被挤压在MQ中,高峰就被削掉了。但是因为消息的积压,在高峰过后的一段时间内,消费消息的速度还是会维持在1000PS,直到消费完所有的积压的消息,这就叫做填谷。

1.3MQ的劣势

系统可用性降低

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

系统的复杂度提高

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

一致性问题

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

1.4AMQP和JMS

实现MQ的大致有两种方式:AMQP和JMS。

AMQP

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

 JMS

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

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

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

AMQP与JMS的区别

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

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

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

1.5 RabbitMQ

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

 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模式,Publicsh/Subscribe发布与订阅模式,Routing路由模式,Topics主题模式,RPC远程调用模式(远程调用不算MQ,暂不介绍);

 2.安装及配置RabbitMQ

(1)安装依赖环境

本次采用cegnos7来安装,首先在线安装依赖环境:

yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make
gcc gcc-c++ kernel-devel m4 ncurses-devel tk tc xz

(2)安装Erlang

# 安装
rpm -ivh erlang-18.3-1.el7.centos.x86_64.rpm

(3)安装RabbitMQ

# 安装
rpm -ivh socat-1.7.3.2-1.1.el7.x86_64.rpm --force --nodeps
# 安装
rpm -ivh rabbitmq-server-3.6.5-1.noarch.rpm

(4)开启管理界面及配置

# 开启管理界⾯
rabbitmq-plugins enable rabbitmq_management
# 修改默认配置信息
vim /usr/lib/rabbitmq/lib/rabbitmq_server-3.6.5/ebin/rabbit.app
# ⽐如修改密码、配置等等,例如:loopback_users 中的 <<"guest">>,只保留guest

(5)启动

service rabbitmq-server start # 启动服务
service rabbitmq-server stop # 停⽌服务
service rabbitmq-server restart # 重启服务

设置配置文件

cd /usr/share/doc/rabbitmq-server-3.6.5/
cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config

(6)配置虚拟主机及用户

        6.1.用户角色

RabbitMQ在安装好后,可以访问 http://ip地址:15672 ;其⾃带了guest/guest的⽤户名和密码;如 果需要创建⾃定义⽤户;那么也可以登录管理界⾯后,如下操作:

 

 ⻆⾊说明:

1、 超级管理员(administrator) 可登陆管理控制台,可查看所有的信息,并且可以对⽤户,策略(policy)进⾏操作。

2、 监控者(monitoring) 可登陆管理控制台,同时可以查看rabbitmq节点的相关信息(进程数,内存使⽤情况,磁盘使⽤情况等)

3、 策略制定者(policymaker) 可登陆管理控制台, 同时可以对policy进⾏管理。但⽆法查看节点的相关信息(上图红框标识的部分)。

4、 普通管理者(management) 仅可登陆管理控制台,⽆法看到节点信息,也⽆法对策略进⾏管理。

5、 其他 ⽆法登陆管理控制台,通常就是普通的⽣产者和消费者。

6.2Virtual Hosts配置

像mysql拥有数据库的概念并且可以指定⽤户对库和表等操作的权限。RabbitMQ也有类似的权限管理; 在RabbitMQ中可以虚拟消息服务器Virtual Host,每个Virtual Hosts相当于⼀个相对独⽴的RabbitMQ 服务器,每个VirtualHost之间是相互隔离的。exchange、queue、message不能互通。 相当于mysql 的db。Virtual Name⼀般以/开头。

6.2.1创建Virtual Hosts

6.2.2设置Virtual Hosts权限

 

 

 3.RabbitMQ入门

简单模式

 上图模型中有以下概念:

        P:生产者,也就是要发送消息的程序

        C:消费者,消息的接受者,会一直等待消息的到来

        queue:消息队列,途中红色部分,类似一个邮箱,可以缓存消息;生产者向其中投递消息,消费者从其中取出消息

3.1搭建示例工程

创建maven工程,向pom.xml文件中添加如下依赖:

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

3.2编写生产者

package com.kkb.rabbitmq.simple;

import com.kkb.rabbitmq.utils.ConnectionUtils;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;

public class Producer {
    public static final String QUEUE_NAME = "simple_queue";

    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(QUEUE_NAME, true, false, false, null);
        // 要发送的信息
        String message = "你好;小兔子!";
        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish("", QUEUE_NAME, null, message.getBytes());
        System.out.println("已发送消息:" + message);
        // 关闭资源
        channel.close();
        connection.close();
    }
}

执行完上述的消息之后,可以登陆rabbitmq的管理控制台,可以发现队列和其他消息。

 3.3编写消费者

package com.kkb.rabbitmq.simple;

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

import java.io.IOException;

public class Consumer {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.QUEUE_NAME, true, false, false, null);
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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("接收到的消息为:" + new String(body, "utf-8"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.QUEUE_NAME, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

编写connection工具类

package com.kkb.rabbitmq.utils;

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

public class ConnectionUtils {
    public static Connection getConnection() throws Exception{
        //创建连接工厂
        ConnectionFactory connectionFactory = new ConnectionFactory();
        //主机地址;默认为 localhost
        connectionFactory.setHost("192.168.92.128");
        //连接端口;默认为 5672
        connectionFactory.setPort(5672);
        //虚拟主机名称;默认为 /
        connectionFactory.setVirtualHost("/xzk");
        //连接用户名;默认为guest
        connectionFactory.setUsername("zxl");
        //连接密码;默认为guest
        connectionFactory.setPassword("zxl");
        //创建连接
        Connection connection = connectionFactory.newConnection();
        return connection;
    }
}

4.RabbitMQ工作模式

4.1Work queues 工作队列模式

4.1.1模式说明

work queue 与简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。

应用场景:对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。

新建一个工程,将上一个场景的消费者多复制一个就是work queue模式。 

启动两个消费者,然后启动生产者发送消息,到IDEA的两个消费者对应的控制台查看是否竞争性的接收消息。

4.2订阅模式概述

 前面两个案例中只有三个角色:        

在订阅者模式中多了一个exchange角色,而且过程略有变化:

        P:生产者,也就是要发送消息的程序,但这个模式中将消息发送给X(交换机)

        C:消费者,消息的接受者,会一直等待消息的到来

        queue:消息队列,途中红色部分,类似一个邮箱,可以缓存消息;

        Exchange:交换机,图中的X。一方面,接收生产者发送的消息。另一方面,知道如何处理消 息,例如递交给某个特别队列、递交给所有队列、或是将消息丢弃。到底如何操作,取决于 Exchange的类型。Exchange有常见以下3种类型:

        Fanout:广播,将消息交给所有绑定到交换机的队列

        Direct:定向,把消息交给符合指定routing key 的队列

        Topic:通配符,把消息交给符合routing pattern(路由模式) 的队列

Exchange(交换机)只负责转发消息,不具备存储消息的能力,因此如果没有任何队列与Exchange绑定,或者没有符合路由规则的队列,那么消息将会丢失!

4.3. Publish/Subscribe发布与订阅模式

4.3.1模式说明

 发布订阅模式: 1、每个消费者监听自己的队列。 2、生产者将消息发给broker,由交换机将消息转发 到绑定此交换机的每个队列,每个绑定交换机的队列都将接收 到消息

代码

生产者:

package com.kkb.rabbitmq.ps;

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

public class Producer {
    public static final String FANOUT_EXCHANGE = "fanout_exchange";
    public static final String FANOUT_QUEUE_1 = "fanout_queue_1";
    public static final String FANOUT_QUEUE_2 = "fanout_queue_2";

    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        /**
         * 声明交换机
         * 参数1:交换机名称
         * 参数2:交换机类型,fanout,topic,direct,headers
         */
        channel.exchangeDeclare(FANOUT_EXCHANGE, BuiltinExchangeType.FANOUT);
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(FANOUT_QUEUE_1, true, false, false, null);
        channel.queueDeclare(FANOUT_QUEUE_2, true, false, false, null);

        //队列绑定交换机
        channel.queueBind(FANOUT_QUEUE_1,FANOUT_EXCHANGE,"");
        channel.queueBind(FANOUT_QUEUE_2,FANOUT_EXCHANGE,"");
        for (int i=1;i<=10;i++) {
            // 要发送的信息
            String message = "你好;小兔子!---fanout queue ----" + i;
            /**
             * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
             * 参数2:路由key,简单模式可以传递队列名称
             * 参数3:消息其它属性
             * 参数4:消息内容
             */
            channel.basicPublish(FANOUT_EXCHANGE, "", null, message.getBytes());
            System.out.println("已发送消息:" + message);
        }
            // 关闭资源
            channel.close();
            connection.close();


    }
}

消费者1:

package com.kkb.rabbitmq.ps;

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

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_1, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.FANOUT_QUEUE_1,Producer.FANOUT_EXCHANGE,"");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_1, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

消费者2:

package com.kkb.rabbitmq.ps;

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

import java.io.IOException;

public class Consumer2 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.FANOUT_QUEUE_2, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.FANOUT_QUEUE_2,Producer.FANOUT_EXCHANGE,"");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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("消费者2---接收到的消息为:" + new String(body, "utf-8"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.FANOUT_QUEUE_2, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

4.3.3小结

交换机需要与队列进行绑定,绑定之后,一个消息可以被多个消费者收到。

发布订阅模式与工作队列模式的区别:

(1)工作队列模式不用定义交换机,而发布订阅模式需要定义交换机。

(2)发布订阅模式的生产方式面向交换机发送消息,工作队列模式的生产方式面向队列发送消息(底层使用默认的交换机)

(3)发布订阅模式需要设置队列和交换机的绑定,工作队列模式不需要设置,实际上工作队列模式会将队列绑定到默认的交换机。

4.4Routing路由模式

路由模式的特点:

        队列与交换机的绑定不能是任意绑定了,而使需要指定一个Routingkey(路由key)

        消息的发送方在 向 Exchange发送消息时,也必须指定消息的 RoutingKey 。

        Exchange不再把消息交给每一个绑定的队列,而是根据消息的 Routing Key 进行判断,只有队列 的 Routingkey 与消息的 Routing key 完全一致,才会接收到消息。

 P:生产者,向Exchange发送消息,发送消息时,会指定一个routing key。

X:Exchange(交换机),接收生产者的消息,然后把消息递交给 与routing key完全匹配的队列

C1:消费者,其所在队列指定了需要routing key 为 error 的消息

C2:消费者,其所在队列指定了需要routing key 为 info、error、warning 的消息

4.4.2代码

生产者:

package com.kkb.rabbitmq.routing;

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

public class Producer {
    public static final String DIRECT_EXCHANGE = "direct_exchange";
    public static final String DIRECT_QUEUE_INSERT = "direct_queue_insert";
    public static final String DIRECT_QUEUE_UPDATE = "direct_queue_update";

    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        /**
         * 声明交换机
         * 参数1:交换机名称
         * 参数2:交换机类型,fanout,topic,direct,headers
         */
        channel.exchangeDeclare(DIRECT_EXCHANGE, BuiltinExchangeType.DIRECT);
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(DIRECT_QUEUE_INSERT, true, false, false, null);
        channel.queueDeclare(DIRECT_QUEUE_UPDATE, true, false, false, null);

        //队列绑定交换机
        channel.queueBind(DIRECT_QUEUE_INSERT,DIRECT_EXCHANGE,"insert");
        channel.queueBind(DIRECT_QUEUE_UPDATE,DIRECT_EXCHANGE,"update");

            // 要发送的信息
            String message = "新增商品消息!---路由模式 ---- insert";
            /**
             * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
             * 参数2:路由key,简单模式可以传递队列名称
             * 参数3:消息其它属性
             * 参数4:消息内容
             */
            channel.basicPublish(DIRECT_EXCHANGE, "insert", null, message.getBytes());
            System.out.println("已发送消息:" + message);
            // 要发送的信息
            message = "修改商品消息!---路由模式 ---- update";
            /**
             * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
             * 参数2:路由key,简单模式可以传递队列名称
             * 参数3:消息其它属性
             * 参数4:消息内容
             */
            channel.basicPublish(DIRECT_EXCHANGE, "update", null, message.getBytes());
            System.out.println("已发送消息:" + message);
            // 关闭资源
            channel.close();
            connection.close();


    }
}

消费者1:

package com.kkb.rabbitmq.routing;

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

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.DIRECT_EXCHANGE, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.DIRECT_QUEUE_INSERT, Producer.DIRECT_EXCHANGE,"insert");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.DIRECT_QUEUE_INSERT, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

消费者2:

package com.kkb.rabbitmq.routing;

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

import java.io.IOException;

public class Consumer2 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.DIRECT_QUEUE_UPDATE, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.DIRECT_QUEUE_UPDATE, Producer.DIRECT_EXCHANGE,"");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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("消费者2---接收到的消息为:" + new String(body, "utf-8"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.DIRECT_QUEUE_UPDATE, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

4.4.4小结:

Routing模式队列在绑定交换机是需要指定Routingkey,消息会发送给符合Routingkey的队列。

4.5Topics通配符模式

4.5.1模式说明

Topic 类型与 Direct 相比,都是可以根据 RoutingKey 把消息路由到不同的队列。只不过 Topic 类型 Exchange 可以让队列在绑定 Routing key 的时候使用通配符!

Routingkey 一般都是有一个或多个单词组成,多个单词之间以”.”分割,例如: item.insert

通配符规则:

# :匹配一个或多个词

* :匹配不多不少恰好1个词

举例:

item.# :能够匹配 item.insert.abc 或者 item.insert

item.* :只能匹配 item.insert

 

 图解:

红色Queue:绑定的是 usa.# ,因此凡是以 usa. 开头的 routing key 都会被匹配到

黄色Queue:绑定的是 #.news ,因此凡是以 .news 结尾的 routing key 都会被匹配

4.5.2代码

 生产者:

package com.kkb.rabbitmq.topic;

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

public class Producer {
    public static final String TOPIC_EXCHANGE = "topic_exchange";
    public static final String TOPIC_QUEUE_ALL = "topic_queue_all";
    public static final String TOPIC_QUEUE_INSERT_UPDATE = "topic_queue_insert_update";

    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();
        /**
         * 声明交换机
         * 参数1:交换机名称
         * 参数2:交换机类型,fanout,topic,direct,headers
         */
        channel.exchangeDeclare(TOPIC_EXCHANGE, BuiltinExchangeType.TOPIC);

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

        //发送消息
        message = "修改商品。 topic模式:routing key item.update";
        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish(TOPIC_EXCHANGE, "item.update", null, message.getBytes());
        System.out.println("已发送消息:" + message);

        //发送消息
        message = "删除商品。 topic模式:routing key item.delete";
        /**
         * 参数1:交换机名称,如果没有指定则使用默认Default Exchage
         * 参数2:路由key,简单模式可以传递队列名称
         * 参数3:消息其它属性
         * 参数4:消息内容
         */
        channel.basicPublish(TOPIC_EXCHANGE, "item.delete", null, message.getBytes());
        System.out.println("已发送消息:" + message);
            // 关闭资源
            channel.close();
            connection.close();


    }
}

消费者1:

package com.kkb.rabbitmq.topic;

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

import java.io.IOException;

public class Consumer1 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();

        //声明交换机
        channel.exchangeDeclare(Producer.TOPIC_EXCHANGE,BuiltinExchangeType.TOPIC);

        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_ALL, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.TOPIC_QUEUE_ALL, Producer.TOPIC_EXCHANGE,"item.*");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_ALL, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

消费者2:

package com.kkb.rabbitmq.topic;

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

import java.io.IOException;

public class Consumer2 {
    public static void main(String[] args) throws Exception{
        Connection connection = ConnectionUtils.getConnection();
        // 创建频道
        Channel channel = connection.createChannel();

        //声明交换机
        channel.exchangeDeclare(Producer.TOPIC_EXCHANGE,BuiltinExchangeType.TOPIC);

        // 声明(创建)队列
        /**
         * 参数1:队列名称
         * 参数2:是否定义持久化队列
         * 参数3:是否独占本次连接(只能有一个consumer监听这个队列)
         * 参数4:是否在不使用的时候自动删除队列,当没有consumer时自动删除
         * 参数5:队列其它参数
         */
        channel.queueDeclare(Producer.TOPIC_QUEUE_INSERT_UPDATE, true, false, false, null);
        //队列绑定交换机
        channel.queueBind(Producer.TOPIC_QUEUE_INSERT_UPDATE, Producer.TOPIC_EXCHANGE,"item.update");
        channel.queueBind(Producer.TOPIC_QUEUE_INSERT_UPDATE, Producer.TOPIC_EXCHANGE,"item.insert");
        //接收消息
        DefaultConsumer consumer = new DefaultConsumer(channel){
            /**
             * 接收到消息执行的回调
             * consumerTag 消息者标签,在channel.basicConsume时候可以指定
             * envelope 消息包的内容,可从中获取消息id,消息routingkey,交换机,消息和重
             传标志(收到消息失败后是否需要重新发送)
             * properties 属性信息
             * body 消息
             */
            @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("消费者2---接收到的消息为:" + new String(body, "utf-8"));
            }
        };
        /**
         * 参数一:队列名称
         * 参数2:是否自动确认,设置为true表示消息接收到自动向mq回复收到了,mq接收到回复会删除消息,设为false则需要手动确认
         * 参数3:消息接收到后回调
         */
        channel.basicConsume(Producer.TOPIC_QUEUE_INSERT_UPDATE, true, consumer);
        // 关闭资源
        //channel.close();
        //connection.close();
    }
}

4.5.3测试

启动所有消费者,然后使用生产者发送消息;在消费者对应的控制台可以查看到生产者发送对应 routing key对应队列的消息;到达按照需要接收的效果;并且这些routing key可以使用通配符。

4.5.4小结

Topic主题模式可以实现 Publish/Subscribe发布与订阅模式 和 Routing路由模式 的功能;只是Topic 在配置routing key 的时候可以使用通配符,显得更加灵活。

4.6模式总结

RabbitMQ工作模式:

1、简单模式 HelloWorld 一个生产者、一个消费者,不需要设置交换机(使用 默认的交换机)

2、工作队列模式 Work Queue 一个生产者、多个消费者(竞争关系),不需要设置交换机(使用默认 的交换机)

3、发布订阅模式 Publish/subscribe 需要设置类型为fanout的交换机,并且交换机和队列进行绑定, 当发送消息到交换机后,交换机会将消息发送到绑定的队列

4、路由模式 Routing 需要设置类型为direct的交换机,交换机和队列进行绑定,并且指定routing key,当发送消息到交换机后,交换机会根据routing key将消息发送到对应的队列

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

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

i不妥协

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值