MQ(Message Queue消息队列)

1.MQ消息中间件

MQ全称 Message Queue(消息队列),是在消息的传输过程中保存消息的容器。它是应用程序和应用程序之间的通信方法。

MQ(Message Queue消息队列)_第1张图片

 

2.为什么要使用MQ

在项目中,可将一些==无需即时返回且耗时==的操作提取出来,进行异步处理,而这种异步处理的方式大大的节省了服务器的请求响应时间,从而提高系统吞吐量

MQ总结为三个好处:

2.1 应用解耦

以电商应用为例,应用中有订单系统、库存系统、物流系统、支付系统。用户创建订单后,如果耦合调用库存系统、物流系统、支付系统,任何一个子系统出了故障,都会造成下单操作异常。当转变成基于消息队列的方式后,系统间调用的问题会减少很多,比如物流系统因为发生故障,需要几分钟来修复。在这几分钟的时间里,物流系统要处理的内容被缓存在消息队列中,用户的下单操作可以正常完成。当物流系统恢复后,继续处理订单信息即可,中间用户感受不到物流系统的故障,提升系统的可用性。

MQ(Message Queue消息队列)_第2张图片

 MQ(Message Queue消息队列)_第3张图片

 

2.2异步提速

MQ(Message Queue消息队列)_第4张图片

 

上面要完成下单需要花费的时间: 20 + 300 + 300 + 300 = 920ms 用户点击完下单按钮后,需要等待920ms才能得到下单响应,太慢!

使用MQ可以解决上述问题

MQ(Message Queue消息队列)_第5张图片 

用户点击完下单按钮后,只需等待25ms就能得到下单响应 (20 + 5 = 25ms)。

提升用户体验和系统吞吐量(单位时间内处理请求的数目)。

2.3削峰填谷

举个例子,如果订单系统最多能处理一千次订单,这个处理能力应付正常时段的下单时绰绰有余,正常时段我们下单一秒后就能返回结果。但是在高峰期,如果有两千次下单操作系统是处理不了的,只能限制订单超过一千后不允许用户下单。使用消息队列做缓冲,我们可以取消这个限制,把一秒内下的订单分散成一段时间来处理,这时有些用户可能在下单十几秒后才能收到下单成功的操作,但是比不能下单的体验要好。

简单来说: 就是在访问量剧增的情况下,但是应用仍然不能停,比如“双十一”下单的人多,但是淘宝这个应用仍然要运行,所以就可以使用消息中间件采用队列的形式减少突然访问的压力


MQ(Message Queue消息队列)_第6张图片

 

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

使用MQ后,可以提高系统稳定性。

3.MQ的缺点

1.系统可用性降低

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

2.系统复杂度提高

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

3.一致性问题

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

4.常见的MQ组件

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

MQ(Message Queue消息队列)_第7张图片

 

5.什么是RabbitMQ

2007 年发布,是一个在 AMQP(高级消息队列协议)基础上完成的,可复用的企业消息系统,是当前最主流的消息中间件之一。

RabbitMQ是一个由erlang开发的AMQP(Advanced Message Queue 高级消息队列协议 )的开源实现,由于erlang 语言的高并发特性,性能较好,本质是个队列,FIFO 先入先出,里面存放的内容是message

RabbitMQ是一个消息中间件:它接受并转发消息。你可以把它当做一个快递站点,当你要发送一个包裹时,你把你的包裹放到快递站,快递员最终会把你的快递送到收件人那里,按照这种逻辑RabbitMQ是一个快递站,一个快递员帮你传递快件。RabbitMQ与快递站的主要区别在于,它不处理快件而是接收,存储和转发消息数据。

6.RabbitMq的原理

MQ(Message Queue消息队列)_第8张图片

 

名词解释:

1.Broker:接收和分发消息的应用,RabbitMQ Server就是 Message Broker

2.Connection:publisher/consumer 和 broker 之间的 TCP 连接

3.Channel:如果每一次访问 RabbitMQ 都建立一个 Connection,在消息量大的时候建立 TCP Connection的开销将是巨大的,效率也较低。Channel 是在 connection 内部建立的逻辑连接,如果应用程序支持多线程,通常每个thread创建单独的 channel 进行通讯,AMQP method 包含了channel id 帮助客户端和message broker 识别 channel,所以 channel 之间是完全隔离的。Channel 作为轻量级的 Connection 极大减少了操作系统建立 TCP connection 的开销.

