消息中间件RabbitMQ(一):基础概念与入门

RabbitMQ

RabbitMQ简介

什么是消息中间件

消息是指在应用间传递的数据,可以是字符串、JSON、对象等等。
看一下百度百科的定义:消息队列中间件(Message Queue Middleware)是指利用高效可靠的消息传递机制进行平台无关的数据交流,并基于数据通信来进行分布式系统的集成。通过提供消息传递和消息排队模型,它可以在分布式环境下扩展进程间的通信。

目前开源的消息中间件比较主流的就是RabbitMQ、Kafka、RocketMQ。消息中间件提供了有保证的消息发送,应用程序人员无须了解远程调用的细节。

消息中间件适用于需要可靠的数据传送的分布式环境,不同的服务之间通过传递消息来触发对方的事件,完成相应的操作。发送者将消息发送给消息服务器,消息服务器将消息存放在队列中,再将消息转发给接收者,如果网络连接不可用,消息中间件会存储信息,直到网络连接可用,再将消息转发给接收者。消息中间件其优点在于能够在客户端和服务器之间提供同步和异步的连接,并且在任何时刻都可以将消息进行传送或者存储。

消息中间件作用

  • 解耦:应用之间相互调用会出现耦合,消息中间件可以很好的解耦。
  • 削峰:在访问量剧增的情况下,可以进行控制,超过一定阀值的请求直接丢弃,能有效缓解短时间的高流量压垮应用。
  • 异步处理
  • … … …

RabbitMQ

RabbitMQ是采用Erlang语言实现AMQP协议的消息中间件,具有可靠性、扩展性、高可用性、灵活的路由等优点,并且支持多种协议,几乎支持所有的常用编程语言,并具有相应的客户端。

RabbitMQ的安装和运行这里就不再做演示,可以百度教程。

AMQP和JMS

JMS(Java Messaging Service ):JMS是由Sun公司早期提出的消息标准,旨在为java应用提供统一的消息操作,和JDBC一样,是API规范。

AMQP(advanced message queuing protocol):AMQP是一种协议,更准确的说是一种binary wire-level protocol(链接协议),和HTTP协议类似,不关心实现的语言,只要大家都按照相应的数据格式去发送报文请求,不同语言的client均可以和不同语言的server链接。RabbitMQ就是AMQP协议的Erlang实现。

AMQP协议本身包括三层:

  • Module Layer:位于协议最高层,主要定义了一些供客户端调用的命令,客户端可以利用这些命令实现自己的业务逻辑。例如:客户端可以实验Queue.Declare命令声明一个队列等。
  • Session Layer:位于中间层,主要负责将客户端的命令发送给服务器,再将服务端的应答返回给客户端,主要为客户端与服务器之间的通信提供可靠性同步机制和错误处理。
  • Transport Layer:位于最底层,主要传输二进制数据流,提供帧的处理,信道复用、错误检测和数据表示等。

RabbitMQ相关概念

RabbitMQ整体上是一个生产者与消费者模型,主要负责接收、存储和转发消息。
RabbitMQ模型架构图如下:
在这里插入图片描述

  • Producer:生产者,生产者创建消息,然后发布到RabbitMQ中。消息一般可以包含两个部分:消息体和标签(Lable)。消息体也可以称之为payload,在实际应用中,消息体一般是带有业务逻辑结构的数据,例如JSON字符串。消息的标签用来表述这条消息,比如一个交换器的名称和路由键,生产者把消息发送给RabbitMQ,RabbitMQ之后会根据标签把消息发送给相关的消费者。

  • Consumer:消费者,消费者连接到RabbitMQ服务器,并订阅到指定队列。当消费者消费一条消息时,只是消费消息的消息体(payload)。在消息路由的过程中,消息的标签会被丢弃,存入到消息队列中的消息只有消息体。

  • Broker:消息中间件的服务节点。一个RabbitMQ Broker可以简单地看作一个RabbitMQ服务节点,或者RabbitMQ服务实例。大多数情况下也可以将一个RabbitMQ Broker看作一台RabbitMQ服务器。