4.Exchange:message 到达 broker 的第一站,根据分发规则,匹配查询表中的 routing key,分发消息到queue 中去。常用的类型有:direct (point-to-point), topic (publish-subscribe) and fanout (multicast)

5.Queue:消息最终被送到这里等待 consumer 取走

6.Binding:exchange 和 queue 之间的虚拟连接,binding 中可以包含 routing key。Binding 信息被保存到 exchange 中的查询表中,用于 message 的分发依据

7.Virtual host:出于多租户和安全因素设计的,把 AMQP 的基本组件划分到一个虚拟的分组中,类似于网络中的 namespace 概念。当多个不同的用户使用同一个 RabbitMQ server 提供的服务时,可以划分出多个vhost,每个用户在自己的 vhost 创建 exchange/queue 等

7.安装RabbitMQ

官网下载

RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQ

7.1安装Socat

MQ(Message Queue消息队列)_第9张图片

 

在可视化图形界面里面在线安装依赖环境

yum install gcc
yum install socat
yum install openssl
yum install openssl-devel

7.2安装Erlang

MQ(Message Queue消息队列)_第10张图片

 

mkdir /rabbitmq && cd /rabbitmq 


# 上传 erlang-22.0.7-1.el7.x86_64.rpm 安装包上传 


# 安装 

rpm -ivh erlang-22.0.7-1.el7.x86_64.rpm 

7.3安装RabbitMQ

cd /rabbitmq 


# 上传 rabbitmq-server-3.7.17-1.el7.noarch.rpm 安装包 上传 


# 安装 


rpm -ivh rabbitmq-server-3.7.17-1.el7.noarch.rpm

7.4开启管理界面及配置

# 开启管理界面 
rabbitmq-plugins enable rabbitmq_management 


# 配置远程可使用guest登录mq 
cd /usr/share/doc/rabbitmq-server-3.7.17 


cp rabbitmq.config.example /etc/rabbitmq/rabbitmq.config 


# 修改配置文件 
vi /etc/rabbitmq/rabbitmq.config

修改 /etc/rabbitmq/rabbitmq.config配置文件:

MQ(Message Queue消息队列)_第11张图片

 

7.5修改完成之后,启动

MQ(Message Queue消息队列)_第12张图片 

MQ(Message Queue消息队列)_第13张图片 

 7.5修改完成之后,启动

 

centos6用这个命令: 
/sbin/service rabbitmq-server restart 


centos7用这个命令: 
systemctl start rabbitmq-server

7.6配置虚拟主机及用户

MQ(Message Queue消息队列)_第14张图片

 

1)用户角色

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

MQ(Message Queue消息队列)_第15张图片 

 

MQ(Message Queue消息队列)_第16张图片 

MQ(Message Queue消息队列)_第17张图片 

角色说明:

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

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

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

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

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

2)Virtual Hosts配置

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

(1)创建Virtual Hosts 

MQ(Message Queue消息队列)_第18张图片

 

(2)设置Virtual Hosts权限

MQ(Message Queue消息队列)_第19张图片

 MQ(Message Queue消息队列)_第20张图片

 

8.RabbitMQ的工作模式

RabbitMQ 提供了 6 种工作模式:

1.简单模式

2.work queues

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

4.Routing 路由模式

5.Topics 主题模式

6.RPC 远程调用模式(远程调用,不太算 MQ;暂不作介绍)。

官网对应模式介绍:RabbitMQ Tutorials — RabbitMQ

MQ(Message Queue消息队列)_第21张图片

 

8.1simple(简单模式)

MQ(Message Queue消息队列)_第22张图片 

 

在上图的模型中,有以下概念:

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

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

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

1)准备新建一个maven工程

MQ(Message Queue消息队列)_第23张图片

2)给rabbitmq-producer 生产者工程,rebbitmq-consumer 消费者工程

添加依赖

        
            com.rabbitmq
            amqp-client
            5.14.2
        
    

rabbitmq-producer 生产者:

package com.hmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class HelloProduct {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        //设置rabbitmq服务的地址,默认localhost
        factory.setHost("192.168.245.223");
        //设置rabbitmq的端口号 AMQP端口
        factory.setPort(5672);
        //设置账号 默认为guest --这里我已经修改过了
        factory.setUsername("hmq");
        //设置密码 默认也是guest
        factory.setPassword("hmq");
        //设置虚拟机主机  默认 /
        factory.setVirtualHost("/PLA");
        //获取连接对戏
        Connection connection = factory.newConnection();//这里需要抛出异常
        //获取channel对象
        Channel channel = connection.createChannel();
        //创建队列
        /**
         * String queue, 队列的名称,如果该名称不存在,则创建;如果存在则不创建
         * boolean durable,该对象是否持久化 当rabbitmq重启后,队列就会消失
         * boolean exclusive,该队列是否被一个消费者独占
         * boolean autoDelete,当没有消费者时,该队列是否被自动删除
         * Map arguments: 额外参数的设置
         * */
        channel.queueDeclare("hello_queue",true,false,false,null);
        //发送消息
        /**
         * String exchange,交换机的名称 简单模式没有交换机使用“表示采用默认交换机”
         * String routingkey,路由标识,如果是简单模式起名为队列的名称
         * BasicProperties props,消息的属性设置 设置为null
         * byte[] body:消息的内容
         * */
        String msg = "hello rabbitmq !!!!!!!!!!!!!!!!";
        channel.basicPublish("","hello_queue",null,msg.getBytes());
        //关闭资源
        //channel.close();
        //connection.close();
    }
}

rebbitmq-consumer 消费者:

package com.hmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class HelloConsumer {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        factory.setVirtualHost("/PLA");//设置虚拟机主机  默认 /
        factory.setUsername("hmq");//设置账号
        factory.setPassword("hmq");//设置密码
        factory.setHost("192.168.245.223");//设置rabbitmq服务的地址
        factory.setPort(5672);//设置rabbitmq的端口号
        //获取连接对戏
        Connection connection = factory.newConnection();
        //获取channel对象
        Channel channel = connection.createChannel();
        //创建队列
        channel.queueDeclare("hello_queue",true,false,false,null);
        //接受队列中的消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             * @param consumerTag:消费者的标签
             * @param envelope:设置 拿到交换机 路由key等信息
             * @param properties:消息的属性对象
             * @param body:消息的内存
             * @throws IOException
             * */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受的内容:"+new String(body));
                System.out.println("消费者的标志:"+consumerTag);
                System.out.println("交换机名称:"+envelope.getExchange());
                System.out.println("路由key标志:"+envelope.getRoutingKey());
                System.out.println("消息属性:"+properties);
            }
        };
        /**
         * String queue,队列名
         * boolean autoAck,是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认消息
         * Consumer callback :回调 当rabbitmq队列中存在消息,则触发该回调
         * */
        channel.basicConsume("hello_queue",true,consumer);
        //是否要关闭connection和channel--不能关闭
    }
}

8.2Work queues(工作模式)

MQ(Message Queue消息队列)_第24张图片

 

Work Queues:与入门程序的简单模式相比,多了一个或一些消费端,多个消费端共同消费同一个队列中的消息。

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

rabbitmq-producer 生产者:

package com.hmq;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class WorkProduct {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        //设置rabbitmq服务的地址,默认localhost
        factory.setHost("192.168.245.223");
        //设置rabbitmq的端口号 AMQP端口
        factory.setPort(5672);
        //设置账号 默认为guest --这里我已经修改过了
        factory.setUsername("hmq");
        //设置密码 默认也是guest
        factory.setPassword("hmq");
        //设置虚拟机主机  默认 /
        factory.setVirtualHost("/PLA");
        //获取连接对戏
        Connection connection = factory.newConnection();//这里需要抛出异常
        //获取channel对象
        Channel channel = connection.createChannel();
        //创建队列
        /**
         * String queue, 队列的名称,如果该名称不存在,则创建;如果存在则不创建
         * boolean durable,该对象是否持久化 当rabbitmq重启后,队列就会消失
         * boolean exclusive,该队列是否被一个消费者独占
         * boolean autoDelete,当没有消费者时,该队列是否被自动删除
         * Map arguments: 额外参数的设置
         * */
        channel.queueDeclare("work_queue",true,false,false,null);
        //发送消息
        /**
         * String exchange,交换机的名称 简单模式没有交换机使用“表示采用默认交换机”
         * String routingkey,路由标识,如果是简单模式起名为队列的名称
         * BasicProperties props,消息的属性设置 设置为null
         * byte[] body:消息的内容
         * */
        for (int i=0;i<10;i++){
            String msg = "hello rabbitmq !!!!!!!!!!!!!!!!"+i;
            channel.basicPublish("","work_queue",null,msg.getBytes());
        }
        //关闭资源
        channel.close();
        connection.close();
    }
}

rebbitmq-consumer 消费者:

package com.hmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class WorkConsumer01 {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        factory.setVirtualHost("/PLA");//设置虚拟机主机  默认 /
        factory.setUsername("hmq");//设置账号
        factory.setPassword("hmq");//设置密码
        factory.setHost("192.168.245.223");//设置rabbitmq服务的地址
        factory.setPort(5672);//设置rabbitmq的端口号
        //获取连接对戏
        Connection connection = factory.newConnection();
        //获取channel对象
        Channel channel = connection.createChannel();
        //创建队列
        //channel.queueDeclare("hello_queue",true,false,false,null);
        //接受队列中的消息
        Consumer consumer = new DefaultConsumer(channel){
            /**
             * @param consumerTag:消费者的标签
             * @param envelope:设置 拿到交换机 路由key等信息
             * @param properties:消息的属性对象
             * @param body:消息的内存
             * @throws IOException
             * */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受的内容:"+new String(body));
            }
        };
        /**
         * String queue,队列名
         * boolean autoAck,是否自动确认,当rabbitmq把消息发送给消费后,消费端自动确认消息
         * Consumer callback :回调 当rabbitmq队列中存在消息,则触发该回调
         * */
        channel.basicConsume("work_queue",true,consumer);
        //是否要关闭connection和channel--不能关闭
    }
}

总结: 在一个队列中如果有多个消费者,那么消费者之间对于同一个消息的关系是竞争的关系。

Work Queues 对于任务过重或任务较多情况使用工作队列可以提高任务处理的速度。例如:短信服务部署多个,只需要有一个节点成功发送即可。

8.3Publish/Subscribe(发布订阅模式)

MQ(Message Queue消息队列)_第25张图片

在订阅模型中,多了一个 Exchange 角色,而且过程

略有变化:

P:生产者,也就是要发送消息的程序,但是不再发送到队列中,而是发给X(交换机)

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

Queue:消息队列,接收消息、缓存消息

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

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

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

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

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

rabbitmq-producer 生产者:

package com.hmq;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class PublishProduct {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        //设置rabbitmq服务的地址,默认localhost
        factory.setHost("192.168.245.223");
        //设置rabbitmq的端口号 AMQP端口
        factory.setPort(5672);
        //设置账号 默认为guest --这里我已经修改过了
        factory.setUsername("hmq");
        //设置密码 默认也是guest
        factory.setPassword("hmq");
        //设置虚拟机主机  默认 /
        factory.setVirtualHost("/PLA");
        //获取连接对戏
        Connection connection = factory.newConnection();//这里需要抛出异常
        //获取channel对象
        Channel channel = connection.createChannel();
        //创建交换机
        /**
         * String exchange 交换机的名称 如果不存在则创建  存在则不创建
         * BuiltinExchangeType type 交换机的类型
         * boolean durable:是否持久化
         * */
        channel.exchangeDeclare("publish_exchange", BuiltinExchangeType.FANOUT,true);
        //c创建队列
        channel.queueDeclare("publish_queue01",true,false,false,null);
        channel.queueDeclare("publish_queue02",true,false,false,null);
        //队列和交换机绑定
        /**
         * String queue
         * String exchange
         * String routingkey:发布订阅模式,没有routingkey 则写为 ""
         * */
        channel.queueBind("publish_queue01","publish_exchange","");
        channel.queueBind("publish_queue02","publish_exchange","");
        String msg = "我的肚子好饿------------";
        channel.basicPublish("publish_exchange","",null,msg.getBytes());
        //关闭资源
        channel.close();
        connection.close();
    }
}