在这里插入图片描述

  • Queue:队列,是RabbitMQ地内部对象,用于存储消息。RabbitMQ中消息都只能存储在队列中。

多个消费者可以订阅同一个队列,这时队列中的消息会被多个消费者轮询处理。

  • Exchange:交换器,生产者将消息发送到交换器,交换器将消息路由到一个或者多个队列中。如果路由不到,或许会返回给生产者,或许直接丢弃。RabbitMQ的交换器有四种类型,不同类型有着不同的路由策略,在后面会详细介绍。

  • RoutingKey:路由键。生产者将消息发给交换器的时候,一般会指定一个RoutingKey,用来指定这个消息的路由规则,这个RoutingKey需要与交换器类型和绑定键(BindingKey)联合使用才能最终生效。在交换器类型和绑定键固定的情况下,生产者可以在发送消息给交换器时,通过指定的RoutingKey来决定消息流向哪里。

  • BindingKey:绑定键。RabbitMQ中通过绑定将交换器与队列关联起来,在绑定的时候一般会指定一个绑定键,这样RabbitMQ就知道如何将消息正确路由到哪个队列了。

RoutingKey和BindingKey对于初学者是蛮混乱的一个概念,我们举个例子,例如我们给Exchange发消息,他怎么知道给哪个Queue发呢?
BindingKey是Exchange和Queue绑定的规则描述,这个描述用来解析当Exchange接收到消息时,Exchange接收到的消息会带有RoutingKey这个字段,Exchange就是根据这个RoutingKey和当前Exchange所有绑定的BindingKey做匹配,如果满足匹配要求,就往BindingKey所绑定的Queue发送消息。 RoutingKey相当于是填写在邮件包裹上的地址,BindingKey相当于实际接收的地址,只有当RoutingKey和BindingKey匹配时,消息才能正确到达目的地。

交换器类型
RabbitMQ常用的交换器类型有四种 fanout、direct、topic、headers。

  • fanout:它会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中,无视RoutingKey和BindingKey。
  • direct:它会把消息路由到RoutingKey和BindingKey完全匹配的队列中。
  • topic:它会把消息路由到RoutingKey和BindingKey相匹配的队列中,匹配规则如下: RoutingKey和BindingKey必须是点号". "分隔的字符串,例如com.rabbitmq.client。BindingKey 中可以存在两种特殊字符串*和#,用于做模糊匹配,其中 * 用于匹配一个单词,#用于匹配多个单词(可以是零个)。

例如,有如下的交换器配置
在这里插入图片描述
路由键为"com.rabbitmq.client" 的消息会同时路由到 Queuel 和 Queue2;
路由键为"com.hidden.client" 的消息只会路由到 Queue2 中:
路由键为"com.hidden.demo" 的消息只会路由到 Queue2 中:
路由键为 “java.rabbitmq.demo” 的消息只会路由到 Queue1中:
路由键为"java.util.concurrent" 的消息将会被丢弃或者返回给生产者(需要设置
mandatory 参数) ,因为它没有匹配任何路由键。

  • headers:headers类型的交换器不依赖路由键的匹配规则来路由消息,而是根据发送消息内容中的headers属性进行匹配。具体的使用就不说了,因为性能很差,不实用,基本不会看到它的存在。

RabbitMQ运行流程

(一)生产者发送消息流程:

(1)生产者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个通道(Channel).

(2)生产者声明一个交换器,并设置相关属性,例如交换器类型、是否持久化等。

(3)生产者声明一个队列并设置相关属性,比如是否排他、是否持久化、是否自动删除等。

(4)生产者通过绑定键将交换器和队列绑定起来。

(5)生产者发送消息至RabbitMQ Broker,其中包含路由键、交换器等信息。

(6)接收到消息后,交换器根据接收到的路由键查找相匹配的队列。