rebbitmq-consumer 消费者:

package com.hmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class PublishConsumer01 {
    public static void main(String[] args) throws Exception{
        ConnectionFactory factory=new ConnectionFactory();
        factory.setVirtualHost("/PLA");
        factory.setUsername("hmq");
        factory.setPassword("hmq");
        factory.setHost("192.168.245.223");
        factory.setPort(5672);
        Connection connection=factory.newConnection();
        Channel channel=connection.createChannel();
//        channel.queueDeclare("hello_queue",true,false,false,null);
        //接受队列中的消息.
        Consumer consumer=new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag: 消费者的标签
             * @param envelope : 设置 拿到你的交换机 路由key等信息
             * @param properties: 消息的属性对象
             * @param body: 消息的内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受的内容:"+new String(body));
            }
        };
        /**
         * String queue, 队列名
         * boolean autoAck,是否自动确认。 当rabbitmq把消息发送给消费后,消费端自动确认消息。
         * Consumer callback:回调。 当rabbitmq队列中存在消息 则触发该回调
         */
        channel.basicConsume("publish_queue01",true,consumer);
        //是否要关闭connection和channel---不能关闭
    }
}

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

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

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

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

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

8.4Routing(路由模式)

MQ(Message Queue消息队列)_第26张图片

 

队列与交换机的绑定,不能是任意绑定了,而是要指定一个 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 的消息

rabbitmq-producer 生产者:

package com.hmq;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class RouterProduct {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        //设置rabbitmq服务的地址,默认localhost
        factory.setHost("192.168.245.223");
        //设置rabbitmq的端口号 AMQP端口
        factory.setPort(5672);
        //设置账号 默认为guest --这里我已经修改过了
        factory.setUsername("hmq");
        //设置密码 默认也是guest
        factory.setPassword("hmq");
        //设置虚拟机主机  默认 /
        factory.setVirtualHost("/PLA");
        //获取连接对戏
        Connection connection = factory.newConnection();//这里需要抛出异常
        //获取channel对象
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("router_exchange", BuiltinExchangeType.DIRECT,true);
        //c创建队列
        channel.queueDeclare("router_queue01",true,false,false,null);
        channel.queueDeclare("router_queue02",true,false,false,null);
        channel.queueBind("router_queue01","router_exchange","error");
        channel.queueBind("router_queue02","router_exchange","error");
        channel.queueBind("router_queue02","router_exchange","info");
        channel.queueBind("router_queue02","router_exchange","warning");
        String msg = "我现在正在写作业,太多了。不想写-------";
        channel.basicPublish("router_exchange","info",null,msg.getBytes());
        //关闭资源
        channel.close();
        connection.close();
    }
}

rebbitmq-consumer 消费者:

package com.hmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class RouterConsumer01 {
    public static void main(String[] args) throws Exception{
        ConnectionFactory factory=new ConnectionFactory();
        factory.setVirtualHost("/PLA");
        factory.setUsername("hmq");
        factory.setPassword("hmq");
        factory.setHost("192.168.245.223");
        factory.setPort(5672);
        Connection connection=factory.newConnection();
        Channel channel=connection.createChannel();
//        channel.queueDeclare("hello_queue",true,false,false,null);
        //接受队列中的消息.
        Consumer consumer=new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag: 消费者的标签
             * @param envelope : 设置 拿到你的交换机 路由key等信息
             * @param properties: 消息的属性对象
             * @param body: 消息的内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受的内容:"+new String(body));
            }
        };
        /**
         * String queue, 队列名
         * boolean autoAck,是否自动确认。 当rabbitmq把消息发送给消费后,消费端自动确认消息。
         * Consumer callback:回调。 当rabbitmq队列中存在消息 则触发该回调
         */
        channel.basicConsume("router_queue01",true,consumer);
        //是否要关闭connection和channel---不能关闭
    }
}

Routing 模式要求队列在绑定交换机时要指定 routing key,消息会转发到符合 routing key 的队列

8.5Topics(主题模式)

MQ(Message Queue消息队列)_第27张图片

 

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

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

通配符规则:# 匹配一个或多个词,* 匹配不多不少恰好1个词,例如:item.# 能够匹配 item.insert.abc 或者 item.insert,item.* 只能匹配 item.insert

MQ(Message Queue消息队列)_第28张图片 

 

rabbitmq-producer 生产者:

package com.hmq;
import com.rabbitmq.client.BuiltinExchangeType;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;
public class TopicProduct {
    public static void main(String[] args) throws Exception{
        //创建连接工厂类并设置连接信息
        ConnectionFactory factory = new ConnectionFactory();
        //设置rabbitmq服务的地址,默认localhost
        factory.setHost("192.168.245.223");
        //设置rabbitmq的端口号 AMQP端口
        factory.setPort(5672);
        //设置账号 默认为guest --这里我已经修改过了
        factory.setUsername("hmq");
        //设置密码 默认也是guest
        factory.setPassword("hmq");
        //设置虚拟机主机  默认 /
        factory.setVirtualHost("/PLA");
        //获取连接对戏
        Connection connection = factory.newConnection();//这里需要抛出异常
        //获取channel对象
        Channel channel = connection.createChannel();
        channel.exchangeDeclare("topic_exchange", BuiltinExchangeType.TOPIC,true);
        //c创建队列
        channel.queueDeclare("topic_queue01",true,false,false,null);
        channel.queueDeclare("topic_queue02",true,false,false,null);
        channel.queueBind("topic_queue01","topic_exchange","*.orange.*");
        channel.queueBind("topic_queue02","topic_exchange","*.*.rabbit");
        channel.queueBind("topic_queue02","topic_exchange","lazy.#");
        String msg = "马上放学了,我还没写完!!!!";
        channel.basicPublish("topic_exchange","lazy.rabbit.orange",null,msg.getBytes());
        //关闭资源
        channel.close();
        connection.close();
    }
}

rebbitmq-consumer 消费者:

package com.hmq;
import com.rabbitmq.client.*;
import java.io.IOException;
public class TopicConsumer01 {
    public static void main(String[] args) throws Exception{
        ConnectionFactory factory=new ConnectionFactory();
        factory.setVirtualHost("/PLA");
        factory.setUsername("hmq");
        factory.setPassword("hmq");
        factory.setHost("192.168.245.223");
        factory.setPort(5672);
        Connection connection=factory.newConnection();
        Channel channel=connection.createChannel();
//        channel.queueDeclare("hello_queue",true,false,false,null);
        //接受队列中的消息.
        Consumer consumer=new DefaultConsumer(channel){
            /**
             *
             * @param consumerTag: 消费者的标签
             * @param envelope : 设置 拿到你的交换机 路由key等信息
             * @param properties: 消息的属性对象
             * @param body: 消息的内容
             * @throws IOException
             */
            @Override
            public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                System.out.println("接受的内容:"+new String(body));
            }
        };
        /**
         * String queue, 队列名
         * boolean autoAck,是否自动确认。 当rabbitmq把消息发送给消费后,消费端自动确认消息。
         * Consumer callback:回调。 当rabbitmq队列中存在消息 则触发该回调
         */
        channel.basicConsume("topic_queue01",true,consumer);
        //是否要关闭connection和channel---不能关闭
    }
}

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

9.springboot整合rabbitmq

9.1生产方

1)创建rabbitmq-springboot-product项目

MQ(Message Queue消息队列)_第29张图片

2)引入rabbitmq整合依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.12.RELEASE
         
    
    com.hmq
    rabbitmq-springboot-product
    0.0.1-SNAPSHOT
    rabbitmq-springboot-product
    rabbitmq-springboot-product
    
        1.8
    
    
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.amqp
            spring-rabbit-test
            test
        
        
        
            com.alibaba
            fastjson
            1.2.83
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    

3)配置文件中添加rabbit服务信息

# rabbitmq的配置
spring.rabbitmq.host=192.168.245.223
spring.rabbitmq.username=hmq
spring.datasource.password=hmq
spring.rabbitmq.virtual-host=/PLA

4)封装类RabbitTemplate--发送消息

5)调用RabbitTemplate中发送消息的方法