(7)如果找到,则将消息存入相应的队列中,如果没有,则根据生产者配置的属性选择丢弃还是回退给生产者。

(8)关闭通道。关闭连接。

(二)消费者接收消息流程

(1)消费者连接到RabbitMQ Broker,建立一个连接(Connection),开启一个通道(Channel).

(2)消费者向RabbitMQ Broker请求消费相应队列中的消息,可能会设置相应的回调函数,以及做一些准备工作。

(3)等待RabbitMQ Broker回应并投递相应队列中的消息,消费者接收并消费消息。

(4)消费者确认(ack)接收到的消息,RabbitMQ从队列中删除相应已经被确认的消息。

(5)关闭通道。关闭连接。

无论是生产者还是消费者,都需要和RabbitMQ Broker建立连接,这个连接就是一个TCP连接,也就是Connection。一旦TCP连接建立起来,客户端就可以建立一个AMQP通道(Channel),每个通道会被指派一个唯一的ID。通道是建立在Connection之上的虚拟连接,RabbitMQ处理每条AMQP指令都是通过通道完成的。

我们完全可以直接使用Connection就能完成工作,为什么还要引入通道的概念呢?因为当我们的应用中有很多线程需要从RabbitMQ生产消费消息时,必然要创建多个Connection,但是建立销毁连接其实是很消耗资源的开销,遇到高峰时很容易遇到性能瓶颈,所以RabbitMQ采用TCP连接复用,不仅减少性能开销,也便于管理。每个线程开启一个通道,复用了Connection连接,同时RabbitMQ可以保证每个线程的私密性。当每个通道流量不大时,能够有效节省TCP连接资源。当流量很大时,这时候多个Channel复用一个Connection就会产生性能瓶颈,此时就需要开启多个Connection。

入门案例

(1)首先创建一个项目,添加依赖

		<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

(2)编写生产者代码

		public class RabbitProducer {
		    public static void main(String[] args) throws IOException, TimeoutException {
		        ConnectionFactory connectionFactory=new ConnectionFactory();
		        connectionFactory.setHost("192.168.1.1");
		        //RabbitMQ服务端默认端口号为5672
		        connectionFactory.setPort(5672);
		        connectionFactory.setUsername("admin");
		        connectionFactory.setPassword("admin");
		        //创建连接
		        Connection connection = connectionFactory.newConnection();
		        //创建通道
		        Channel channel = connection.createChannel();
		        //创建一个名为exchangeDemo,direct类型的,持久化的,非自动删除的交换器,这些参数后面都会讲到
		        channel.exchangeDeclare("exchangeDemo","direct",true,false,null);
		         //创建一个名为queueDemo,持久化的,非排他的,非自动删除的队列
		        channel.queueDeclare("queueDemo",true,false,false,null);
		        //将交换器和队列通过路由键绑定
		        channel.queueBind("queueDemo","exchangeDemo","routingKeyDemo");
		        String message="hello worldaaa";
		        //发送消息,指定交换器和路由键
		        channel.basicPublish("exchangeDemo","routingKeyDemo", MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
		        //关闭资源
		        channel.close();
		        connection.close();
		    }
		}

发送消息之后在管理台能看到我们创建的connection、channel、exchange等相关信息,还能看到消息。
在这里插入图片描述

(3)编写消费者代码

public class RabbitConsumer {
    public static void main(String[] args) throws IOException, TimeoutException, InterruptedException {
        ConnectionFactory connectionFactory=new ConnectionFactory();
        connectionFactory.setHost("192.168.1.1");
        connectionFactory.setPort(5672);
        connectionFactory.setUsername("admin");
        connectionFactory.setPassword("admin");
        Connection connection = connectionFactory.newConnection();
        Channel channel = connection.createChannel();
        //消费消息的逻辑
        DefaultConsumer 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));
            }
        };
        //从队列中获取消息并消费
        channel.basicConsume("queueDemo",true,consumer);
        TimeUnit.SECONDS.sleep(5);
        channel.close();
        connection.close();
    }
}

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值