package com.hmq;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class RabbitmqSpringbootProductApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void contextLoads() {
        rabbitTemplate.convertAndSend("topic_exchange","lazy.PLA","你困吗?小霍??????");
    }
}

9.2消费方

1)创建rabbitmq-springboot-consumer项目

MQ(Message Queue消息队列)_第30张图片

 

2)依赖



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.3.12.RELEASE
         
    
    com.hmq
    rabbitmq-springboot-consumer
    0.0.1-SNAPSHOT
    rabbitmq-springboot-consumer
    rabbitmq-springboot-consumer
    
        1.8
    
    
    
        
            com.alibaba
            fastjson
            1.2.83
        
        
            org.springframework.boot
            spring-boot-starter-amqp
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
        
            org.springframework.amqp
            spring-rabbit-test
            test
        
    
    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
                
                    
                        
                            org.projectlombok
                            lombok
                        
                    
                
            
        
    

3)配置

# rabbitmq的配置
spring.rabbitmq.host=192.168.245.223
spring.rabbitmq.username=hmq
spring.rabbitmq.password=hmq
spring.rabbitmq.virtual-host=/PLA

4)在RabbitTemplate中添加发送消息的方法

package com.hmq;
import com.alibaba.fastjson.JSON;
import org.junit.jupiter.api.Test;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.HashMap;
import java.util.Map;
@SpringBootTest
class RabbitmqSpringbootProductApplicationTests {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Test
    void contextLoads() {
        Map map = new HashMap<>();
        map.put("orderNo","1110");
        map.put("productId",5);
        map.put("num",10);
        rabbitTemplate.convertAndSend("topic_exchange","lazy.PLA", JSON.toJSONString(map));
    }
}

5)创建类--创建监听方法即可@RabbitListener

package com.hmq.listener;
import com.alibaba.fastjson.JSON;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.Map;


@Component //交于容器创建并管理
public class MyListener {
    @RabbitListener(queues = {"topic_queue02"})
    //queues:表示监听的队列名
    public void hello01(Message message){
        //把监听到的消息封装到Message类对象中
        byte[] body = message.getBody();
        String s = new String(body);
        Map map = JSON.parseObject(s, HashMap.class);
        System.out.println("消息内容:"+map);
    }
}

9.3通过代码创建交换机和队列

创建config包下RabbitConfig

package com.hmq.config;
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitConfig {
    public static final String EXCHANGE_NAME="topic_exchange01";
    @Bean
    public Exchange exchange(){
        Exchange topic_exchange02 = ExchangeBuilder.topicExchange("topic_exchange02").build();
        return topic_exchange02;
    }
    @Bean
    public Queue queue(){
        Queue topic_queue03 = QueueBuilder.durable("topic_queue03").build();
        return topic_queue03;
    }
    @Bean
    public Binding binding(){
        Binding noargs = BindingBuilder.bind(queue()).to(exchange()).with("hmq.#").noargs();
        return noargs;
    }
    //如果交换机要绑定多个队列,需要在写一个bind方法
}

修改:

MQ(Message Queue消息队列)_第31张图片

总结:

1. rabbitMQ消息中间件:

  (1)优点: 应用解耦  异步提速  肖锋填谷

  (2)常见的组件: connection[channel]===exchange---binding--routingkey---queue

  (3)常见的工作模式: 简单模式 工作模式  发布订阅模式 路由模式  主题模式

  (4)springboot和rabbitmq整合。

      [1]生产方:

      (1)依赖 (2)配置rabbitmq的信息  (3)RabbitTemplate.convertAndSend()

      [2]消费方:

      (1)依赖 (2)配置rabbitmq的信息  (3)创建类@Component  方法@RabbitLinstener(queues={})

  (5)如何保证消息的可靠性.

     (1)可靠性投递。comfirm机制 return机制

     (2)持久化: [1]队列持久化 [2]消息持久化

     (3)搭建rabbitmq集群--【找资料】

     (4)可靠的消费消息: ACK机制。 basicAck()  basicNack()

  (6)完成延迟队列: 

       TTL+死信队列

你可能感兴趣的:(java,spring,前端,maven,spring,boot,mybatis)